/** * Production QA sweep: every key route at 390 and 1440 px — HTTP status, console errors, horizontal overflow, screenshot. * Run: node qa/screens.mjs [BASE_URL] (default http://localhost:8321) */ 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:8321'; const OUT = new URL('./screens/sweep/', import.meta.url).pathname; mkdirSync(OUT, { recursive: true }); const PAGES = ['/', '/explore', '/search?q=starlink', '/satellites', '/satellites?constellation=starlink&status=ACTIVE', '/satellite/iss-zarya-25544', '/satellite/sputnik-1-2', '/constellations', '/constellation/starlink', '/operators', '/operator/spacex', '/countries', '/country/canada', '/launches', '/launch/1998-067', '/launch-sites', '/debris', '/reentries', '/events', '/stats', '/rankings?metric=operators', '/sources', '/methodology', '/status', '/developers', '/about', '/privacy', '/terms', '/admin', '/does-not-exist']; const WIDTHS = [390, 1440]; const browser = await chromium.launch(); let failures = 0; 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 }); const page = await ctx.newPage(); for (const path of PAGES) { 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(800); const overflow = await page.evaluate(() => document.documentElement.scrollWidth - document.documentElement.clientWidth).catch(() => -1); const expected = path === '/does-not-exist' ? 404 : path === '/admin' ? [200, 307] : 200; const status = res.status(); const okStatus = Array.isArray(expected) ? expected.includes(status) : status === expected; const filtered = errors.filter((e) => !/WebGL|webgl|THREE\.WebGLRenderer|favicon/.test(e)); const ok = okStatus && overflow <= 0 && filtered.length === 0; if (!ok) failures++; console.log(`${ok ? 'OK ' : 'FAIL'} ${width} ${status} ${String(Date.now() - t0).padStart(5)}ms overflow=${overflow} errors=${filtered.length} ${path}${filtered.length ? ' :: ' + filtered[0].slice(0, 120) : ''}`); await page.screenshot({ path: `${OUT}${width}-${path.replace(/[^a-z0-9]+/gi, '_').replace(/^_|_$/g, '') || 'home'}.png`, fullPage: false }).catch(() => undefined); page.off('pageerror', onErr); page.off('console', onCon); } await ctx.close(); } await browser.close(); console.log(failures ? `\n${failures} failure(s)` : '\nall pages OK'); process.exit(failures ? 1 : 0);