import { test, expect, type Page } from "@playwright/test"; import { existsSync } from "node:fs"; import { resolve } from "node:path"; const romPath = resolve( process.env.CRYSTAL_ROM_PATH ?? "Pokemon - Crystal Version (UE) (V1.1) [C][!].gbc", ); test.beforeEach(() => test.skip(!existsSync(romPath), "Requires the local user ROM.")); async function start(page: Page) { await page.route("**/src/game/runtime.ts", async (route) => { const response = await route.fetch(), source = await response.text(); const needle = "const world = new GameWorld(assets);"; expect(source).toContain(needle); // Fixture worlds stand in for a saved session, so no introduction runs. expect(source).toContain("this.world?.beginIntro();"); await route.fulfill({ response, body: source.replace("this.world?.beginIntro();", "").replace( needle, `const { pokedexFixtureWorld } = await import('/tests/pokedex-fixture.ts'); const world = pokedexFixtureWorld(assets);`, ), }); }); await page.clock.install({ time: new Date("2026-09-22T12:00:00") }); await page.goto("/"); await page.locator("#rom-file").setInputFiles(romPath); await expect(page.locator("#status")).toContainText("verified"); await page.clock.pauseAt(new Date("2026-09-22T12:05:00")); await page.locator("#play").click(); } test("settings rebind a key, scale the text and survive a reload", async ({ page }) => { await start(page); const dialog = page.locator("#settings-dialog"); await page.locator("#settings").click(); await expect(dialog).toBeVisible(); const startRow = dialog.locator("#key-table tr", { hasText: "START" }); await expect(startRow).toContainText("Enter"); // Escape cancels a key change without closing the dialog. await startRow.getByRole("button", { name: /Change the key for START/ }).click(); await expect(startRow).toContainText("Press a key"); await page.keyboard.press("Escape"); await expect(dialog).toBeVisible(); await expect(page.locator("#settings-status")).toHaveText("Key change cancelled."); // Space is reserved; Q becomes START. await startRow.getByRole("button", { name: /Change the key for START/ }).click(); await page.keyboard.press("Space"); await expect(page.locator("#settings-status")).toContainText("keeps its usual role"); await startRow.getByRole("button", { name: /Change the key for START/ }).click(); await page.keyboard.press("KeyQ"); await expect(startRow).toContainText("Q"); await expect(page.locator("#settings-status")).toHaveText("START is now Q."); await dialog.getByLabel("Large", { exact: true }).check(); await expect(page.locator("html")).toHaveAttribute("style", /--text-scale: 1.25/); await page.locator("#close-settings").click(); await expect(page.locator("#game-screen")).toHaveAttribute("aria-label", /Q is START/); // Enter no longer opens the start menu; Q does. await page.locator("#play").click(); await page.locator("#game-screen").focus(); await page.keyboard.press("Enter"); await page.clock.runFor(200); await expect(page.locator("#dialogue-choice")).toBeHidden(); await page.keyboard.press("KeyQ"); await expect(page.locator("#dialogue-choice").getByRole("option", { name: /^POKéDEX/ })).toBeVisible(); // The choices stay after a reload. await page.reload(); await expect(page.locator("html")).toHaveAttribute("style", /--text-scale: 1.25/); await page.locator("#settings").click(); await expect(page.locator("#key-table tr", { hasText: "START" })).toContainText("Q"); await page.locator("#reset-keys").click(); await expect(page.locator("#key-table tr", { hasText: "START" })).toContainText("Enter"); }); test("help lists the current keys", async ({ page }) => { await start(page); await page.locator("#help").click(); const keys = page.locator("#help-keys"); await expect(keys).toContainText("Interact / Continue"); await expect(keys).toContainText("X (A)"); await expect(keys).toContainText("Enter (START)"); });