- Day-Care and breeding: deposits, withdrawal pricing and growth, compatibility, egg species, DVs and moves, DayCareStep, egg collection and the Odd Egg, compared with the cartridge's routines. - CountStep: walking happiness, DoEggStep, DayCareStep and DoBikeStep in the original's order; HatchEggs with the ROM's hatch animation, compared frame by frame with the original. - MAIL: all ten stationery types and the Portrait MAIL picture, compared with the cartridge's tilemaps, graphics and palettes. - Printing: Bill's PC boxes, Pokedex PRNT, MAIL and the Photo Studio take the original's absent-printer path. - Hall of Fame records (saved apart from the League flag, ready for the League to call) and the Hall of Fame PC, compared with the cartridge's _HallOfFamePC on 255 pages. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
646 lines
27 KiB
TypeScript
646 lines
27 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,
|
|
room:
|
|
| boolean
|
|
| "poster"
|
|
| "center"
|
|
| "candy"
|
|
| "pokegear"
|
|
| "mom"
|
|
| "fly"
|
|
| "headbutt"
|
|
| "fishing"
|
|
| "mail" = 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);
|
|
// 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 { 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("SEL registers the Bicycle, and Select rides it with keyboard and touch", async ({
|
|
page,
|
|
}) => {
|
|
await start(page);
|
|
const text = page.locator("#dialogue-text"),
|
|
choices = page.locator("#dialogue-choice"),
|
|
pack = page.locator("#mart");
|
|
await page.keyboard.press("Enter");
|
|
await choices.getByRole("option", { name: /^PACK/ }).click();
|
|
// Right twice: the Balls pocket, then Key Items.
|
|
await page.keyboard.press("ArrowRight");
|
|
await page.keyboard.press("ArrowRight");
|
|
await pack.getByRole("button", { name: "BICYCLE", exact: true }).click();
|
|
await choices.getByRole("option", { name: "SEL", exact: true }).click();
|
|
await expect(text).toContainText("Registered the");
|
|
await page.keyboard.press("KeyX");
|
|
await page.keyboard.press("KeyZ");
|
|
await page.keyboard.press("KeyZ");
|
|
await expect(choices).toBeHidden();
|
|
await page.keyboard.press("ShiftLeft");
|
|
await page.clock.runFor(500);
|
|
await expect(page.locator("#game-notice")).toHaveText("");
|
|
expect((await backup(page)).state.playerState).toBe(1);
|
|
await page.locator("#tab-game").click();
|
|
await page.locator("#play").click();
|
|
await page.getByRole("button", { name: "select", exact: true }).click();
|
|
await page.clock.runFor(500);
|
|
expect((await backup(page)).state.playerState).toBe(0);
|
|
});
|
|
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("Select without a registered item explains SEL, and the Bicycle stays packed indoors", async ({
|
|
page,
|
|
}) => {
|
|
await start(page, true);
|
|
const text = page.locator("#dialogue-text"),
|
|
choices = page.locator("#dialogue-choice"),
|
|
pack = page.locator("#mart");
|
|
await page.keyboard.press("ShiftLeft");
|
|
await expect(text).toContainText("An item in your");
|
|
await page.keyboard.press("KeyX");
|
|
await page.keyboard.press("KeyX");
|
|
await page.keyboard.press("Enter");
|
|
await choices.getByRole("option", { name: /^PACK/ }).click();
|
|
await page.keyboard.press("ArrowLeft");
|
|
await page.keyboard.press("ArrowLeft");
|
|
await pack.getByRole("button", { name: "BICYCLE", exact: true }).click();
|
|
await choices.getByRole("option", { name: "USE", exact: true }).click();
|
|
await expect(text).toContainText("This isn't the");
|
|
await page.keyboard.press("KeyX");
|
|
await pack.getByRole("button", { name: "BACK", exact: true }).click();
|
|
await page.keyboard.press("KeyZ");
|
|
await page.clock.runFor(500);
|
|
await expect(page.locator("#game-notice")).toHaveText("");
|
|
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 }]);
|
|
});
|
|
|
|
test("Start opens the start menu: SWITCH reorders the party and the pack tosses an item", async ({
|
|
page,
|
|
}) => {
|
|
await start(page, "center");
|
|
const text = page.locator("#dialogue-text"),
|
|
choices = page.locator("#dialogue-choice"),
|
|
pack = page.locator("#mart");
|
|
await page.keyboard.press("Enter");
|
|
await expect(choices.getByRole("option", { name: /^POKéMON/ })).toBeVisible();
|
|
await expect(choices.getByRole("option", { name: /^PACK/ })).toBeVisible();
|
|
// Movement is frozen while the menu is open.
|
|
await page.keyboard.down("ArrowLeft");
|
|
await page.clock.runFor(500);
|
|
await page.keyboard.up("ArrowLeft");
|
|
await expect(page.locator("#game-screen")).toHaveAttribute("data-x", "9");
|
|
await choices.getByRole("option", { name: /^POKéMON/ }).click();
|
|
await expect(text).toHaveText("Choose a POKéMON.");
|
|
await choices.getByRole("option", { name: /^CYNDAQUIL/ }).click();
|
|
await choices.getByRole("option", { name: "SWITCH", exact: true }).click();
|
|
await expect(text).toHaveText("Move to where?");
|
|
await choices.getByRole("option", { name: /^PIDGEY/ }).click();
|
|
await expect(choices.getByRole("option").first()).toHaveAccessibleName(
|
|
/^PIDGEY/,
|
|
);
|
|
// B leaves the party menu for the start menu.
|
|
await page.keyboard.press("KeyZ");
|
|
await choices.getByRole("option", { name: /^PACK/ }).click();
|
|
await expect(pack).toHaveAttribute("aria-label", "Pack");
|
|
await expect(
|
|
pack.getByRole("button", { name: "ITEM POCKET", exact: true }),
|
|
).toHaveAttribute("aria-pressed", "true");
|
|
// Right twice: the Balls pocket, then Key Items.
|
|
await page.keyboard.press("ArrowRight");
|
|
await page.keyboard.press("ArrowRight");
|
|
await expect(pack.getByRole("button", { name: "BICYCLE" })).toBeVisible();
|
|
await pack.getByRole("button", { name: "ITEM POCKET", exact: true }).click();
|
|
await pack.getByRole("button", { name: /POTION/ }).click();
|
|
await choices.getByRole("option", { name: "TOSS", exact: true }).click();
|
|
await expect(page.locator("#mart-prompt")).toContainText("Throw away how");
|
|
await pack.getByRole("button", { name: "TOSS", exact: true }).click();
|
|
await expect(text).toContainText("Throw away 1 POTION(S)?");
|
|
await choices.getByRole("button", { name: /YES/ }).click();
|
|
await expect(text).toContainText("Threw away");
|
|
await page.keyboard.press("KeyX");
|
|
await pack.getByRole("button", { name: "BACK", exact: true }).click();
|
|
await choices.getByRole("option", { name: /^EXIT/ }).click();
|
|
await page.clock.runFor(500);
|
|
await expect(page.locator("#game-notice")).toHaveText("");
|
|
const { state } = await backup(page);
|
|
expect(state.party.map((mon: { species: number }) => mon.species)).toEqual([
|
|
16, 155,
|
|
]);
|
|
expect(state.bag.items).toEqual([{ item: 18, quantity: 4 }]);
|
|
});
|
|
|
|
test("a Rare Candy levels up and evolves a Pokémon through the pack", async ({
|
|
page,
|
|
}) => {
|
|
await start(page, "candy");
|
|
const text = page.locator("#dialogue-text"),
|
|
choices = page.locator("#dialogue-choice"),
|
|
pack = page.locator("#mart");
|
|
await page.keyboard.press("Enter");
|
|
await choices.getByRole("option", { name: /^PACK/ }).click();
|
|
await pack.getByRole("button", { name: /RARE CANDY/ }).click();
|
|
await choices.getByRole("option", { name: "USE", exact: true }).click();
|
|
await choices.getByRole("option", { name: /^CYNDAQUIL/ }).click();
|
|
await expect(text).toContainText("grew to");
|
|
await page.keyboard.press("KeyX");
|
|
await expect(text).toContainText("ATTACK");
|
|
await page.keyboard.press("KeyX");
|
|
await expect(text).toContainText("is evolving!");
|
|
await page.clock.runFor(1500);
|
|
await expect(page.locator("#pokepic")).toBeVisible();
|
|
await page.clock.runFor(8000);
|
|
await expect(text).toContainText("Congratulations!");
|
|
await page.keyboard.press("KeyX");
|
|
await expect(text).toContainText("QUILAVA!");
|
|
await page.keyboard.press("KeyX");
|
|
await expect(pack).toHaveAttribute("aria-label", "Pack");
|
|
await pack.getByRole("button", { name: "BACK", exact: true }).click();
|
|
await choices.getByRole("option", { name: /^EXIT/ }).click();
|
|
await page.clock.runFor(500);
|
|
await expect(page.locator("#game-notice")).toHaveText("");
|
|
const { state } = await backup(page);
|
|
expect([state.party[0].species, state.party[0].level]).toEqual([156, 14]);
|
|
expect(state.bag.items).toEqual([{ item: 18, quantity: 5 }]);
|
|
});
|
|
|
|
test("the Pokégear shows its cards and calls Elm", async ({ page }) => {
|
|
await start(page, "pokegear");
|
|
const text = page.locator("#dialogue-text"),
|
|
choices = page.locator("#dialogue-choice"),
|
|
gear = page.locator("#pokegear"),
|
|
card = page.locator("#pokegear-card");
|
|
await page.keyboard.press("Enter");
|
|
await choices.getByRole("option", { name: /^POKéGEAR/ }).click();
|
|
await expect(gear).toBeVisible();
|
|
await expect(gear.locator('[aria-current="true"]')).toHaveText("CLOCK");
|
|
await expect(card).toContainText("TUESDAY");
|
|
await expect(card).toContainText("12:00 PM");
|
|
await expect(text).toHaveText("Press any button to exit.");
|
|
await page.keyboard.press("ArrowRight");
|
|
await expect(card).toContainText("▶ NEW BARK TOWN");
|
|
await expect(card.locator("canvas")).toHaveAttribute(
|
|
"aria-label",
|
|
"Town Map: NEW BARK TOWN, you are here",
|
|
);
|
|
await expect(page.locator("#native-dialogue")).toBeHidden();
|
|
await page.keyboard.press("ArrowUp");
|
|
await expect(card).toContainText("▶ ROUTE 29");
|
|
// The on-screen controls drive the Pokégear too.
|
|
const right = page.locator('[data-game-button="right"]');
|
|
await right.scrollIntoViewIfNeeded();
|
|
const bounds = (await right.boundingBox())!;
|
|
await page.mouse.move(
|
|
bounds.x + bounds.width / 2,
|
|
bounds.y + bounds.height / 2,
|
|
);
|
|
await page.mouse.down();
|
|
await page.mouse.up();
|
|
await page.locator("#game-screen").focus();
|
|
await expect(card.getByRole("list", { name: "Phone numbers" })).toContainText(
|
|
"PROF.ELM:",
|
|
);
|
|
await expect(text).toHaveText(/Whom do you want\s+to call\?/);
|
|
await page.keyboard.press("ArrowDown");
|
|
await page.keyboard.press("KeyX");
|
|
await expect(card).toContainText("CALL");
|
|
await expect(card).toContainText("CANCEL");
|
|
await page.keyboard.press("KeyX");
|
|
await expect(text).toHaveText("……");
|
|
await page.clock.runFor(1000);
|
|
await expect(text).toHaveText("Hello, CHRIS?");
|
|
await page.keyboard.press("KeyX");
|
|
await page.keyboard.press("KeyX");
|
|
await page.keyboard.press("KeyX");
|
|
await expect(text).toHaveText("Click!");
|
|
await page.clock.runFor(3000);
|
|
await expect(text).toHaveText(/Whom do you want\s+to call\?/);
|
|
await page.keyboard.press("KeyZ");
|
|
await expect(gear).toBeHidden();
|
|
await choices.getByRole("option", { name: /^EXIT/ }).click();
|
|
await page.clock.runFor(500);
|
|
await expect(page.locator("#game-notice")).toHaveText("");
|
|
});
|
|
|
|
test("the trainer card turns to the badges and back", async ({ page }) => {
|
|
await start(page, "pokegear");
|
|
const choices = page.locator("#dialogue-choice"),
|
|
card = page.locator("#trainer-card"),
|
|
info = page.locator("#trainer-card-info"),
|
|
body = page.locator("#trainer-card-page");
|
|
await page.keyboard.press("Enter");
|
|
await choices.getByRole("option", { name: /^CHRIS/ }).click();
|
|
await expect(card).toBeVisible();
|
|
await expect(page.locator("#native-dialogue")).toBeHidden();
|
|
await expect(info).toContainText("NAME/");
|
|
await expect(info).toContainText("CHRIS");
|
|
await expect(info).toContainText("ID NO");
|
|
await expect(info).toContainText("MONEY");
|
|
await expect(page.locator("#trainer-card-pic")).toHaveAttribute(
|
|
"aria-label",
|
|
"Chris",
|
|
);
|
|
await expect(body).toContainText("PLAY TIME");
|
|
await expect(body).toContainText("BADGES▶");
|
|
await page.keyboard.press("ArrowRight");
|
|
await expect(body.locator("canvas")).toHaveAttribute(
|
|
"aria-label",
|
|
"0 of 8 Johto badges",
|
|
);
|
|
await page.keyboard.press("ArrowLeft");
|
|
await expect(body).toContainText("PLAY TIME");
|
|
await page.keyboard.press("KeyZ");
|
|
await expect(card).toBeHidden();
|
|
await choices.getByRole("option", { name: /^EXIT/ }).click();
|
|
await page.clock.runFor(500);
|
|
await expect(page.locator("#game-notice")).toHaveText("");
|
|
});
|
|
|
|
test("the options change the battle style and the menu account", async ({
|
|
page,
|
|
}) => {
|
|
await start(page, "pokegear");
|
|
const choices = page.locator("#dialogue-choice"),
|
|
screen = page.locator("#options"),
|
|
list = page.locator("#options-list");
|
|
await page.keyboard.press("Enter");
|
|
await expect(choices).toContainText("Party Pokémon status");
|
|
await choices.getByRole("option", { name: /^OPTION/ }).click();
|
|
await expect(screen).toBeVisible();
|
|
await expect(page.locator("#native-dialogue")).toBeHidden();
|
|
await expect(list.locator('[aria-current="true"]')).toContainText(
|
|
"TEXT SPEED",
|
|
);
|
|
await page.keyboard.press("ArrowDown");
|
|
await page.keyboard.press("ArrowDown");
|
|
await page.keyboard.press("ArrowRight");
|
|
await expect(list.locator('[aria-current="true"]')).toHaveText(
|
|
"BATTLE STYLESET",
|
|
);
|
|
await page.keyboard.press("ArrowDown");
|
|
await page.keyboard.press("ArrowDown");
|
|
await page.keyboard.press("ArrowDown");
|
|
await page.keyboard.press("ArrowLeft");
|
|
await expect(list.locator('[aria-current="true"]')).toHaveText(
|
|
"MENU ACCOUNTOFF",
|
|
);
|
|
await page.keyboard.press("KeyZ");
|
|
await expect(screen).toBeHidden();
|
|
await expect(choices).not.toContainText("Party Pokémon status");
|
|
await choices.getByRole("option", { name: /^EXIT/ }).click();
|
|
await page.clock.runFor(500);
|
|
await expect(page.locator("#game-notice")).toHaveText("");
|
|
const { state } = await backup(page);
|
|
expect(state.options).toEqual({
|
|
options: 3 | (1 << 6),
|
|
textboxFrame: 0,
|
|
printerBrightness: 0x40,
|
|
options2: 0,
|
|
});
|
|
});
|
|
|
|
test("Fly picks a visited town on the Fly map and lands there", async ({
|
|
page,
|
|
}) => {
|
|
await start(page, "fly");
|
|
const choices = page.locator("#dialogue-choice"),
|
|
flyMap = page.locator("#fly-map"),
|
|
bubble = page.locator("#fly-map-bubble");
|
|
await page.keyboard.press("Enter");
|
|
await choices.getByRole("option", { name: /^POKéMON/ }).click();
|
|
await choices.getByRole("option", { name: /^PIDGEOT/ }).click();
|
|
await choices.getByRole("option", { name: "FLY", exact: true }).click();
|
|
await expect(flyMap).toBeVisible();
|
|
await expect(page.locator("#native-dialogue")).toBeHidden();
|
|
await expect(bubble).toContainText("Where?");
|
|
await expect(bubble).toContainText("NEW BARK TOWN");
|
|
await expect(page.locator("#fly-map-canvas")).toHaveAttribute(
|
|
"aria-label",
|
|
"Fly map: NEW BARK TOWN, you are here",
|
|
);
|
|
// Up skips to the next visited flypoint; B returns to the party menu.
|
|
await page.keyboard.press("ArrowUp");
|
|
await expect(bubble).toContainText("CHERRYGROVE CITY");
|
|
await page.keyboard.press("KeyZ");
|
|
await expect(flyMap).toBeHidden();
|
|
await expect(page.locator("#dialogue-text")).toHaveText("Choose a POKéMON.");
|
|
await choices.getByRole("option", { name: /^PIDGEOT/ }).click();
|
|
await choices.getByRole("option", { name: "FLY", exact: true }).click();
|
|
await page.keyboard.press("ArrowUp");
|
|
await expect(bubble).toContainText("CHERRYGROVE CITY");
|
|
await page.keyboard.press("KeyX");
|
|
await expect(flyMap).toBeHidden();
|
|
await page.clock.runFor(5_000);
|
|
const screen = page.locator("#game-screen");
|
|
await expect(screen).toHaveAttribute("data-map", "26:3");
|
|
await expect(screen).toHaveAttribute("data-x", "29");
|
|
await expect(screen).toHaveAttribute("data-y", "4");
|
|
await expect(screen).toHaveAttribute("data-activity", "idle");
|
|
await expect(page.locator("#game-notice")).toHaveText("");
|
|
const { state } = await backup(page);
|
|
expect([state.map, state.x, state.y]).toEqual(["26:3", 29, 4]);
|
|
});
|
|
|
|
test("Headbutt shakes a tree and a sleeping Hoothoot falls out", async ({
|
|
page,
|
|
}) => {
|
|
await start(page, "headbutt");
|
|
const text = page.locator("#dialogue-text"),
|
|
choices = page.locator("#dialogue-choice"),
|
|
screen = page.locator("#game-screen");
|
|
await page.keyboard.press("KeyX");
|
|
await expect(text).toHaveText(/A POKéMON could be\s+in this tree\./);
|
|
await page.keyboard.press("KeyX");
|
|
await expect(text).toHaveText(/Want to HEADBUTT\s+it\?/);
|
|
await choices.getByRole("button", { name: "YES" }).click();
|
|
await expect(text).toHaveText(/TOTODILE did a\s+HEADBUTT!/);
|
|
await page.keyboard.press("KeyX");
|
|
await expect(page.locator("#native-dialogue")).toBeHidden();
|
|
await page.clock.runFor(5_000);
|
|
await expect(page.locator("#battle")).toBeVisible();
|
|
await expect(page.locator("#battle-text")).toHaveText(
|
|
/HOOTHOOT fell\s+out of the tree!/,
|
|
);
|
|
await expect(page.locator("#battle-foe-name")).toHaveText("HOOTHOOT");
|
|
await expect(page.locator("#battle-foe-status")).toHaveText("SLP");
|
|
await expect(page.locator("#game-notice")).toHaveText("");
|
|
await expect(screen).toHaveAttribute("data-map", "24:3");
|
|
});
|
|
|
|
test("Select moves an item within the pack", async ({ page }) => {
|
|
await start(page, "candy");
|
|
const choices = page.locator("#dialogue-choice"),
|
|
pack = page.locator("#mart");
|
|
await page.keyboard.press("Enter");
|
|
await choices.getByRole("option", { name: /^PACK/ }).click();
|
|
const entries = pack.locator("#mart-menu button");
|
|
await expect(entries.first()).toContainText("POTION");
|
|
// Select on the highlighted Potions, down to the Rare Candy, Select.
|
|
await page.keyboard.press("ShiftLeft");
|
|
await expect(page.locator("#mart-prompt")).toHaveText(
|
|
"Where should this be moved to?",
|
|
);
|
|
await expect(entries.first()).toContainText("▷POTION");
|
|
await page.keyboard.press("ArrowDown");
|
|
await page.keyboard.press("ShiftLeft");
|
|
await expect(page.locator("#mart-prompt")).toHaveText("");
|
|
await expect(entries.nth(0)).toContainText("RARE CANDY");
|
|
await expect(entries.nth(1)).toContainText("POTION");
|
|
// B leaves the pack, then the start menu, which commits the change.
|
|
await page.keyboard.press("KeyZ");
|
|
await expect(pack).toBeHidden();
|
|
await page.keyboard.press("KeyZ");
|
|
await page.clock.runFor(500);
|
|
await expect(page.locator("#native-dialogue")).toBeHidden();
|
|
const { state } = await backup(page);
|
|
expect(state.bag.items.map((slot: { item: number }) => slot.item)).toEqual([
|
|
0x20, 18,
|
|
]);
|
|
});
|
|
|
|
test("MAIL is written when given, and read from the party menu", async ({
|
|
page,
|
|
}) => {
|
|
await start(page, "mail");
|
|
const text = page.locator("#dialogue-text"),
|
|
choices = page.locator("#dialogue-choice"),
|
|
pack = page.locator("#mart"),
|
|
letter = page.locator("#mail-view");
|
|
await page.keyboard.press("Enter");
|
|
await choices.getByRole("option", { name: /^PACK/ }).click();
|
|
await pack.getByRole("button", { name: /^FLOWER MAIL/ }).click();
|
|
await choices.getByRole("option", { name: "GIVE", exact: true }).click();
|
|
await choices.getByRole("option", { name: /^CYNDAQUIL/ }).click();
|
|
await expect(text).toHaveText(/Made CYNDAQUIL\s+hold FLOWER MAIL\./);
|
|
await page.keyboard.press("KeyX");
|
|
await choices.getByLabel("MAIL line 1").fill("HELLO, CHRIS!");
|
|
await choices.getByLabel("MAIL line 2").fill("SEE YOU!");
|
|
await choices.getByRole("button", { name: "OK" }).click();
|
|
await expect(pack).toBeVisible();
|
|
await page.keyboard.press("KeyZ");
|
|
await choices.getByRole("option", { name: /^POKéMON/ }).click();
|
|
await choices.getByRole("option", { name: /^CYNDAQUIL/ }).click();
|
|
await choices.getByRole("option", { name: "MAIL", exact: true }).click();
|
|
await choices.getByRole("option", { name: "READ", exact: true }).click();
|
|
await expect(letter).toBeVisible();
|
|
await expect(page.locator("#mail-type")).toHaveText("FLOWER MAIL");
|
|
await expect(page.locator("#mail-message")).toHaveText(
|
|
"HELLO, CHRIS! \nSEE YOU!",
|
|
);
|
|
await expect(page.locator("#mail-author")).toHaveText("CHRIS");
|
|
await expect(page.locator("#mail-canvas")).toBeVisible();
|
|
await page.keyboard.press("Enter");
|
|
await expect(letter).toBeHidden();
|
|
await expect(page.locator("#tile-screen-canvas")).toHaveAttribute("aria-label", /Printer Error 2/);
|
|
await page.keyboard.press("KeyX");
|
|
await expect(page.locator("#tile-screen")).toBeVisible();
|
|
await page.keyboard.press("KeyZ");
|
|
await expect(letter).toBeVisible();
|
|
await expect(page.locator("#mail-message")).toHaveText("HELLO, CHRIS! \nSEE YOU!");
|
|
await page.locator("#mail-close").click();
|
|
await expect(letter).toBeHidden();
|
|
await expect(page.locator("#game-notice")).toHaveText("");
|
|
});
|
|
|
|
test("the Old Rod hooks a Magikarp from New Bark Town's shore", async ({
|
|
page,
|
|
}) => {
|
|
await start(page, "fishing");
|
|
const text = page.locator("#dialogue-text"),
|
|
choices = page.locator("#dialogue-choice"),
|
|
pack = page.locator("#mart");
|
|
await page.keyboard.press("Enter");
|
|
await choices.getByRole("option", { name: /^PACK/ }).click();
|
|
await page.keyboard.press("ArrowRight");
|
|
await page.keyboard.press("ArrowRight");
|
|
await pack.getByRole("button", { name: "OLD ROD", exact: true }).click();
|
|
await choices.getByRole("option", { name: "USE", exact: true }).click();
|
|
// The cast, 40 frames, four bites with "!", 40 more frames.
|
|
await page.clock.runFor(5_000);
|
|
await expect(text).toHaveText(/Oh!\s+A bite!/);
|
|
await page.keyboard.press("KeyX");
|
|
await page.clock.runFor(5_000);
|
|
await expect(page.locator("#battle")).toBeVisible();
|
|
await expect(page.locator("#battle-text")).toHaveText(
|
|
/The hooked\s+MAGIKARP\s+attacked!/,
|
|
);
|
|
await expect(page.locator("#battle-foe-name")).toHaveText("MAGIKARP");
|
|
await expect(page.locator("#game-notice")).toHaveText("");
|
|
});
|
|
|
|
test("Mom keeps the player's money with the digit box", async ({ page }) => {
|
|
await start(page, "mom");
|
|
const text = page.locator("#dialogue-text"),
|
|
choices = page.locator("#dialogue-choice"),
|
|
box = choices.locator(".mom-bank");
|
|
await page.keyboard.press("KeyX");
|
|
await expect(text).toHaveText(/Hi! Welcome home!/);
|
|
await page.keyboard.press("KeyX");
|
|
await page.keyboard.press("KeyX");
|
|
await expect(text).toHaveText(/Or is this about\s+your money\?/);
|
|
await choices.getByRole("button", { name: "YES" }).click();
|
|
await expect(text).toHaveText(/What do you want\s+to do\?/);
|
|
await choices.getByRole("option", { name: "SAVE" }).click();
|
|
await expect(text).toHaveText(/How much do you\s+want to save\?/);
|
|
await expect(box).toContainText("SAVED¥2300");
|
|
await expect(box).toContainText("HELD¥3000");
|
|
await expect(box).toContainText("DEPOSIT¥000000");
|
|
await expect(box.locator(".mom-bank-cursor")).toHaveCount(1);
|
|
// The keyboard moves to the thousands and raises it; the on-screen
|
|
// buttons raise it again.
|
|
for (let i = 0; i < 3; i++) await page.keyboard.press("ArrowLeft");
|
|
await page.keyboard.press("ArrowUp");
|
|
await expect(box).toContainText("DEPOSIT¥001000");
|
|
await choices.getByRole("button", { name: "Increase digit" }).click();
|
|
await expect(box).toContainText("DEPOSIT¥002000");
|
|
await page.locator("#game-screen").focus();
|
|
await page.keyboard.press("KeyX");
|
|
await expect(text).toHaveText(/Your money's safe\s+here! Get going!/);
|
|
await page.keyboard.press("KeyX");
|
|
await expect(page.locator("#native-dialogue")).toBeHidden();
|
|
const { state } = await backup(page);
|
|
expect([state.money, state.momsMoney]).toEqual([1000, 4300]);
|
|
});
|