import { test, expect, type Page } from "@playwright/test"; import { existsSync, readFileSync } 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, room: boolean | "poster" | "center" = false) { 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); await route.fulfill({ response, body: source.replace( needle, `const { foundationFixture } = await import('/tests/foundation-fixture.ts'); const world = foundationFixture(assets, ${JSON.stringify(room)});`, ), }); }); 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(); } async function backup(page: Page) { await page.locator("#tab-saves").click(); const download = page.waitForEvent("download"); await page.locator("#backup").click(); const path = (await (await download).path())!; const record = JSON.parse(readFileSync(path, "utf8")); return { path, state: JSON.parse(Buffer.from(record.state, "base64").toString("utf8")), }; } test("Start opens Key Items, freezes movement, and uses the Bicycle with keyboard and touch", async ({ page, }) => { await start(page); await page.keyboard.press("Enter"); await expect(page.locator("#field-menu")).toBeVisible(); await page.keyboard.down("ArrowDown"); await page.clock.runFor(1000); await page.keyboard.up("ArrowDown"); await expect(page.locator("#game-screen")).toHaveAttribute("data-y", "6"); await page .locator("#field-items") .getByRole("button", { name: "BICYCLE", exact: true }) .click(); await page.keyboard.press("KeyX"); await page.clock.runFor(500); await expect(page.locator("#field-menu")).toBeHidden(); await expect(page.locator("#game-notice")).toHaveText(""); expect((await backup(page)).state.playerState).toBe(1); }); test("the bedroom PC puts away a decoration and saves the result", async ({ page, }) => { await start(page, true); await page.keyboard.press("KeyX"); await expect(page.locator("#game-notice")).toHaveText(""); await expect(page.locator("#dialogue-text")).toContainText( "turned on the PC.", ); await page.keyboard.press("KeyX"); const choices = page.locator("#dialogue-choice"); await choices .getByRole("option", { name: "DECORATION", exact: true }) .click(); await expect(page.locator("#dialogue-text")).toHaveText("DECORATION"); await choices.getByRole("option", { name: "BED", exact: true }).click(); await choices .getByRole("option", { name: "PUT IT AWAY", exact: true }) .click(); await page.keyboard.press("KeyX"); await choices.getByRole("option", { name: "EXIT", exact: true }).click(); await page.clock.runFor(500); await expect(page.locator("#game-notice")).toHaveText(""); expect((await backup(page)).state.decorations.bed).toBe(0); }); test("B cancels the pack and an indoor Bicycle attempt leaves the player walking", async ({ page, }) => { await start(page, true); await page.keyboard.press("Enter"); await page.keyboard.press("KeyZ"); await expect(page.locator("#field-menu")).toBeHidden(); await page.keyboard.press("Enter"); await page .locator("#field-items") .getByRole("button", { name: "BICYCLE", exact: true }) .click(); await expect(page.locator("#game-notice")).toContainText("can’t use"); expect((await backup(page)).state.playerState).toBe(0); }); test("the Town Map poster opens a native map and closes with B", async ({ page, }) => { await page.setViewportSize({ width: 320, height: 568 }); await start(page, "poster"); await page.keyboard.press("KeyX"); await expect(page.locator("#dialogue-text")).toContainText("TOWN MAP"); await page.keyboard.press("KeyX"); await expect(page.locator("#town-map-view")).toBeVisible(); const colors = await page .locator("#town-map-canvas") .evaluate( (canvas: HTMLCanvasElement) => new Set(canvas.getContext("2d")!.getImageData(0, 0, 160, 144).data) .size, ); expect(colors).toBeGreaterThan(8); await page.keyboard.press("KeyZ"); await expect(page.locator("#town-map-view")).toBeHidden(); await expect(page.locator("#game-notice")).toHaveText(""); }); test("the Pokémon Center PC deposits a Pokémon and an item", async ({ page, }) => { await start(page, "center"); const text = page.locator("#dialogue-text"), choices = page.locator("#dialogue-choice"); await page.keyboard.press("KeyX"); await expect(text).toContainText("turned on the PC."); await page.keyboard.press("KeyX"); await choices.getByRole("option", { name: "BILL's PC", exact: true }).click(); await expect(text).toContainText("BILL's PC"); await page.keyboard.press("KeyX"); await page.keyboard.press("KeyX"); await choices .getByRole("option", { name: "DEPOSIT POKéMON", exact: true }) .click(); await choices.getByRole("option", { name: "PIDGEY :L4" }).click(); await choices.getByRole("option", { name: "DEPOSIT", exact: true }).click(); await expect(text).toHaveText("Stored PIDGEY!"); await page.clock.runFor(2000); await expect(choices.getByRole("option", { name: "PIDGEY :L4" })).toHaveCount( 0, ); // B leaves the party list, then Bill's PC. await page.keyboard.press("KeyZ"); await page.keyboard.press("KeyZ"); await expect(choices.getByRole("option", { name: "TURN OFF" })).toBeVisible(); await choices .getByRole("option", { name: /'s PC$/ }) .last() .click(); await page.keyboard.press("KeyX"); await page.keyboard.press("KeyX"); await choices .getByRole("option", { name: "DEPOSIT ITEM", exact: true }) .click(); const storage = page.locator("#mart"); await expect(storage).toHaveAttribute("aria-label", "Item storage"); await expect(page.locator("#mart-money")).toBeHidden(); await storage.getByRole("button", { name: /POTION/ }).click(); await storage.getByRole("button", { name: "Plus 1", exact: true }).click(); await storage.getByRole("button", { name: "DEPOSIT", exact: true }).click(); await expect(text).toContainText("Deposited 2 POTION(S)."); await page.keyboard.press("KeyX"); await storage.getByRole("button", { name: "BACK", exact: true }).click(); await choices.getByRole("option", { name: "LOG OFF", exact: true }).click(); await choices.getByRole("option", { name: "TURN OFF", exact: true }).click(); await page.keyboard.press("KeyX"); await page.clock.runFor(500); await expect(page.locator("#game-notice")).toHaveText(""); const { state } = await backup(page); expect(state.party).toHaveLength(1); expect( state.storage.boxes[0].map((mon: { species: number }) => mon.species), ).toEqual([16]); expect(state.pcItems).toEqual([{ item: 18, quantity: 2 }]); expect(state.bag.items).toEqual([{ item: 18, quantity: 3 }]); });