From 5b7fdd7ef5b5df8caf0c0fce7f9f665dde6a83fd Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Wed, 22 Jul 2026 19:34:10 -0700 Subject: [PATCH] Capture daemon startup logs before window ready in e2e test (#10040) The daemon health-check guard logs during main-process startup, which can complete before the renderer window resolves. Moved stderr listening to the launch options so early logs aren't missed. Also made the assertion regex pattern-based instead of exact-string matching to tolerate benign log rewording, and added a check that the replace path stayed off. --- ...mon-slow-health-check-preservation.spec.ts | 20 ++++++++++++------- tests/e2e/helpers/orca-restart.ts | 20 +++++++++++++++++-- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/tests/e2e/daemon-slow-health-check-preservation.spec.ts b/tests/e2e/daemon-slow-health-check-preservation.spec.ts index 1f5e14510..ee583dade 100644 --- a/tests/e2e/daemon-slow-health-check-preservation.spec.ts +++ b/tests/e2e/daemon-slow-health-check-preservation.spec.ts @@ -83,11 +83,13 @@ test('preserves a live daemon PTY when the daemon is too slow for the startup he } }, RESUME_DAEMON_AFTER_MS) try { - const secondLaunch = await session.launch() - secondApp = secondLaunch.app - secondApp.process().stderr?.on('data', (chunk: Buffer) => { - stderrLines.push(chunk.toString()) + // Why: capture stderr from process start — the daemon guard logs its + // preservation decision during main-process startup, which can complete + // before firstWindow resolves, so a post-launch listener would miss it. + const secondLaunch = await session.launch({ + onStderr: (chunk) => stderrLines.push(chunk) }) + secondApp = secondLaunch.app await waitForSessionReady(secondLaunch.page) await expect @@ -101,11 +103,15 @@ test('preserves a live daemon PTY when the daemon is too slow for the startup he await waitForPaneCount(secondLaunch.page, 1, 30_000) await waitForTerminalOutput(secondLaunch.page, marker, 20_000) - // The guard path must actually have run: the daemon failed the health - // check and was preserved because its live session was verified. + // The guard path must actually have run and chosen preserve over replace: + // the daemon failed the health check yet was kept because its live session + // was verified. Match the stable "preserve…daemon…health check" concepts + // (not the exact wording) so a benign log reword doesn't flake, and + // confirm the replace path stayed off. await expect .poll(() => stderrLines.join(''), { timeout: 10_000 }) - .toContain('Preserving daemon that failed the health check') + .toMatch(/preserv\w*\s+daemon[^\n]*health check/i) + expect(stderrLines.join('')).not.toMatch(/\breplacing daemon\b/i) expect(readDaemonPid(session.userDataDir)).toBe(daemonPid) // Why: a killed daemon cold-restores scrollback from history, so the // marker text alone cannot distinguish a live session from a dead one. diff --git a/tests/e2e/helpers/orca-restart.ts b/tests/e2e/helpers/orca-restart.ts index bc14c3ccc..f31b5edb2 100644 --- a/tests/e2e/helpers/orca-restart.ts +++ b/tests/e2e/helpers/orca-restart.ts @@ -34,9 +34,19 @@ type LaunchedOrca = { page: Page } +type LaunchOptions = { + /** + * Called for each chunk the relaunched main process writes to stderr. The + * listener is attached before `firstWindow()` resolves so main-process + * startup logs (e.g. the daemon health-check guard) can't be emitted before + * the test starts capturing. + */ + onStderr?: (chunk: string) => void +} + type RestartSession = { userDataDir: string - launch: () => Promise + launch: (options?: LaunchOptions) => Promise /** Gracefully close a launch, letting beforeunload flush session state. */ close: (app: ElectronApplication) => Promise /** Remove the shared userDataDir after the test is done. */ @@ -137,7 +147,7 @@ export function createRestartSession( `${JSON.stringify(getE2ECompletedOnboardingProfile(), null, 2)}\n` ) - const launch = async (): Promise => { + const launch = async (options?: LaunchOptions): Promise => { runtimeWsPort ??= await reserveRestartRuntimeWsPort() const app = await electron.launch({ args: getOrcaElectronLaunchArgs(mainPath, headful), @@ -146,6 +156,12 @@ export function createRestartSession( ORCA_E2E_RUNTIME_WS_PORT: String(runtimeWsPort) } }) + // Why: attach before firstWindow — the main-process daemon guard can emit + // its decision line during startup, before the renderer window is ready. + if (options?.onStderr) { + const onStderr = options.onStderr + app.process().stderr?.on('data', (chunk: Buffer) => onStderr(chunk.toString())) + } try { const resolvedHome = await app.evaluate(({ app }) => app.getPath('home')) assertElectronResolvedIsolatedHome(resolvedHome, homeIsolation)