mirror of
https://github.com/stablyai/orca.git
synced 2026-09-23 16:02:24 +00:00
* 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.
92 lines
3.8 KiB
TypeScript
92 lines
3.8 KiB
TypeScript
import { readFileSync, readdirSync, statSync } from 'node:fs'
|
|
import { join, relative, resolve } from 'node:path'
|
|
import { describe, expect, it } from 'vitest'
|
|
|
|
/**
|
|
* One Playwright worker imports many spec files into one Node process, and the app fixtures
|
|
* launch Electron with a spread of that process's `process.env`. A module-scope write therefore
|
|
* reconfigures every app launched by every spec that follows in the same worker — and it runs at
|
|
* import time, before any hook or `finally` exists that could undo it, so unlike a write inside a
|
|
* test body it cannot be restored at all.
|
|
*
|
|
* That is not a hypothetical: a parking-delay override written this way shrank the terminal
|
|
* cold-park delay from 30s to 2s for later specs, which unmounted panes those specs still needed.
|
|
* Use `test.use({ orcaAppExtraEnv })`, which Playwright scopes to the file.
|
|
*/
|
|
const E2E_ROOT = resolve(__dirname)
|
|
|
|
/**
|
|
* Module scope is read off column 0. The tree is prettier-formatted, so every statement nested in
|
|
* a function, hook, block, or `app.evaluate` callback is indented — including the in-body writes
|
|
* that legitimately save and restore around a relaunch. A write that starts a line is top-level.
|
|
*/
|
|
const MODULE_SCOPE_ENV_WRITE =
|
|
/^(?:process\.env\.[A-Za-z_][A-Za-z0-9_]*\s*(?:\??\|\||\?\?|)=[^=]|process\.env\[|delete\s+process\.env[.[]|Object\.assign\(\s*process\.env)/
|
|
|
|
/**
|
|
* The count of files writing `process.env` at module scope.
|
|
*
|
|
* May only ever be DECREASED. Raising it is never the fix: the replacement is a fixture option,
|
|
* which is strictly more capable here because it reaches the app launch without touching the
|
|
* worker every other spec shares.
|
|
*/
|
|
const MODULE_SCOPE_ENV_WRITER_PIN = 0
|
|
|
|
const SCANNED_EXTENSIONS = ['.ts', '.tsx']
|
|
const IGNORED_DIRECTORIES = new Set(['node_modules', 'dist', 'out', 'build', '__fixtures__'])
|
|
|
|
function collectE2eFiles(root: string): string[] {
|
|
const found: string[] = []
|
|
for (const entry of readdirSync(root)) {
|
|
if (IGNORED_DIRECTORIES.has(entry)) {
|
|
continue
|
|
}
|
|
const path = join(root, entry)
|
|
if (statSync(path).isDirectory()) {
|
|
found.push(...collectE2eFiles(path))
|
|
} else if (SCANNED_EXTENSIONS.some((extension) => path.endsWith(extension))) {
|
|
found.push(path)
|
|
}
|
|
}
|
|
return found
|
|
}
|
|
|
|
function findModuleScopeEnvWrites(path: string): string[] {
|
|
return readFileSync(path, 'utf8')
|
|
.split('\n')
|
|
.flatMap((line, index) =>
|
|
MODULE_SCOPE_ENV_WRITE.test(line)
|
|
? [`${relative(E2E_ROOT, path)}:${index + 1}: ${line.trim()}`]
|
|
: []
|
|
)
|
|
}
|
|
|
|
describe('e2e worker env isolation', () => {
|
|
const offenders = collectE2eFiles(E2E_ROOT).flatMap(findModuleScopeEnvWrites)
|
|
|
|
it('no e2e file writes process.env at module scope', () => {
|
|
expect(offenders).toEqual([])
|
|
})
|
|
|
|
it('holds the module-scope env writer count at its ratchet', () => {
|
|
const files = new Set(offenders.map((offender) => offender.split(':')[0]))
|
|
expect(files.size).toBeLessThanOrEqual(MODULE_SCOPE_ENV_WRITER_PIN)
|
|
})
|
|
|
|
it('detects the shape it is meant to catch', () => {
|
|
// Guards the regex itself: a green that cannot go red would pass this whole file forever.
|
|
expect(MODULE_SCOPE_ENV_WRITE.test("process.env.ORCA_E2E_X ??= '1'")).toBe(true)
|
|
expect(MODULE_SCOPE_ENV_WRITE.test("process.env.ORCA_E2E_X = '1'")).toBe(true)
|
|
expect(MODULE_SCOPE_ENV_WRITE.test('delete process.env.ORCA_E2E_X')).toBe(true)
|
|
expect(MODULE_SCOPE_ENV_WRITE.test("Object.assign(process.env, { ORCA_E2E_X: '1' })")).toBe(
|
|
true
|
|
)
|
|
// Reads, and writes nested in any body, stay legal.
|
|
expect(MODULE_SCOPE_ENV_WRITE.test('const x = Number(process.env.ORCA_E2E_X) || 500')).toBe(
|
|
false
|
|
)
|
|
expect(MODULE_SCOPE_ENV_WRITE.test(" process.env.ORCA_E2E_X = '1'")).toBe(false)
|
|
expect(MODULE_SCOPE_ENV_WRITE.test("if (process.env.ORCA_E2E_X === '1') {")).toBe(false)
|
|
})
|
|
})
|