ci / test (push) Successful in 45m25s
- Vendor codex-ui-stack host/ as cloud/codex-ui-host/ (original/ not vendored, 650MB) - Bridge emulates 7 methods missing in codex 0.164.0-alpha.9, exposes 29 experimental-protocol methods (150 -> 179), Linux binary candidates, read-only POST /__codex_git - GET /console 302s to the UI host (:8095) when reachable, else falls back to the classic React console; GET /console-classic always serves it - Alignment tables: docs/codex-native-ui-alignment.md - Tests: test_console 7, new test_codex_ui_host 4, handler method pin 247 -> 249
110 lines
3.8 KiB
JavaScript
Executable File
110 lines
3.8 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
/**
|
|
* Load the local web host in the headless browser, capture console + exceptions,
|
|
* then dump the resulting page state and a screenshot.
|
|
*
|
|
* node host/cdp-load.mjs --port 9333 --url http://127.0.0.1:8095/ --out /tmp/shot.png
|
|
*/
|
|
|
|
import fs from "node:fs";
|
|
|
|
const argv = process.argv.slice(2);
|
|
const argOf = (name, fallback) => {
|
|
const i = argv.indexOf(name);
|
|
return i !== -1 && argv[i + 1] ? argv[i + 1] : fallback;
|
|
};
|
|
const PORT = argOf("--port", "9333");
|
|
const URL_ = argOf("--url", "http://127.0.0.1:8095/");
|
|
const OUT = argOf("--out", "/tmp/codexweb.png");
|
|
const SETTLE = Number(argOf("--settle", "6000"));
|
|
|
|
const version = await (await fetch(`http://127.0.0.1:${PORT}/json/version`)).json();
|
|
const ws = new WebSocket(version.webSocketDebuggerUrl);
|
|
let seq = 0;
|
|
const pending = new Map();
|
|
const events = [];
|
|
|
|
const send = (method, params = {}, sessionId) =>
|
|
new Promise((resolve, reject) => {
|
|
const id = ++seq;
|
|
pending.set(id, { resolve, reject });
|
|
const msg = { id, method, params };
|
|
if (sessionId) msg.sessionId = sessionId;
|
|
ws.send(JSON.stringify(msg));
|
|
});
|
|
|
|
ws.addEventListener("message", (ev) => {
|
|
const msg = JSON.parse(ev.data);
|
|
if (msg.method) {
|
|
if (msg.method === "Runtime.consoleAPICalled") {
|
|
const text = (msg.params.args || [])
|
|
.map((a) => a.value ?? a.description ?? a.unserializableValue ?? "")
|
|
.join(" ")
|
|
.slice(0, 600);
|
|
events.push(`[${msg.params.type}] ${text}`);
|
|
} else if (msg.method === "Runtime.exceptionThrown") {
|
|
const d = msg.params.exceptionDetails;
|
|
events.push(`[EXCEPTION] ${d.exception?.description ?? d.text}`.slice(0, 2200));
|
|
} else if (msg.method === "Log.entryAdded") {
|
|
const e = msg.params.entry;
|
|
events.push(`[log:${e.level}] ${e.text}`.slice(0, 500));
|
|
}
|
|
}
|
|
if (!msg.id || !pending.has(msg.id)) return;
|
|
const { resolve, reject } = pending.get(msg.id);
|
|
pending.delete(msg.id);
|
|
msg.error ? reject(new Error(JSON.stringify(msg.error).slice(0, 400))) : resolve(msg.result);
|
|
});
|
|
|
|
await new Promise((r) => ws.addEventListener("open", r, { once: true }));
|
|
|
|
const { targetInfos } = await send("Target.getTargets");
|
|
const pages = targetInfos.filter((t) => t.type === "page");
|
|
const main = pages.find((t) => t.url.includes("127.0.0.1")) ?? pages[0];
|
|
if (!main) {
|
|
console.error("no page target");
|
|
process.exit(1);
|
|
}
|
|
|
|
const { sessionId } = await send("Target.attachToTarget", { targetId: main.targetId, flatten: true });
|
|
await send("Page.enable", {}, sessionId);
|
|
await send("Runtime.enable", {}, sessionId);
|
|
await send("Log.enable", {}, sessionId);
|
|
|
|
await send("Page.navigate", { url: URL_ }, sessionId);
|
|
// Runtime.enable replays the previous page's console buffer; drop it so the
|
|
// report only covers this load.
|
|
events.length = 0;
|
|
await new Promise((r) => setTimeout(r, SETTLE));
|
|
|
|
const state = await send(
|
|
"Runtime.evaluate",
|
|
{
|
|
expression: `(() => {
|
|
const root = document.querySelector('#root');
|
|
const text = (root?.innerText || '').replace(/\\s+/g, ' ').trim();
|
|
return JSON.stringify({
|
|
url: location.href,
|
|
title: document.title,
|
|
rootChildren: root ? root.children.length : -1,
|
|
rootBytes: root ? root.innerHTML.length : 0,
|
|
textLength: text.length,
|
|
textSample: text.slice(0, 500),
|
|
bootstrapped: !!globalThis.__codexBootstrapped,
|
|
hasShim: typeof globalThis.__codexHost,
|
|
}, null, 1);
|
|
})()`,
|
|
returnByValue: true,
|
|
},
|
|
sessionId,
|
|
);
|
|
|
|
console.log("STATE:", state.result.value);
|
|
console.log(`\nEVENTS (${events.length}):`);
|
|
for (const e of events.slice(0, 60)) console.log(" " + e);
|
|
|
|
const shot = await send("Page.captureScreenshot", { format: "png" }, sessionId);
|
|
fs.writeFileSync(OUT, Buffer.from(shot.data, "base64"));
|
|
console.log(`\nscreenshot: ${OUT} (${fs.statSync(OUT).size} bytes)`);
|
|
ws.close();
|