test(e2e): register a real runtime host and publish the alt-screen frame as its snapshot

Two long-running scheduled-E2E failures on main were stale test setup, not
product defects.

`onboarding.spec.ts:420` seeded the Active Server by faking a runtime
environment in the renderer store and writing `activeRuntimeEnvironmentId`
through the generic `settings:set` IPC. Since #10011 that setter strips the
key, and the dedicated `settings:set-active-runtime-environment-preference`
handler resolves the id against the main-process environment store — CI
logged `RuntimeEnvironmentStoreError: Unknown environment: env-e2e` from
`runtimeEnvironments:subscribe`/`:call` alongside the assertion failure.
Register the host for real via `runtimeEnvironments:addFromPairingCode`
(offline; no live server) and write the preference through its own channel.

`terminal-tab-switch-visual-restore.spec.ts:604` wrote alt-screen frames
straight into the renderer's xterm, so those bytes never transited the PTY
and main's model could not contain them. On cycle 0 the freshly spawned
shell still has queued startup output, so hiding the pane makes main's
hidden-delivery gate drop bytes and latch a reveal restore, which repaints
main's snapshot over the fabricated frame; later cycles run against an idle
shell and survive. Arm the existing `setHiddenSnapshotOverride` seam (already
used by sibling tests in this file) with the same frame so the live-write and
restore paths render identically, and keep the `markerPresent` assertion.

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Jinjing
2026-07-25 13:58:39 -07:00
co-authored by Orca
parent e564603d54
commit 0fa49cf162
2 changed files with 76 additions and 78 deletions
+34 -30
View File
@@ -12,6 +12,7 @@ import { waitForSessionReady } from './helpers/store'
import type { Page } from '@stablyai/playwright-test'
import type { GlobalSettings, TuiAgent } from '../../src/shared/types'
import { ONBOARDING_FINAL_STEP } from '../../src/shared/constants'
import { encodePairingOffer, PAIRING_OFFER_VERSION } from '../../src/shared/pairing'
type OnboardingState = {
closedAt: number | null
@@ -421,43 +422,38 @@ test.describe('Onboarding flow', () => {
await expect(orcaPage.getByRole('heading', { name: /Pick your default agent/i })).toBeVisible({
timeout: 15_000
})
await orcaPage.evaluate(async () => {
// Why: since #10011 `settings:set` strips activeRuntimeEnvironmentId — the
// durable Active Server preference is only writable through its dedicated
// handler, which resolves the id against the main-process environment
// store. So the host has to be registered for real, not faked in the
// renderer. Pairing is offline (no live server needed).
const pairingCode = encodePairingOffer({
v: PAIRING_OFFER_VERSION,
scope: 'runtime',
endpoint: 'wss://e2e.invalid/ws',
deviceToken: 'e2e-device-token',
publicKeyB64: 'ZTJlLXB1YmxpYy1rZXk'
})
const environmentId = await orcaPage.evaluate(async (code) => {
const store = window.__store
if (!store) {
throw new Error('window.__store is not available')
}
const { environment } = await window.api.runtimeEnvironments.addFromPairingCode({
name: 'E2E Server',
pairingCode: code
})
// Why: after #5071 the server-path add step gates on the registered
// runtime-environment list (store.runtimeEnvironments), not just the
// activeRuntimeEnvironmentId setting. Seed a redacted environment so the
// host option exists and the "on host" add UI renders.
const now = Date.now()
store.getState().setRuntimeEnvironments([
{
id: 'env-e2e',
name: 'E2E Server',
createdAt: now,
updatedAt: now,
lastUsedAt: null,
runtimeId: null,
source: 'manual',
endpoints: [
{
id: 'ws-env-e2e',
kind: 'websocket',
label: 'WebSocket',
endpoint: 'wss://e2e.invalid/ws'
}
],
preferredEndpointId: 'ws-env-e2e'
}
])
// activeRuntimeEnvironmentId setting.
store.getState().setRuntimeEnvironments(await window.api.runtimeEnvironments.list())
// Why: a runtime host is only auto-selectable (health 'available') when it
// has a live, protocol-compatible status; without one it reads
// 'disconnected' and the Add Project dialog falls back to Local Mac.
// runtimeProtocolVersion 3 clears MIN_COMPATIBLE_RUNTIME_SERVER_VERSION.
store.getState().setRuntimeEnvironmentStatus('env-e2e', {
store.getState().setRuntimeEnvironmentStatus(environment.id, {
status: {
runtimeId: 'env-e2e-runtime',
runtimeId: `${environment.id}-runtime`,
rendererGraphEpoch: 0,
graphStatus: 'ready',
authoritativeWindowId: null,
@@ -466,15 +462,23 @@ test.describe('Onboarding flow', () => {
runtimeProtocolVersion: 3,
minCompatibleRuntimeClientVersion: 1
},
checkedAt: now
checkedAt: Date.now()
})
await store.getState().updateSettings({ activeRuntimeEnvironmentId: 'env-e2e' })
})
// Why: the store's switchRuntimeEnvironment probes reachability, which a
// synthetic host can't satisfy — write the preference directly and push
// the returned settings in rather than refetching (fetchSettings would
// kick off a status hydrate that clobbers the seeded 'available' health).
const settings = await window.api.settings.setActiveRuntimeEnvironmentPreference({
environmentId: environment.id
})
store.setState({ settings })
return environment.id
}, pairingCode)
await expect
.poll(async () => (await getSettings(orcaPage)).activeRuntimeEnvironmentId, {
timeout: 5_000
})
.toBe('env-e2e')
.toBe(environmentId)
await onboardingFooterButton(orcaPage, SKIP_TO_PROJECT_SETUP_BUTTON).click()
@@ -289,6 +289,35 @@ async function injectPaneData(
}
}
function buildAltScreenFrame(marker: string, frame: number): string {
const progress = `${'█'.repeat((frame % 8) + 1)}${'░'.repeat(8 - ((frame % 8) + 1))}`
return [
'\x1b[?2026h',
'\x1b[?1049h',
'\x1b[2J\x1b[H',
'\x1b[?25l',
`╭────────────────────────────────────────────────────────────────────╮`,
`│ ${marker} frame ${String(frame).padStart(3, '0')} ${progress} │`,
`│ Dimension │ Rating │`,
`╰────────────────────────────────────────────────────────────────────╯`,
'\x1b[?2026l'
].join('\r\n')
}
async function writeToPaneTerminal(page: Page, tabId: string, data: string): Promise<void> {
await page.evaluate(
({ tabId, data }) => {
const manager = window.__paneManagers?.get(tabId)
const pane = manager?.getActivePane?.() ?? manager?.getPanes?.()[0]
if (!pane) {
throw new Error(`No terminal pane for tab ${tabId}`)
}
return new Promise<void>((resolve) => pane.terminal.write(data, resolve))
},
{ tabId, data }
)
}
async function setHiddenSnapshotOverride(
page: Page,
ptyId: string,
@@ -611,63 +640,28 @@ test.describe('Terminal tab switch visual restore', () => {
const { firstTabId, secondTabId } = await ensureTwoTerminalTabs(orcaPage)
await forceWebglOnActiveTab(orcaPage)
await waitForPanePtyIdOnTab(orcaPage, firstTabId)
const runId = `${Date.now()}`
const finalMarker = `${TAB_SWITCH_MARKER_PREFIX}_${runId}_ALT_24`
await orcaPage.evaluate(
({ tabId, finalMarker }) => {
const manager = window.__paneManagers?.get(tabId)
const pane = manager?.getActivePane?.() ?? manager?.getPanes?.()[0]
if (!pane) {
throw new Error(`No terminal pane for tab ${tabId}`)
}
const frames = Array.from({ length: 25 }, (_, frame) => {
const progress = `${'█'.repeat((frame % 8) + 1)}${'░'.repeat(8 - ((frame % 8) + 1))}`
return [
'\x1b[?2026h',
'\x1b[?1049h',
'\x1b[2J\x1b[H',
'\x1b[?25l',
`╭────────────────────────────────────────────────────────────────────╮`,
`│ ${finalMarker} frame ${String(frame).padStart(3, '0')} ${progress} │`,
`│ Dimension │ Rating │`,
`╰────────────────────────────────────────────────────────────────────╯`,
'\x1b[?2026l'
].join('\r\n')
}).join('')
return new Promise<void>((resolve) => pane.terminal.write(frames, resolve))
},
{ tabId: firstTabId, finalMarker }
await writeToPaneTerminal(
orcaPage,
firstTabId,
Array.from({ length: 25 }, (_, frame) => buildAltScreenFrame(finalMarker, frame)).join('')
)
const corruptionReports: string[] = []
for (let cycle = 0; cycle < 6; cycle += 1) {
const redraw = buildAltScreenFrame(finalMarker, cycle * 4)
// Why: this frame never transits the PTY, so a reveal restore would
// repaint main's model over it. Publish it as the snapshot too, so both
// the live-write and restore paths land the same frame. Identity is
// re-read per cycle because a reattach would re-key the override.
const { ptyId, cols, rows } = await readPaneIdentityOnTab(orcaPage, firstTabId)
await setHiddenSnapshotOverride(orcaPage, ptyId, { data: redraw, cols, rows })
await activateTerminalTab(orcaPage, secondTabId)
await orcaPage.evaluate(
({ tabId, finalMarker, cycle }) => {
const manager = window.__paneManagers?.get(tabId)
const pane = manager?.getActivePane?.() ?? manager?.getPanes?.()[0]
if (!pane) {
throw new Error(`No terminal pane for tab ${tabId}`)
}
const frame = cycle * 4
const progress = `${'█'.repeat((frame % 8) + 1)}${'░'.repeat(8 - ((frame % 8) + 1))}`
const redraw = [
'\x1b[?2026h',
'\x1b[?1049h',
'\x1b[2J\x1b[H',
'\x1b[?25l',
`╭────────────────────────────────────────────────────────────────────╮`,
`│ ${finalMarker} frame ${String(frame).padStart(3, '0')} ${progress} │`,
`│ Dimension │ Rating │`,
`╰────────────────────────────────────────────────────────────────────╯`,
'\x1b[?2026l'
].join('\r\n')
return new Promise<void>((resolve) => pane.terminal.write(redraw, resolve))
},
{ tabId: firstTabId, finalMarker, cycle }
)
await writeToPaneTerminal(orcaPage, firstTabId, redraw)
await activateTerminalTab(orcaPage, firstTabId)
const geometry = await readTabTerminalGeometry(orcaPage, firstTabId, `${runId}_ALT`)