/** * QA sweep: key routes at 390 and 1440 px, dark and light — HTTP status, console errors, horizontal overflow, screenshot. * Also asserts that the homepage counters match GET /api/v1/stats. * Run: node qa/screens.mjs [BASE_URL] [API_URL] (defaults http://localhost:8330, http://127.0.0.1:8331) */ 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:8330'; const API = process.argv[3] ?? 'http://127.0.0.1:8331'; const OUT = new URL('./screens/', import.meta.url).pathname; mkdirSync(OUT, { recursive: true }); const PAGES = ['/', '/search?q=claude', '/models', '/models?openness=open-weights&sort=params', '/models/claude-opus-5', '/companies', '/companies/anthropic', '/providers', '/providers/anthropic-2', '/benchmarks', '/benchmarks/tau-bench', '/hardware/nvidia-dgx-b200', '/hardware', '/papers', '/frameworks', '/datasets', '/tools', '/changes', `/changes/${new Date().toISOString().slice(0, 10)}`, '/timeline', '/compare', '/explore', '/methodology', '/sources', '/about', '/developers', '/bot', '/models/does-not-exist', '/does-not-exist']; const WIDTHS = [390, 1440]; const THEMES = ['dark', 'light']; const fmt0 = new Intl.NumberFormat('en-US', { maximumFractionDigits: 0 }); const browser = await chromium.launch(); let failures = 0; for (const theme of THEMES) { for (const width of WIDTHS) { const mobile = width < 768; const ctx = await browser.newContext({ viewport: { width, height: mobile ? 844 : 900 }, deviceScaleFactor: 1, isMobile: mobile, hasTouch: mobile, colorScheme: theme }); await ctx.addInitScript((t) => localStorage.setItem('aia-theme', t), theme); const page = await ctx.newPage(); for (const path of PAGES) { if (theme === 'light' && !['/', '/models', '/models/claude-opus-5', '/changes', '/search?q=claude', '/companies/anthropic'].includes(path)) continue; const errors = []; const onErr = (e) => errors.push(String(e)); const onCon = (m) => { if (m.type() === 'error') errors.push(m.text()); }; page.on('pageerror', onErr); page.on('console', onCon); const t0 = Date.now(); const res = await page.goto(BASE + path, { waitUntil: 'networkidle', timeout: 60000 }).catch((e) => ({ status: () => `ERR ${e.message.slice(0, 40)}` })); await page.waitForTimeout(600); const overflow = await page.evaluate(() => document.documentElement.scrollWidth - document.documentElement.clientWidth).catch(() => -1); const applied = await page.evaluate(() => document.documentElement.getAttribute('data-theme')).catch(() => null); const expected = /does-not-exist/.test(path) ? 404 : 200; const status = res.status(); // Dynamic-route 404s use Next's client-rendered error shell; React DEV warns about the inline theme