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
This commit is contained in:
Jinjing
2026-09-16 15:27:35 -07:00
committed by GitHub
parent 85d1ffc072
commit 852ee907ee
5 changed files with 68 additions and 13 deletions
@@ -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 }) => {
@@ -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')
+40 -1
View File
@@ -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()
+7 -5
View File
@@ -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
+7 -2
View File
@@ -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), {