/** * QA sweep: key routes at 390 and 1440 px, dark and light — HTTP status, console errors, horizontal overflow, screenshot. * Run: node qa/screens.mjs [BASE_URL] (default http://localhost:8390) * Playwright is resolved from the uqo-eval project on this machine (see import below); adjust if needed. */ import { chromium } from "/Users/simon-pierreboucher/Desktop/uqo-eval/node_modules/playwright/index.mjs"; import { mkdirSync } from "node:fs"; const BASE = process.argv[2] ?? "http://localhost:8390"; const OUT = new URL("./screens/", import.meta.url).pathname; mkdirSync(OUT, { recursive: true }); const PAGES = ["/", "/live", "/markets", "/crypto", "/stocks", "/indices", "/forex", "/rates", "/commodities", "/instruments/crypto_btc_usd", "/instruments/AAPL", "/instruments/US10Y", "/exchanges", "/exchanges/xnas", "/countries", "/countries/CA", "/events", "/halts", "/filings", "/sources", "/coverage", "/coverage?tier=A", "/connectors", "/data-health", "/search?q=bitcoin", "/compare?ids=crypto_btc_usd,crypto_eth_usd", "/status", "/methodology", "/developers", "/licensing", "/admin", "/does-not-exist"]; const WIDTHS = [390, 1440]; const THEMES = ["dark", "light"]; const browser = await chromium.launch(); let failures = 0; for (const theme of THEMES) { for (const width of WIDTHS) { const ctx = await browser.newContext({ viewport: { width, height: width < 600 ? 844 : 900 }, deviceScaleFactor: 1 }); await ctx.addInitScript((t) => localStorage.setItem("ma-theme", t), theme); for (const path of PAGES) { const page = await ctx.newPage(); const errors = []; page.on("console", (m) => m.type() === "error" && errors.push(m.text())); page.on("pageerror", (e) => errors.push(`pageerror: ${e.message}`)); const res = await page.goto(BASE + path, { waitUntil: "networkidle", timeout: 60_000 }).catch((e) => ({ status: () => `ERR ${e.message}` })); await page.waitForTimeout(1200); const overflow = await page.evaluate(() => document.documentElement.scrollWidth > document.documentElement.clientWidth + 1); const status = res.status(); const expected = path === "/does-not-exist" ? 404 : 200; const errs = errors.filter((e) => !/favicon|404 \(Not Found\)|hydrat/i.test(e) || /Hydration failed|did not match/.test(e)); const ok = status === expected && !overflow && errs.length === 0; if (!ok) failures++; console.log(`${ok ? "OK " : "FAIL"} ${theme.padEnd(5)} ${String(width).padEnd(4)} ${String(status).padEnd(4)} ${overflow ? "OVERFLOW " : ""}${path}${errs.length ? `\n ${errs.slice(0, 3).join("\n ")}` : ""}`); await page.screenshot({ path: `${OUT}${theme}-${width}-${path.replace(/[^a-z0-9]+/gi, "_").replace(/^_|_$/g, "") || "home"}.png`, fullPage: width >= 600 }); await page.close(); } await ctx.close(); } } await browser.close(); console.log(failures ? `\n${failures} failing checks` : "\nall checks passed"); process.exit(failures ? 1 : 0);