import { test, expect, type Page } from "@playwright/test"; import { existsSync } from "node:fs"; import { resolve } from "node:path"; import { completeIntro } from "./intro-driver"; const romPath = resolve( process.env.CRYSTAL_ROM_PATH ?? "Pokemon - Crystal Version (UE) (V1.1) [C][!].gbc", ); /** Tap everything the page sends to the speakers with an analyser, so the * test can measure what is actually being played. */ async function listen(page: Page) { await page.addInitScript(() => { const connect = AudioNode.prototype.connect as (...args: unknown[]) => unknown; const tapped = window as unknown as { __analysers: AnalyserNode[] }; tapped.__analysers = []; AudioNode.prototype.connect = function (this: AudioNode, ...args: unknown[]) { if (args[0] instanceof AudioDestinationNode) { const analyser = this.context.createAnalyser(); analyser.fftSize = 2048; connect.call(this, analyser); tapped.__analysers.push(analyser); } return connect.apply(this, args); } as typeof AudioNode.prototype.connect; }); } /** The loudest RMS level heard over `ms`. */ async function level(page: Page, ms: number) { return page.evaluate(async (ms) => { const analysers = (window as unknown as { __analysers: AnalyserNode[] }).__analysers; let loudest = 0; const end = performance.now() + ms; while (performance.now() < end) { for (const analyser of analysers) { // A suspended context sends nothing; its analyser keeps old data. if (analyser.context.state !== "running") continue; const data = new Float32Array(analyser.fftSize); analyser.getFloatTimeDomainData(data); let sum = 0; for (const value of data) sum += value * value; loudest = Math.max(loudest, Math.sqrt(sum / data.length)); } await new Promise((done) => setTimeout(done, 50)); } return loudest; }, ms); } test("music plays after the first press and stops while paused", async ({ page }) => { test.skip(!existsSync(romPath), "Requires a local user-supplied ROM."); test.setTimeout(120_000); await listen(page); await page.goto("/"); await page.locator("#rom-file").setInputFiles(romPath); await expect(page.locator("#status")).toContainText("verified"); await page.locator("#play").click(); await completeIntro(page); // The bedroom's music plays once the introduction hands over. await expect.poll(() => level(page, 500), { timeout: 15_000 }).toBeGreaterThan(0.01); // Leaving the game tab pauses play, and the sound with it. await page.locator("#tab-saves").click(); await expect.poll(() => level(page, 500), { timeout: 10_000 }).toBeLessThan(0.001); // Returning and playing again resumes the music. await page.locator("#tab-game").click(); await page.locator("#play").click(); await expect.poll(() => level(page, 500), { timeout: 15_000 }).toBeGreaterThan(0.01); }); test("Settings turns the sound off and on", async ({ page }) => { test.skip(!existsSync(romPath), "Requires a local user-supplied ROM."); test.setTimeout(120_000); await listen(page); await page.goto("/"); await page.locator("#rom-file").setInputFiles(romPath); await expect(page.locator("#status")).toContainText("verified"); await page.locator("#settings").click(); await page.locator('input[name="volume"][value="0"]').check(); await page.locator("#close-settings").click(); await page.locator("#play").click(); await completeIntro(page); expect(await level(page, 1500)).toBeLessThan(0.001); await page.locator("#settings").click(); await page.locator('input[name="volume"][value="1"]').check(); await page.locator("#close-settings").click(); await page.locator("#play").click(); await expect.poll(() => level(page, 500), { timeout: 15_000 }).toBeGreaterThan(0.01); });