// Screenshots and checks of the store gallery pages (/themes/, /plugins/) of the built site. // Usage: (cd apps/web && bun run build && npx vite preview --port 1481) then // node scripts/gallery-shots.mjs [baseUrl] [outDir] import { chromium } from 'playwright'; import { mkdirSync } from 'node:fs'; import { join } from 'node:path'; const base = process.argv[2] ?? 'http://127.0.0.1:1481'; const out = process.argv[3] ?? join(import.meta.dirname, '..', '..', '..', 'verification', 'store'); mkdirSync(out, { recursive: true }); const failures = []; const check = (label, ok, detail = '') => { console.log(`${ok ? 'ok ' : 'FAIL'} ${label}${detail ? ` (${detail})` : ''}`); if (!ok) failures.push(label); }; const browser = await chromium.launch(); for (const [page, kind] of [['themes', 'theme'], ['plugins', 'plugin']]) { for (const [label, viewport] of [['desktop', { width: 1360, height: 1000 }], ['mobile', { width: 390, height: 844 }]]) { const tab = await browser.newPage({ viewport }); const broken = []; tab.on('response', res => { if (res.status() >= 400) broken.push(`${res.status()} ${res.url()}`); }); tab.on('pageerror', error => failures.push(`${page}: ${error.message}`)); await tab.goto(`${base}/${page}/`); await tab.waitForLoadState('networkidle'); const cards = await tab.locator('.gal-card').count(); const links = await tab.locator('a[href^="waybetter://install?kind="]').evaluateAll(as => as.map(a => a.getAttribute('href'))); const images = await tab.locator('.gal-img').evaluateAll(imgs => imgs.filter(img => img.naturalWidth === 0).length); if (label === 'desktop') { check(`${page}: cards`, cards >= (kind === 'theme' ? 13 : 14), `${cards}`); check(`${page}: every card has an install link`, links.length === cards && links.every(href => href.startsWith(`waybetter://install?kind=${kind}&url=https%3A%2F%2Fwaybetter.fyi%2Fstore%2F`))); check(`${page}: previews load`, images === 0, `${images} broken`); check(`${page}: no 4xx/5xx`, broken.filter(b => !b.includes('/v1/')).length === 0, broken.join(', ')); check(`${page}: downloads point at the store files`, (await tab.locator('a[download]').count()) === cards); if (kind === 'plugin') check('plugins: permission chips', (await tab.locator('.gal-perms li').count()) >= cards); } check(`${page} ${label}: no horizontal scroll`, await tab.evaluate(() => document.documentElement.scrollWidth <= innerWidth + 1)); await tab.screenshot({ path: join(out, `site-${page}-${label}.png`), fullPage: label === 'desktop' ? false : false }); if (label === 'desktop') { await tab.locator('.gal-filters button').nth(1).click(); const shown = await tab.locator('.gal-card:not([hidden])').count(); check(`${page}: category filter`, shown > 0 && shown < cards, `${shown}`); await tab.locator('.gal-filters button').first().click(); await tab.locator('[data-gallery-search]').fill(kind === 'theme' ? 'nord' : 'regex'); check(`${page}: search`, (await tab.locator('.gal-card:not([hidden])').count()) === 1); await tab.locator('[data-gallery-search]').fill(''); await tab.evaluate(() => scrollTo(0, 560)); await tab.waitForTimeout(200); await tab.screenshot({ path: join(out, `site-${page}-grid.png`) }); } await tab.close(); } } await browser.close(); console.log(failures.length ? `\n${failures.length} failure(s):\n- ${failures.join('\n- ')}` : '\nall checks passed'); process.exit(failures.length ? 1 : 0);