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: "PLAY SOUNDTRACK" }).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: "PLAY SOUNDTRACK" }), ).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); });