import type { Page } from "@playwright/test"; import { createTempGitRepo } from "./workspace"; import { connectTerminalClient, navigateToTerminal, setupDeterministicPrompt, type TerminalPerfDaemonClient, } from "./terminal-perf"; interface TempRepo { path: string; cleanup: () => Promise; } export interface TerminalInstance { id: string; name: string; cwd: string; } export class TerminalE2EHarness { readonly client: TerminalPerfDaemonClient; readonly tempRepo: TempRepo; readonly workspaceId: string; private constructor(input: { client: TerminalPerfDaemonClient; tempRepo: TempRepo; workspaceId: string; }) { this.client = input.client; this.tempRepo = input.tempRepo; this.workspaceId = input.workspaceId; } static async create(input: { tempPrefix: string }): Promise { const tempRepo = await createTempGitRepo(input.tempPrefix); const client = await connectTerminalClient(); const seedResult = await client.openProject(tempRepo.path); if (!seedResult.workspace) { await client.close().catch(() => {}); await tempRepo.cleanup().catch(() => {}); throw new Error(seedResult.error ?? "Failed to seed workspace"); } return new TerminalE2EHarness({ client, tempRepo, workspaceId: seedResult.workspace.id, }); } async cleanup(): Promise { await this.client.close().catch(() => {}); await this.tempRepo.cleanup().catch(() => {}); } async createTerminal(input: { name: string }): Promise { const result = await this.client.createTerminal(this.tempRepo.path, input.name); if (!result.terminal) { throw new Error(`Failed to create terminal: ${result.error}`); } return result.terminal; } async killTerminal(terminalId: string): Promise { await this.client.killTerminal(terminalId).catch(() => {}); } async openTerminal(page: Page, input: { terminalId: string }): Promise { await navigateToTerminal(page, { workspaceId: this.workspaceId, terminalId: input.terminalId, }); } terminalSurface(page: Page) { return page.locator('[data-testid="terminal-surface"]'); } async setupPrompt(page: Page, sentinel?: string): Promise { await setupDeterministicPrompt(page, sentinel); } } export async function withTerminalInApp( page: Page, harness: TerminalE2EHarness, input: { name: string }, fn: (terminal: TerminalInstance) => Promise, ): Promise { const terminal = await harness.createTerminal({ name: input.name }); try { await harness.openTerminal(page, { terminalId: terminal.id }); return await fn(terminal); } finally { await harness.killTerminal(terminal.id); } }