mirror of
https://github.com/stablyai/orca.git
synced 2026-09-21 16:02:20 +00:00
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:
@@ -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')
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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), {
|
||||
|
||||
Reference in New Issue
Block a user