refactor(app): remove dead e2e fixtures, helpers, and types

Delete unused code from the e2e test infrastructure:

- fixtures: withProject, withBackendProject, withMockProject, runProject,
  AssistantFixture type, assistant fixture
- actions: clickPopoverButton, seedSessionPermission, seedSessionTodos
- Update AGENTS.md to reflect the project fixture pattern
pull/20593/head
Kit Langton 2026-04-02 12:00:17 -04:00
parent bedd661fd4
commit bcddb59c9c
3 changed files with 7 additions and 190 deletions

View File

@ -59,8 +59,10 @@ test("test description", async ({ page, sdk, gotoSession }) => {
### Using Fixtures
- `page` - Playwright page
- `sdk` - OpenCode SDK client for API calls
- `gotoSession(sessionID?)` - Navigate to session
- `llm` - Mock LLM server for queuing responses (`text`, `tool`, `toolMatch`, `textMatch`, etc.)
- `project` - Golden-path project fixture (call `project.open()` first, then use `project.sdk`, `project.prompt(...)`, `project.gotoSession(...)`, `project.trackSession(...)`)
- `sdk` - OpenCode SDK client for API calls (worker-scoped, shared directory)
- `gotoSession(sessionID?)` - Navigate to session (worker-scoped, shared directory)
### Helper Functions
@ -73,12 +75,9 @@ test("test description", async ({ page, sdk, gotoSession }) => {
- `waitTerminalReady(page, { term? })` - Wait for a mounted terminal to connect and finish rendering output
- `runTerminal(page, { cmd, token, term?, timeout? })` - Type into the terminal via the browser and wait for rendered output
- `withSession(sdk, title, callback)` - Create temp session
- `withProject(...)` - Create temp project/workspace
- `sessionIDFromUrl(url)` - Read session ID from URL
- `slugFromUrl(url)` - Read workspace slug from URL
- `waitSlug(page, skip?)` - Wait for resolved workspace slug
- `trackSession(sessionID, directory?)` - Register session for fixture cleanup
- `trackDirectory(directory)` - Register directory for fixture cleanup
- `clickListItem(container, filter)` - Click list item by key/text
**Selectors** (`selectors.ts`):
@ -128,9 +127,9 @@ test("test with cleanup", async ({ page, sdk, gotoSession }) => {
})
```
- Prefer `withSession(...)` for temp sessions
- In `withProject(...)` tests that create sessions or extra workspaces, call `trackSession(sessionID, directory?)` and `trackDirectory(directory)`
- This lets fixture teardown abort, wait for idle, and clean up safely under CI concurrency
- Prefer the `project` fixture for tests that need a dedicated project with LLM mocking — call `project.open()` then use `project.prompt(...)`, `project.trackSession(...)`, etc.
- Use `withSession(sdk, title, callback)` for lightweight temp sessions on the shared worker directory
- Call `project.trackSession(sessionID, directory?)` and `project.trackDirectory(directory)` for any resources created outside the fixture so teardown can clean them up
- Avoid calling `sdk.session.delete(...)` directly
### Timeouts

View File

@ -637,12 +637,6 @@ export async function openSharePopover(page: Page) {
return { rightSection: scroller, popoverBody }
}
export async function clickPopoverButton(page: Page, buttonName: string | RegExp) {
const button = page.getByRole("button").filter({ hasText: buttonName }).first()
await expect(button).toBeVisible()
await button.click()
}
export async function clickListItem(
container: Locator | Page,
filter: string | RegExp | { key?: string; text?: string | RegExp; keyStartsWith?: string },
@ -808,40 +802,6 @@ export async function seedSessionQuestion(
return { id: result.id }
}
export async function seedSessionPermission(
sdk: ReturnType<typeof createSdk>,
input: {
sessionID: string
permission: string
patterns: string[]
description?: string
},
) {
const text = [
"Your only valid response is one bash tool call.",
`Use this JSON input: ${JSON.stringify({
command: input.patterns[0] ? `ls ${JSON.stringify(input.patterns[0])}` : "pwd",
workdir: "/",
description: input.description ?? `seed ${input.permission} permission request`,
})}`,
"Do not output plain text.",
].join("\n")
const result = await seed({
sdk,
sessionID: input.sessionID,
prompt: text,
timeout: 30_000,
probe: async () => {
const list = await sdk.permission.list().then((x) => x.data ?? [])
return list.find((item) => item.sessionID === input.sessionID)
},
})
if (!result) throw new Error("Timed out seeding permission request")
return { id: result.id }
}
export async function seedSessionTask(
sdk: ReturnType<typeof createSdk>,
input: {
@ -900,36 +860,6 @@ export async function seedSessionTask(
return result
}
export async function seedSessionTodos(
sdk: ReturnType<typeof createSdk>,
input: {
sessionID: string
todos: Array<{ content: string; status: string; priority: string }>
},
) {
const text = [
"Your only valid response is one todowrite tool call.",
`Use this JSON input: ${JSON.stringify({ todos: input.todos })}`,
"Do not output plain text.",
].join("\n")
const target = JSON.stringify(input.todos)
const result = await seed({
sdk,
sessionID: input.sessionID,
prompt: text,
timeout: 30_000,
probe: async () => {
const todos = await sdk.session.todo({ sessionID: input.sessionID }).then((x) => x.data ?? [])
if (JSON.stringify(todos) !== target) return
return true
},
})
if (!result) throw new Error("Timed out seeding todos")
return true
}
export async function clearSessionDockSeed(sdk: ReturnType<typeof createSdk>, sessionID: string) {
const [questions, permissions] = await Promise.all([
sdk.question.list().then((x) => x.data ?? []),

View File

@ -18,7 +18,6 @@ import {
waitSlug,
withNoReplyPrompt,
} from "./actions"
import { openaiModel, withMockOpenAI } from "./prompt/mock"
import { promptSelector } from "./selectors"
import { createSdk, dirSlug, getWorktree, sessionPath } from "./utils"
@ -59,19 +58,6 @@ type LLMWorker = LLMFixture & {
reset: () => Promise<void>
}
type AssistantFixture = {
reply: (value: string, opts?: { usage?: Usage }) => Promise<void>
tool: (name: string, input: unknown) => Promise<void>
toolHang: (name: string, input: unknown) => Promise<void>
reason: (value: string, opts?: { text?: string; usage?: Usage }) => Promise<void>
fail: (message?: unknown) => Promise<void>
error: (status: number, body: unknown) => Promise<void>
hang: () => Promise<void>
hold: (value: string, wait: PromiseLike<unknown>) => Promise<void>
calls: () => Promise<number>
pending: () => Promise<number>
}
export const settingsKey = "settings.v3"
const seedModel = (() => {
@ -143,13 +129,9 @@ type ProjectFixture = ProjectHandle & {
type TestFixtures = {
llm: LLMFixture
assistant: AssistantFixture
project: ProjectFixture
sdk: ReturnType<typeof createSdk>
gotoSession: (sessionID?: string) => Promise<void>
withProject: <T>(callback: (project: ProjectHandle) => Promise<T>, options?: ProjectOptions) => Promise<T>
withBackendProject: <T>(callback: (project: ProjectHandle) => Promise<T>, options?: ProjectOptions) => Promise<T>
withMockProject: <T>(callback: (project: ProjectHandle) => Promise<T>, options?: ProjectOptions) => Promise<T>
}
type WorkerFixtures = {
@ -238,20 +220,6 @@ export const test = base.extend<TestFixtures, WorkerFixtures>({
throw new Error(`TestLLMServer still has ${pending} queued response(s) after the test finished`)
}
},
assistant: async ({ llm }, use) => {
await use({
reply: llm.text,
tool: llm.tool,
toolHang: llm.toolHang,
reason: llm.reason,
fail: llm.fail,
error: llm.error,
hang: llm.hang,
hold: llm.hold,
calls: llm.calls,
pending: llm.pending,
})
},
page: async ({ page }, use) => {
let boundary: string | undefined
setHealthPhase(page, "test")
@ -312,29 +280,6 @@ export const test = base.extend<TestFixtures, WorkerFixtures>({
await item.cleanup()
}
},
withProject: async ({ page }, use) => {
await use((callback, options) => runProject(page, callback, options))
},
withBackendProject: async ({ page, backend }, use) => {
await use((callback, options) =>
runProject(page, callback, { ...options, serverUrl: backend.url, sdk: backend.sdk }),
)
},
withMockProject: async ({ page, llm, backend }, use) => {
await use((callback, options) =>
withMockOpenAI({
serverUrl: backend.url,
llmUrl: llm.url,
fn: () =>
runProject(page, callback, {
...options,
model: options?.model ?? openaiModel,
serverUrl: backend.url,
sdk: backend.sdk,
}),
}),
)
},
})
function makeProject(
@ -560,63 +505,6 @@ function makeProject(
}
}
async function runProject<T>(
page: Page,
callback: (project: ProjectHandle) => Promise<T>,
options?: ProjectOptions & {
serverUrl?: string
sdk?: (directory?: string) => ReturnType<typeof createSdk>
},
) {
const url = options?.serverUrl
const root = await createTestProject(url ? { serverUrl: url } : undefined)
const sdk = options?.sdk?.(root) ?? createSdk(root, url)
const sessions = new Map<string, string>()
const dirs = new Set<string>()
await options?.setup?.(root)
await seedStorage(page, {
directory: root,
extra: options?.extra,
model: options?.model,
serverUrl: url,
})
const gotoSession = async (sessionID?: string) => {
await visit(page, sessionPath(root, sessionID))
await waitSession(page, {
directory: root,
sessionID,
serverUrl: url,
allowAnySession: !sessionID,
})
const current = sessionIDFromUrl(page.url())
if (current) trackSession(current)
}
const trackSession = (sessionID: string, directory?: string) => {
sessions.set(sessionID, directory ?? root)
}
const trackDirectory = (directory: string) => {
if (directory !== root) dirs.add(directory)
}
try {
await options?.beforeGoto?.({ directory: root, sdk })
await gotoSession()
const slug = await waitSlug(page)
return await callback({ directory: root, slug, gotoSession, trackSession, trackDirectory, sdk })
} finally {
setHealthPhase(page, "cleanup")
await Promise.allSettled(
Array.from(sessions, ([sessionID, directory]) => cleanupSession({ sessionID, directory, serverUrl: url })),
)
await Promise.allSettled(Array.from(dirs, (directory) => cleanupTestProject(directory)))
await cleanupTestProject(root)
setHealthPhase(page, "test")
}
}
async function seedStorage(
page: Page,
input: {