/** * Viewport-sized segments for visual review (the full-page PNGs of polish-sweep are too tall to read): * top + evenly spaced offsets per route × width × theme → qa/screens/polish/seg/---.png * node qa/polish-segments.mjs [BASE_URL] [routes…] (defaults: key routes, widths 320 + 390 + 1440, light + dark at 390) */ import { chromium } from '/Users/simon-pierreboucher/Desktop/uqo-eval/node_modules/playwright/index.mjs'; import { mkdirSync } from 'node:fs'; const args = process.argv.slice(2); const BASE = args.find((a) => a.startsWith('http')) ?? 'http://localhost:8290'; const custom = args.filter((a) => a.startsWith('/')); const OUT = new URL('./screens/polish/seg/', import.meta.url).pathname; mkdirSync(OUT, { recursive: true }); const 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']; const ALL_COMBOS = [ [320, 'light'], [390, 'light'], [390, 'dark'], [1440, 'light'], [1440, 'dark'], ]; // --desktop → 1440 only; --phone → 320/390 only const COMBOS = args.includes('--desktop') ? ALL_COMBOS.filter(([w]) => w >= 1024) : args.includes('--phone') ? ALL_COMBOS.filter(([w]) => w < 1024) : ALL_COMBOS; const slug = (p) => (p === '/' ? 'home' : p.slice(1).replace(/[/?=&]+/g, '_')); const browser = await chromium.launch(); for (const [width, theme] of COMBOS) { const mobile = width < 768; const height = mobile ? 844 : 900; const ctx = await browser.newContext({ viewport: { width, height }, deviceScaleFactor: 1, isMobile: mobile, hasTouch: mobile, colorScheme: theme }); const page = await ctx.newPage(); for (const path of ROUTES) { let docH = 0; for (let attempt = 0; attempt < 3; attempt++) { try { await page.goto(BASE + path, { waitUntil: 'networkidle', timeout: 60_000 }); await page.evaluate(() => document.fonts.ready); docH = await page.evaluate(async () => { const h = document.documentElement.scrollHeight; for (let y = 0; y < h; y += 700) { window.scrollTo(0, y); await new Promise((r) => setTimeout(r, 50)); } window.scrollTo(0, 0); return document.documentElement.scrollHeight; }); break; } catch (e) { if (attempt === 2) console.log(`FAILED ${path}: ${String(e).slice(0, 120)}`); await page.waitForTimeout(1500); } } if (!docH) continue; await page.waitForLoadState('networkidle').catch(() => {}); await page.waitForTimeout(400); const n = Math.min(mobile ? 6 : 4, Math.max(1, Math.ceil(docH / height))); for (let i = 0; i < n; i++) { const y = n === 1 ? 0 : Math.round((i * (docH - height)) / (n - 1)); await page.evaluate((yy) => window.scrollTo(0, yy), y); await page.waitForTimeout(250); await page.screenshot({ path: `${OUT}${slug(path)}-${width}-${theme}-${i}.png`, fullPage: false }); } console.log(`${width} ${theme} ${path} h=${docH} segments=${n}`); } await ctx.close(); } await browser.close();