test: Playwright E2E with real backend + fake TTS engine

e2e/ drives the web build (vite preview) against a per-worker uvicorn:
each Playwright worker boots its own backend on its own port with a
temp cwd, so SQLite and audio files are fully isolated and parallel-
safe. The page fixture seeds the persisted voicebox-server store with
the worker's URL before any script runs.

VOICEBOX_FAKE_TTS=1 short-circuits get_tts_backend_for_engine to a
backend that synthesizes a sine tone sized to the text — the real task
queue, SSE progress, history rows, and audio serving all run, only
inference is fake. backend/requirements-ci.txt is the slim dependency
set validated to boot the app on a CPU-only runner.

Specs: startup, settings layout, seeded profile in /voices, and
generate-to-completed-audio through the full pipeline.
This commit is contained in:
Jamie Pine
2026-08-08 15:40:36 -07:00
parent c7a3ff5d65
commit 37b7110e52
12 changed files with 379 additions and 0 deletions
+31
View File
@@ -0,0 +1,31 @@
import { expect, test } from '../fixtures';
import { seedProfile } from '../helpers/api';
test('generate speech end to end through the fake TTS pipeline', async ({ page, backend }) => {
const profile = await seedProfile(backend.url, 'Narrator');
await page.goto('/');
// Select the seeded voice, type into the generate box, and submit.
await page.getByText(profile.name).first().click();
const input = page.getByRole('textbox').first();
await input.click();
await page.keyboard.type('The quick brown fox jumps over the lazy dog.');
await page.getByRole('button', { name: 'Generate speech' }).click();
// The row lands in history and completes via the real queue + SSE.
await expect(page.getByText('The quick brown fox', { exact: false }).first()).toBeVisible({
timeout: 15_000,
});
await expect
.poll(
async () => {
const res = await fetch(`${backend.url}/history?limit=10`);
const body = (await res.json()) as { items: { status: string }[] };
return body.items[0]?.status;
},
{ timeout: 20_000 },
)
.toBe('completed');
});