In the start menu's pack, Select marks an item and asks where it should go; A or Select on another row moves it there, merging stacks of the same item unless either holds 99, and B ends the move. The packs that GIVE and the PC open ignore Select, as in the original. Left and right on the MOVE screen show the previous or next party member's moves, skipping Eggs, unless a move is being moved. Part of #27 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
601 lines
25 KiB
TypeScript
601 lines
25 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" = 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("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]);
|
|
});
|