115 lines
4.7 KiB
JavaScript
115 lines
4.7 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 tile to its solution orientation (all synchronous, no await).
|
|
const rot = await page.evaluate(() => {
|
|
const sc = window.game.scene.getScene('PipePuzzleGame');
|
|
const { n, solution } = 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++) {
|
|
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); });
|