spb/countryatlas
Public
TypeScript 57%
Python 38.6%
JavaScript 3.6%
CSS 0.6%
1/**2 * QA for the Compare + Rankings pages: screenshots at 320/375/390/430/1280/1440 → qa/screens/compare-rankings/,3 * plus checks: horizontal overflow, tap targets < 44 px (mobile), console errors, sticky controls height,4 * URL state round-trip (reload keeps state), 404s. Run: node qa/compare-rankings.mjs [BASE_URL]5 */6import { chromium } from '/Users/simon-pierreboucher/Desktop/uqo-eval/node_modules/playwright/index.mjs';7import { mkdirSync, writeFileSync } from 'node:fs';89const BASE = process.argv[2] ?? 'http://localhost:8290';10const OUT = new URL('./screens/compare-rankings/', import.meta.url).pathname;11mkdirSync(OUT, { recursive: true });1213const PAGES = [14 ['compare', '/compare'],15 ['cmp-snapshot', '/compare/canada/united-states/france'],16 ['cmp-economy', '/compare/canada/united-states/france?tab=economy'],17 ['cmp-hero', '/compare/canada/united-states/france?tab=economy&indicator=gdp-per-capita&from=1990&mode=index100'],18 ['rankings', '/rankings'],19 ['rk-gdppc', '/rankings/gdp-per-capita'],20 ['rk-life-oecd', '/rankings/life-expectancy?group=oecd'],21];22const WIDTHS = [320, 375, 390, 430, 1280, 1440];23const report = [];24const browser = await chromium.launch();2526for (const width of WIDTHS) {27 const mobile = width < 768;28 const ctx = await browser.newContext({ viewport: { width, height: mobile ? 800 : 900 }, deviceScaleFactor: 1, isMobile: mobile, hasTouch: mobile, colorScheme: 'light' });29 const page = await ctx.newPage();30 const errors = [];31 page.on('pageerror', (e) => errors.push(String(e)));32 page.on('console', (m) => { if (m.type() === 'error') errors.push(m.text()); });33 for (const [name, path] of PAGES) {34 await page.goto(BASE + path, { waitUntil: 'networkidle' });35 await page.evaluate(() => document.fonts.ready);36 await page.waitForTimeout(700);37 const m = await page.evaluate(() => {38 const de = document.documentElement;39 const overflow = de.scrollWidth - de.clientWidth;40 const wide = [...document.querySelectorAll('body *')].filter((el) => { const r = el.getBoundingClientRect(); return r.right > de.clientWidth + 1 && r.width > 0 && getComputedStyle(el).position !== 'fixed'; }).slice(0, 6).map((el) => `${el.tagName.toLowerCase()}.${String(el.className).split(' ').slice(0, 3).join('.')} right=${Math.round(el.getBoundingClientRect().right)}`);41 const small = [...document.querySelectorAll('a,button,[role=button],input,select,summary')].filter((el) => { const r = el.getBoundingClientRect(); if (r.width === 0 || r.height === 0) return false; const cs = getComputedStyle(el); if (cs.visibility === 'hidden') return false; return r.height < 44 && r.width < 44; }).map((el) => `${el.tagName.toLowerCase()} "${(el.getAttribute('aria-label') || el.textContent || '').trim().slice(0, 30)}" ${Math.round(el.getBoundingClientRect().width)}x${Math.round(el.getBoundingClientRect().height)}`);42 const smallH = [...document.querySelectorAll('a,button,[role=button],input,select,summary')].filter((el) => { const r = el.getBoundingClientRect(); return r.width > 0 && r.height > 0 && r.height < 32; }).map((el) => `${el.tagName.toLowerCase()} "${(el.getAttribute('aria-label') || el.textContent || '').trim().slice(0, 30)}" h=${Math.round(el.getBoundingClientRect().height)}`);43 const sticky = [...document.querySelectorAll('.sticky')].filter((el) => el.getBoundingClientRect().height > 0).map((el) => Math.round(el.getBoundingClientRect().height));44 return { overflow, wide, small: small.slice(0, 12), smallH: smallH.slice(0, 12), sticky, docH: de.scrollHeight };45 });46 await page.screenshot({ path: `${OUT}${name}-${width}.png`, fullPage: true });47 report.push({ name, path, width, ...m, errors: errors.splice(0) });48 }49 await ctx.close();50}5152// URL state round-trip + 404s (desktop)53{54 const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } });55 const page = await ctx.newPage();56 const checks = [];57 await page.goto(`${BASE}/compare/canada/united-states/france?tab=economy`, { waitUntil: 'networkidle' });58 await page.getByRole('radio', { name: 'Index = 100' }).click();59 await page.waitForTimeout(1200);60 const url1 = page.url();61 await page.reload({ waitUntil: 'networkidle' });62 const modeChecked = await page.getByRole('radio', { name: 'Index = 100' }).getAttribute('aria-checked');63 checks.push({ check: 'compare mode round-trip', url: url1, ok: url1.includes('mode=index100') && modeChecked === 'true' });64 await page.getByRole('tab', { name: 'Population' }).click();65 await page.waitForTimeout(1200);66 checks.push({ check: 'tab switch updates URL', url: page.url(), ok: page.url().includes('tab=population') });67 await page.goto(`${BASE}/rankings/gdp-per-capita`, { waitUntil: 'networkidle' });68 await page.locator('select[aria-label="Year"]:visible').first().selectOption('2015');69 await page.waitForTimeout(1500);70 const url2 = page.url();71 await page.reload({ waitUntil: 'networkidle' });72 const yearVal = await page.locator('select[aria-label="Year"]:visible').first().inputValue();73 checks.push({ check: 'ranking year round-trip', url: url2, ok: url2.includes('year=2015') && yearVal === '2015' });74 for (const p of ['/compare/canada', '/rankings/not-a-thing', '/compare/canada/nowhere-land']) {75 const res = await page.goto(BASE + p, { waitUntil: 'domcontentloaded' });76 checks.push({ check: `404 ${p}`, status: res?.status(), ok: res?.status() === 404 });77 }78 report.push({ checks });79 await ctx.close();80}81await browser.close();82writeFileSync(`${OUT}report.json`, JSON.stringify(report, null, 2));83for (const r of report) {84 if (r.checks) { for (const c of r.checks) console.log(c.ok ? 'OK ' : 'FAIL', c.check, c.url ?? c.status ?? ''); continue; }85 const flags = [r.overflow > 0 ? `OVERFLOW ${r.overflow}px ${r.wide.join(' | ')}` : '', r.width < 768 && r.small.length ? `small: ${r.small.join(' | ')}` : '', r.errors.length ? `errors: ${r.errors.join(' | ').slice(0, 300)}` : ''].filter(Boolean);86 console.log(`${r.name}@${r.width} h=${r.docH} sticky=[${r.sticky.join(',')}] ${flags.join(' ; ')}`);87}88