Files
aetherbound-guild/prototype/site/verify-p8-2.js
T

180 lines
11 KiB
JavaScript

#!/usr/bin/env node
"use strict";
const assert = require("node:assert/strict");
const fs = require("node:fs");
const path = require("node:path");
const { chromium } = require("playwright");
const root = path.resolve(__dirname, "../..");
const baseURL = process.env.ABG_PROTOTYPE_URL || "http://127.0.0.1:8766";
const outputDir = process.env.ABG_PROTOTYPE_EVIDENCE || path.join(root, "prototype/gallery/evidence/p8-2");
const browserPath = process.env.ABG_BROWSER_PATH || "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
async function screenshot(page, name) {
const destination = path.join(outputDir, name);
await page.screenshot({ path: destination, fullPage: true });
return destination;
}
async function noHorizontalOverflow(page, label) {
const dimensions = await page.evaluate(() => ({ scrollWidth: document.documentElement.scrollWidth, clientWidth: document.documentElement.clientWidth }));
assert.ok(dimensions.scrollWidth <= dimensions.clientWidth + 1, `${label}: ${JSON.stringify(dimensions)}`);
}
async function stageAndActionFit(page, selector, label) {
const metrics = await page.locator(selector).evaluate((action) => {
const a = action.getBoundingClientRect();
const s = action.closest(".game-stage").getBoundingClientRect();
return { aLeft: a.left, aRight: a.right, aTop: a.top, aBottom: a.bottom, sLeft: s.left, sRight: s.right, sTop: s.top, sBottom: s.bottom, viewportWidth: innerWidth, viewportHeight: innerHeight };
});
assert.ok(metrics.sLeft >= -0.5 && metrics.sRight <= metrics.viewportWidth + 0.5 && metrics.sTop >= -0.5 && metrics.sBottom <= metrics.viewportHeight + 0.5, `${label} stage: ${JSON.stringify(metrics)}`);
assert.ok(metrics.aLeft >= metrics.sLeft && metrics.aRight <= metrics.sRight && metrics.aTop >= metrics.sTop && metrics.aBottom <= metrics.sBottom, `${label} action: ${JSON.stringify(metrics)}`);
}
async function hire(page, offerId, expectedBefore, expectedAfter, companyAfter) {
assert.equal(await page.locator(".p82-resource strong").textContent(), String(expectedBefore));
assert.equal(await page.locator(".p82-offers > button").count(), 6 - companyAfter, "stable unsold offer count");
assert.equal(await page.locator(".p82-offers > button.is-focused").count(), 0, "no preselected offer");
const beforeText = await page.locator(".p82-offers").textContent();
await page.locator(`[data-market-focus="${offerId}"]`).click();
assert.equal(await page.locator(".p82-resource strong").textContent(), String(expectedBefore), "inspect spends nothing");
assert.match(await page.locator(".p82-offer-inspect").textContent(), /LV 1.*XP 0\/30/s);
assert.match(await page.locator(".p82-offer-inspect").textContent(), /唯一装备槽/);
assert.ok((await page.locator(".p82-offers").textContent()).includes(beforeText.trim().slice(0, 30)), "inspect does not reroll board");
await page.locator("[data-market-review]").click();
assert.match(await page.locator(".p82-transaction").textContent(), /14 金币/);
assert.match(await page.locator(".p82-transaction").textContent(), new RegExp(`${expectedBefore} → ${expectedAfter}`));
await page.locator("[data-market-hire-confirm]").click();
assert.equal(await page.locator(".p82-company-bench article:not(.is-empty)").count(), companyAfter);
assert.match(await page.locator(".p82-simple-receipt").textContent(), new RegExp(`${expectedBefore} → ${expectedAfter}`));
assert.match(await page.locator(".p82-simple-receipt").textContent(), new RegExp(`${companyAfter - 1} → ${companyAfter}`));
}
async function completeToEquipment(page) {
await hire(page, "MKT-R1-01", 52, 38, 3);
await page.locator("[data-market-continue]").click();
await hire(page, "MKT-R1-02", 38, 24, 4);
await page.locator("[data-market-continue]").click();
}
async function buyAndEquip(page) {
assert.equal(await page.locator(".p82-resource strong").textContent(), "24");
assert.match(await page.locator(".p82-item-card").textContent(), /12 金币/);
assert.match(await page.locator(".p82-equipment-offer").textContent(), /不会自动给任何队员装备/);
await page.locator("[data-market-item-review]").click();
assert.match(await page.locator(".p82-item-review").textContent(), /24 → 12/);
assert.match(await page.locator(".p82-item-review").textContent(), /无 · 稍后选择/);
await page.locator("[data-market-item-confirm]").click();
assert.match(await page.locator(".p82-item-receipt").textContent(), /24 → 12/);
assert.match(await page.locator(".p82-item-receipt").textContent(), /无人/);
await page.locator("[data-market-open-equip]").click();
assert.equal(await page.locator(".p82-equip-company button.is-selected").count(), 0, "no automatic wearer");
await page.locator('[data-market-equip-target="OFF-03"]').click();
assert.match(await page.locator(".p82-equip-delta").textContent(), /EQ-001 → EQ-012/);
assert.match(await page.locator(".p82-equip-delta").textContent(), /退回库存.*EQ-001/s);
await page.locator("[data-market-equip-confirm]").click();
assert.match(await page.locator(".p82-simple-receipt").textContent(), /有效装备数.*1 → 1/s);
assert.match(await page.locator(".p82-equip-receipt").textContent(), /退回库存.*EQ-001/s);
}
async function orderAndFinish(page) {
await page.locator("[data-market-open-order]").click();
const original = await page.locator("[data-market-order-unit]").evaluateAll((buttons) => buttons.map((button) => button.dataset.marketOrderUnit));
assert.deepEqual(original, ["OFF-03", "OFF-04", "MKT-R1-01", "MKT-R1-02"]);
assert.ok(await page.locator("[data-market-order-save]").isDisabled(), "cannot save unchanged order");
await page.locator('[data-market-order-unit="OFF-03"]').click();
assert.equal(await page.locator("[data-market-order-unit].is-source").count(), 1);
await page.locator('[data-market-order-unit="MKT-R1-02"]').click();
const changed = await page.locator("[data-market-order-unit]").evaluateAll((buttons) => buttons.map((button) => button.dataset.marketOrderUnit));
assert.deepEqual(changed, ["MKT-R1-02", "OFF-04", "MKT-R1-01", "OFF-03"]);
await page.locator("[data-market-order-undo]").click();
assert.deepEqual(await page.locator("[data-market-order-unit]").evaluateAll((buttons) => buttons.map((button) => button.dataset.marketOrderUnit)), original, "undo restores order");
await page.locator('[data-market-order-unit="OFF-04"]').click();
await page.locator('[data-market-order-unit="MKT-R1-01"]').click();
assert.ok(await page.locator("[data-market-order-save]").isEnabled());
await page.locator("[data-market-order-save]").click();
assert.match(await page.locator(".p82-ready-facts").textContent(), /金币\s*12/);
assert.match(await page.locator(".p82-ready-facts").textContent(), /队伍 \/ 容量\s*4 \/ 4/);
assert.match(await page.locator(".p82-ready").textContent(), /P8\.3 尚未开启/);
}
async function desktopAudit(browser, evidence) {
const page = await browser.newPage({ viewport: { width: 1440, height: 1000 }, deviceScaleFactor: 1 });
const errors = [];
page.on("console", (message) => { if (message.type() === "error") errors.push(message.text()); });
page.on("pageerror", (error) => errors.push(error.message));
await page.goto(`${baseURL}?package=p8-2#playtest`, { waitUntil: "networkidle" });
assert.equal(await page.locator("html").getAttribute("lang"), "zh-CN");
assert.equal(await page.locator(".p82-company-bench article:not(.is-empty)").count(), 2);
assert.match(await page.locator(".p82-resource").textContent(), /队伍 2 \/ 容量 4/);
evidence.push(await screenshot(page, "desktop-market-company2.png"));
await hire(page, "MKT-R1-01", 52, 38, 3);
evidence.push(await screenshot(page, "desktop-hire1-receipt.png"));
await page.locator("[data-market-continue]").click();
await hire(page, "MKT-R1-02", 38, 24, 4);
evidence.push(await screenshot(page, "desktop-hire2-receipt.png"));
await page.locator("[data-market-continue]").click();
evidence.push(await screenshot(page, "desktop-equipment-offer.png"));
await buyAndEquip(page);
evidence.push(await screenshot(page, "desktop-equipment-receipt.png"));
await page.locator("[data-market-open-order]").click();
await page.locator('[data-market-order-unit="OFF-04"]').click();
await page.locator('[data-market-order-unit="MKT-R1-01"]').click();
evidence.push(await screenshot(page, "desktop-order-changed.png"));
await page.locator("[data-market-order-save]").click();
evidence.push(await screenshot(page, "desktop-preparation-receipt.png"));
assert.match(await page.locator(".p82-ready").textContent(), /P8\.3 尚未开启/);
await page.locator("[data-market-restart]").click();
assert.equal(await page.locator(".p82-resource strong").textContent(), "52");
assert.deepEqual(errors, [], `desktop errors: ${errors.join(" | ")}`);
await page.close();
}
async function responsiveAudit(browser, evidence) {
for (const fixture of [
{ name: "phone-landscape-market.png", viewport: { width: 844, height: 390 } },
{ name: "phone-portrait-market.png", viewport: { width: 390, height: 844 } },
]) {
const page = await browser.newPage({ viewport: fixture.viewport, deviceScaleFactor: 1 });
await page.goto(`${baseURL}?package=p8-2#playtest`, { waitUntil: "networkidle" });
await page.locator('[data-market-focus="MKT-R1-01"]').click();
await noHorizontalOverflow(page, fixture.name);
await stageAndActionFit(page, "[data-market-review]", fixture.name);
assert.equal(await page.locator(".p82-offers > button:visible").count(), 3);
evidence.push(await screenshot(page, fixture.name));
await page.close();
}
}
async function bundleAudit(browser) {
const bundle = path.join(root, "prototype/gallery/aetherbound-guild-prototype.html");
const page = await browser.newPage({ viewport: { width: 1280, height: 800 } });
const external = [];
page.on("request", (request) => { if (!request.url().startsWith("file:") && !request.url().startsWith("data:")) external.push(request.url()); });
await page.goto(`file://${bundle}?package=p8-2#playtest`, { waitUntil: "load" });
assert.equal(await page.locator(".p82-offers > button").count(), 3);
assert.deepEqual(external, []);
await page.close();
}
async function main() {
fs.mkdirSync(outputDir, { recursive: true });
const browser = await chromium.launch({ headless: true, executablePath: browserPath });
const evidence = [];
try {
await desktopAudit(browser, evidence);
await responsiveAudit(browser, evidence);
await bundleAudit(browser);
} finally {
await browser.close();
}
console.log("ABG_P8_2_BROWSER_OK hires=2 hire_price=14 coin=52,38,24,12 company=2,3,4 capacity=4 item_buy=1 item_equip=1 active_slots=1 order_swap=1 undo=1 responsive=desktop,phone_landscape,phone_portrait bundle=offline");
console.log(`evidence=${evidence.join(",")}`);
}
main().catch((error) => {
console.error(error.stack || error);
process.exitCode = 1;
});