spb/satelliteindex
Public
TypeScript 66.5%
Python 30.9%
JavaScript 1.4%
CSS 0.7%
1// QA for the meta/admin surface: screenshots at 390 and 1440, console errors, horizontal overflow, admin auth flow.2// Usage: node qa/meta-screens.mjs [baseUrl] (default http://localhost:8319)3import { mkdirSync } from 'node:fs';4import path from 'node:path';5import { chromium } from '/Users/simon-pierreboucher/Desktop/uqo-eval/node_modules/playwright/index.mjs';67const BASE = process.argv[2] ?? 'http://localhost:8319';8const OUT = path.resolve(import.meta.dirname, 'screens/meta');9mkdirSync(OUT, { recursive: true });1011const PUBLIC = ['/sources', '/methodology', '/status', '/status/data', '/developers', '/about', '/privacy', '/terms', '/admin/login'];12const ADMIN = ['/admin', '/admin/connectors/celestrak_gp', '/admin/raw', '/admin/data-quality', '/admin/entity-resolution', '/admin/costs'];13const VIEWPORTS = [14 { name: '390', width: 390, height: 844, isMobile: true, deviceScaleFactor: 2 },15 { name: '1440', width: 1440, height: 900, isMobile: false, deviceScaleFactor: 1 },16];1718const browser = await chromium.launch();19const problems = [];2021async function audit(page, route, tag) {22 const errors = [];23 const onConsole = (m) => m.type() === 'error' && errors.push(m.text());24 const onPageError = (e) => errors.push(`pageerror: ${e.message}`);25 page.on('console', onConsole);26 page.on('pageerror', onPageError);27 const res = await page.goto(`${BASE}${route}`, { waitUntil: 'networkidle', timeout: 120_000 });28 await page.waitForTimeout(400);29 const { sw, iw, title } = await page.evaluate(() => ({ sw: document.documentElement.scrollWidth, iw: window.innerWidth, title: document.title }));30 const file = `${route.replace(/^\//, '').replace(/[\/?=&]+/g, '_') || 'home'}-${tag}.png`;31 await page.screenshot({ path: path.join(OUT, file), fullPage: true });32 page.off('console', onConsole);33 page.off('pageerror', onPageError);34 const status = res?.status();35 const line = `${String(status).padEnd(4)} ${tag.padEnd(5)} ${route.padEnd(36)} sw=${sw} iw=${iw} ${title}`;36 console.log(line);37 if (status !== 200) problems.push(`${route}@${tag}: HTTP ${status}`);38 // Mobile emulation zooms out when content is wider than the viewport, inflating innerWidth: compare with the requested width too.39 const want = Number(tag);40 if (sw > iw || iw !== want || sw > want) problems.push(`${route}@${tag}: horizontal overflow scrollWidth=${sw} innerWidth=${iw} viewport=${want}`);41 const realErrors = errors.filter((e) => !/favicon|apple-icon/.test(e));42 if (realErrors.length) problems.push(`${route}@${tag}: console errors\n ${realErrors.join('\n ')}`);43}4445for (const vp of VIEWPORTS) {46 const ctx = await browser.newContext({ viewport: { width: vp.width, height: vp.height }, isMobile: vp.isMobile, deviceScaleFactor: vp.deviceScaleFactor, hasTouch: vp.isMobile });47 const page = await ctx.newPage();48 for (const r of PUBLIC) await audit(page, r, vp.name);4950 // Unauthenticated /admin must redirect to /admin/login.51 const anon = await page.goto(`${BASE}/admin`, { waitUntil: 'networkidle' });52 const landed = new URL(page.url()).pathname;53 console.log(`redirect check: /admin -> ${landed} (${anon?.status()})`);54 if (landed !== '/admin/login') problems.push(`anonymous /admin landed on ${landed}`);5556 // Login with the dev token, then audit admin pages.57 await page.goto(`${BASE}/admin/login?next=%2Fadmin%2Fcosts`, { waitUntil: 'networkidle' });58 await page.fill('input[name=token]', 'wrong-token');59 await page.click('button[type=submit]');60 await page.waitForURL(/error=invalid/);61 console.log('wrong token -> error shown');62 await page.fill('input[name=token]', 'dev-admin-token');63 await page.click('button[type=submit]');64 await page.waitForURL((u) => u.pathname === '/admin/costs', { timeout: 60_000 });65 console.log(`login ok -> ${new URL(page.url()).pathname}`);66 const cookies = await ctx.cookies();67 const c = cookies.find((k) => k.name === 'si_admin');68 if (!c || !c.httpOnly) problems.push('si_admin cookie missing or not httpOnly');69 for (const r of ADMIN) await audit(page, r, vp.name);7071 // Sign out clears the session.72 await page.click('nav[aria-label=Admin] button[type=submit]');73 await page.waitForURL(/\/admin\/login/);74 const after = await page.goto(`${BASE}/admin`, { waitUntil: 'networkidle' });75 if (new URL(page.url()).pathname !== '/admin/login') problems.push('after logout /admin did not redirect');76 console.log(`logout ok -> ${new URL(page.url()).pathname} (${after?.status()})`);77 await ctx.close();78}79await browser.close();8081console.log('\n' + (problems.length ? `PROBLEMS (${problems.length}):\n- ${problems.join('\n- ')}` : 'OK: no overflow, no console errors, auth flow verified'));82process.exit(problems.length ? 1 : 0);83