Files
orca/tests/e2e/headless-serve-desktop-activation.spec.ts
T
Brennan Benson 4c49989c2e refactor(codex): delete the unreachable managed shared-mirror lane (#12614)
PR 9501 shipped real-home routing for the host system default, and the
env override that could turn it back off was never a shipped control. The
managed-account half of the shared runtime mirror has been unreachable
since: every host account routes to its own self-contained CODEX_HOME
before that code runs.

Delete the flag module and its env plumbing plus the managed branch of
syncForCurrentSelection and the six helpers only it called. The three
lanes that still use the shared mirror -- Windows, a custom CODEX_HOME,
and a hook-lane gate that reports unusable -- are untouched, as are every
legacy migration and the WSL read-back helpers.
2026-08-05 12:57:02 -07:00

282 lines
10 KiB
TypeScript

import { spawn, type ChildProcess } from 'node:child_process'
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import { _electron as electron, type ElectronApplication } from '@stablyai/playwright-test'
import { test, expect, forwardElectronProcessLogs } from './helpers/orca-app'
import { TEST_REPO_PATH_FILE } from './global-setup'
import { getE2ECompletedOnboardingProfile } from './helpers/e2e-completed-onboarding-profile'
import { getOrcaElectronLaunchArgs } from './helpers/electron-launch-args'
import { cleanupE2EDaemons, closeElectronAppForE2E } from './helpers/electron-process-shutdown'
import {
assertElectronResolvedIsolatedHome,
createElectronHomeIsolation,
type ElectronHomeIsolation
} from './helpers/electron-home-isolation'
import {
discoverActivePtyId,
execInTerminal,
getTerminalContent,
waitForActiveTerminalManager,
waitForPaneCount,
waitForTerminalOutput
} from './helpers/terminal'
import { ensureTerminalVisible, waitForActiveWorktree, waitForSessionReady } from './helpers/store'
import { RuntimeClient } from '../../src/cli/runtime/client'
import { RuntimeClientError } from '../../src/cli/runtime/types'
import type {
RuntimeStatus,
RuntimeTerminalCreate,
RuntimeTerminalRead
} from '../../src/shared/runtime-types'
import { PROTOCOL_VERSION } from '../../src/main/daemon/types'
import { parsePaneKey } from '../../src/shared/stable-pane-id'
import { DEFAULT_LOCAL_ORCA_PROFILE_ID } from '../../src/shared/orca-profiles'
const electronPackageDir = path.join(process.cwd(), 'node_modules', 'electron')
const electronPath = path.join(
electronPackageDir,
'dist',
readFileSync(path.join(electronPackageDir, 'path.txt'), 'utf8').trim()
)
function createHeadlessLaunchIsolation(userDataDir: string): ElectronHomeIsolation {
const { ELECTRON_RUN_AS_NODE: _unused, ...cleanEnv } = process.env
void _unused
return createElectronHomeIsolation({
inheritedEnv: cleanEnv,
launchEnv: {
NODE_ENV: 'development',
ORCA_E2E_HEADLESS: '1',
// Why: production builds always use the lock; this opt-in makes the dev
// E2E bundle exercise the same second-instance ownership path.
ORCA_E2E_ENFORCE_SINGLE_INSTANCE_LOCK: '1'
},
extraEnv: {},
userDataDir
})
}
function readDaemonPid(userDataDir: string): number {
const raw = readFileSync(
path.join(userDataDir, 'daemon', `daemon-v${PROTOCOL_VERSION}.pid`),
'utf8'
)
const parsed = JSON.parse(raw) as { pid?: unknown }
if (typeof parsed.pid !== 'number') {
throw new Error(`Daemon pid file did not contain a numeric pid: ${raw}`)
}
return parsed.pid
}
function readPersistedPromotionBinding(
userDataDir: string,
worktreeId: string,
tabId: string,
leafId: string
): { tabId: string; leafId: string; ptyId: string } | null {
try {
const persisted = JSON.parse(
readFileSync(
path.join(userDataDir, 'profiles', DEFAULT_LOCAL_ORCA_PROFILE_ID, 'orca-data.json'),
'utf8'
)
) as {
workspaceSession?: {
tabsByWorktree?: Record<string, { id?: string; ptyId?: string | null }[]>
terminalLayoutsByTabId?: Record<string, { ptyIdsByLeafId?: Record<string, string | null> }>
}
}
const tab = persisted.workspaceSession?.tabsByWorktree?.[worktreeId]?.find(
(candidate) => candidate.id === tabId
)
const ptyId =
persisted.workspaceSession?.terminalLayoutsByTabId?.[tabId]?.ptyIdsByLeafId?.[leafId]
return tab && ptyId ? { tabId, leafId, ptyId } : null
} catch {
return null
}
}
async function waitForProcessExit(child: ChildProcess, timeoutMs: number): Promise<boolean> {
if (child.exitCode !== null || child.signalCode !== null) {
return true
}
return await new Promise((resolve) => {
const onExit = (): void => {
clearTimeout(timeout)
resolve(true)
}
const timeout = setTimeout(() => {
child.off('exit', onExit)
resolve(false)
}, timeoutMs)
child.once('exit', onExit)
})
}
test.describe.configure({ mode: 'serial' })
test('promotes the headless owner without replacing its daemon terminal', async (// oxlint-disable-next-line no-empty-pattern -- This lifecycle test owns both launches and intentionally opts out of the default app fixture.
{}, testInfo) => {
const repoPath = readFileSync(TEST_REPO_PATH_FILE, 'utf8').trim()
if (!repoPath || !existsSync(repoPath)) {
test.skip(true, 'Global setup did not produce a seeded test repo')
return
}
const mainPath = path.join(process.cwd(), 'out', 'main', 'index.js')
const userDataDir = mkdtempSync(path.join(os.tmpdir(), 'orca-e2e-serve-promotion-'))
const homeIsolation = createHeadlessLaunchIsolation(userDataDir)
const env = homeIsolation.env
let serveApp: ElectronApplication | null = null
let activatingProcess: ChildProcess | null = null
writeFileSync(
path.join(userDataDir, 'orca-data.json'),
`${JSON.stringify(getE2ECompletedOnboardingProfile(), null, 2)}\n`
)
try {
serveApp = await electron.launch({
args: [...getOrcaElectronLaunchArgs(mainPath, false), '--serve', '--serve-no-pairing'],
env
})
const resolvedHome = await serveApp.evaluate(({ app }) => app.getPath('home'))
assertElectronResolvedIsolatedHome(resolvedHome, homeIsolation)
// Why: this spec bypasses the app fixture, so opt into its gated Electron log capture for CI failures.
forwardElectronProcessLogs(serveApp, testInfo)
const ownerPid = serveApp.process().pid
const client = new RuntimeClient(userDataDir, 5_000)
await expect
.poll(
async () => {
try {
return (await client.getCliStatus()).result.app.desktopWindowStatus
} catch (error) {
if (error instanceof RuntimeClientError && error.code === 'runtime_unavailable') {
return 'starting'
}
throw error
}
},
{
timeout: 60_000,
message: 'headless serve never became safely openable'
}
)
.toBe('openable')
const beforeStatus = await client.call<RuntimeStatus>('status.get')
const daemonPidBefore = readDaemonPid(userDataDir)
await client.call('repo.add', { path: repoPath, kind: 'git' })
const created = await client.call<{ terminal: RuntimeTerminalCreate }>('terminal.create', {
worktree: `path:${repoPath}`,
title: 'Serve promotion continuity'
})
const terminal = created.result.terminal
const originalPtyId = terminal.ptyId
const originalTabId = terminal.tabId
const paneIdentity = terminal.paneKey ? parsePaneKey(terminal.paneKey) : null
if (!originalPtyId || !originalTabId || !paneIdentity) {
throw new Error('Headless terminal did not expose its durable pane and daemon PTY identity')
}
const beforeMarker = `SERVE_PROMOTION_BEFORE_${Date.now()}`
await client.call('terminal.send', {
terminal: terminal.handle,
text: `echo ${beforeMarker}`,
enter: true
})
await expect
.poll(
async () => {
const response = await client.call<{ terminal: RuntimeTerminalRead }>('terminal.read', {
terminal: terminal.handle,
limit: 200
})
return response.result.terminal.tail.join('\n')
},
{ timeout: 15_000 }
)
.toContain(beforeMarker)
const forwardAppLogs = process.env.ORCA_E2E_FORWARD_APP_LOGS === '1'
activatingProcess = spawn(electronPath, getOrcaElectronLaunchArgs(mainPath, false), {
env,
stdio: forwardAppLogs ? 'pipe' : 'ignore'
})
activatingProcess.on('error', (error) => {
console.error('[e2e] activating process failed to spawn:', error)
})
if (forwardAppLogs) {
const prefix = '[e2e] activating process'
activatingProcess.stdout?.on('data', (chunk: Buffer) => {
console.log(`${prefix} stdout: ${chunk.toString().trimEnd()}`)
})
activatingProcess.stderr?.on('data', (chunk: Buffer) => {
console.error(`${prefix} stderr: ${chunk.toString().trimEnd()}`)
})
activatingProcess.on('exit', (code, signal) => {
console.log(`${prefix} exit: code=${code ?? 'null'} signal=${signal ?? 'null'}`)
})
}
await expect
.poll(
() =>
readPersistedPromotionBinding(
userDataDir,
terminal.worktreeId,
originalTabId,
paneIdentity.leafId
),
{
timeout: 15_000,
message: 'headless terminal binding was not persisted during desktop promotion'
}
)
.toEqual({
tabId: originalTabId,
leafId: paneIdentity.leafId,
ptyId: originalPtyId
})
const page = await serveApp.firstWindow({ timeout: 60_000 })
await page.waitForLoadState('domcontentloaded')
await page.waitForFunction(() => Boolean(window.__store), null, { timeout: 30_000 })
await waitForSessionReady(page)
await waitForActiveWorktree(page)
await ensureTerminalVisible(page)
await waitForActiveTerminalManager(page, 30_000)
await waitForPaneCount(page, 1, 30_000)
const promotedPtyId = await discoverActivePtyId(page)
const afterStatus = await client.call<RuntimeStatus>('status.get')
expect(serveApp.process().pid).toBe(ownerPid)
expect(afterStatus.result.runtimeId).toBe(beforeStatus.result.runtimeId)
expect(afterStatus.result.desktopWindowStatus).toBe('available')
expect(promotedPtyId).toBe(terminal.ptyId)
expect(readDaemonPid(userDataDir)).toBe(daemonPidBefore)
expect(await waitForProcessExit(activatingProcess, 10_000)).toBe(true)
await waitForTerminalOutput(page, beforeMarker, 30_000)
const afterMarker = `SERVE_PROMOTION_AFTER_${Date.now()}`
await execInTerminal(page, promotedPtyId, `echo ${afterMarker}`)
await waitForTerminalOutput(page, afterMarker, 15_000)
await expect(page.locator('.xterm:visible').first()).toBeVisible()
expect(await getTerminalContent(page)).toContain(beforeMarker)
} finally {
if (activatingProcess && activatingProcess.exitCode === null) {
activatingProcess.kill('SIGKILL')
await waitForProcessExit(activatingProcess, 5_000)
}
if (serveApp) {
await closeElectronAppForE2E(serveApp)
}
await cleanupE2EDaemons(userDataDir)
rmSync(userDataDir, { recursive: true, force: true, maxRetries: 20, retryDelay: 100 })
}
})