/* Reproduce the user's problem: full console, error overlay, and clicking the Theme toggle. */ const { spawn } = require("node:child_process"); const fs = require("node:fs"); const http = require("node:http"); const path = require("node:path"); const EDGE = "C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe"; const PORT = 9350; const BASE = "http://localhost:3210"; const OUT = path.join(__dirname, "..", "shots"); function httpJson(p) { return new Promise((resolve, reject) => { const req = http.request({ host: "127.0.0.1", port: PORT, path: p }, (res) => { let d = ""; res.on("data", (c) => (d += c)); res.on("end", () => { try { resolve(JSON.parse(d)); } catch { resolve(d); } }); }); req.on("error", reject); req.end(); }); } (async () => { const profile = fs.mkdtempSync("/tmp/edge-prof-"); const proc = spawn(EDGE, ["--headless=new", `--remote-debugging-port=${PORT}`, `--user-data-dir=${profile}`, "--no-first-run", "--no-default-browser-check", "--disable-gpu", "about:blank"], { stdio: "ignore" }); let targets = null; for (let i = 0; i < 40; i++) { await new Promise((r) => setTimeout(r, 500)); try { targets = await httpJson("/json/list"); break; } catch {} } if (!targets) { console.error("no targets"); process.exit(1); } const page = targets.find((t) => t.type === "page"); const ws = new WebSocket(page.webSocketDebuggerUrl); await new Promise((r) => (ws.onopen = r)); let id = 0; const send = (method, params = {}) => new Promise((resolve) => { const myId = ++id; ws.send(JSON.stringify({ id: myId, method, params })); const t = setInterval(() => { const m = ws._buf.find((b) => b.id === myId); if (m) { clearInterval(t); resolve(m.result ?? m.error); } }, 10); }); ws._buf = []; const full = (a) => (a.value ?? a.description ?? a.unserializableValue ?? a.type ?? "").toString(); ws.onmessage = (m) => { const msg = JSON.parse(m.data); if (msg.id) { ws._buf.push(msg); return; } if (msg.method === "Runtime.consoleAPICalled") console.log(`[console:${msg.params.type}]`, msg.params.args.map(full).join(" ")); if (msg.method === "Runtime.exceptionThrown") console.log("[EXCEPTION]", JSON.stringify(msg.params.exceptionDetails.exception?.description ?? msg.params.exceptionDetails.text)); if (msg.method === "Log.entryAdded" && msg.params.entry.level === "error") console.log("[log:error]", msg.params.entry.text); }; await send("Runtime.enable"); await send("Page.enable"); await send("Network.enable"); const cookieFile = process.argv[2]; const cookieValue = cookieFile ? (fs.readFileSync(cookieFile, "utf8").split("\n").map((l) => l.trim()).find((l) => l.includes("authjs.session-token")) || "").split("\t").pop() : null; if (cookieValue) await send("Network.setCookies", { cookies: [{ name: "authjs.session-token", value: cookieValue, domain: "localhost", path: "/", httpOnly: true, sameSite: "Lax" }] }); await send("Emulation.setDeviceMetricsOverride", { width: 1440, height: 900, deviceScaleFactor: 1, mobile: false }); await send("Page.navigate", { url: BASE + "/" }); await new Promise((r) => setTimeout(r, 4500)); const state = await send("Runtime.evaluate", { expression: `(() => ({ title: document.title, bodyLen: document.body.innerHTML.length, hasNextErrorOverlay: !!document.querySelector('nextjs-portal, [data-nextjs-dialog], iframe[title="error"]'), themeBtn: !!Array.from(document.querySelectorAll('button')).find(b => /theme/i.test(b.textContent||'') || b.getAttribute('aria-label') && /theme/i.test(b.getAttribute('aria-label'))), }))()`, returnByValue: true }); console.log("STATE", JSON.stringify(state.result.value)); // Find the theme trigger in the sidebar and click it. const clickRes = await send("Runtime.evaluate", { expression: `(() => { const btns = Array.from(document.querySelectorAll('button')); const t = btns.find(b => /theme/i.test(b.textContent||'') || (b.getAttribute('aria-label')||'').match(/theme/i)); if (!t) return 'no-theme-button'; const r = t.getBoundingClientRect(); return JSON.stringify({ label: (t.textContent||t.getAttribute('aria-label')).trim(), x: r.x + r.width/2, y: r.y + r.height/2 }); })()`, returnByValue: true }); console.log("THEME BTN", clickRes.result?.value); if (clickRes.result?.value && clickRes.result.value !== "no-theme-button") { const p = JSON.parse(clickRes.result.value); await send("Input.dispatchMouseEvent", { type: "mousePressed", x: p.x, y: p.y, button: "left", clickCount: 1 }); await send("Input.dispatchMouseEvent", { type: "mouseReleased", x: p.x, y: p.y, button: "left", clickCount: 1 }); await new Promise((r) => setTimeout(r, 800)); const menu = await send("Runtime.evaluate", { expression: `(() => { const opts = Array.from(document.querySelectorAll('button, [role="radio"], [role="option"]')).map(e => (e.textContent||'').trim()).filter(t => /^(Default|Sunset|Dark|Ocean|System)$/i.test(t)); return JSON.stringify(opts); })()`, returnByValue: true }); console.log("MENU OPTIONS", menu.result?.value); // click the "Ocean" option and verify the theme actually switches const oceanPos = await send("Runtime.evaluate", { expression: `(() => { const b = Array.from(document.querySelectorAll("button")).find(x => (x.textContent||"").trim() === "Ocean"); if (!b) return null; const r = b.getBoundingClientRect(); return JSON.stringify({ x: r.x + 10, y: r.y + r.height/2 }); })()`, returnByValue: true }); if (oceanPos.result?.value) { const pos = JSON.parse(oceanPos.result.value); await send("Input.dispatchMouseEvent", { type: "mousePressed", x: pos.x, y: pos.y, button: "left", clickCount: 1 }); await send("Input.dispatchMouseEvent", { type: "mouseReleased", x: pos.x, y: pos.y, button: "left", clickCount: 1 }); await new Promise((r) => setTimeout(r, 1200)); const after = await send("Runtime.evaluate", { expression: `JSON.stringify({ html: document.documentElement.className.split(" ").filter(Boolean).slice(-1), cardBg: (() => { const c = document.querySelector("[aria-label^=\"Drag to move\"]")?.closest("[class*=\\\"rounded-2xl\\\"]"); return c ? c.getAttribute("style")?.match(/background-color: ([^;]+)/)?.[1] : null; })(), ls: localStorage.getItem("theme"), })`, returnByValue: true }); console.log("AFTER-CLICK", after.result?.value); } } proc.kill(); process.exit(0); })().catch((e) => { console.error("FAIL", e.message); process.exit(1); });