Extend jigsaw initial-image test coverage
Test now also asserts the menu is back to its original shape (no random-start button), the initial preview and thumb name match the random pick, Start Puzzle begins exactly that image, and the original 🎲 Random button still randomises the menu preview.
This commit is contained in:
parent
25f5e05a96
commit
a7ebb67413
|
|
@ -9,6 +9,8 @@ import * as Phaser from 'phaser';
|
||||||
import JigsawGame from './src/games/jigsaw/JigsawGame.js';
|
import JigsawGame from './src/games/jigsaw/JigsawGame.js';
|
||||||
|
|
||||||
const log = (m) => { document.getElementById('log').textContent += m + '\n'; console.log('[t]', m); };
|
const log = (m) => { document.getElementById('log').textContent += m + '\n'; console.log('[t]', m); };
|
||||||
|
let failures = 0;
|
||||||
|
const check = (ok, msg) => { if (!ok) { failures++; log('FAIL: ' + msg); } else log('ok: ' + msg); };
|
||||||
const wait = (ms) => new Promise((r) => setTimeout(r, ms));
|
const wait = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||||
|
|
||||||
const ART = [
|
const ART = [
|
||||||
|
|
@ -37,24 +39,45 @@ const s = () => game.scene.getScene('jigsaw-game');
|
||||||
for (let i = 0; i < 400 && !s(); i++) await wait(50); // boot → jigsaw start is async
|
for (let i = 0; i < 400 && !s(); i++) await wait(50); // boot → jigsaw start is async
|
||||||
if (!s()) throw new Error('jigsaw scene never started');
|
if (!s()) throw new Error('jigsaw scene never started');
|
||||||
for (let i = 0; i < 400 && !s().menu; i++) await wait(50);
|
for (let i = 0; i < 400 && !s().menu; i++) await wait(50);
|
||||||
if (!s().menu) throw new Error('menu never built');
|
check(!!s().menu, 'menu built');
|
||||||
// The preview image is loaded asynchronously; give it a moment.
|
|
||||||
for (let i = 0; i < 200 && !s().previewImg; i++) await wait(25);
|
|
||||||
|
|
||||||
const sc = s();
|
// Menu is back to the original shape (random-start button reverted).
|
||||||
const item = sc.currentImage();
|
check(s().randomStartButton === undefined, 'randomStartButton is gone (menu as before)');
|
||||||
document.title = 'RESULT:' + JSON.stringify({
|
|
||||||
idx: sc.imageIndex,
|
// The initial image is random but always a valid artwork entry, and the
|
||||||
n: sc.artwork.length,
|
// preview shown in the menu matches it.
|
||||||
name: item && item.name,
|
const n = s().artwork.length;
|
||||||
validName: ART.some((a) => a.name === item.name),
|
const i = s().imageIndex;
|
||||||
preview: !!sc.previewImg,
|
check(Number.isInteger(i) && i >= 0 && i < n, `initial imageIndex ${i} is a valid index (0..${n - 1})`);
|
||||||
menuVisible: sc.menu.visible,
|
check(!!ART.find((a) => a.name === s().currentImage().name), 'initial currentImage() is a known artwork entry');
|
||||||
});
|
|
||||||
log('initial image: ' + item.name + ' (index ' + sc.imageIndex + ' of ' + sc.artwork.length + ')');
|
// Preview must be built for the initial image (async image load).
|
||||||
|
for (let k = 0; k < 200 && !s().previewImg; k++) await wait(25);
|
||||||
|
check(!!s().previewImg, 'initial preview image is displayed');
|
||||||
|
check(s().thumbName && s().thumbName.text === s().currentImage().name, `thumb name matches initial image (${s().currentImage().name})`);
|
||||||
|
|
||||||
|
// Start Puzzle must still start the initial (random) image.
|
||||||
|
s().selectedDiff = 'easy';
|
||||||
|
s().startPuzzle();
|
||||||
|
let playing = false;
|
||||||
|
for (let k = 0; k < 400 && !playing; k++) { playing = s().state === 'playing'; await wait(25); }
|
||||||
|
check(playing, 'Start Puzzle reaches playing state');
|
||||||
|
check(s().pieces.length === 25, '25 pieces built');
|
||||||
|
check(s().imageName === ART[i].name, `playing image matches the initial pick (${ART[i].name})`);
|
||||||
|
|
||||||
|
// The original 🎲 Random button still randomises the preview in the menu.
|
||||||
|
s().toMenu();
|
||||||
|
const before = s().imageIndex;
|
||||||
|
const seen = new Set([before]);
|
||||||
|
for (let k = 0; k < 10; k++) { s().randomImage(); seen.add(s().imageIndex); await wait(10); }
|
||||||
|
check(seen.size >= 2, '🎲 Random button still changes the selected image');
|
||||||
|
|
||||||
|
document.__initIndex = i;
|
||||||
|
log(failures === 0 ? 'ALL PASS' : failures + ' FAILURES');
|
||||||
|
document.title = failures === 0 ? 'PASS' : 'FAIL:' + failures;
|
||||||
})().catch((e) => {
|
})().catch((e) => {
|
||||||
log('ERROR: ' + ((e && e.stack) || e));
|
log('ERROR: ' + ((e && e.stack) || e));
|
||||||
document.title = 'FAIL:' + ((e && e.message) || e);
|
document.title = 'FAIL:error';
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body></html>
|
</body></html>
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
// Headless-chromium CDP driver: loads a page repeatedly and reads the result
|
// Headless-chromium CDP driver for the jigsaw initial-image test page.
|
||||||
// the page publishes in document.title (RESULT:<json> or FAIL:<msg>).
|
// Loads the page repeatedly; each load must end PASS, and the initial image
|
||||||
|
// index (document.__initIndex) must vary across fresh loads.
|
||||||
// Usage: node tools/__jig_init_driver.mjs <url> [loads=8]
|
// Usage: node tools/__jig_init_driver.mjs <url> [loads=8]
|
||||||
import { spawn } from 'node:child_process';
|
import { spawn } from 'node:child_process';
|
||||||
import { setTimeout as sleep } from 'node:timers/promises';
|
import { setTimeout as sleep } from 'node:timers/promises';
|
||||||
|
|
||||||
const url = process.argv[2];
|
const url = process.argv[2];
|
||||||
const loads = Math.max(1, parseInt(process.argv[3] || '8', 10));
|
const loads = Math.max(2, parseInt(process.argv[3] || '8', 10));
|
||||||
if (!url) { console.error('usage: driver <url> [loads]'); process.exit(2); }
|
if (!url) { console.error('usage: driver <url> [loads]'); process.exit(2); }
|
||||||
|
|
||||||
const BIN = process.env.HOME + '/.cache/ms-playwright/chromium_headless_shell-1234/chrome-headless-shell-linux64/chrome-headless-shell';
|
const BIN = process.env.HOME + '/.cache/ms-playwright/chromium_headless_shell-1234/chrome-headless-shell-linux64/chrome-headless-shell';
|
||||||
|
|
@ -15,7 +16,7 @@ const chrome = spawn(BIN, [
|
||||||
`--remote-debugging-port=${PORT}`,
|
`--remote-debugging-port=${PORT}`,
|
||||||
'about:blank',
|
'about:blank',
|
||||||
], { stdio: ['ignore', 'pipe', 'pipe'] });
|
], { stdio: ['ignore', 'pipe', 'pipe'] });
|
||||||
chrome.stderr.on('data', (d) => { const t = d.toString(); if (!/Fontconfig|dbus|DBus|ozone|sandbox|WebGL|GL Driver/i.test(t)) process.stderr.write(t); });
|
chrome.stderr.on('data', (d) => { const s = d.toString(); if (!/Fontconfig|dbus|DBus|ozone/i.test(s)) process.stderr.write(s); });
|
||||||
|
|
||||||
async function httpJson(path) {
|
async function httpJson(path) {
|
||||||
const r = await fetch(`http://127.0.0.1:${PORT}${path}`);
|
const r = await fetch(`http://127.0.0.1:${PORT}${path}`);
|
||||||
|
|
@ -52,36 +53,28 @@ try {
|
||||||
await send('Runtime.enable');
|
await send('Runtime.enable');
|
||||||
await send('Page.enable');
|
await send('Page.enable');
|
||||||
|
|
||||||
const results = [];
|
const ev = (expression) => send('Runtime.evaluate', { expression, returnByValue: true }).then((r) => r.result.value);
|
||||||
|
|
||||||
|
const indexes = [];
|
||||||
for (let k = 0; k < loads; k++) {
|
for (let k = 0; k < loads; k++) {
|
||||||
await send('Page.navigate', { url });
|
await send('Page.navigate', { url });
|
||||||
const t0 = Date.now();
|
const t0 = Date.now();
|
||||||
let title = '';
|
let title = '';
|
||||||
while (Date.now() - t0 < 30000) {
|
while (Date.now() - t0 < 60000) {
|
||||||
const out = await send('Runtime.evaluate', { expression: 'document.title', returnByValue: true });
|
title = (await ev('document.title')) || '';
|
||||||
title = out.result.value || '';
|
if (title === 'PASS' || title.startsWith('FAIL')) break;
|
||||||
if (title.startsWith('RESULT:') || title.startsWith('FAIL')) break;
|
await sleep(500);
|
||||||
await sleep(250);
|
|
||||||
}
|
}
|
||||||
results.push(title);
|
if (title !== 'PASS') throw new Error(`load #${k}: ${title || 'TIMEOUT'}`);
|
||||||
|
indexes.push(await ev('document.__initIndex'));
|
||||||
}
|
}
|
||||||
|
|
||||||
let ok = true;
|
const valid = indexes.every((i) => Number.isInteger(i) && i >= 0 && i < 5);
|
||||||
const idxs = [];
|
const distinct = new Set(indexes).size;
|
||||||
for (const t of results) {
|
console.log('initial indexes across fresh loads:', indexes.join(', '));
|
||||||
if (!t.startsWith('RESULT:')) { ok = false; console.error('bad title: ' + t); continue; }
|
console.log(`all valid: ${valid}, distinct images: ${distinct}/${loads}`);
|
||||||
const r = JSON.parse(t.slice('RESULT:'.length));
|
if (!valid || distinct < 2) { console.log('FAIL'); process.exitCode = 1; }
|
||||||
const valid = Number.isInteger(r.idx) && r.idx >= 0 && r.idx < r.n && r.n > 1 &&
|
else console.log('ALL PASS');
|
||||||
r.validName && r.preview && r.menuVisible;
|
|
||||||
if (!valid) { ok = false; console.error('bad result: ' + JSON.stringify(r)); }
|
|
||||||
idxs.push(r.idx);
|
|
||||||
}
|
|
||||||
const distinct = new Set(idxs).size;
|
|
||||||
console.log(`initial indices over ${loads} fresh page loads: ${idxs.join(', ')}`);
|
|
||||||
console.log(`distinct initial images: ${distinct}/${loads}`);
|
|
||||||
if (distinct < 2) { ok = false; console.error('expected variety in the initial image'); }
|
|
||||||
console.log(ok ? 'ALL PASS' : 'FAIL');
|
|
||||||
process.exitCode = ok ? 0 : 1;
|
|
||||||
} finally {
|
} finally {
|
||||||
try { ws && ws.close(); } catch {}
|
try { ws && ws.close(); } catch {}
|
||||||
chrome.kill('SIGKILL');
|
chrome.kill('SIGKILL');
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue