test(e2e): track WSL stub agent staging state for precise cleanup

Refactor `stageWslGoldenStubAgent` to track which artifacts it creates
during setup, then only remove those artifacts during cleanup. This
prevents the test from destructively removing pre-existing symlinks or
state from previous runs, improving test isolation and idempotency.
This commit is contained in:
Jinjing
2026-08-23 12:57:55 -07:00
parent c170ac2317
commit e996259b58
3 changed files with 36 additions and 29 deletions
+4 -10
View File
@@ -17,11 +17,7 @@ import { ensureTerminalVisible, waitForActiveWorktree, waitForSessionReady } fro
import { getTerminalContent } from './helpers/terminal'
import type { BuiltInWindowsTerminalShell } from '../../src/shared/windows-terminal-shell'
// Covers the full tab-bar `+` agent launch chain — detection row, startup-plan
// build, tab create, PTY spawn under the configured runtime, startup-command
// injection. golden-agent-tui-launch.spec.ts covers one agent on the default
// shell and never runs in the Windows golden lane, so a Windows-only break
// anywhere in that chain ships as "clicking an agent does nothing".
// Covers the Windows-only tab-bar launch path that the default-shell test misses.
test.use({ launchEnv: getGoldenStubAgentLaunchEnv() })
@@ -80,10 +76,8 @@ test.describe('Windows runtimes', () => {
const distro = await getFirstWslDistro(orcaPage)
test.skip(!distro, 'No WSL distro is available on this Windows host')
test.skip(
!stageWslGoldenStubAgent(distro!),
'WSL distro would not accept the staged stub agent'
)
const stage = stageWslGoldenStubAgent(distro!)
test.skip(!stage, 'WSL distro would not accept the staged stub agent')
try {
// Why the runtime switch and not terminalWindowsShell: WSL is a project
@@ -97,7 +91,7 @@ test.describe('Windows runtimes', () => {
// ran in WSL rather than on the Windows host.
expect(await getTerminalContent(orcaPage)).toContain(GOLDEN_STUB_READY_MARKER)
} finally {
removeWslGoldenStubAgent(distro!)
removeWslGoldenStubAgent(distro!, stage!)
}
})
})
+1 -2
View File
@@ -7,8 +7,7 @@ import type { BuiltInWindowsTerminalShell } from '../../../src/shared/windows-te
export const GOLDEN_STUB_READY_MARKER = 'GOLDEN_STUB_AGENT_READY'
export const GOLDEN_STUB_EXIT_MARKER = 'GOLDEN_STUB_AGENT_EXITED'
/** Agents the fixture dir shims onto PATH, so detection lists them and the
* tab-bar `+` menu renders a launch row for each. */
/** Agents exposed by the fixture directory for tab-bar detection. */
export const GOLDEN_STUB_AGENTS = [
{ id: 'codex', menuItemName: /^Codex(?:\s|$)/i },
{ id: 'claude', menuItemName: /^Claude(?:\s|$)/i }
+31 -17
View File
@@ -2,8 +2,7 @@ import { execFileSync } from 'node:child_process'
import type { Page } from '@stablyai/playwright-test'
import { buildWslExecArgs } from '../../../src/shared/wsl-login-shell-command'
/** Distro path the stub is staged at. Only reachable from inside WSL, so the
* stub's marker in a pane is itself proof the pane executed in the distro. */
/** A WSL-only path makes the stub marker proof that the pane ran in the distro. */
const WSL_STUB_PATH = '/usr/local/bin/golden-stub-agent'
const WSL_STUB_AGENT_LINK = '/usr/local/bin/codex'
@@ -12,18 +11,23 @@ const WSL_STUB_AGENT_LINK = '/usr/local/bin/codex'
const STAGE_SCRIPT =
`mkdir -p /usr/local/bin && ` +
`printf '#!/bin/sh\\necho GOLDEN_STUB_AGENT_READY\\nexec sleep 3600\\n' > ${WSL_STUB_PATH} && ` +
`chmod 0755 ${WSL_STUB_PATH} && ` +
`ln -sf ${WSL_STUB_PATH} ${WSL_STUB_AGENT_LINK}`
`chmod 0755 ${WSL_STUB_PATH}`
const UNSTAGE_SCRIPT = `rm -f ${WSL_STUB_AGENT_LINK} ${WSL_STUB_PATH}`
const STAGE_CODEX_LINK_IF_MISSING_SCRIPT =
`if [ -e ${WSL_STUB_AGENT_LINK} ] || [ -L ${WSL_STUB_AGENT_LINK} ]; then ` +
`printf existing; else ln -s ${WSL_STUB_PATH} ${WSL_STUB_AGENT_LINK} && printf created; fi`
const REMOVE_STUB_SCRIPT = `rm -f ${WSL_STUB_PATH}`
const REMOVE_CODEX_LINK_AND_STUB_SCRIPT = `rm -f ${WSL_STUB_AGENT_LINK} ${WSL_STUB_PATH}`
// Why --exec (via buildWslExecArgs): under `--`, wsl.exe expands `$name` in
// every argument and silently rewrites the script.
function runInWslAsRoot(distro: string, script: string): void {
execFileSync('wsl.exe', ['-u', 'root', ...buildWslExecArgs(distro, ['sh', '-c', script])], {
stdio: 'pipe',
windowsHide: true
})
function runInWslAsRoot(distro: string, script: string): string {
return execFileSync(
'wsl.exe',
['-u', 'root', ...buildWslExecArgs(distro, ['sh', '-c', script])],
{ encoding: 'utf8', stdio: 'pipe', windowsHide: true }
)
}
export async function getFirstWslDistro(page: Page): Promise<string | null> {
@@ -34,20 +38,30 @@ export async function getFirstWslDistro(page: Page): Promise<string | null> {
return wsl.available ? (wsl.distros[0] ?? null) : null
}
/** Returns false when the distro won't take the stub (no root interop, read-only
* rootfs); callers skip rather than fail, since that is a host limitation. */
export function stageWslGoldenStubAgent(distro: string): boolean {
export type WslGoldenStubAgentStage = {
createdCodexLink: boolean
}
/** Returns null when the distro cannot stage the stub. */
export function stageWslGoldenStubAgent(distro: string): WslGoldenStubAgentStage | null {
try {
runInWslAsRoot(distro, STAGE_SCRIPT)
return true
return {
createdCodexLink:
runInWslAsRoot(distro, STAGE_CODEX_LINK_IF_MISSING_SCRIPT).trim() === 'created'
}
} catch {
return false
removeWslGoldenStubAgent(distro, { createdCodexLink: false })
return null
}
}
export function removeWslGoldenStubAgent(distro: string): void {
export function removeWslGoldenStubAgent(distro: string, stage: WslGoldenStubAgentStage): void {
try {
runInWslAsRoot(distro, UNSTAGE_SCRIPT)
runInWslAsRoot(
distro,
stage.createdCodexLink ? REMOVE_CODEX_LINK_AND_STUB_SCRIPT : REMOVE_STUB_SCRIPT
)
} catch {
// Best-effort cleanup; a leftover stub only affects this fixture's own name.
}