Files
orca/tests/e2e/helpers/terminal-accessibility-tree.ts
Neil 2a53293b11 test(e2e): stop a spec's parking-delay override from leaking into the rest of its worker (#21571)
* test(e2e): scope the parking-delay override to the spec that needs it

A Playwright worker imports many spec files into one Node process, and the app
fixtures launch Electron with a spread of that process's env. The split-
orientation spec set ORCA_E2E_TERMINAL_PARKING_DELAY_MS at module scope, so the
2s override outlived the file and reconfigured every app launched by every spec
that followed it in the same worker — measured directly: a probe spec sees a
30000ms cold-park delay on its own and 2000ms when that spec runs first.

Module scope is the part that cannot be undone. A write inside a test body can
save and restore, as four other specs here do; a write at import time runs
before any hook exists to restore it. test.use({ orcaAppExtraEnv }) reaches the
app launch without touching the worker every other spec shares.

The ratchet holds the module-scope writer count at zero.

* test(e2e): re-apply screen-reader mode while reading the accessibility tree

Separate from the env leak above, and unproven against the CI failure it
resembles: this is robustness, not a diagnosed fix.

screenReaderMode is an option on the xterm instance and the accessibility tree
belongs to that instance's DOM. The SSH cold-activation spec set it once,
imperatively, then waited on the node. A pane that parks and remounts, or
rebinds after a reconnect, comes back as a new instance with the option off, so
the one-shot mutation stops producing the node the wait is waiting for and the
wait reports "element(s) not found" rather than a content mismatch.

The helper re-applies the option inside the poll and returns null when the node
is absent, so a replaced instance is retried instead of being fatal. Five other
specs still use the one-shot pattern and are left alone.
2026-09-18 23:25:52 -07:00

47 lines
1.8 KiB
TypeScript

import type { Page } from '@stablyai/playwright-test'
import { expect } from '@stablyai/playwright-test'
/**
* Reads a tab's `.xterm-accessibility-tree` text, enabling screen-reader mode on the pane it
* finds each time it is called.
*
* Why re-apply rather than set it once up front: `screenReaderMode` is an option on the xterm
* instance, and the node it renders belongs to that instance's DOM. A pane that parks and
* remounts, or rebinds after a reconnect, comes back as a new instance with the option off and
* no tree — so a one-shot mutation before the read stops producing the very node the read is
* waiting for, and the wait fails as "element not found" rather than as a content mismatch.
*
* Returns null when the pane or the node is not there yet, so a poll keeps retrying.
*/
export function readTerminalAccessibilityText(page: Page, tabId: string): Promise<string | null> {
return page.evaluate((id) => {
const manager = window.__paneManagers?.get(id)
const pane = manager?.getActivePane?.() ?? manager?.getPanes?.()[0]
if (!pane) {
return null
}
if (!pane.terminal.options.screenReaderMode) {
pane.terminal.options.screenReaderMode = true
pane.terminal.refresh(0, pane.terminal.rows - 1)
}
const node = document.querySelector(
`[data-terminal-tab-id="${CSS.escape(id)}"] .xterm-accessibility-tree`
)
return node?.textContent ?? null
}, tabId)
}
export async function expectTerminalAccessibilityText(
page: Page,
tabId: string,
expected: string,
timeoutMs = 30_000
): Promise<void> {
await expect
.poll(() => readTerminalAccessibilityText(page, tabId), {
timeout: timeoutMs,
message: `terminal tab ${tabId} never rendered ${expected} in its accessibility tree`
})
.toContain(expected)
}