Files
Perler_beads/tools/runtime-verify-custom-level.cjs
2026-10-20 13:13:03 +08:00

73 lines
4.3 KiB
JavaScript

const fs = require('fs');
const path = require('path');
const assert = require('assert');
const { chromium } = require('playwright');
const root = path.resolve(__dirname, '..');
const level = JSON.parse(fs.readFileSync(path.join(root, 'assets/resources/levels/custom-origin-003.json')));
const solution = level.solution_lanes;
const outDir = path.join(root, 'artifacts');
fs.mkdirSync(outDir, { recursive: true });
(async () => {
const browser = await chromium.launch({ channel: 'chrome', headless: true });
const page = await browser.newPage({ viewport: { width: 430, height: 932 }, deviceScaleFactor: 1 });
page.on('console', message => {
if (message.type() === 'error') console.error('browser:', message.text());
});
await page.goto(`http://127.0.0.1:8765/?version=pass29&custom=1&r=${Date.now()}`, { waitUntil: 'networkidle' });
await page.waitForFunction(() => globalThis.Laya?.stage, null, { timeout: 15000 });
await page.evaluate(() => {
const visit = node => {
for (const component of node._components || []) {
if (Array.isArray(component.queues) && typeof component.selectLane === 'function') globalThis.g = component;
}
for (const child of node._children || []) visit(child);
};
visit(Laya.stage);
});
await page.waitForFunction(() => globalThis.g && g.currentResource === 'custom-origin-003' && !g.busy, null, { timeout: 15000 });
const initial = await page.evaluate(() => ({
resource: g.currentResource,
dimensions: [g.sourceBoardSize.rows, g.sourceBoardSize.cols],
total: g.total,
lanes: g.queues.length,
cars: g.queues.reduce((sum, lane) => sum + lane.length, 0),
slots: g.slotCount
}));
assert.deepStrictEqual(initial, { resource: 'custom-origin-003', dimensions: [48, 38], total: 1824, lanes: 5, cars: 72, slots: 5 });
await page.screenshot({ path: path.join(outDir, 'custom-level-003-start.png'), fullPage: true });
await page.evaluate(() => { Laya.timer.scale = 20; });
for (let step = 0; step < solution.length; step++) {
const lane = solution[step];
const before = await page.evaluate(index => g.queues[index].length, lane);
assert(before > 0, `step ${step + 1}: lane ${lane} is empty`);
await page.evaluate(index => g.selectLane(index), lane);
await page.waitForFunction(() => g.activeMoves === 0, null, { timeout: 10000 });
const state = await page.evaluate(() => ({ finished: g.levelFinished, modal: Boolean(g.modal), cleared: g.cleared, occupied: g.occupiedSlotCount() }));
if (state.modal && !state.finished) throw new Error(`unexpected failure after step ${step + 1}: ${JSON.stringify(state)}`);
}
await page.waitForFunction(() => g.levelFinished && g.cleared === g.total, null, { timeout: 10000 });
const final = await page.evaluate(() => ({ finished: g.levelFinished, cleared: g.cleared, total: g.total, carsLeft: g.queues.reduce((sum, lane) => sum + lane.length, 0), parked: g.parked.length }));
assert.deepStrictEqual(final, { finished: true, cleared: 1824, total: 1824, carsLeft: 0, parked: 0 });
await page.screenshot({ path: path.join(outDir, 'custom-level-003-complete.png'), fullPage: true });
await page.evaluate(() => g.loadLevel(0));
await page.waitForFunction(() => g.currentResource === 'custom-origin-003' && !g.busy && !g.modal && g.cleared === 0, null, { timeout: 15000 });
await page.evaluate(() => { Laya.timer.scale = 20; });
for (const lane of level.failure_probe_lanes) {
await page.evaluate(index => g.selectLane(index), lane);
await page.waitForFunction(() => g.activeMoves === 0, null, { timeout: 10000 });
}
const wrongPath = await page.evaluate(() => ({ finished: g.levelFinished, modal: Boolean(g.modal), cleared: g.cleared, occupied: g.occupiedSlotCount() }));
assert.deepStrictEqual(wrongPath, { finished: false, modal: true, cleared: 0, occupied: 5 });
await page.screenshot({ path: path.join(outDir, 'custom-level-003-fail.png'), fullPage: true });
console.log(JSON.stringify({ initial, style: level.graphic_style.id, orderAlgorithm: level.vehicle_order_algorithm.id, seed: level.vehicle_order_algorithm.seed, final, wrongPath, routeSteps: solution.length, screenshots: ['artifacts/custom-level-003-start.png', 'artifacts/custom-level-003-complete.png', 'artifacts/custom-level-003-fail.png'] }));
await browser.close();
})().catch(error => {
console.error(error.stack || error);
process.exitCode = 1;
});