Files
ark-stronghold-protocol/test/sim/loadout.browser.test.js
T
sganggs 7f134737ce Dev: puppeteer-core 25 (browser tests adapted)
- puppeteer-core ^23.11.1 -> ^25.12.0 (dev only); npm audit: 7 high (basic-ftp via
  @puppeteer/browsers / proxy-agent, extract-zip) -> 0; production audit stays clean
- puppeteer-core 25 is ESM-only and needs Node >= 22.12: the render / sim browser suites
  load it with await import() instead of createRequire
- headless: 'new' -> headless: true (same new headless mode; 'new' is no longer a typed value)
- fieldview CPU throttle: page.emulateCPUThrottling() instead of the deprecated page.target() CDP session
- THIRD-PARTY-NOTICES version, DESIGN test note
2026-10-02 17:58:35 +08:00

74 lines
4.4 KiB
JavaScript

// test/sim/loadout.browser.test.js — operator loadouts (DESIGN §16) in the BROWSER sim: headless Chrome loads the
// served /sim/ modules and /data/*.json through the client's own loader (public/js/battle/runner.js loadBrowserSim),
// builds a BattleSpec whose units carry skillIndex / moduleId and must resolve the same defs and produce the same result
// digest as Node (server re-simulation / verification depend on it).
//
// Opt-in (starts Chrome): RENDER_E2E=1 node --test test/sim/loadout.browser.test.js (or SIM_E2E=1)
// Run browser test files one at a time. Chrome path: $CHROME_PATH or the macOS default.
import { test, describe, before, after } from 'node:test';
import assert from 'node:assert/strict';
import { existsSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { buildBattleSpec, createBattleFromSpec, resultDigest } from '../../server/sim/spec.js';
import { DataSource, getDefaultSource } from '../../server/sim/simdata.js';
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
const CHROME = process.env.CHROME_PATH || '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
const enabled = (process.env.RENDER_E2E === '1' || process.env.SIM_E2E === '1') && existsSync(CHROME);
const skip = enabled ? false : 'set RENDER_E2E=1 or SIM_E2E=1 (needs Chrome)';
describe('operator loadouts in the browser sim', { skip }, () => {
let srv, browser;
before(async () => {
const puppeteer = (await import('puppeteer-core')).default;
const { startServer } = await import('../../server/index.js');
srv = await startServer({ port: 0, host: '127.0.0.1', quiet: true });
browser = await puppeteer.launch({ executablePath: CHROME, headless: true, args: ['--no-first-run'] });
});
after(async () => {
await browser?.close();
await srv?.close();
});
test('a spec with loadouts resolves the same defs and gives the same digest in Chrome as in Node', async () => {
const ds = getDefaultSource();
const tpl = ds.getWave('act1autochess_03');
const spec = buildBattleSpec({
battleId: 'lo1', fieldId: 'n:p1', kind: 'normal', seed: 90210, modeId: 'mode_multi_normal', round: 3, stageId: 'act2autochess_m01',
timeLimit: 110, players: [{ playerId: 'p1', seat: 0, side: 'L', colOffset: 0, bonds: {}, playerEffects: [], units: [
{ uid: 1, chessId: 'chess_char_1_01_a', row: 10, col: 5, dir: 'RIGHT', skillIndex: 0 },
{ uid: 2, chessId: 'chess_char_1_02_a', row: 10, col: 8, dir: 'RIGHT', skillIndex: 0 },
{ uid: 3, chessId: 'chess_char_6_11_b', row: 11, col: 4, dir: 'RIGHT', skillIndex: 1, moduleId: 'uniequip_003_mlyss' },
{ uid: 4, kind: 'token', tokenId: 'token_10030_mlyss_wtrman', ownerUid: 3, row: 12, col: 6 },
] }],
spawns: tpl.spawns.filter((s) => !s.action && !s.slot).map((s) => ({ time: s.time, enemyKey: s.key, routeIndex: s.routeIndex, count: s.count, interval: s.interval })),
routes: tpl.routes, flags: {}, waveId: 'act1autochess_03',
});
const nb = createBattleFromSpec(spec, new DataSource(ds.raw, null), { quiet: true, recordEvents: false });
const nodeUnits = nb.allyUnits.map((u) => [u.uid, u.def.skill?.id ?? null, u.base.maxHp, u.base.atk]);
const nodeHash = resultDigest(nb.runToEnd(4000)).hash;
const page = await browser.newPage();
const problems = [];
page.on('pageerror', (e) => problems.push(`pageerror: ${e.message}`));
page.on('response', (r) => { if (r.status() >= 400 && !/favicon/.test(r.url())) problems.push(`HTTP ${r.status()} ${r.url()}`); });
await page.goto(`http://127.0.0.1:${srv.port}/sim/spec.js`);
const out = await page.evaluate(async (s) => {
const { loadBrowserSim } = await import('/js/battle/runner.js');
const { spec: S, ds } = await loadBrowserSim();
const b = S.createBattleFromSpec(s, ds, { quiet: true, recordEvents: false });
const units = b.allyUnits.map((u) => [u.uid, u.def.skill?.id ?? null, u.base.maxHp, u.base.atk]);
return { units, hash: S.resultDigest(b.runToEnd(4000)).hash };
}, spec);
await page.close();
assert.deepEqual(problems, []);
assert.deepEqual(out.units, nodeUnits, 'same loadout defs in the browser');
assert.equal(nodeUnits[0][1], 'skchr_inside_1');
assert.equal(nodeUnits[2][1], 'skchr_mlyss_2');
assert.equal(nodeUnits[3][1], 'sktok_mlyss_wtrman_2');
assert.equal(out.hash, nodeHash, 'browser and Node results are identical');
});
});