From 852ee907ee3aa6fe644628a5d656ba8b7635f50d Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Wed, 16 Sep 2026 15:27:35 -0700 Subject: [PATCH] fix(e2e): stabilize flaky E2E tests against timing races (#20900) * fix(e2e): stabilize flaky E2E tests against timing races - Paired terminal: use stable cold activation assertion instead of racy one-shot read; background tabs park eagerly. - Native chat: scope hydration assertions to transcript subtree to avoid false positives from UI chrome (worktree rows, tab titles). - Onboarding: inject verified status snapshot with max sequence to prevent hydration from downgrading host health during skip-to- project-setup. - Paired web: encode host health faults in snapshots with high sequence so real hydrations cannot outbid injected state. - Quick open: clear prior tooltips and increase hover timeouts to handle streaming result remounting. - Terminal attention: pass 'terminal-bell' to unread marker to match production contract (reads marker value, not presence). * fix one last test --- .../paired-terminal-cold-activation-oracle.ts | 12 ++++-- .../e2e/native-chat-first-flush-race.spec.ts | 7 +++- tests/e2e/onboarding.spec.ts | 41 ++++++++++++++++++- tests/e2e/quick-open-file-paths.spec.ts | 12 +++--- tests/e2e/terminal-attention.spec.ts | 9 +++- 5 files changed, 68 insertions(+), 13 deletions(-) diff --git a/tests/e2e/helpers/paired-terminal-cold-activation-oracle.ts b/tests/e2e/helpers/paired-terminal-cold-activation-oracle.ts index f11bb6e284a..19128fbc131 100644 --- a/tests/e2e/helpers/paired-terminal-cold-activation-oracle.ts +++ b/tests/e2e/helpers/paired-terminal-cold-activation-oracle.ts @@ -4,8 +4,7 @@ import { toWebTerminalSurfaceTabId } from '../../../src/shared/terminal-surface- import { expect } from './orca-app' import { callColdActivationRuntime, - expectStableColdActivationMountState, - readColdActivationMountState + expectStableColdActivationMountState } from './paired-terminal-cold-activation-observation' import { createPairedTerminalParkingFixture } from './paired-terminal-parking-fixture' import { getTerminalContent, waitForActivePanePtyId } from './terminal' @@ -161,7 +160,14 @@ export async function runPairedTerminalColdActivationOracle( originalPtyId: originalPtyIds[index]! })) const tabIds = tabs.map((tab) => tab.tabId) - expect(await readColdActivationMountState(page, tabIds)).toEqual({ mounted: 0, parked: 0 }) + // Why: background tabs park eagerly (parking delay is 100ms while tab + // creation plus the PTY-id poll above takes far longer), so a one-shot + // read races the sweeper. Parked-but-unmounted is the cold resting state + // this oracle asserts again after first activation (1 mounted + 7 parked). + await expectStableColdActivationMountState(page, tabIds, { + mounted: 0, + parked: TARGET_TAB_COUNT + }) await page.evaluate( ({ activeTabId, targetWorktreeId }) => { diff --git a/tests/e2e/native-chat-first-flush-race.spec.ts b/tests/e2e/native-chat-first-flush-race.spec.ts index 2e85e2dc132..979cb33117c 100644 --- a/tests/e2e/native-chat-first-flush-race.spec.ts +++ b/tests/e2e/native-chat-first-flush-race.spec.ts @@ -166,8 +166,11 @@ test.describe('Native chat first-flush transcript race (#8401)', () => { 'The main process now retries a not-yet-flushed transcript instead of caching a permanent miss.' writeFileSync(transcriptPath, claudeTranscriptLines({ sessionId, userText, assistantText })) - await expect(orcaPage.getByText(userText)).toBeVisible({ timeout: 30_000 }) - await expect(orcaPage.getByText(assistantText)).toBeVisible({ timeout: 30_000 }) + // Why: the user text also surfaces as chrome (worktree row, tab + // title), so scope hydration assertions to the transcript subtree. + const transcript = orcaPage.locator('[data-native-chat-root="true"]') + await expect(transcript.getByText(userText)).toBeVisible({ timeout: 30_000 }) + await expect(transcript.getByText(assistantText)).toBeVisible({ timeout: 30_000 }) await expect(orcaPage.getByText(ERROR_TITLE)).toHaveCount(0) await orcaPage.screenshot({ path: path.join(screenshotDir, '02-hydrated.png') diff --git a/tests/e2e/onboarding.spec.ts b/tests/e2e/onboarding.spec.ts index 4836e08265c..2852ad43c07 100644 --- a/tests/e2e/onboarding.spec.ts +++ b/tests/e2e/onboarding.spec.ts @@ -496,12 +496,51 @@ test.describe('Onboarding flow', () => { }) .toBe(environmentId) + // Why: runtime-host health now derives from the status snapshot (transport + // + verification) whenever one exists, and a snapshot also blocks later + // status-only writes — so a status seed alone no longer reads 'available' + // and the host selector falls back to Local. Publish a verified, ready + // snapshot with a high sequence so later real snapshots cannot downgrade + // it, modelling a reachable host for the skip-to-project-setup path. + await orcaPage.evaluate((id) => { + const store = window.__store + if (!store) { + throw new Error('window.__store is not available') + } + const state = store.getState() + const environment = state.runtimeEnvironments.find((entry) => entry.id === id) + if (!environment) { + throw new Error('runtime environment was not registered') + } + state.applyRuntimeHostStatusSnapshot({ + environmentId: id, + pairingRevision: environment.pairingRevision ?? environment.createdAt, + sequence: 2_147_483_647, + checkedAt: Date.now(), + status: { + runtimeId: `${id}-runtime`, + rendererGraphEpoch: 0, + graphStatus: 'ready', + authoritativeWindowId: null, + liveTabCount: 0, + liveLeafCount: 0, + runtimeProtocolVersion: 3, + minCompatibleRuntimeClientVersion: 1 + }, + verification: 'verified', + transport: 'ready', + remoteControl: null + }) + }, environmentId) + await onboardingFooterButton(orcaPage, SKIP_TO_PROJECT_SETUP_BUTTON).click() await expectAddProjectDialog(orcaPage) // The runtime env is selected as the Add Project host and the browse action // is host-scoped, proving the server project-setup UI is preserved on skip. - await expect(orcaPage.getByText('Existing Git repository or folder on this host')).toBeVisible() + await expect(orcaPage.getByText('Existing Git repository or folder on this host')).toBeVisible({ + timeout: 30_000 + }) await expect(orcaPage.getByRole('button', { name: /Browse folder/i })).toBeVisible() await expect(orcaPage.getByRole('button', { name: /Clone from URL/i })).toBeVisible() await expect(orcaPage.getByRole('button', { name: /Create new project/i })).toBeVisible() diff --git a/tests/e2e/quick-open-file-paths.spec.ts b/tests/e2e/quick-open-file-paths.spec.ts index 8625af42082..68933086815 100644 --- a/tests/e2e/quick-open-file-paths.spec.ts +++ b/tests/e2e/quick-open-file-paths.spec.ts @@ -45,12 +45,14 @@ test('cmd+p quick open prioritizes the filename and reveals the full path on hov const tooltip = orcaPage .locator('[data-slot="tooltip-content"]') .filter({ hasText: relativeFilePath }) - // Streaming results can remount the row under a stationary pointer. + // Streaming results can remount the row under a stationary pointer, and a + // tooltip left open from a prior attempt can swallow the next hover. await expect(async () => { - await row.hover({ position: { x: 20, y: 12 }, timeout: 1_000 }) - await row.hover({ position: { x: 40, y: 12 }, timeout: 1_000 }) - await expect(tooltip).toBeVisible({ timeout: 1_000 }) - }).toPass({ timeout: 10_000, intervals: [100, 250, 500] }) + await orcaPage.mouse.move(8, 8) + await row.hover({ position: { x: 20, y: 12 }, timeout: 2_000 }) + await row.hover({ position: { x: 40, y: 12 }, timeout: 2_000 }) + await expect(tooltip).toBeVisible({ timeout: 2_000 }) + }).toPass({ timeout: 15_000, intervals: [100, 250, 500] }) // Exact cursor placement is arithmetic, unit-tested via cursorTooltipOffsets. // Asserting it here measures the app mid-reflow and is flaky; what E2E is diff --git a/tests/e2e/terminal-attention.spec.ts b/tests/e2e/terminal-attention.spec.ts index 22e334f53c8..3bf067f4569 100644 --- a/tests/e2e/terminal-attention.spec.ts +++ b/tests/e2e/terminal-attention.spec.ts @@ -191,7 +191,10 @@ test.describe('Terminal attention', () => { throw new Error(`No owner worktree found for terminal tab ${tabId}`) } state.markWorktreeUnread(ownerWorktreeId) - state.markTerminalTabUnread(tabId) + // Why: the attention contract reads the marker value, not key presence + // (#20525). Production always marks with 'terminal-bell'; a bare call + // stores undefined, which the DOM correctly ignores. + state.markTerminalTabUnread(tabId, 'terminal-bell') }, secondTabId) await expect @@ -310,7 +313,9 @@ test.describe('Terminal attention', () => { // Focused BEL owns the tab indicator; seed pane attention separately so the // Escape path proves it clears both store surfaces that pty-connection owns. await orcaPage.evaluate((paneKey) => { - window.__store?.getState().markTerminalPaneUnread(paneKey) + // Why: consumers read the marker value, not key presence (#20525); a bare + // call seeds `undefined`, which the pane attention DOM correctly ignores. + window.__store?.getState().markTerminalPaneUnread(paneKey, 'terminal-bell') }, activePaneKey) await expect .poll(async () => (await getUnreadTerminalPaneKeys(orcaPage)).includes(activePaneKey), {