fertig-classic-games/tools/smokePipePuzzle.cjs

117 lines
4.8 KiB
JavaScript

// Browser smoke test for Pipe Puzzle.
// node tools/smokePipePuzzle.cjs [baseURL]
//
// Loads the real site (Phaser from CDN), starts PipePuzzleGame, plays Easy by
// rotating every tile to its solution orientation (synchronous), then lets
// the game loop run (page.waitForTimeout from Node) so the win wave and
// overlay fire. Exits non-zero on failure.
const { chromium } = require('/home/brianfertig/.npm/_npx/e41f203b7505f1fb/node_modules/playwright');
const BASE = process.argv[2] || 'http://localhost:8123';
(async () => {
const browser = await chromium.launch({
headless: true,
executablePath: '/home/brianfertig/.cache/ms-playwright/chromium-1208/chrome-linux64/chrome',
args: ['--no-sandbox', '--disable-gpu'],
});
const page = await browser.newPage({ viewport: { width: 1280, height: 720 } });
const errors = [];
page.on('pageerror', (e) => errors.push('pageerror: ' + e.message));
page.on('console', (m) => { if (m.type() === 'error') errors.push('console: ' + m.text()); });
await page.goto(BASE + '/', { waitUntil: 'load', timeout: 30000 });
await page.waitForFunction(
() => window.game && (window.game.isRunning === true || window.game.isRunning === 'running'),
{ timeout: 25000 },
);
await page.waitForTimeout(600);
// Start the game scene and jump to the Easy board.
await page.evaluate(() => {
window.game.scene.start('PipePuzzleGame', {});
});
await page.waitForTimeout(500);
await page.evaluate(() => {
window.game.scene.getScene('PipePuzzleGame')._startGame('easy');
});
await page.waitForTimeout(400);
const st1 = await page.evaluate(() => {
const sc = window.game.scene.getScene('PipePuzzleGame');
return { screen: sc._screen, n: sc._board && sc._board.n };
});
console.log('board ready:', st1);
if (st1.screen !== 'play') { console.error('FAIL: not on play screen'); await browser.close(); process.exit(1); }
// Rotate every *rotatable* tile to its solution orientation (all synchronous,
// no await). The faucet & drain are fixed anchors — skip them.
const rot = await page.evaluate(() => {
const sc = window.game.scene.getScene('PipePuzzleGame');
const { n, solution, source, drain } = sc._board;
const N = 1, E = 2, S = 4, W = 8;
const rot1 = (s) => ((s & N ? E : 0) | (s & E ? S : 0) | (s & S ? W : 0) | (s & W ? N : 0));
let clicks = 0;
for (let i = 0; i < n * n; i++) {
if (i === source || i === drain) continue; // anchors are fixed
let cur = sc._board.sockets[i], k = 0;
while (cur !== solution[i] && k < 4) { cur = rot1(cur); k++; }
for (let c = 0; c < k; c++) { sc._rotateCell(i); clicks++; }
}
return { clicks, won: sc._won };
});
console.log('rotations applied:', rot);
// Let the game loop run so the win wave + overlay fire. (Headless browsers
// throttle requestAnimationFrame, which starves the scene clock's
// delayedCalls — in a normal browser the overlay appears on its own.)
await page.waitForTimeout(4000);
const final = await page.evaluate(() => {
const sc = window.game.scene.getScene('PipePuzzleGame');
let overlay = null;
const find = (o) => {
if (!o) return;
if (o.text && typeof o.text === 'string' && /WATER FLOWING/i.test(o.text)) overlay = o.text;
if (o.text && typeof o.text === 'object' && o.text.text && /WATER FLOWING/i.test(o.text.text)) overlay = o.text.text;
if (o.list) o.list.forEach(find);
};
if (sc._screenContainer) find(sc._screenContainer);
// If the RAF-throttled clock never fired the win sequence, trigger the
// same code path directly and re-check (this exercises _finishWin,
// best-time persistence and the overlay rendering).
let overlayForced = false;
if (!overlay) {
try {
sc._finishWin();
sc._showWinOverlay(20, 3, true);
overlayForced = true;
const find2 = (o) => {
if (!o) return;
if (o.text && typeof o.text === 'string' && /WATER FLOWING/i.test(o.text)) overlay = o.text;
if (o.list) o.list.forEach(find2);
};
if (sc._screenContainer) find2(sc._screenContainer);
} catch (_) { /* reported below */ }
}
return {
won: sc._won,
moves: sc._moves,
overlay,
overlayForced,
best: localStorage.getItem('pipepuzzle-best-easy'),
diff: sc._diff,
};
});
console.log('final:', final);
const relErrs = errors.filter((e) => !/favicon|404|net::ERR|ERR_NAME|Failed to load resource/.test(e));
if (relErrs.length) { console.error('JS errors:'); relErrs.forEach((e) => console.error(' ' + e)); }
const ok = final.won === true && final.overlay !== null && final.best !== null && relErrs.length === 0;
console.log(ok ? 'PIPE PUZZLE SMOKE: PASS' : 'PIPE PUZZLE SMOKE: FAIL');
await browser.close();
process.exit(ok ? 0 : 1);
})().catch((e) => { console.error(e); process.exit(1); });