spb/countryatlas
Public
TypeScript 57%
Python 38.6%
JavaScript 3.6%
CSS 0.6%
1/**2 * Viewport-sized segments for visual review (the full-page PNGs of polish-sweep are too tall to read):3 * top + evenly spaced offsets per route × width × theme → qa/screens/polish/seg/<route>-<width>-<theme>-<i>.png4 * node qa/polish-segments.mjs [BASE_URL] [routes…] (defaults: key routes, widths 320 + 390 + 1440, light + dark at 390)5 */6import { chromium } from '/Users/simon-pierreboucher/Desktop/uqo-eval/node_modules/playwright/index.mjs';7import { mkdirSync } from 'node:fs';89const args = process.argv.slice(2);10const BASE = args.find((a) => a.startsWith('http')) ?? 'http://localhost:8290';11const custom = args.filter((a) => a.startsWith('/'));12const OUT = new URL('./screens/polish/seg/', import.meta.url).pathname;13mkdirSync(OUT, { recursive: true });14const ROUTES = custom.length ? custom : ['/', '/countries', '/countries/canada', '/countries/canada/economy', '/compare/canada/united-states/france', '/compare/canada/united-states/france?tab=economy', '/rankings', '/rankings/gdp-per-capita', '/indicators/life-expectancy', '/regions/oecd', '/explore', '/changes', '/sources', '/methodology', '/api', '/data', '/indicators', '/regions', '/compare'];15const ALL_COMBOS = [16 [320, 'light'],17 [390, 'light'],18 [390, 'dark'],19 [1440, 'light'],20 [1440, 'dark'],21];22// --desktop → 1440 only; --phone → 320/390 only23const COMBOS = args.includes('--desktop') ? ALL_COMBOS.filter(([w]) => w >= 1024) : args.includes('--phone') ? ALL_COMBOS.filter(([w]) => w < 1024) : ALL_COMBOS;24const slug = (p) => (p === '/' ? 'home' : p.slice(1).replace(/[/?=&]+/g, '_'));25const browser = await chromium.launch();26for (const [width, theme] of COMBOS) {27 const mobile = width < 768;28 const height = mobile ? 844 : 900;29 const ctx = await browser.newContext({ viewport: { width, height }, deviceScaleFactor: 1, isMobile: mobile, hasTouch: mobile, colorScheme: theme });30 const page = await ctx.newPage();31 for (const path of ROUTES) {32 let docH = 0;33 for (let attempt = 0; attempt < 3; attempt++) {34 try {35 await page.goto(BASE + path, { waitUntil: 'networkidle', timeout: 60_000 });36 await page.evaluate(() => document.fonts.ready);37 docH = await page.evaluate(async () => {38 const h = document.documentElement.scrollHeight;39 for (let y = 0; y < h; y += 700) {40 window.scrollTo(0, y);41 await new Promise((r) => setTimeout(r, 50));42 }43 window.scrollTo(0, 0);44 return document.documentElement.scrollHeight;45 });46 break;47 } catch (e) {48 if (attempt === 2) console.log(`FAILED ${path}: ${String(e).slice(0, 120)}`);49 await page.waitForTimeout(1500);50 }51 }52 if (!docH) continue;53 await page.waitForLoadState('networkidle').catch(() => {});54 await page.waitForTimeout(400);55 const n = Math.min(mobile ? 6 : 4, Math.max(1, Math.ceil(docH / height)));56 for (let i = 0; i < n; i++) {57 const y = n === 1 ? 0 : Math.round((i * (docH - height)) / (n - 1));58 await page.evaluate((yy) => window.scrollTo(0, yy), y);59 await page.waitForTimeout(250);60 await page.screenshot({ path: `${OUT}${slug(path)}-${width}-${theme}-${i}.png`, fullPage: false });61 }62 console.log(`${width} ${theme} ${path} h=${docH} segments=${n}`);63 }64 await ctx.close();65}66await browser.close();67