157 lines
8.4 KiB
JavaScript
157 lines
8.4 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-5");
|
|
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 fit(page, label) {
|
|
const metrics = await page.evaluate(() => ({
|
|
scrollWidth: document.documentElement.scrollWidth,
|
|
clientWidth: document.documentElement.clientWidth,
|
|
clientHeight: innerHeight,
|
|
stage: (() => { const r = document.querySelector(".game-stage").getBoundingClientRect(); return { left: r.left, right: r.right, top: r.top, bottom: r.bottom }; })(),
|
|
}));
|
|
assert.ok(metrics.scrollWidth <= metrics.clientWidth + 1, `${label} horizontal overflow: ${JSON.stringify(metrics)}`);
|
|
assert.ok(metrics.stage.left >= -1 && metrics.stage.right <= metrics.clientWidth + 1, `${label} stage horizontal fit`);
|
|
assert.ok(metrics.stage.top >= -1 && metrics.stage.bottom <= metrics.clientHeight + 1, `${label} stage vertical fit`);
|
|
}
|
|
|
|
async function open(page) {
|
|
await page.goto(`${baseURL}?package=p8-5#playtest`, { waitUntil: "networkidle" });
|
|
await page.locator(".p85-intro").waitFor();
|
|
assert.equal(await page.locator(".p85-progress li").count(), 12, "twelve P8.5 steps");
|
|
assert.match(await page.locator(".p85-intro").textContent(), /完整的公会循环|complete Guild cycle/);
|
|
}
|
|
|
|
async function advanceToBattle1(page) {
|
|
await page.locator("[data-p85-start]").click();
|
|
await page.locator("[data-p85-opening-commit]").click();
|
|
for (const id of ["mael", "ilya", "equip"]) await page.locator(`[data-p85-market="${id}"]`).click();
|
|
assert.match(await page.locator(".p85-market").textContent(), /52.*38.*24.*12|52.*38.*24.*12/);
|
|
await page.locator("[data-p85-market-next]").click();
|
|
await page.locator("[data-p85-risk1-commit]").click();
|
|
assert.equal(await page.locator(".p85-battle").count(), 1, "first battle opens after risk commit");
|
|
}
|
|
|
|
async function advanceBattle(page, second = false, beforeDone = null) {
|
|
const next = second ? "[data-p85-battle2-next]" : "[data-p85-battle1-next]";
|
|
const done = second ? "[data-p85-battle2-done]" : "[data-p85-battle1-done]";
|
|
const expected = second ? 5 : 6;
|
|
for (let i = 0; i < expected - 1; i++) await page.locator(next).click();
|
|
assert.equal(await page.locator(".p85-battle-log .is-reached").count(), expected, `${second ? "second" : "first"} battle event count before terminal`);
|
|
if (beforeDone) await beforeDone();
|
|
await page.locator(done).click();
|
|
}
|
|
|
|
async function mainPath(browser, evidence) {
|
|
const page = await browser.newPage({ viewport: { width: 1440, height: 1000 }, deviceScaleFactor: 1 });
|
|
await open(page);
|
|
evidence.push(await screenshot(page, "desktop-intro.png"));
|
|
await advanceToBattle1(page);
|
|
assert.equal(await page.locator(".p83-arena").count(), 1, "first cycle uses dynamic horizontal arena");
|
|
assert.equal(await page.locator(".p83-fighter.is-ally").count(), 4, "first cycle shows four moving allies");
|
|
evidence.push(await screenshot(page, "desktop-battle-one.png"));
|
|
await advanceBattle(page);
|
|
await page.locator("[data-p85-result1-next]").click();
|
|
await page.locator('[data-p85-reward="AR-043"]').click();
|
|
await page.locator("[data-p85-reward-claim]").click();
|
|
assert.match(await page.locator(".p85-recovery").textContent(), /EQ-014/);
|
|
await page.locator('[data-p85-recovery="credit"]').click();
|
|
await page.locator("[data-p85-recovery-commit]").click();
|
|
assert.match(await page.locator(".p85-loadout").textContent(), /46.*0.*4|46.*Company/);
|
|
await page.locator('[data-p85-equip="equip"]').click();
|
|
await page.locator("[data-p85-equip-commit]").click();
|
|
assert.match(await page.locator(".p85-risk").textContent(), /开阔跨线|针雨走廊|Open Span|Needle Rain/);
|
|
await page.locator('[data-p85-risk2="RSK-P85-02"]').click();
|
|
await page.locator("[data-p85-risk2-commit]").click();
|
|
assert.equal(await page.locator(".p85-dynamic-arena").count(), 1, "second cycle uses distinct dynamic arena");
|
|
assert.equal(await page.locator('[data-fighter-id="RCV-P84-01"]').count(), 1, "Vela replaces Ilya in cycle two");
|
|
assert.equal(await page.locator(".p85-audio-caption").count(), 1, "battle event has a caption");
|
|
assert.equal(await page.locator("[data-p85-audio]").count(), 1, "prototype sound cue toggle is visible");
|
|
await advanceBattle(page, true, async () => { evidence.push(await screenshot(page, "desktop-battle-two.png")); });
|
|
await page.locator("[data-p85-result2-next]").click();
|
|
assert.equal(await page.locator(".p85-receipt").count(), 1, "integrated receipt exists");
|
|
assert.match(await page.locator(".p85-receipt").textContent(), /EQ-014.*Vela/s);
|
|
assert.match(await page.locator(".p85-receipt").textContent(), /RSK-P85-02/);
|
|
await page.locator("[data-p85-restart]").click();
|
|
assert.equal(await page.locator(".p85-intro").count(), 1, "restart returns to fresh intro");
|
|
await fit(page, "desktop");
|
|
await page.close();
|
|
}
|
|
|
|
async function counterfactual(browser) {
|
|
const page = await browser.newPage({ viewport: { width: 1280, height: 800 } });
|
|
await open(page);
|
|
await advanceToBattle1(page);
|
|
await advanceBattle(page);
|
|
await page.locator("[data-p85-result1-next]").click();
|
|
await page.locator('[data-p85-reward="AR-021"]').click();
|
|
await page.locator("[data-p85-reward-claim]").click();
|
|
await page.locator('[data-p85-recovery="short"]').click();
|
|
await page.locator("[data-p85-recovery-commit]").click();
|
|
await page.locator('[data-p85-equip="leave"]').click();
|
|
await page.locator("[data-p85-equip-commit]").click();
|
|
await page.locator('[data-p85-risk2="RSK-P85-01"]').click();
|
|
await page.locator("[data-p85-risk2-commit]").click();
|
|
assert.match(await page.locator(".p85-battle").textContent(), /Open Span|Gale Hounds|疾风猎犬/);
|
|
await page.close();
|
|
}
|
|
|
|
async function responsive(browser, evidence) {
|
|
for (const fixture of [
|
|
{ name: "phone-landscape.png", viewport: { width: 844, height: 390 } },
|
|
{ name: "phone-portrait.png", viewport: { width: 390, height: 844 } },
|
|
]) {
|
|
const page = await browser.newPage({ viewport: fixture.viewport });
|
|
await open(page);
|
|
await page.locator('[data-locale="zh_CN"]').click();
|
|
await fit(page, fixture.name);
|
|
evidence.push(await screenshot(page, fixture.name));
|
|
await page.close();
|
|
}
|
|
}
|
|
|
|
async function offline(browser) {
|
|
const bundle = path.join(root, "prototype/gallery/aetherbound-guild-prototype.html");
|
|
assert.ok(fs.statSync(bundle).size > 1_000_000, "Gallery bundle contains embedded media");
|
|
const page = await browser.newPage({ viewport: { width: 1280, height: 800 } });
|
|
const requests = [];
|
|
page.on("request", (request) => requests.push(request.url()));
|
|
await page.goto(`file://${bundle}?package=p8-5#playtest`, { waitUntil: "load" });
|
|
assert.equal(await page.locator(".p85-intro").count(), 1, "offline P8.5 opens");
|
|
assert.equal(requests.filter((url) => /^https?:/.test(url)).length, 0, "offline bundle makes no network requests");
|
|
await page.close();
|
|
}
|
|
|
|
async function main() {
|
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
const browser = await chromium.launch({ headless: true, executablePath: browserPath });
|
|
const evidence = [];
|
|
try {
|
|
await mainPath(browser, evidence);
|
|
await counterfactual(browser);
|
|
await responsive(browser, evidence);
|
|
await offline(browser);
|
|
} finally {
|
|
await browser.close();
|
|
}
|
|
fs.writeFileSync(path.join(outputDir, "screenshots.txt"), `${evidence.map((entry) => path.basename(entry)).join("\n")}\n`);
|
|
console.log("ABG_P8_5_BROWSER_OK fresh_load=1 steps=12 cycle_one=market,risk,battle,result,recovery cycle_two=loadout,risk,battle,result continuation=1 casualty_inherited=Ilya item_return=EQ-014 reward=AR-043 recovery=credit equip_counterfactual=leave risk_counterfactual=Open_Span responsive=desktop,phone_landscape,phone_portrait locale=zh_CN bundle=offline duplicate_commit=1");
|
|
}
|
|
|
|
const timeout = setTimeout(() => { console.error("P8.5 verifier timeout"); process.exitCode = 1; }, 120000);
|
|
main().then(() => clearTimeout(timeout)).catch((error) => { clearTimeout(timeout); console.error(error.stack || error); process.exitCode = 1; });
|