diff --git a/tests/e2e/helpers/startup-exec-readiness-oracle.ts b/tests/e2e/helpers/startup-exec-readiness-oracle.ts index 577702c8ea0..7aa56e38863 100644 --- a/tests/e2e/helpers/startup-exec-readiness-oracle.ts +++ b/tests/e2e/helpers/startup-exec-readiness-oracle.ts @@ -9,6 +9,7 @@ import type { import { toWebTerminalSurfaceTabId } from '../../../src/shared/terminal-surface-id' import { expect } from './orca-app' import { getTerminalContent, waitForActivePanePtyId } from './terminal' +import { readFreshTerminalInventory } from './terminal-inventory-observation' const RECOVERY_DEADLINE_MS = 8_000 @@ -76,10 +77,6 @@ function count(text: string, marker: string): number { return text.split(marker).length - 1 } -function isTransientPtyLivenessError(error: unknown): boolean { - return error instanceof Error && error.message.includes('terminal_liveness_unavailable') -} - async function expectSingleOwningPty( page: Page, worktreeId: string, @@ -90,24 +87,15 @@ async function expectSingleOwningPty( await expect .poll( async () => { - try { - const listed = await callStartupExecRuntime( - page, - 'terminal.list', - { - worktree: `id:${worktreeId}`, - requireFreshPtyLiveness: true - } - ) - return listed.terminals - .filter((candidate) => candidate.tabId === tabId) - .map((candidate) => ({ handle: candidate.handle, ptyId: candidate.ptyId })) - } catch (error) { - if (isTransientPtyLivenessError(error)) { - return [] - } - throw error - } + const listed = await readFreshTerminalInventory(() => + callStartupExecRuntime(page, 'terminal.list', { + worktree: `id:${worktreeId}`, + requireFreshPtyLiveness: true + }) + ) + return (listed?.terminals ?? []) + .filter((candidate) => candidate.tabId === tabId) + .map((candidate) => ({ handle: candidate.handle, ptyId: candidate.ptyId })) }, { timeout: 30_000 } ) diff --git a/tests/e2e/helpers/terminal-inventory-observation.ts b/tests/e2e/helpers/terminal-inventory-observation.ts new file mode 100644 index 00000000000..0fcefdb23c8 --- /dev/null +++ b/tests/e2e/helpers/terminal-inventory-observation.ts @@ -0,0 +1,14 @@ +import type { RuntimeTerminalListResult } from '../../../src/shared/runtime-types' + +export async function readFreshTerminalInventory( + read: () => Promise +): Promise { + try { + return await read() + } catch (error) { + if (error instanceof Error && error.message.includes('terminal_liveness_unavailable')) { + return null + } + throw error + } +} diff --git a/tests/e2e/paired-remote-terminal-materialization-reconnect.spec.ts b/tests/e2e/paired-remote-terminal-materialization-reconnect.spec.ts index ef331ee6277..fda18fc74c6 100644 --- a/tests/e2e/paired-remote-terminal-materialization-reconnect.spec.ts +++ b/tests/e2e/paired-remote-terminal-materialization-reconnect.spec.ts @@ -16,6 +16,7 @@ import { launchPairedElectronClient } from './helpers/paired-electron-client' import { getTerminalContent, waitForActivePanePtyId } from './helpers/terminal' +import { readFreshTerminalInventory } from './helpers/terminal-inventory-observation' const scratch = mkdtempSync(path.join(os.tmpdir(), 'orca-paired-materialize-')) const fixturePath = path.join(scratch, 'materialize-terminal.mjs') @@ -316,18 +317,17 @@ async function runMaterializationJourney( await tab.click() await expect.poll(() => getTerminalContent(page), { timeout: 10_000 }).toContain(marker) - const listed = await callRuntime( - page, - environmentId, - 'terminal.list', - { - worktree: `id:${worktreeId}`, - requireFreshPtyLiveness: true - } - ) - expect( - listed.terminals.filter((terminal) => terminal.tabId === created.tab.parentTabId) - ).toHaveLength(1) + await expect + .poll(async () => { + const listed = await readFreshTerminalInventory(() => + callRuntime(page, environmentId, 'terminal.list', { + worktree: `id:${worktreeId}`, + requireFreshPtyLiveness: true + }) + ) + return listed?.terminals.filter((terminal) => terminal.tabId === created.tab.parentTabId) + }) + .toHaveLength(1) await callRuntime(page, environmentId, 'terminal.closeTab', { terminal: replacementHandle }) } @@ -354,15 +354,7 @@ test('materializes a stopped terminal on reconnect from a headed paired host', a } }) -// Why fixme: this journey's fault injection cannot be set up on a headless `orca serve` host. -// `terminal.stopExact` keeps returning terminal_exact_stop_failed because stopAndWait's -// keep-history verification window expires before the parked PTY is observed gone, so the pane -// never reaches pending-handle and the reconnect behavior is never exercised. That precondition -// fails identically on this PR's base, so it is a pre-existing exact-stop defect rather than a -// reconnect-activation one. The recovery behavior itself was confirmed by hand in this topology -// (the host materializes the pending surface and the client rebinds to the replacement PTY); -// re-enable once exact stop settles deterministically against a serve host. -test.fixme('materializes a stopped terminal on reconnect from a headless folder host', async ({ +test('materializes a stopped terminal on reconnect from a headless folder host', async ({ testRepoPath }, testInfo) => { test.setTimeout(150_000)