orbit/dev/cdp-cast.mjs

71 lines
2.6 KiB
JavaScript

// Dev-only: CDP screencast while the scout-dest dialog is open — checks
// whether ANY live frame paints the dialog plate (vs. the stale-capture
// hypothesis). Usage:
// node dev/cdp-cast.mjs <url> <dir> '<readyExpr>' [ms]
import { mkdir, writeFile } from 'node:fs/promises';
const [url, dir, readyExpr, ms] = process.argv.slice(2);
const CDP = process.env.CDP_PORT ? `127.0.0.1:${CDP_PORT}` : '127.0.0.1:9333';
const dur = Number(ms ?? 4000);
let target = await (await fetch(`http://${CDP}/json/list`)).json()
.then((ts) => ts.find((t) => t.type === 'page'));
if (!target) {
target = await (await fetch(`http://${CDP}/json/new?${encodeURIComponent(url)}`, { method: 'PUT' })).json();
}
const ws = new WebSocket(target.webSocketDebuggerUrl);
let id = 0;
const pending = new Map();
const send = (method, params = {}) => new Promise((res, rej) => {
const mid = ++id;
pending.set(mid, { res, rej });
ws.send(JSON.stringify({ id: mid, method, params }));
});
const frames = [];
await new Promise((res, rej) => { ws.onopen = res; ws.onerror = (e) => rej(new Error('ws')); });
ws.onmessage = (m) => {
const msg = JSON.parse(m.data);
if (msg.id && pending.has(msg.id)) {
const { res, rej } = pending.get(msg.id);
pending.delete(msg.id);
msg.error ? rej(new Error(msg.error.message)) : res(msg.result);
} else if (msg.method === 'Page.screencastFrame') {
frames.push(msg.params.data);
send('Page.screencastFrameAck', { sessionId: msg.params.sessionId });
}
};
await send('Page.enable');
await send('Runtime.enable');
await send('Network.enable');
await send('Network.setCacheDisabled', { cacheDisabled: true });
await send('Page.navigate', { url: 'about:blank' });
await new Promise((r) => setTimeout(r, 300));
await send('Page.navigate', { url });
const evaluate = async (expr) => {
const r = await send('Runtime.evaluate', { expression: expr, returnByValue: true, awaitPromise: true });
if (r.exceptionDetails) throw new Error('page: ' + (r.exceptionDetails.exception?.description ?? r.exceptionDetails.text));
return r.result?.value;
};
const deadline = Date.now() + 120000;
while (Date.now() < deadline) {
const v = await evaluate(readyExpr).catch(() => null);
if (v) break;
await new Promise((r) => setTimeout(r, 200));
}
await send('Page.startScreencast', { format: 'png', everyNthFrame: 1 });
await new Promise((r) => setTimeout(r, dur));
await send('Page.stopScreencast');
await mkdir(dir, { recursive: true });
let i = 0;
for (const data of frames) {
await writeFile(`${dir}/frame-${String(i).padStart(3, '0')}.png`, Buffer.from(data, 'base64'));
i++;
}
console.log(`captured ${i} frames in ${dir}`);
ws.close();