118 lines
6.0 KiB
JavaScript
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); });
|