import fs from 'node:fs'; import path from 'node:path'; import { _electron as electron } from 'playwright'; import { copyPath, createTempWorkspace, findFirstMatchingPath, logStep, repoRoot, runCommand, sleep, startFixtureServer, waitFor } from './app-update-e2e-helpers.mjs'; const OLD_VERSION = '0.7.1'; const NEW_VERSION = '0.7.3'; const SETTINGS_HASH = '#/all/settings#interface-settings'; const MAC_ARCH = process.arch === 'x64' ? 'x64' : 'arm64'; const findPackagedMacApp = async () => { const outDirectory = path.join(repoRoot, 'out'); return findFirstMatchingPath(outDirectory, (entryPath, entry) => entry.isDirectory() && entryPath.endsWith('.app')); }; const buildPackagedApp = async ({ version, fixturePort }) => { await runCommand('corepack', ['yarn', 'electron:package'], { env: { VITE_APP_VERSION: version, VITE_APP_UPDATE_ALLOWED_DOWNLOAD_HOSTS: '127.0.0.1', VITE_APP_UPDATE_RELEASE_API_URL: `http://127.0.0.1:${fixturePort}/releases/latest`, VITE_E2E_START_HASH: SETTINGS_HASH, }, }); const packagedAppPath = await findPackagedMacApp(); if (!packagedAppPath) { throw new Error('Could not find the packaged macOS app'); } return packagedAppPath; }; const zipMacApp = async (appPath, zipPath) => { await fs.promises.rm(zipPath, { force: true }); await runCommand('/usr/bin/ditto', ['-c', '-k', '--sequesterRsrc', '--keepParent', appPath, zipPath]); }; const main = async () => { if (process.platform !== 'darwin') { throw new Error('Electron update e2e is only implemented for macOS in this harness'); } const workspace = await createTempWorkspace('electron-update-e2e'); const fixtureServer = await startFixtureServer(); try { const packagedOldAppPath = await buildPackagedApp({ version: OLD_VERSION, fixturePort: fixtureServer.port, }); const installedAppPath = path.join(workspace, 'installed', '5chan.app'); await copyPath(packagedOldAppPath, installedAppPath); const packagedNewAppPath = await buildPackagedApp({ version: NEW_VERSION, fixturePort: fixtureServer.port, }); const zippedNewAppPath = path.join(workspace, `5chan-darwin-${MAC_ARCH}-v${NEW_VERSION}.zip`); await zipMacApp(packagedNewAppPath, zippedNewAppPath); fixtureServer.setRelease(NEW_VERSION, [ { name: path.basename(zippedNewAppPath), filePath: zippedNewAppPath, }, ]); const versionMetadataPath = path.join(installedAppPath, 'Contents', 'Resources', 'app', 'build', 'version.json'); const sandboxHome = path.join(workspace, 'home'); const updaterDebugLogPath = path.join(workspace, 'app-updater.log'); const pkcDataPath = path.join(sandboxHome, 'Library', 'Application Support', 'pkc'); await fs.promises.mkdir(pkcDataPath, { recursive: true }); await fs.promises.writeFile(path.join(pkcDataPath, 'auth-key'), 'e2e-auth-key', 'utf8'); const electronApp = await electron.launch({ executablePath: path.join(installedAppPath, 'Contents', 'MacOS', '5chan'), env: { ...process.env, APP_UPDATE_ALLOWED_DOWNLOAD_HOSTS: '127.0.0.1', APP_UPDATE_DEBUG: '1', APP_UPDATE_DEBUG_LOG_PATH: updaterDebugLogPath, HOME: sandboxHome, }, }); const firstWindow = await electronApp.firstWindow(); firstWindow.on('dialog', async (dialog) => { logStep(`electron app dialog: ${dialog.message()}`); await dialog.dismiss(); }); await firstWindow.waitForLoadState('domcontentloaded'); await firstWindow.getByRole('button', { name: 'Check' }).waitFor({ timeout: 120000, }); await firstWindow.getByRole('button', { name: 'Check' }).click(); await firstWindow.getByRole('button', { name: 'Download' }).waitFor({ timeout: 120000, }); await firstWindow.getByText(`v${NEW_VERSION}`).waitFor({ timeout: 120000, }); await firstWindow.getByRole('button', { name: 'Download' }).evaluate((button) => { button.click(); }); await waitFor( async () => { try { const payload = JSON.parse(await fs.promises.readFile(versionMetadataPath, 'utf8')); return payload.version === NEW_VERSION; } catch { return false; } }, { timeoutMs: 180000, intervalMs: 2000, description: `the installed app bundle to be replaced with v${NEW_VERSION}`, }, ); await waitFor( async () => { const result = await runCommand('/usr/bin/pgrep', ['-f', path.join(installedAppPath, 'Contents', 'MacOS', '5chan')], { captureOutput: true, }).catch(() => null); return result?.stdout?.trim()?.length ? result.stdout.trim() : false; }, { timeoutMs: 120000, intervalMs: 2000, description: 'the updated macOS app to relaunch', }, ); logStep(`electron update e2e passed: ${installedAppPath} now contains version ${NEW_VERSION}`); } finally { await sleep(2000); await fixtureServer.close(); await fs.promises.rm(workspace, { recursive: true, force: true }).catch(() => undefined); } }; await main();