Pattern: Playwright E2E harness for PrePitch
Source: raw/eval-2026-04-19-prepitch-playwright.md (CON-124 evaluator PASS, 7/9 strict).
Scope: end-to-end browser + backend-WS tests for the PrePitch roleplay app.
Structure
apps/prepitch/
├── playwright.config.ts # single chromium project, workers=1, fullyParallel=false
└── e2e/playwright/
├── tests/
│ ├── text-mode.spec.ts
│ ├── voice-legacy.spec.ts
│ ├── voice-convai.spec.ts
│ ├── backend-ws.spec.ts
│ ├── backend-convai-sse.spec.ts
│ ├── agent-vs-agent-text.spec.ts
│ └── agent-vs-agent-voice.spec.ts
└── fixtures/
├── stubs/speech-recognition.js # MUST be .js — addInitScript never transpiles .ts
├── stubs/convai-websocket.js # stubs raw window.WebSocket at wss://mock-convai.test/*
└── audio/{tts,stt}.ts # real OpenAI TTS + Whisper wrappers for agent-vs-agent
Rules that bit us
- Playwright
addInitScriptrequires plain JS..tsstubs never execute — the injected file is taken as-is. Symptom:window.__fakeSRnever appears, SRstartnever replaces the global. Fix: shipfixtures/stubs/*.js. - Default per-test timeout (60s) is too short for PrePitch flows. Real Claude first-chunk is 3–6s; full buyer_response is 10–20s; debrief 15–90s. Use
testInfo.setTimeout(180_000..480_000)inside individual tests instead of a blunt global. fullyParallelmust be false — sessions share a single Prisma DB and the in-memoryactiveSessionsmap./personasvs/personas?template=trueare different lists. Browser specs MUST use/personassoselectOption(...)matches an actual<option value>. Backend-direct WS specs can use either.- Convai stub layer: stubbing raw
window.WebSocketat the wss URL is more robust than stubbing the@11labs/clientmodule surface. Document the deviation if your spec nameselevenlabs-client.ts. - Fake audio for agent-vs-agent-voice: pass
--use-fake-ui-for-media-stream+--use-fake-device-for-media-streamtochromium.launch. Gate the real-API path behindPREPITCH_E2E_RUN_A2A_VOICE=1— 480s runs can still time out on headless WSL hosts. - No dedicated
e2e/playwright/tsconfig.jsonexists. Rootbunx tsc --noEmitcovers the tree, but any spec command that references-p e2e/playwright/tsconfig.jsonwill fail until the file is added.
Run commands
cd apps/prepitch
bunx playwright test --list # sanity: expect 8 tests across 7 files
bunx playwright test # full run, dev servers auto-start via webServer config
PREPITCH_E2E_RUN_A2A_VOICE=1 bunx playwright test agent-vs-agent-voice