import { expect, test } from "@playwright/test"; import fs from "node:fs"; import path from "node:path"; const root = path.resolve(import.meta.dirname, "../../.."); const golden = JSON.parse(fs.readFileSync(path.join(root, "tests/golden/M11-13/sequencer-audio-recovery.json"), "utf8")) as { outputGain: number; lifecycle: Array<[string, string, boolean, number, string | null]>; missingDevice: [string, string, string]; }; test("M11-13 suspends, resumes, mutes, recovers, and closes a Chromium AudioContext", async ({ page }) => { await page.goto("/"); await page.evaluate((outputGain) => { const button = document.createElement("button"); button.id = "m11-audio-user-gesture"; button.textContent = "Start audio test"; document.body.append(button); const result = new Promise((resolve, reject) => { button.addEventListener("click", async () => { try { const { SequencerAudioSession } = await import("/src/sequencer/SequencerAudioSession.ts"); const summarize = (report: import("/src/sequencer/SequencerAudioSession.ts").SequencerAudioSessionReportIR) => [ report.contextState, report.outputState, report.muted, report.outputGain, report.issueCode, ]; const supported = "AudioContext" in globalThis; const session = new SequencerAudioSession({ outputGain }); const initialized = await session.initialize(); const lifecycle = []; lifecycle.push(summarize(await session.suspend())); lifecycle.push(summarize(session.setMuted(true))); lifecycle.push(summarize(await session.resume())); lifecycle.push(summarize(session.setMuted(false))); lifecycle.push(summarize(await session.suspend())); lifecycle.push(summarize(await session.resume())); lifecycle.push(summarize(await session.close())); const missingSession = new SequencerAudioSession({ scope: {}, outputGain }); const missing = await missingSession.initialize(); const missingDevice = [missing.contextState, missing.outputState, missing.issueCode]; const missingClosed = await missingSession.close(); resolve({ supported, initialized, lifecycle, missingDevice, missingClosed }); } catch (error) { reject(error); } }, { once: true }); }); (globalThis as typeof globalThis & { __m11AudioResult?: Promise }).__m11AudioResult = result; }, golden.outputGain); await page.locator("#m11-audio-user-gesture").click(); const result = await page.evaluate(() => (globalThis as typeof globalThis & { __m11AudioResult: Promise }).__m11AudioResult) as { supported: boolean; initialized: { contextState: string; issueCode: string | null }; lifecycle: Array<[string, string, boolean, number, string | null]>; missingDevice: [string, string, string]; missingClosed: { contextState: string; outputState: string; outputGain: number }; }; expect(result.supported).toBe(true); expect(["RUNNING", "SUSPENDED"]).toContain(result.initialized.contextState); expect(result.initialized.issueCode).toBeNull(); expect(result.lifecycle).toEqual(golden.lifecycle); expect(result.missingDevice).toEqual(golden.missingDevice); expect(result.missingClosed).toMatchObject({ contextState: "CLOSED", outputState: "SILENT", outputGain: 0 }); });