spb/internetpressure
Public
TypeScript 36.3%
Python 31.8%
Go 18%
JavaScript 9.8%
Shell 1.9%
SQL 1.4%
CSS 0.5%
1#!/usr/bin/env node2/**3 * Visual QA: screenshots at 1440×900 and 390×844 + console errors + horizontal overflow check.4 * node qa/screens.mjs [baseUrl] [pathFilter] (default http://localhost:8351)5 * Playwright is borrowed from ~/Desktop/uqo-eval/node_modules (not a dependency of this app).6 */7import { mkdirSync, existsSync } from 'node:fs';8import path from 'node:path';910const PW_ROOT = '/Users/simon-pierreboucher/Desktop/uqo-eval/node_modules/playwright/index.mjs';11if (!existsSync(PW_ROOT)) {12 console.error(`Playwright not found at ${PW_ROOT} — skipping visual QA.`);13 process.exit(0);14}15const { chromium } = await import(PW_ROOT);1617const BASE = process.argv[2] ?? 'http://localhost:8351';18const FILTER = process.argv[3] ?? '';19const OUT = path.resolve(import.meta.dirname, 'screens');20mkdirSync(OUT, { recursive: true });2122const ALL = ['/', '/country/ca', '/asn/13335', '/service/cloudflare', '/routes', '/incidents', '/history', '/admin', '/internet/na-east', '/event/2026-09-12-north-america-east-latency-anomaly', '/bgp', '/probes', '/targets', '/methodology', '/api', '/services', '/asns', '/history/2026/9'];23const PAGES = FILTER ? ALL.filter((p) => (FILTER === '/' ? p === '/' : p.startsWith(FILTER))) : ALL;24const VIEWPORTS = [25 { name: '1440', width: 1440, height: 900, isMobile: false, deviceScaleFactor: 1 },26 { name: '390', width: 390, height: 844, isMobile: true, hasTouch: true, deviceScaleFactor: 2 },27];2829const browser = await chromium.launch();30let failures = 0;31for (const vp of VIEWPORTS) {32 const ctx = await browser.newContext({ viewport: { width: vp.width, height: vp.height }, isMobile: vp.isMobile, hasTouch: vp.hasTouch ?? false, deviceScaleFactor: vp.deviceScaleFactor, colorScheme: 'dark' });33 // admin token gate: pre-seed sessionStorage so /admin renders its sections34 await ctx.addInitScript(() => {35 try {36 window.sessionStorage.setItem('ip.admin-token', 'dev-admin-token');37 } catch {}38 });39 for (const p of PAGES) {40 const page = await ctx.newPage();41 const errors = [];42 page.on('console', (m) => {43 if (m.type() === 'error') errors.push(m.text());44 });45 page.on('pageerror', (e) => errors.push(`pageerror: ${e.message}`));46 const t0 = Date.now();47 let status = 0;48 try {49 // `networkidle` never fires: the SSE stream (/api/v1/live) stays open by design.50 const res = await page.goto(BASE + p, { waitUntil: 'load', timeout: 90_000 });51 status = res?.status() ?? 0;52 } catch (e) {53 errors.push(`goto: ${e.message}`);54 }55 await page.waitForTimeout(p === '/' || p.includes('/country/') || p.includes('/probes') || p.includes('/internet/') ? 4000 : 1500); // let the map tiles / charts settle56 const { sw, iw, h1, wide } = await page.evaluate(() => {57 // With mobile emulation the layout viewport grows to fit overflowing content: compare against the device width.58 const iw = Math.min(window.innerWidth, window.screen.width);59 const wide = [];60 for (const el of document.querySelectorAll('body *')) {61 const r = el.getBoundingClientRect();62 if (r.right > iw + 1 && r.width > 40 && !el.closest('.maplibregl-map') && !el.closest('.scroll-x') && !el.closest('.snap-row')) wide.push(`${el.tagName.toLowerCase()}${el.className && typeof el.className === 'string' ? '.' + el.className.split(' ').slice(0, 3).join('.') : ''}@${Math.round(r.right)}`);63 }64 return { sw: document.documentElement.scrollWidth, iw, h1: document.querySelector('h1')?.textContent?.trim() ?? document.title, wide: wide.slice(0, 6) };65 });66 if (wide.length) console.log(` wide: ${wide.join(' | ')}`);67 const name = (p.replace(/^\//, '').replace(/[\/?=&]+/g, '_') || 'home').slice(0, 60);68 await page.screenshot({ path: `${OUT}/${name}-${vp.name}.png`, fullPage: true });69 const realErrors = errors.filter((e) => !/openfreemap|tiles\.|Failed to load resource.*(png|pbf|json)|AbortError|net::ERR_/.test(e));70 const overflow = sw > iw;71 const bad = overflow || realErrors.length || status !== 200;72 if (bad) failures++;73 console.log(`${bad ? 'FAIL' : 'ok '} ${vp.name}px ${p} status=${status} scrollWidth=${sw}/${iw} ${Date.now() - t0}ms "${(h1 ?? '').slice(0, 60)}"${realErrors.length ? '\n console: ' + realErrors.slice(0, 3).join(' | ').slice(0, 400) : ''}`);74 await page.close();75 }76 await ctx.close();77}78await browser.close();79console.log(`\nScreenshots in ${OUT}`);80process.exit(failures ? 1 : 0);81