import { expect, test as base, type Page } from "@playwright/test"; import { ScriptedTranscriptionServer } from "./scriptedTranscriptionServer"; type HarnessStartOptions = { resilience?: Record; speechPlan?: Array<{ kind: "speech" | "silence"; durationMs: number }>; }; type HarnessEvent = { name: string; detail: unknown; timestamp: number; }; type ResilienceStatus = { connectionState?: string; websocketState?: string; totalBufferedAudioBytes?: number; [key: string]: unknown; }; class ResilienceHarness { constructor( private readonly page: Page, private readonly server: ScriptedTranscriptionServer ) {} public async goto() { this.page.on("pageerror", (error) => { console.error(`Harness page error: ${error.message}`); }); await this.page.goto(this.server.getHarnessUrl()); await this.page.waitForFunction(() => Boolean(window.testApi), undefined, { timeout: 10_000, }); } public async start(options: HarnessStartOptions = {}) { await this.page.evaluate((startOptions) => window.testApi.start(startOptions), options); } public async pause() { await this.page.evaluate(() => window.testApi.pause()); } public async resume() { await this.page.evaluate(() => window.testApi.resume()); } public async stop() { return this.page.evaluate(() => window.testApi.stop()); } public async waitForEvent(eventName: string, timeoutMs = 10_000) { await this.page.waitForFunction( (targetEventName) => window.testApi .getEvents() .some((event: HarnessEvent) => event.name === targetEventName), eventName, { timeout: timeoutMs } ); } public async getEvents() { return this.page.evaluate(() => window.testApi.getEvents() as HarnessEvent[]); } public async getStatuses() { return this.page.evaluate( () => window.testApi.getStatuses() as ResilienceStatus[] ); } public async getFinals() { return this.page.evaluate(() => window.testApi.getFinals() as string[]); } public async waitForFinalCount(count: number, timeoutMs = 10_000) { await this.page.waitForFunction( (targetCount) => window.testApi.getFinals().length >= targetCount, count, { timeout: timeoutMs } ); } public async getTranscript() { return this.page.evaluate(() => window.testApi.getTranscript() as string); } } export const test = base.extend<{ scriptedServer: ScriptedTranscriptionServer; harness: ResilienceHarness; }>({ scriptedServer: async ({}, use) => { const rootDir = process.cwd(); const scriptedServer = new ScriptedTranscriptionServer(rootDir); await scriptedServer.start(); await use(scriptedServer); await scriptedServer.stop(); }, harness: async ({ page, scriptedServer }, use) => { const harness = new ResilienceHarness(page, scriptedServer); await harness.goto(); await use(harness); }, }); export { expect };