spb/satelliteindex
Public
TypeScript 66.5%
Python 30.9%
JavaScript 1.4%
CSS 0.7%
1/**2 * QA for the satellite / launches pages: screenshots at 390 and 1440, console errors, horizontal overflow,3 * DOM-order = visual-order check on the satellite detail page.4 * node qa/satellite-qa.mjs [baseUrl]5 */6import { mkdirSync } from 'node:fs';7import path from 'node:path';8import { chromium } from '/Users/simon-pierreboucher/Desktop/uqo-eval/node_modules/playwright/index.mjs';910const BASE = process.argv[2] ?? 'http://localhost:8319';11const OUT = path.resolve(import.meta.dirname, 'screens/satellite');12mkdirSync(OUT, { recursive: true });1314const PAGES = [15 ['satellite-iss', '/satellite/iss-zarya-25544'],16 ['satellite-starlink', '/satellite/starlink-38436-100641'],17 ['satellite-sputnik', '/satellite/sputnik-1-2'],18 ['satellites', '/satellites'],19 ['satellites-starlink-active', '/satellites?constellation=starlink&status=ACTIVE'],20 ['satellites-empty', '/satellites?status=PLANNED&object_type=DEBRIS'],21 ['launches', '/launches'],22 ['launches-2026', '/launches?year=2026'],23 ['launch-1998-067', '/launch/1998-067'],24 ['launch-sites', '/launch-sites'],25 ['launch-site-baikonur', '/launch-sites/baikonur-cosmodrome-tyuratam'],26];27const VIEWPORTS = [28 ['390', { width: 390, height: 844 }],29 ['1440', { width: 1440, height: 900 }],30];3132const browser = await chromium.launch();33let failures = 0;34for (const [vpName, viewport] of VIEWPORTS) {35 const ctx = await browser.newContext({ viewport, deviceScaleFactor: 1, colorScheme: 'dark' });36 for (const [name, url] of PAGES) {37 const page = await ctx.newPage();38 const errors = [];39 page.on('console', (m) => m.type() === 'error' && errors.push(m.text()));40 page.on('pageerror', (e) => errors.push(`pageerror: ${e.message}`));41 const res = await page.goto(BASE + url, { waitUntil: 'networkidle', timeout: 90_000 });42 await page.waitForTimeout(1200);43 const metrics = await page.evaluate(() => {44 const de = document.documentElement;45 const overflow = de.scrollWidth - de.clientWidth;46 const wide = [...document.querySelectorAll('body *')].filter((el) => el.getBoundingClientRect().right > de.clientWidth + 1).slice(0, 5).map((el) => `${el.tagName.toLowerCase()}.${String(el.className).split(' ').slice(0, 2).join('.')}`);47 // DOM order == visual order for the detail-page sections48 const ids = ['live', 'orbit', 'mission', 'ownership', 'launch', 'history', 'registration', 'sources', 'events', 'related', 'identifiers'];49 const tops = ids.map((id) => document.getElementById(id)?.getBoundingClientRect().top).filter((t) => t !== undefined);50 const ordered = tops.every((t, i) => i === 0 || t >= tops[i - 1]);51 const h1 = document.querySelector('h1');52 const h1Top = h1 ? h1.getBoundingClientRect().top + window.scrollY : null;53 const firstSectionTop = tops[0] !== undefined ? tops[0] + window.scrollY : null;54 const usesOrder = [...document.querySelectorAll('main *')].some((el) => getComputedStyle(el).order !== '0');55 return { overflow, wide, ordered, heroFirst: h1Top === null || firstSectionTop === null || h1Top < firstSectionTop, usesOrder, scrollY: window.scrollY, title: document.title };56 });57 const file = path.join(OUT, `${name}-${vpName}.png`);58 await page.screenshot({ path: file, fullPage: true });59 const bad = (res?.status() ?? 0) >= 400 || errors.length || metrics.overflow > 0 || !metrics.ordered || !metrics.heroFirst || metrics.usesOrder || metrics.scrollY !== 0;60 if (bad) failures++;61 console.log(`${bad ? 'FAIL' : ' ok '} ${vpName.padEnd(4)} ${url.padEnd(52)} ${res?.status()} overflow=${metrics.overflow} order=${metrics.ordered ? 'ok' : 'BAD'} hero=${metrics.heroFirst ? 'first' : 'NOT-FIRST'} cssOrder=${metrics.usesOrder ? 'USED' : 'none'} errors=${errors.length}`);62 if (errors.length) console.log(' ', errors.slice(0, 3).join('\n '));63 if (metrics.wide.length) console.log(' wide:', metrics.wide.join(', '));64 await page.close();65 }66 await ctx.close();67}68await browser.close();69console.log(failures ? `\n${failures} page(s) failed` : '\nall pages passed');70process.exit(failures ? 1 : 0);71