Files
HEROLL/mvp/tests/browser-smoke.mjs

181 lines
12 KiB
JavaScript

import assert from "node:assert/strict";
import { chromium } from "file:///C:/Users/toshio/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/node_modules/playwright/index.mjs";
const BASE = process.env.HEROLL_MVP_URL ?? "http://127.0.0.1:4173/mvp/";
const browser = await chromium.launch({
headless: true,
executablePath: "C:/Program Files/Google/Chrome/Application/chrome.exe",
});
const context = await browser.newContext({ viewport: { width: 750, height: 1334 }, deviceScaleFactor: 1 });
const page = await context.newPage();
const errors = [];
const failedRequests = [];
page.on("console", (message) => {
if (message.type() === "error") {
const location = message.location();
errors.push(`console: ${message.text()} @ ${location.url || "unknown"}:${location.lineNumber ?? 0}`);
}
});
page.on("pageerror", (error) => errors.push(`pageerror: ${error.message}`));
page.on("requestfailed", (request) => failedRequests.push(`${request.url()} ${request.failure()?.errorText}`));
page.on("response", (response) => { if (response.status() >= 400) errors.push(`http ${response.status()}: ${response.url()}`); });
try {
await page.goto(BASE, { waitUntil: "networkidle" });
await page.locator("#seed-input").fill("BROWSER-SMOKE-001");
await page.locator("#new-run-button").click();
await page.locator("#game-screen.active").waitFor();
assert.equal(await page.locator(".board-tile").count(), 40);
assert.equal(await page.locator(".board-tile.logical").count(), 40);
assert.equal(await page.locator(".board-tile.presentation").count(), 0);
assert.equal(await page.locator(".board-tile.miniboss").count(), 1);
assert.equal(await page.locator(".board-tile.infected_boss").count(), 1);
assert.equal(await page.locator("#floor-value").textContent(), "0/2");
assert.equal(await page.locator("#auto-indicator").textContent(), "AUTO");
assert.equal(await page.locator("button.boost-button, button[data-placement]").count(), 0, "界面不得出现广告增强按钮");
const layout = await page.evaluate(() => Object.fromEntries(["#top-hud", ".resource-chip", "#auto-indicator", "#floor-meter", "#board-ring", "#boss-figure", "#bottom-hud", "#gear-slots", "#roll-button", "#potion-button"].map((selector) => {
const rect = document.querySelector(selector).getBoundingClientRect();
return [selector, { x: rect.x, y: rect.y, width: rect.width, height: rect.height }];
})));
assert.ok(layout[".resource-chip"].x < 30 && layout[".resource-chip"].y < 100);
assert.ok(layout["#auto-indicator"].x > 500 && layout["#floor-meter"].x > 680);
assert.ok(Math.abs(layout["#boss-figure"].x + layout["#boss-figure"].width / 2 - 375) < 4);
assert.ok(layout["#bottom-hud"].y > 940 && layout["#bottom-hud"].y < 1010 && layout["#gear-slots"].y > 1020);
assert.ok(layout["#roll-button"].y > 1200 && layout["#potion-button"].x > 630);
console.log("STEP board layout passed");
await page.screenshot({ path: "tests/artifacts/heroll-mvp-board.png", fullPage: true });
console.log("STEP board screenshot captured");
await page.locator("#roll-button").click();
await page.waitForTimeout(180);
assert.equal(await page.locator(".die-face").count(), 2);
const rolledSum = Number(await page.locator("#dice-result").getAttribute("data-sum"));
assert.equal(Number(await page.locator(".dice-sum").textContent()), rolledSum);
console.log("STEP dice rendered");
await page.screenshot({ path: "tests/artifacts/heroll-mvp-dice.png", fullPage: true });
await page.locator("#game-screen.animating-move").waitFor({ state: "visible", timeout: 2500 });
assert.equal(await page.locator("#modal-layer .modal").count(), 0, "移动期间不允许事件或战斗结算硬切入");
await page.locator("#game-screen.animating-move").waitFor({ state: "hidden", timeout: 6000 });
const landedIndex = Number(await page.locator(".board-tile.current").getAttribute("data-visual-index"));
assert.equal(landedIndex, rolledSum, `从START ROLL ${rolledSum}必须顺时针移动${rolledSum}个实际地块`);
assert.match(await page.locator("#points-value").textContent(), /^\d+$/);
assert.equal(await page.locator("#floor-value").textContent(), "1/2");
await page.waitForTimeout(1200);
await page.locator("#debug-toggle").click();
await page.locator("#debug-points").fill("999");
await page.locator("button[data-debug=apply]").click();
await page.locator("button[data-debug=merchant]").click();
await page.locator(".shop-modal").waitFor();
assert.equal(await page.locator(".shop-offer").count(), 6);
const fixedPotionLabels = await page.locator(".shop-offer").evaluateAll((nodes) => nodes.slice(0, 3).map((node) => node.querySelector("strong")?.textContent ?? ""));
assert.deepEqual(fixedPotionLabels, ["补充 1 瓶丹药(不超过上限)", "丹药持有上限 +1", "丹药基础恢复 +30 HP"]);
assert.match(await page.locator(".shop-offer").first().textContent(), /已满/);
await page.locator("#debug-panel").evaluate((element) => { element.hidden = true; });
await page.locator("#tutorial-layer").evaluate((element) => { element.innerHTML = ""; });
await page.locator("#toast-layer").evaluate((element) => { element.innerHTML = ""; });
await page.screenshot({ path: "tests/artifacts/heroll-mvp-shop.png", fullPage: true });
await page.locator("#debug-panel").evaluate((element) => { element.hidden = false; });
await page.locator("#debug-panel").evaluate((element) => { element.hidden = true; });
await page.locator("button[data-action=shop-reroll]").click();
assert.equal(await page.locator(".shop-offer").count(), 6);
await page.locator("button[data-action=event-close]").click();
await page.locator("#debug-panel").evaluate((element) => { element.hidden = false; });
await page.locator("button[data-debug=dig]").click();
await page.getByRole("heading", { name: "挖掘" }).waitFor();
assert.match(await page.locator(".modal p").first().textContent(), /5/);
await page.locator(".dig-cell").first().click();
if (await page.getByRole("heading", { name: "装备掉落" }).count()) await page.locator("button[data-action=gear-salvage]").click();
await page.locator("button[data-action=event-close]").click();
await page.locator("button[data-debug=chest]").click();
await page.locator("button[data-action=event-claim]").click();
await page.getByRole("heading", { name: "装备掉落" }).waitFor();
assert.equal(await page.locator(".gear-card").count(), 1);
await page.locator("button[data-action=gear-equip]").click();
await page.locator("button[data-debug=blessing]").click();
await page.locator("button[data-action=event-claim]").click();
await page.getByRole("heading", { name: "祝福揭示" }).waitFor();
await page.locator("button[data-action=blessing-accept]").click();
await page.locator("button[data-debug=campfire]").click();
await page.getByRole("heading", { name: "营火" }).waitFor();
await page.locator("button[data-action=event-claim]").click();
await page.locator("button[data-debug=smithy]").click();
await page.getByRole("heading", { name: "铁匠铺" }).waitFor();
const smithTarget = page.locator("button[data-action=smith]:not([disabled])").first();
if (await smithTarget.count()) await smithTarget.click();
else await page.locator("button[data-action=event-close]").click();
await page.locator("button[data-debug=legend]").click();
await page.locator("button[data-action=event-claim]").click();
await page.getByRole("heading", { name: "装备掉落" }).waitFor();
await page.locator("button[data-action=gear-salvage]").click();
for (const [debug, heading] of [["lotto", "彩票"], ["blackjack", "21点"], ["machine", "骰子机"]]) {
await page.locator(`button[data-debug=${debug}]`).click();
await page.getByRole("heading", { name: heading }).waitFor();
await page.locator("button[data-action=event-claim]").click();
}
await page.locator("button[data-debug=claw]").click();
await page.getByRole("heading", { name: "抓娃娃" }).waitFor();
await page.locator("button[data-action=event-claim]").click();
if (await page.getByRole("heading", { name: "装备掉落" }).count()) await page.locator("button[data-action=gear-salvage]").click();
if (await page.getByRole("heading", { name: "抓娃娃" }).count()) await page.locator("button[data-action=event-close]").click();
await page.locator("button[data-debug=carriage]").click();
await page.getByRole("heading", { name: "黄金马车" }).waitFor();
await page.locator("button[data-action=carriage-points]").click();
await page.locator("button[data-debug=bubble]").click();
await page.getByRole("heading", { name: "泡泡龙" }).waitFor();
for (let shot = 0; shot < 3; shot += 1) await page.locator("button[data-action=event-claim]").click();
await page.locator("button[data-action=event-close]").click();
await page.locator("button[data-debug=battle]").click();
await page.locator("#battle-view.active").waitFor();
await page.locator("#battle-announcer.show").waitFor();
await page.locator("#debug-panel").evaluate((element) => { element.hidden = true; });
await page.locator("#tutorial-layer").evaluate((element) => { element.innerHTML = ""; });
await page.locator("#toast-layer").evaluate((element) => { element.innerHTML = ""; });
await page.screenshot({ path: "tests/artifacts/heroll-mvp-battle.png", fullPage: true });
await page.locator("#debug-panel").evaluate((element) => { element.hidden = false; });
await page.locator("button[data-debug=death]").click();
await page.locator("#battle-announcer.show").waitFor();
assert.equal(await page.locator(".result-modal.defeat-modal").count(), 0, "失败结算必须等待受击与 DEFEAT 演出完成");
await page.locator(".result-modal.defeat-modal").waitFor({ timeout: 4000 });
assert.equal(await page.locator("button.boost-button, button[data-placement], button[data-action=revive]").count(), 0, "失败界面不得出现广告复活按钮");
await page.locator("button[data-action=retry]").click();
await page.locator("#game-screen.active").waitFor();
await page.locator("#debug-panel").evaluate((element) => { element.hidden = false; });
await page.locator("button[data-debug=boss]").click();
await page.locator("#battle-announcer.show").waitFor();
await page.locator("button[data-debug=win]").click();
await page.locator("#battle-announcer.show").waitFor();
assert.match(await page.locator("#battle-announcer").textContent(), /VICTORY/);
assert.equal(await page.locator(".result-modal").count(), 0, "最终结算不得跳过 VICTORY 演出直接出现");
await page.locator(".result-modal.victory-modal").waitFor({ timeout: 4000 });
assert.match(await page.locator(".result-modal h2").textContent(), /STAGE CLEAR/);
assert.equal(await page.locator(".result-rewards .count-up").count(), 3);
await page.waitForTimeout(1050);
assert.equal(await page.locator(".result-rewards .count-up").first().textContent(), await page.locator("#points-value").textContent());
await page.screenshot({ path: "tests/artifacts/heroll-mvp-result.png", fullPage: true });
const saved = await page.evaluate(() => localStorage.getItem("heroll.validation.run.v1"));
assert.ok(saved && saved.length > 100);
await page.locator("#debug-panel").evaluate((element) => { element.hidden = true; });
await page.screenshot({ path: "tests/artifacts/heroll-mvp-smoke.png", fullPage: true });
assert.deepEqual(errors, [], `浏览器错误:\n${errors.join("\n")}`);
assert.deepEqual(failedRequests, [], `资源失败:\n${failedRequests.join("\n")}`);
console.log("PASS browser smoke: fixed board/clockwise move → shop → events → gear → blessing → battle → no-ad defeat → victory/result → save");
console.log("SCREENSHOTS tests/artifacts/heroll-mvp-{board,dice,shop,battle,result,smoke}.png");
} finally {
await browser.close();
}