/* 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); });