fix(css,preview): scope utilities under the view root, preview with real boards and foreign css
This commit is contained in:
1 parent
0a7742eb5e
commit
8c971e431a
10 files changed
+458
-14
No files matched your search
@@ -0,0 +1,137 @@
|
||||
// node preview/shot.mjs <out.png> <url> [width] [height]
|
||||
//
|
||||
// Headless Chrome over the DevTools protocol: a screenshot plus everything the
|
||||
// page said on the console, and a note if the page stopped answering — which
|
||||
// a plain --screenshot run would only report as a silent timeout.
|
||||
// DUMP=1 also print the page text
|
||||
// PROBE=js evaluate an expression at the end and print its value
|
||||
import { spawn } from "node:child_process";
|
||||
import { writeFileSync } from "node:fs";
|
||||
|
||||
const [out, url, w = "1100", h = "720"] = process.argv.slice(2);
|
||||
if (!out || !url) {
|
||||
console.error("usage: node preview/shot.mjs <out.png> <url> [width] [height]");
|
||||
process.exit(2);
|
||||
}
|
||||
const port = 9334;
|
||||
const chrome =
|
||||
process.env.CHROME ?? "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
|
||||
const proc = spawn(
|
||||
chrome,
|
||||
[
|
||||
"--headless=new",
|
||||
"--disable-gpu",
|
||||
"--hide-scrollbars",
|
||||
`--remote-debugging-port=${port}`,
|
||||
`--window-size=${w},${h}`,
|
||||
"--user-data-dir=/tmp/bcal-chrome-profile",
|
||||
"about:blank",
|
||||
],
|
||||
{ stdio: "ignore" },
|
||||
);
|
||||
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
let targets = [];
|
||||
for (let i = 0; i < 40; i += 1) {
|
||||
try {
|
||||
targets = await (await fetch(`http://127.0.0.1:${port}/json`)).json();
|
||||
if (targets.length > 0) break;
|
||||
} catch {
|
||||
/* not up yet */
|
||||
}
|
||||
await wait(250);
|
||||
}
|
||||
const page = targets.find((target) => target.type === "page");
|
||||
if (!page) {
|
||||
console.error("shot: chrome did not come up");
|
||||
proc.kill();
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const ws = new WebSocket(page.webSocketDebuggerUrl);
|
||||
await new Promise((resolve) => {
|
||||
ws.onopen = resolve;
|
||||
});
|
||||
let id = 0;
|
||||
const pending = new Map();
|
||||
const logs = [];
|
||||
ws.onmessage = (message) => {
|
||||
const data = JSON.parse(String(message.data));
|
||||
if (data.id && pending.has(data.id)) {
|
||||
pending.get(data.id)(data);
|
||||
pending.delete(data.id);
|
||||
}
|
||||
if (data.method === "Runtime.consoleAPICalled") {
|
||||
logs.push(
|
||||
`${data.params.type}: ${data.params.args
|
||||
.map((arg) => arg.value ?? arg.description)
|
||||
.join(" ")}`,
|
||||
);
|
||||
}
|
||||
if (data.method === "Runtime.exceptionThrown") {
|
||||
const details = data.params.exceptionDetails;
|
||||
logs.push(`EXC: ${details.exception?.description ?? details.text}`);
|
||||
}
|
||||
};
|
||||
const send = (method, params = {}, timeout = 15_000) =>
|
||||
new Promise((resolve, reject) => {
|
||||
const next = ++id;
|
||||
const timer = setTimeout(() => {
|
||||
pending.delete(next);
|
||||
reject(new Error(`${method} timed out after ${timeout} ms`));
|
||||
}, timeout);
|
||||
pending.set(next, (value) => {
|
||||
clearTimeout(timer);
|
||||
resolve(value);
|
||||
});
|
||||
ws.send(JSON.stringify({ id: next, method, params }));
|
||||
});
|
||||
|
||||
await send("Emulation.setDeviceMetricsOverride", {
|
||||
width: Number(w),
|
||||
height: Number(h),
|
||||
deviceScaleFactor: 2,
|
||||
mobile: false,
|
||||
});
|
||||
await send("Page.enable");
|
||||
await send("Runtime.enable");
|
||||
await send("Page.navigate", { url });
|
||||
await wait(Number(process.env.SETTLE ?? 2500));
|
||||
|
||||
let alive = true;
|
||||
try {
|
||||
const ping = await send("Runtime.evaluate", { expression: "1 + 1", returnByValue: true }, 5000);
|
||||
alive = ping.result?.result?.value === 2;
|
||||
} catch {
|
||||
alive = false;
|
||||
}
|
||||
if (!alive) {
|
||||
console.log("shot: PAGE UNRESPONSIVE (main thread busy — a hang)");
|
||||
}
|
||||
|
||||
if (alive && process.env.PROBE) {
|
||||
const probe = await send("Runtime.evaluate", {
|
||||
expression: process.env.PROBE,
|
||||
returnByValue: true,
|
||||
});
|
||||
console.log("PROBE:", JSON.stringify(probe.result?.result?.value ?? probe.result));
|
||||
}
|
||||
if (alive && process.env.DUMP) {
|
||||
const text = await send("Runtime.evaluate", {
|
||||
expression: "document.body.innerText.slice(0, 1500)",
|
||||
returnByValue: true,
|
||||
});
|
||||
console.log("TEXT:", text.result?.result?.value);
|
||||
}
|
||||
|
||||
try {
|
||||
const shot = await send("Page.captureScreenshot", { format: "png" }, 10_000);
|
||||
writeFileSync(out, Buffer.from(shot.result.data, "base64"));
|
||||
console.log("wrote", out);
|
||||
} catch (error) {
|
||||
console.log("shot: no screenshot —", error.message);
|
||||
}
|
||||
if (logs.length > 0) console.log("LOGS:\n" + logs.join("\n"));
|
||||
ws.close();
|
||||
proc.kill();
|
||||
process.exit(alive ? 0 : 3);
|
||||
Reference in new issue
Block a user