83 lines
5.0 KiB
JavaScript
83 lines
5.0 KiB
JavaScript
/* Faithful theme test: sets next-themes' localStorage.theme (the theme NAME)
|
|
and reloads -- exactly what the in-app theme toggle does -- then screenshots. */
|
|
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 = 9341;
|
|
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 = [];
|
|
ws.onmessage = (m) => { const msg = JSON.parse(m.data); if (msg.id) { ws._buf.push(msg); return; }
|
|
if (msg.method === "Runtime.consoleAPICalled" && msg.params.type === "error") {
|
|
const txt = msg.params.args.map((a) => a.value ?? a.description ?? a.type).join(" ");
|
|
if (!/hydrated|React DevTools|HMR/.test(txt)) console.log("[err]", txt.slice(0, 140));
|
|
} };
|
|
|
|
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;
|
|
// Cookie is applied per-job below (login pages need a logged-out state).
|
|
|
|
const jobs = [
|
|
{ name: "01-login-default", url: `${BASE}/login`, theme: "default", cookie: false, w: 1280, h: 800 },
|
|
{ name: "02-home-default", url: `${BASE}/`, theme: "default", cookie: true, w: 1600, h: 1000 },
|
|
{ name: "03-home-sunset", url: `${BASE}/`, theme: "sunset", cookie: true, w: 1600, h: 1000 },
|
|
{ name: "04-home-dark", url: `${BASE}/`, theme: "dark", cookie: true, w: 1600, h: 1000 },
|
|
{ name: "05-home-ocean", url: `${BASE}/`, theme: "ocean", cookie: true, w: 1600, h: 1000 },
|
|
{ name: "06-projects-dark", url: `${BASE}/projects`, theme: "dark", cookie: true, w: 1600, h: 1000 },
|
|
{ name: "07-chat-ocean", url: `${BASE}/chat`, theme: "ocean", cookie: true, w: 1600, h: 1000 },
|
|
{ name: "08-admin-default", url: `${BASE}/admin`, theme: "default", cookie: true, w: 1600, h: 1000 },
|
|
{ name: "09-login-sunset", url: `${BASE}/login`, theme: "sunset", cookie: false, w: 1280, h: 800 },
|
|
{ name: "10-home-default-mobile", url: `${BASE}/`, theme: "default", cookie: true, w: 430, h: 932 },
|
|
];
|
|
|
|
for (const job of jobs) {
|
|
if (job.cookie && cookieValue) {
|
|
await send("Network.setCookies", { cookies: [{ name: "authjs.session-token", value: cookieValue, domain: "localhost", path: "/", httpOnly: true, sameSite: "Lax" }] });
|
|
} else {
|
|
await send("Network.deleteCookies", { name: "authjs.session-token", domain: "localhost" });
|
|
}
|
|
await send("Emulation.setDeviceMetricsOverride", { width: job.w, height: job.h, deviceScaleFactor: 1, mobile: job.w < 500 });
|
|
await send("Page.navigate", { url: job.url });
|
|
await new Promise((r) => setTimeout(r, 3500));
|
|
// Switch theme the way the app does: write the theme NAME to next-themes'
|
|
// storage key, then reload so it applies on a fresh (SSR+hydration) render.
|
|
await send("Runtime.evaluate", { expression: `localStorage.setItem('theme', ${JSON.stringify(job.theme)}); 'ok'` });
|
|
await send("Page.reload");
|
|
await new Promise((r) => setTimeout(r, 3800));
|
|
const htmlClass = (await send("Runtime.evaluate", { expression: `document.documentElement.className`, returnByValue: true }))?.result?.value || "";
|
|
const shot = await send("Page.captureScreenshot", { format: "png", captureBeyondViewport: false });
|
|
fs.writeFileSync(path.join(OUT, `${job.name}.png`), Buffer.from(shot.data, "base64"));
|
|
console.log(`saved ${job.name} (html=...${htmlClass.split(" ").filter(Boolean).slice(-1)[0]})`);
|
|
}
|
|
proc.kill(); process.exit(0);
|
|
})().catch((e) => { console.error(e); process.exit(1); });
|