Checks / web (push) Canceled after 0s
A new game now runs NewGame's InitGender and OakSpeech natively before the bedroom: Boy or Girl, InitClock's hour and minute steppers with their YES/NO confirmations, Oak's speech with Wooper, and NamePlayer's preset names or naming field (a blank name takes CHRIS or KRIS). Texts, menus and time strings come from a new INTRO profile table verified by check:reference; Oak's and the player's pictures decode from TrainerPicPointers, ChrisPic/KrisPic and TrainerPalettes. The chosen time is kept as a minute offset from the device clock, which time of day, the weekday, NPC hours and the daily reset now read. The intro runs only when nothing is saved; a save made during it starts it again. Saves gain intro and clock.minuteOffset, migrated by absence. The shell's name and character fields are gone. Intentional change: the steppers start at the device time rather than 10:00. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
132 lines
5.0 KiB
TypeScript
132 lines
5.0 KiB
TypeScript
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) {
|
|
await page.clock.install({ time: new Date("2026-09-25T14:37: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-25T14:37:05"));
|
|
await page.locator("#play").click();
|
|
}
|
|
/** Press A until the dialogue shows `text`. */
|
|
async function advanceTo(page: Page, text: string) {
|
|
const dialogue = page.locator("#dialogue-text");
|
|
for (let guard = 0; guard < 40; guard++) {
|
|
if ((await dialogue.textContent())?.includes(text)) return;
|
|
await page.keyboard.press("KeyX");
|
|
}
|
|
await expect(dialogue).toContainText(text);
|
|
}
|
|
|
|
test("a new game asks for gender, the clock and a name before the bedroom", async ({
|
|
page,
|
|
}) => {
|
|
const errors: string[] = [];
|
|
page.on("pageerror", (error) => errors.push(error.message));
|
|
await start(page);
|
|
const screen = page.locator("#game-screen"),
|
|
choice = page.locator("#dialogue-choice"),
|
|
text = page.locator("#dialogue-text"),
|
|
picture = page.locator("#pokepic-canvas");
|
|
await expect(screen).toHaveAttribute("data-intro", "true");
|
|
await expect(page.locator("#intro-screen")).toBeVisible();
|
|
await expect(text).toHaveText("Are you a boy?\nOr are you a girl?");
|
|
// Down, then A chooses "Girl"; B is ignored.
|
|
await page.keyboard.press("KeyZ");
|
|
await page.keyboard.press("ArrowDown");
|
|
await expect(choice.getByRole("option", { name: "Girl" })).toHaveAttribute(
|
|
"aria-selected",
|
|
"true",
|
|
);
|
|
await page.keyboard.press("KeyX");
|
|
await advanceTo(page, "What time is it?");
|
|
// The hour starts at the device time; Up three times is 5 PM.
|
|
const value = choice.locator("output");
|
|
await expect(value).toHaveText("DAY 2 o'clock");
|
|
for (let i = 0; i < 3; i++) await page.keyboard.press("ArrowUp");
|
|
await expect(value).toHaveText("DAY 5 o'clock");
|
|
await page.keyboard.press("KeyX");
|
|
await expect(text).toHaveText("What?\nDAY 5 o'clock?");
|
|
await choice.getByRole("button", { name: "YES" }).click();
|
|
await expect(text).toHaveText("How many minutes?");
|
|
await expect(value).toHaveText("37 min.");
|
|
await choice.getByRole("button", { name: "Earlier" }).click();
|
|
await expect(value).toHaveText("36 min.");
|
|
await choice.getByRole("button", { name: "OK" }).click();
|
|
await expect(text).toHaveText("Whoa! 36 min.?");
|
|
await page.keyboard.press("KeyX");
|
|
await expect(text).toHaveText("DAY 5:36!\nYikes! I over-\nslept!");
|
|
await page.keyboard.press("KeyX");
|
|
await expect(picture).toHaveAttribute("aria-label", "Professor Oak");
|
|
await advanceTo(page, "This world is in-");
|
|
await expect(picture).toHaveAttribute("aria-label", "WOOPER");
|
|
await advanceTo(page, "Now, what did you");
|
|
await expect(picture).toHaveAttribute("aria-label", "Kris");
|
|
await page.keyboard.press("KeyX");
|
|
await expect(choice.getByRole("option")).toHaveText([
|
|
"NEW NAME",
|
|
"KRIS",
|
|
"AMANDA",
|
|
"JUANA",
|
|
"JODI",
|
|
]);
|
|
await page.keyboard.press("KeyX");
|
|
await expect(text).toHaveText("YOUR NAME?");
|
|
await choice.locator("input").fill("LYRA");
|
|
await choice.locator("input").press("Enter");
|
|
await expect(text).toHaveText("LYRA, are you\nready?");
|
|
await advanceTo(page, "I'll be seeing you");
|
|
await page.keyboard.press("KeyX");
|
|
await expect(screen).toHaveAttribute("data-intro", "false");
|
|
await expect(page.locator("#intro-screen")).toBeHidden();
|
|
await expect(page.locator("#pokepic")).toBeHidden();
|
|
await expect(screen).toHaveAttribute("data-map", "24:7");
|
|
|
|
await page.locator("#tab-saves").click();
|
|
const download = page.waitForEvent("download");
|
|
await page.locator("#backup").click();
|
|
const record = JSON.parse(
|
|
readFileSync((await (await download).path())!, "utf8"),
|
|
);
|
|
const save = JSON.parse(Buffer.from(record.state, "base64").toString("utf8"));
|
|
expect(save).toMatchObject({
|
|
intro: false,
|
|
name: "LYRA",
|
|
avatar: "kris",
|
|
clock: { minuteOffset: 17 * 60 + 36 - (14 * 60 + 37) },
|
|
});
|
|
expect(errors).toEqual([]);
|
|
});
|
|
|
|
test("a session saved during the introduction starts it again", async ({
|
|
page,
|
|
}) => {
|
|
await start(page);
|
|
await page.keyboard.press("KeyX");
|
|
await advanceTo(page, "What time is it?");
|
|
// Leaving the game tab pauses and saves.
|
|
await page.locator("#tab-saves").click();
|
|
await expect(page.locator("#save-status")).toContainText("Session saved");
|
|
await page.reload();
|
|
await page.locator("#rom-file").setInputFiles(romPath);
|
|
await expect(page.locator("#status")).toContainText("saved session");
|
|
await page.locator("#play").click();
|
|
await expect(page.locator("#game-screen")).toHaveAttribute(
|
|
"data-intro",
|
|
"true",
|
|
);
|
|
await expect(page.locator("#dialogue-text")).toHaveText(
|
|
"Are you a boy?\nOr are you a girl?",
|
|
);
|
|
});
|