spb/datacenterindex
Public
HTML 53.9%
TypeScript 44.5%
JavaScript 0.6%
SQL 0.5%
1// Admin console sweep: node apps/web/qa/admin-sweep.mjs (Playwright from ~/Desktop/uqo-eval)2// env: BASE (default http://localhost:8314), TOKEN (dev-admin-token), OUT (default qa/sweep-admin), PAGES=name,name, WIDTHS=390,12803// Logs in through /admin/login (httpOnly cookie), then screenshots every admin page and reports page-level horizontal4// overflow (wide tables must scroll inside their own container, never the page).5import { createRequire } from "node:module";6import { mkdirSync, writeFileSync } from "node:fs";7import { dirname, resolve } from "node:path";8import { fileURLToPath } from "node:url";910const require = createRequire("/Users/simon-pierreboucher/Desktop/uqo-eval/package.json");11const { chromium } = require("playwright");12const here = dirname(fileURLToPath(import.meta.url));13const OUT = process.env.OUT ?? resolve(here, "sweep-admin");14mkdirSync(OUT, { recursive: true });15const BASE = process.env.BASE ?? "http://localhost:8314";16const TOKEN = process.env.TOKEN ?? "dev-admin-token";17const DOC = process.env.DOC ?? "doc_8b4ef3d4844a957b";18const RUN = process.env.RUN ?? "run_4ke18ugp5yd3";1920const PAGES = [21 ["connectors", "/admin/connectors"],22 ["connectors-full", "/admin/connectors?cols=full"],23 ["quality", "/admin/quality"],24 ["data-gaps", "/admin/data-gaps"],25 ["matches", "/admin/matches"],26 ["projects", "/admin/projects"],27 ["document-trace", `/admin/documents/${DOC}?tab=trace`],28 ["document-fixture", `/admin/documents/${DOC}?fixture=1`],29 ["run", `/admin/runs/${RUN}`],30];31const ONLY = process.env.PAGES ? new Set(process.env.PAGES.split(",")) : null;32const WIDTHS = (process.env.WIDTHS ?? "390,1280").split(",").map(Number);3334function audit() {35 const doc = document.documentElement;36 const vw = doc.clientWidth;37 const wide = [];38 for (const el of document.querySelectorAll("body *")) {39 const r = el.getBoundingClientRect();40 if (r.width > 0 && r.right > vw + 1 && getComputedStyle(el).position !== "fixed") {41 // inside a horizontally scrolling container is fine42 let p = el.parentElement;43 let scrolls = false;44 while (p && p !== document.body) {45 const cs = getComputedStyle(p);46 if ((cs.overflowX === "auto" || cs.overflowX === "scroll") && p.scrollWidth > p.clientWidth + 1) {47 scrolls = true;48 break;49 }50 p = p.parentElement;51 }52 if (!scrolls) wide.push(`${el.tagName.toLowerCase()}.${(typeof el.className === "string" ? el.className : "").split(" ").filter(Boolean).slice(0, 3).join(".")}:+${Math.round(r.right - vw)}`);53 }54 }55 const overflow = doc.scrollWidth > doc.clientWidth ? { scrollWidth: doc.scrollWidth, clientWidth: doc.clientWidth, wide: wide.slice(0, 6) } : null;56 const h1 = document.querySelectorAll("h1").length;57 const alerts = Array.from(document.querySelectorAll("[role=alert]")).map((a) => a.textContent.trim().slice(0, 120));58 return { overflow, h1, alerts, height: doc.scrollHeight, title: document.title };59}6061const browser = await chromium.launch();62const results = [];63for (const w of WIDTHS) {64 const mobile = w < 700;65 const ctx = await browser.newContext({ viewport: { width: w, height: mobile ? 844 : 900 }, deviceScaleFactor: 1, isMobile: mobile, hasTouch: mobile, locale: "en-CA", timezoneId: "America/Toronto" });66 const page = await ctx.newPage();67 // login68 await page.goto(`${BASE}/admin/login`, { waitUntil: "load", timeout: 90000 });69 await page.fill('input[name="token"]', TOKEN);70 await page.click('button[type="submit"]');71 await page.waitForURL((u) => u.pathname.startsWith("/admin") && !u.pathname.startsWith("/admin/login"), { timeout: 60000 }).catch((e) => console.log("login failed", String(e).slice(0, 100)));72 for (const [name, path] of PAGES) {73 if (ONLY && !ONLY.has(name)) continue;74 const errors = [];75 const onConsole = (m) => {76 if (m.type() === "error") errors.push(m.text().slice(0, 200));77 };78 page.on("console", onConsole);79 const row = { name, path, w };80 try {81 const resp = await page.goto(`${BASE}${path}`, { waitUntil: "load", timeout: 90000 });82 row.status = resp?.status();83 await page.waitForLoadState("networkidle", { timeout: 20000 }).catch(() => {});84 await page.waitForTimeout(name.startsWith("connectors") ? 6000 : 1200);85 await page.waitForFunction(() => !document.querySelector(".animate-pulse-soft"), null, { timeout: 40000 }).catch(() => {});86 Object.assign(row, await page.evaluate(audit));87 await page.screenshot({ path: resolve(OUT, `${name}-${w}.png`), fullPage: row.height < 16000, timeout: 90000 });88 } catch (e) {89 row.error = String(e).slice(0, 200);90 }91 page.off("console", onConsole);92 row.errors = [...new Set(errors)];93 results.push(row);94 const flags = [];95 if (row.overflow) flags.push(`OVERFLOW ${JSON.stringify(row.overflow.wide)}`);96 if (row.h1 !== 1) flags.push(`h1×${row.h1}`);97 if (row.alerts?.length) flags.push(`alerts:${row.alerts.length}`);98 if (row.errors.length) flags.push(`console:${row.errors.length}`);99 if (row.error) flags.push("ERROR");100 console.log(`${String(w).padStart(4)} ${name.padEnd(18)} ${String(row.status ?? "").padEnd(3)} h=${String(row.height ?? "").padStart(5)} ${flags.length ? flags.join(" ") : "ok"}`);101 if (row.alerts?.length) for (const a of row.alerts) console.log(` alert: ${a}`);102 }103 await ctx.close();104}105await browser.close();106writeFileSync(resolve(OUT, "results.json"), JSON.stringify({ base: BASE, at: new Date().toISOString(), results }, null, 1));107console.log(`wrote ${OUT}/results.json`);108