Organize/.cdp/live-switch.cjs

118 lines
6.0 KiB
JavaScript

/* Live-switch test with waits + evidence screenshots. */
const { spawn } = require("node:child_process");
const fs = require("node:fs");
const http = require("node:http");
const EDGE = "C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe";
const PORT = 9368;
function httpJson(p){
return new Promise((res, rej) => {
const r = http.request({ host: "127.0.0.1", port: PORT, path: p }, (x) => {
let d = "";
x.on("data", (c) => { d += c; });
x.on("end", () => { try { res(JSON.parse(d)); } catch (e) { res(d); } });
});
r.on("error", rej);
r.end();
});
}
(async () => {
const prof = fs.mkdtempSync(require("node:os").tmpdir() + "/edge-prof-");
const proc = spawn(EDGE, ["--headless=new", `--remote-debugging-port=${PORT}`, `--user-data-dir=${prof}`, "--no-first-run", "--no-default-browser-check", "--disable-gpu", "about:blank"], { stdio: "ignore" });
let tg = null;
for (let i = 0; i < 40; i++) { await new Promise(r => setTimeout(r, 500)); try { tg = await httpJson("/json/list"); break; } catch (e) {} }
const page = tg.find(t => t.type === "page");
const ws = new WebSocket(page.webSocketDebuggerUrl);
await new Promise(r => (ws.onopen = r));
let id = 0;
const send = (m, p = {}) => new Promise(res => {
const i2 = ++id;
ws.send(JSON.stringify({ id: i2, method: m, params: p }));
const t = setInterval(() => { const f = ws._buf.find(b => b.id === i2); if (f) { clearInterval(t); res(f.result ?? f.error); } }, 10);
});
ws._buf = [];
const errs = [];
ws.onmessage = (m) => {
const j = JSON.parse(m.data);
if (j.id) { ws._buf.push(j); return; }
if (j.method === "Runtime.consoleAPICalled" && j.params.type === "error") errs.push(j.params.args.map(a => (a.value ?? a.description ?? "")).join(" ").slice(0, 120));
if (j.method === "Runtime.exceptionThrown") errs.push("EXC: " + (j.params.exceptionDetails?.exception?.description || "").slice(0, 120));
};
await send("Runtime.enable");
await send("Page.enable");
await send("Network.enable");
const cv = fs.readFileSync(process.argv[1], "utf8").split("\n").map(l => l.trim()).find(l => l.includes("authjs.session-token")).split("\t").pop();
await send("Network.setCookies", { cookies: [{ name: "authjs.session-token", value: cv, domain: "localhost", path: "/", httpOnly: true, sameSite: "Lax" }] });
await send("Emulation.setDeviceMetricsOverride", { width: 1440, height: 900, deviceScaleFactor: 1 });
await send("Page.navigate", { url: "http://localhost:3210/" });
let ready = false;
for (let i = 0; i < 45; i++) {
await new Promise(r => setTimeout(r, 1000));
ready = await send("Runtime.evaluate", { expression: "document.querySelectorAll('button').length > 5", returnByValue: true }).then(r => r.result?.value);
if (ready) break;
}
console.log("page ready:", ready);
const ev = (expr) => send("Runtime.evaluate", { expression: expr, returnByValue: true }).then(r => r.result?.value);
const click = async (x, y) => {
await send("Input.dispatchMouseEvent", { type: "mousePressed", x, y, button: "left", clickCount: 1 });
await send("Input.dispatchMouseEvent", { type: "mouseReleased", x, y, button: "left", clickCount: 1 });
};
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
const cardStyle = async () => ev(`(() => {
const c = Array.from(document.querySelectorAll('[class*=rounded-2xl]')).find(x => /background-color/.test(x.getAttribute('style') || ''));
return c ? c.getAttribute('style').slice(0, 150) : 'no-card';
})()`);
const shot = async (name) => {
const s = await send("Page.captureScreenshot", { format: "png" });
fs.writeFileSync("shots/" + name, Buffer.from(s.data, "base64"));
};
const selectTheme = async (name) => {
const v = await ev(`(() => {
const b = Array.from(document.querySelectorAll('button')).find(x => (x.textContent || '').trim() === 'Theme');
if (!b) return null;
const r = b.getBoundingClientRect();
return JSON.stringify({ x: Math.round(r.x + r.width / 2), y: Math.round(r.y + r.height / 2) });
})()`);
if (!v) return "no theme button";
const tp = JSON.parse(v);
await click(tp.x, tp.y);
let labels = null;
for (let i = 0; i < 16; i++) {
await sleep(150);
labels = await ev(`(() => {
const m = document.querySelector('[data-slot="dropdown-menu-content"]');
if (!m || m.querySelectorAll('button').length < 4) return null;
return JSON.stringify(Array.from(m.querySelectorAll('button')).map(b => (b.textContent || '').trim()));
})()`);
if (labels) break;
}
if (!labels) return "menu never populated";
const idx = JSON.parse(labels).indexOf(name);
if (idx === -1) return "option missing: " + labels;
const p = JSON.parse(await ev(`(() => {
const m = document.querySelector('[data-slot="dropdown-menu-content"]');
const b = m.querySelectorAll('button')[${idx}];
const r = b.getBoundingClientRect();
return JSON.stringify({ x: Math.round(r.x + 12), y: Math.round(r.y + r.height / 2) });
})()`));
await click(p.x, p.y);
await sleep(900);
return "ok";
};
const A = await cardStyle(); console.log("A dark :", A); await shot("12-live-dark.png");
console.log("-> Sunset :", await selectTheme("Sunset"));
const B = await cardStyle(); console.log("B sunset :", B); await shot("13-live-sunset.png");
console.log("-> Ocean :", await selectTheme("Ocean"));
const C = await cardStyle(); console.log("C ocean :", C); await shot("14-live-ocean.png");
console.log("-> Default:", await selectTheme("Default"));
const D = await cardStyle(); console.log("D default :", D); await shot("15-live-default.png");
console.log("CHANGED A!=B:", A !== B, " B!=C:", B !== C, " C!=D:", C !== D);
const real = errs.filter(e => !/favicon|manifest|icon|404/i.test(e));
console.log("CONSOLE ERRORS:", real.length);
real.slice(0, 4).forEach(e => console.log(" -", e));
proc.kill();
process.exit(0);
})().catch(e => { console.error("FAIL", e.message); process.exit(1); });