import { test as base, chromium, type Page, type BrowserContext, type Browser } from '@playwright/test'; import { join } from 'node:path'; import { installTestServerEndpoint } from '../helpers/seed-test-endpoint'; import { startTestServer, type TestServerHandle } from '../helpers/test-server'; export interface Client { page: Page; context: BrowserContext; } interface MultiClientFixture { createClient: () => Promise; testServer: TestServerHandle; } const FAKE_AUDIO_FILE = join(__dirname, 'test-tone.wav'); const CHROMIUM_FAKE_MEDIA_ARGS = [ '--use-fake-device-for-media-stream', '--use-fake-ui-for-media-stream', `--use-file-for-fake-audio-capture=${FAKE_AUDIO_FILE}`, '--autoplay-policy=no-user-gesture-required' ]; export const test = base.extend({ testServer: async ({ playwright: _playwright }, use: (testServer: TestServerHandle) => Promise) => { const testServer = await startTestServer(); await use(testServer); await testServer.stop(); }, createClient: async ({ testServer }, use) => { const browsers: Browser[] = []; const clients: Client[] = []; const factory = async (): Promise => { // Launch a dedicated browser per client so each gets its own fake // audio device - shared browsers can starve the first context's // audio capture under load. const browser = await chromium.launch({ args: CHROMIUM_FAKE_MEDIA_ARGS }); browsers.push(browser); const context = await browser.newContext({ permissions: ['microphone', 'camera'], baseURL: 'http://localhost:4200' }); await installTestServerEndpoint(context, testServer.port); const page = await context.newPage(); clients.push({ page, context }); return { page, context }; }; await use(factory); for (const client of clients) { await client.context.close(); } for (const browser of browsers) { await browser.close(); } } }); export { expect } from '@playwright/test';