SPB Git forge
3commits 1branches 0releases
417.0 KBsize
maindefault branch
10 days agolast push
TypeScript 66.5% Python 30.9% JavaScript 1.4% CSS 0.7%
4.6 KB · 83 lines javascript
Raw Blame History
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