import { expect, test } from '../helpers/serve-dist'; import { assertSemanticFaultOracle, authorizedFaultUrl, collectBrowserErrors, readUnhandledRejections, triggerFaultAction, } from '../helpers/test-build-auth'; import { installSceneInstrumentation, readSceneInstrumentation } from '../helpers/scene-instrumentation'; test.beforeEach(async ({ page }, testInfo) => { test.skip(testInfo.project.name !== 'desktop-chromium', 'Audio fault matrix runs once on current desktop Chromium.'); await installSceneInstrumentation(page); }); const startAudio = async (page: import('@playwright/test').Page): Promise => { await page.getByRole('button', { name: 'START THE DRIVE' }).click(); }; const assertAudioContained = async ( page: import('@playwright/test').Page, pageErrors: readonly string[], expectedLiveContexts: number, ): Promise => { await assertSemanticFaultOracle(page); expect(pageErrors).toEqual([]); expect(await readUnhandledRejections(page)).toEqual([]); const resources = await readSceneInstrumentation(page); expect(resources.unhandledRejections).toEqual([]); expect(resources.audioContextsConstructed).toBe(1); expect(resources.liveAudioContexts).toBe(expectedLiveContexts); }; test('F10 contains initial Web Audio start rejection and creates no reusable graph', async ({ page, distUrl }) => { const errors = await collectBrowserErrors(page); await page.goto(authorizedFaultUrl(distUrl, 'F10-audio-start-rejection'), { waitUntil: 'domcontentloaded' }); await startAudio(page); await expect(page.getByText('Audio unavailable. Continue without sound.')).toBeVisible(); await expect(page.getByRole('button', { name: 'START THE DRIVE' })).toHaveCount(0); await assertSemanticFaultOracle(page); expect(errors.pageErrors).toEqual([]); expect(await readUnhandledRejections(page)).toEqual([]); expect((await readSceneInstrumentation(page)).audioContextsConstructed).toBe(0); }); test('F11 keeps an interrupted intact graph suspended after resume rejection', async ({ page, distUrl }) => { const errors = await collectBrowserErrors(page); await page.goto(authorizedFaultUrl(distUrl, 'F11-audio-resume-rejection'), { waitUntil: 'domcontentloaded' }); await startAudio(page); await expect(page.getByRole('button', { name: 'Mute sound' })).toBeVisible(); await triggerFaultAction(page, 'audio-interruption'); await expect(page.getByRole('button', { name: 'Resume sound' })).toBeVisible(); await page.getByRole('button', { name: 'Resume sound' }).click(); await expect(page.getByRole('button', { name: 'Resume sound' })).toBeVisible(); await expect(page.getByText('Audio unavailable. Continue without sound.')).toHaveCount(0); await assertAudioContained(page, errors.pageErrors, 1); }); test('F12 tears down a graph that becomes invalid during resume', async ({ page, distUrl }) => { const errors = await collectBrowserErrors(page); await page.goto(authorizedFaultUrl(distUrl, 'F12-audio-graph-invalid'), { waitUntil: 'domcontentloaded' }); await startAudio(page); await triggerFaultAction(page, 'audio-interruption'); await expect(page.getByRole('button', { name: 'Resume sound' })).toBeVisible(); await page.getByRole('button', { name: 'Resume sound' }).click(); await expect(page.getByText('Audio unavailable. Continue without sound.')).toBeVisible(); await expect.poll(async () => (await readSceneInstrumentation(page)).liveAudioContexts).toBe(0); await assertAudioContained(page, errors.pageErrors, 0); }); test('F13 synchronously silences and closes the graph when hide-time suspend rejects', async ({ page, distUrl }) => { const errors = await collectBrowserErrors(page); await page.goto(authorizedFaultUrl(distUrl, 'F13-audio-suspend-rejection'), { waitUntil: 'domcontentloaded' }); await startAudio(page); await expect(page.getByRole('button', { name: 'Mute sound' })).toBeVisible(); await page.evaluate(() => { Object.defineProperty(document, 'visibilityState', { configurable: true, value: 'hidden' }); document.dispatchEvent(new Event('visibilitychange')); }); await expect(page.getByText('Audio unavailable. Continue without sound.')).toBeVisible(); await expect.poll(async () => (await readSceneInstrumentation(page)).liveAudioContexts).toBe(0); await assertAudioContained(page, errors.pageErrors, 0); });