// Headless-chromium CDP driver for the jigsaw initial-image test page. // 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 [loads=8] import { spawn } from 'node:child_process'; import { setTimeout as sleep } from 'node:timers/promises'; const url = process.argv[2]; const loads = Math.max(2, parseInt(process.argv[3] || '8', 10)); if (!url) { console.error('usage: driver [loads]'); process.exit(2); } const BIN = process.env.HOME + '/.cache/ms-playwright/chromium_headless_shell-1234/chrome-headless-shell-linux64/chrome-headless-shell'; const PORT = 9333; const chrome = spawn(BIN, [ '--headless', '--no-sandbox', '--disable-gpu', `--remote-debugging-port=${PORT}`, 'about:blank', ], { stdio: ['ignore', 'pipe', 'pipe'] }); 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) { const r = await fetch(`http://127.0.0.1:${PORT}${path}`); return r.json(); } let ws, idc = 0; const pending = new Map(); const send = (method, params = {}) => new Promise((resolve, reject) => { const id = ++idc; pending.set(id, { resolve, reject }); ws.send(JSON.stringify({ id, method, params })); }); try { let targets = null; for (let i = 0; i < 60; i++) { try { targets = await httpJson('/json/list'); break; } catch { await sleep(250); } } if (!targets) throw new Error('chrome devtools endpoint never came up'); const page = targets.find((t) => t.type === 'page'); if (!page) throw new Error('no page target'); ws = new WebSocket(page.webSocketDebuggerUrl); await new Promise((res, rej) => { ws.onopen = res; ws.onerror = rej; }); ws.onmessage = (m) => { const msg = JSON.parse(m.data); if (msg.id && pending.has(msg.id)) { const { resolve, reject } = pending.get(msg.id); pending.delete(msg.id); if (msg.error) reject(new Error(msg.error.message)); else resolve(msg.result); } }; await send('Runtime.enable'); await send('Page.enable'); const ev = (expression) => send('Runtime.evaluate', { expression, returnByValue: true }).then((r) => r.result.value); const indexes = []; for (let k = 0; k < loads; k++) { await send('Page.navigate', { url }); const t0 = Date.now(); let title = ''; while (Date.now() - t0 < 60000) { title = (await ev('document.title')) || ''; if (title === 'PASS' || title.startsWith('FAIL')) break; await sleep(500); } if (title !== 'PASS') throw new Error(`load #${k}: ${title || 'TIMEOUT'}`); indexes.push(await ev('document.__initIndex')); } const valid = indexes.every((i) => Number.isInteger(i) && i >= 0 && i < 5); const distinct = new Set(indexes).size; console.log('initial indexes across fresh loads:', indexes.join(', ')); console.log(`all valid: ${valid}, distinct images: ${distinct}/${loads}`); if (!valid || distinct < 2) { console.log('FAIL'); process.exitCode = 1; } else console.log('ALL PASS'); } finally { try { ws && ws.close(); } catch {} chrome.kill('SIGKILL'); }