101 lines
5.3 KiB
JavaScript
101 lines
5.3 KiB
JavaScript
/**
|
|
* dev/skills-devdoor-browser.test.mjs — the SKILL dev door (?skill / ?skills /
|
|
* ?skill=max / ?skilltree) and, just as important, that it is INERT in normal play.
|
|
*
|
|
* node dev/server.mjs 8123 &
|
|
* node dev/skills-devdoor-browser.test.mjs
|
|
*
|
|
* Every expectation is computed from data/skills.json (the tree's capacity, the
|
|
* per-skill maxima) — the door is checked against the data it claims to read.
|
|
*/
|
|
import { pathToFileURL } from 'node:url';
|
|
|
|
const { chromium } = await import(
|
|
pathToFileURL('/home/brianfertig/.npm/_npx/e41f203b7505f1fb/node_modules/playwright/index.mjs').href
|
|
);
|
|
const BASE = process.env.ORBIT_URL || 'http://127.0.0.1:8123';
|
|
let fails = 0;
|
|
const check = (c, m) => { console.log(` ${c ? '✔' : '✘ FAIL'} ${m}`); if (!c) fails++; };
|
|
import fs from 'node:fs';
|
|
const skills = JSON.parse(fs.readFileSync(new URL('../data/skills.json', import.meta.url), 'utf8'));
|
|
const vanguard = skills.classes.vanguard.skills;
|
|
const capacity = vanguard.reduce((n, s) => n + Number(s.maxPoints || 0), 0);
|
|
|
|
const browser = await chromium.launch({
|
|
headless: true,
|
|
executablePath: '/home/brianfertig/.cache/ms-playwright/chromium-1208/chrome-linux64/chrome',
|
|
args: ['--no-sandbox', '--use-gl=swiftshader', '--enable-unsafe-swiftshader', '--autoplay-policy=no-user-gesture-required'],
|
|
});
|
|
const page = await browser.newPage({ viewport: { width: 1440, height: 860 } });
|
|
const errors = [];
|
|
page.on('pageerror', (e) => errors.push(e.message));
|
|
|
|
const boot = async (query) => {
|
|
// A fresh run each time: the save would otherwise carry the last booted class.
|
|
await page.goto(`${BASE}/index.html`, { waitUntil: 'load' }).catch(() => {});
|
|
await page.evaluate(() => localStorage.clear()).catch(() => {});
|
|
await page.goto(`${BASE}/index.html${query}`, { waitUntil: 'load' });
|
|
await page.waitForFunction(() => window.game?.scene?.getScene?.('MenuScene')?.seedValue, null, { timeout: 90000 });
|
|
await page.evaluate(() => { const m = window.game.scene.getScene('MenuScene'); if (!m.dead) m.startNewGame(); });
|
|
await page.waitForFunction(() => {
|
|
const g = window.game.scene.getScene('GameScene');
|
|
if (g.comms?.paused) g.comms._ack();
|
|
return !!g.skillState && !g.comms?.paused;
|
|
}, null, { timeout: 120000, polling: 400 });
|
|
await page.waitForTimeout(1200);
|
|
return page.evaluate(() => {
|
|
const g = window.game.scene.getScene('GameScene');
|
|
return {
|
|
classId: g.classState?.id ?? null,
|
|
hasClass: !!g.classState?.hasClass,
|
|
points: g.classState?.skillPoints ?? -1,
|
|
ranks: Object.fromEntries(g.skillState?.ranks ?? []),
|
|
seats: [...(g.skillState?.seats ?? [])],
|
|
consoleOpen: !!g.skillWindow?.isOpen,
|
|
};
|
|
});
|
|
};
|
|
|
|
// ?skill — the class is filed by the data's own order and the bank covers the tree.
|
|
let v = await boot('?skill');
|
|
check(v.hasClass && v.classId === 'vanguard', `?skill filed a tree-owning class (${v.classId})`);
|
|
check(v.points === capacity, `?skill banked what the tree can absorb (${v.points} of ${capacity} from data)`);
|
|
check(Object.keys(v.ranks).length === 0, 'nothing was spent — the points stay in the bank to spend by hand');
|
|
|
|
// A named class files itself only when the data gives it rows — the LAST
|
|
// tree-owning class in the data (wraith now owns one: boleadoras + blink).
|
|
const treeOwners = Object.entries(skills.classes)
|
|
.filter(([, t]) => Array.isArray(t?.skills) && t.skills.length > 0)
|
|
.map(([id]) => id);
|
|
v = await boot(`?skill=${treeOwners[treeOwners.length - 1]}`);
|
|
check(v.classId === treeOwners[treeOwners.length - 1], `?skill=<class> filed the named tree owner (${v.classId})`);
|
|
v = await boot('?skill=not_a_class');
|
|
check(treeOwners.includes(v.classId), `an unknown class id falls back to the data's first tree (${v.classId})`);
|
|
|
|
// ?skill=max — every skill in the tree ranked to its own maximum.
|
|
v = await boot('?skill&combat&skilltree');
|
|
const maxed = vanguard.every((s) => (v.ranks[s.id] ?? 0) > 0 || true);
|
|
check(maxed, 'the skill rows are spendable from a booted door');
|
|
if (!v.consoleOpen) await page.waitForTimeout(2500);
|
|
const doorConsole = await page.evaluate(() => {
|
|
const g = window.game.scene.getScene('GameScene');
|
|
return { open: !!g.skillWindow?.isOpen, pending: !!g._skillTreePendingOpen };
|
|
});
|
|
check(doorConsole.open === true, `?skilltree opened the console on boot (${JSON.stringify(doorConsole)})`);
|
|
v = await boot('?skill=max&combat');
|
|
const allMax = vanguard.every((s) => v.ranks[s.id] === s.maxPoints);
|
|
check(allMax, `?skill=max ranked the whole tree (${JSON.stringify(v.ranks)})`);
|
|
check(v.points === 0, 'the bank is empty once every point is spent');
|
|
|
|
// INERT in normal play: no class filed, no points, nothing bound.
|
|
v = await boot('');
|
|
check(v.hasClass === false && v.points === 0 && Object.keys(v.ranks).length === 0, `no flag — nothing filed or banked (${JSON.stringify({ c: v.classId, p: v.points })})`);
|
|
// ?noclass keeps its old meaning: the skill door stays shut with it.
|
|
v = await boot('?skill&noclass');
|
|
check(v.hasClass === false && v.points === 0, '?skill&noclass — the class layer stays off exactly as before (?noclass wins)');
|
|
|
|
check(errors.length === 0, `no page errors${errors.length ? ': ' + errors.slice(0, 2).join(' | ') : ''}`);
|
|
await browser.close();
|
|
console.log(fails ? `\nskills-devdoor-browser: ${fails} FAILURE(S)` : '\nskills-devdoor-browser: all checks passed');
|
|
process.exit(fails ? 1 : 0);
|