test: add golden e2e tests for agent TUI launch and shell recovery

Add test fixtures and E2E tests to verify agent TUI functionality:
- Stub agent implementation supports cross-platform execution (Unix/Windows)
- Test verifies multiline composer with Shift+Enter support in agent TUI
- Test verifies clean shell resumes after agent exit without state leakage
This commit is contained in:
Jinjing
2026-08-13 09:06:24 -07:00
parent 1d2b8c7f42
commit f4782e8db6
8 changed files with 224 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
#!/bin/sh
exec "$(dirname "$0")/golden-stub-agent" "$@"
@@ -0,0 +1,2 @@
@echo off
call "%~dp0golden-stub-agent.cmd" %*
+2
View File
@@ -0,0 +1,2 @@
#!/bin/sh
exec node "$(dirname "$0")/golden-stub-agent.js" "$@"
@@ -0,0 +1,2 @@
@echo off
node "%~dp0golden-stub-agent.js" %*
+98
View File
@@ -0,0 +1,98 @@
#!/usr/bin/env node
const READY_MARKER = 'GOLDEN_STUB_AGENT_READY'
const EXIT_MARKER = 'GOLDEN_STUB_AGENT_EXITED'
let composer = ''
let lastSubmission = ''
let pendingInput = ''
let exiting = false
function render() {
const lines = composer.split('\n')
const renderedComposer = lines.map((line, index) => `${index === 0 ? '> ' : ' '}${line}`)
process.stdout.write(
`\x1b]0;Golden Stub Agent\x07${[
'\x1b[H\x1b[2JGolden Stub Agent',
`[${READY_MARKER}]`,
'',
...renderedComposer,
'',
'Shift+Enter inserts a newline. Type exit then Enter to quit.',
...(lastSubmission ? [`[GOLDEN_STUB_AGENT_SUBMITTED] ${lastSubmission}`] : [])
].join('\r\n')}`
)
}
function exitCleanly() {
if (exiting) {
return
}
exiting = true
if (process.stdin.isTTY) {
process.stdin.setRawMode(false)
}
process.stdout.write(`\x1b[?1049l[${EXIT_MARKER}]\r\n`, () => process.exit(0))
}
function submit() {
if (composer.trim() === 'exit') {
exitCleanly()
return
}
lastSubmission = composer
composer = ''
render()
}
function consumeInput() {
while (pendingInput.length > 0 && !exiting) {
const shiftEnter = ['\x1b[13;2u', '\x1b[13;2~', '\x1b\r'].find((sequence) =>
pendingInput.startsWith(sequence)
)
if (shiftEnter) {
pendingInput = pendingInput.slice(shiftEnter.length)
composer += '\n'
render()
continue
}
if (
pendingInput === '\x1b' ||
('\x1b[13;2u'.startsWith(pendingInput) && pendingInput.startsWith('\x1b[')) ||
('\x1b[13;2~'.startsWith(pendingInput) && pendingInput.startsWith('\x1b['))
) {
return
}
const char = pendingInput[0]
pendingInput = pendingInput.slice(1)
if (char === '\r' || char === '\n') {
submit()
} else if (char === '\x04') {
exitCleanly()
} else if (char === '\x7f' || char === '\b') {
composer = composer.slice(0, -1)
render()
} else if (char >= ' ') {
composer += char
render()
}
}
}
if (process.stdin.isTTY) {
process.stdin.setRawMode(true)
}
process.stdin.setEncoding('utf8')
process.stdin.on('data', (chunk) => {
pendingInput += chunk
consumeInput()
})
process.stdin.resume()
for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP']) {
process.on(signal, exitCleanly)
}
process.stdout.write('\x1b[?1049h')
render()
+32
View File
@@ -0,0 +1,32 @@
import { expect, test } from './helpers/orca-app'
import {
configureGoldenStubAgent,
getGoldenStubAgentLaunchEnv,
launchGoldenStubAgentFromNewTab
} from './helpers/golden-stub-agent'
import { ensureTerminalVisible, waitForActiveWorktree, waitForSessionReady } from './helpers/store'
import { focusActiveTerminalInput, getTerminalContent } from './helpers/terminal'
test.use({ launchEnv: getGoldenStubAgentLaunchEnv() })
test('@golden launches an agent TUI with a live multiline composer', async ({ orcaPage }) => {
await waitForSessionReady(orcaPage)
await waitForActiveWorktree(orcaPage)
await ensureTerminalVisible(orcaPage)
await configureGoldenStubAgent(orcaPage)
await launchGoldenStubAgentFromNewTab(orcaPage)
const activeTab = orcaPage.locator('[data-testid="sortable-tab"][data-active="true"]')
await expect(activeTab).toHaveAttribute('data-tab-title', /Codex|Golden Stub Agent/i)
await expect(activeTab).not.toHaveAttribute('data-tab-title', /^Terminal \d+$/)
await focusActiveTerminalInput(orcaPage)
await orcaPage.keyboard.type('hello from e2e')
await orcaPage.keyboard.press('Shift+Enter')
await orcaPage.keyboard.type('second line')
await expect
.poll(() => getTerminalContent(orcaPage), { timeout: 10_000 })
.toContain('> hello from e2e\r\n second line')
expect(await getTerminalContent(orcaPage)).not.toContain('GOLDEN_STUB_AGENT_SUBMITTED')
})
@@ -0,0 +1,47 @@
import { expect, test } from './helpers/orca-app'
import {
configureGoldenStubAgent,
getGoldenStubAgentLaunchEnv,
GOLDEN_STUB_EXIT_MARKER,
launchGoldenStubAgentFromNewTab
} from './helpers/golden-stub-agent'
import { ensureTerminalVisible, waitForActiveWorktree, waitForSessionReady } from './helpers/store'
import {
focusActiveTerminalInput,
waitForActivePanePtyId,
waitForTerminalOutput
} from './helpers/terminal'
test.use({ launchEnv: getGoldenStubAgentLaunchEnv() })
test('@golden opens a clean live shell after an agent exits', async ({ orcaPage }) => {
await waitForSessionReady(orcaPage)
await waitForActiveWorktree(orcaPage)
await ensureTerminalVisible(orcaPage)
await configureGoldenStubAgent(orcaPage)
await launchGoldenStubAgentFromNewTab(orcaPage)
await orcaPage.keyboard.type('exit')
await orcaPage.keyboard.press('Enter')
await waitForTerminalOutput(orcaPage, GOLDEN_STUB_EXIT_MARKER, 15_000)
const tabsBeforeShell = await orcaPage.locator('[data-testid="sortable-tab"]').count()
await orcaPage.getByRole('button', { name: 'New tab' }).click({ force: true })
await orcaPage
.getByRole('menuitem', { name: /New Terminal/i })
.first()
.click({ force: true })
await expect(orcaPage.locator('[data-testid="sortable-tab"]')).toHaveCount(tabsBeforeShell + 1)
await waitForActivePanePtyId(orcaPage)
await focusActiveTerminalInput(orcaPage)
await orcaPage.keyboard.type('echo after-agent')
await orcaPage.keyboard.press('Enter')
await waitForTerminalOutput(orcaPage, 'after-agent', 15_000)
await orcaPage.keyboard.press('Shift+Enter')
await orcaPage.keyboard.type('echo after-shift-enter')
await orcaPage.keyboard.press('Enter')
await waitForTerminalOutput(orcaPage, 'after-shift-enter', 15_000)
await expect(orcaPage.locator('[data-testid="sortable-tab"]')).toHaveCount(tabsBeforeShell + 1)
})
+39
View File
@@ -0,0 +1,39 @@
import path from 'node:path'
import type { Page } from '@stablyai/playwright-test'
import { expect } from '@stablyai/playwright-test'
import { focusActiveTerminalInput, waitForTerminalOutput } from './terminal'
export const GOLDEN_STUB_READY_MARKER = 'GOLDEN_STUB_AGENT_READY'
export const GOLDEN_STUB_EXIT_MARKER = 'GOLDEN_STUB_AGENT_EXITED'
const fixtureDir = path.join(process.cwd(), 'tests', 'e2e', 'fixtures', 'golden-stub-agent')
export function getGoldenStubAgentLaunchEnv(): NodeJS.ProcessEnv {
const pathKey = Object.keys(process.env).find((key) => key.toLowerCase() === 'path') ?? 'PATH'
return {
[pathKey]: [fixtureDir, process.env[pathKey] ?? ''].filter(Boolean).join(path.delimiter)
}
}
export async function configureGoldenStubAgent(page: Page): Promise<void> {
await page.evaluate(async () => {
const store = window.__store
if (!store) {
throw new Error('Orca store is unavailable')
}
await store.getState().updateSettings({
defaultTuiAgent: 'codex',
agentCmdOverrides: { codex: 'golden-stub-agent' },
agentDefaultArgs: { codex: '' }
})
})
}
export async function launchGoldenStubAgentFromNewTab(page: Page): Promise<void> {
await page.getByRole('button', { name: 'New tab' }).click({ force: true })
const launchOption = page.getByRole('menuitem', { name: /^Codex(?:\s|$)/i }).first()
await expect(launchOption).toBeVisible({ timeout: 15_000 })
await launchOption.click({ force: true })
await focusActiveTerminalInput(page)
await waitForTerminalOutput(page, GOLDEN_STUB_READY_MARKER, 20_000)
}