/** * Stream D1 QA sweep — models · artifacts · families · benchmarks · compare · licences. * Widths 320 · 360 · 375 · 390 · 430 · 768 · 1366 · 1440 · 1920, dark + light. Slugs are discovered live from the API. * Asserts: HTTP status (308 for merged / artifact slugs under /models), zero console errors, no horizontal overflow * (scrollWidth ≤ clientWidth), the evidence drawer opens from a model-page value, the matrix renders ≥ 1 heatmap cell, * the leaderboard has one row per model (no duplicate model slug in the first 50 rows). * Run: node qa/d1.mjs [BASE=http://localhost:8341] [API=http://127.0.0.1:8332] (FAST=1 → 390 + 1440 only) */ import { chromium } from '/Users/simon-pierreboucher/Desktop/uqo-eval/node_modules/playwright/index.mjs'; import { mkdirSync } from 'node:fs'; const BASE = process.argv[2] ?? 'http://localhost:8341'; const API = (process.argv[3] ?? 'http://127.0.0.1:8332') + '/api/v1'; const OUT = new URL('./screens/d1/', import.meta.url).pathname; mkdirSync(OUT, { recursive: true }); const WIDTHS = process.env.FAST === '1' ? [390, 1440] : [320, 360, 375, 390, 430, 768, 1366, 1440, 1920]; const THEMES = ['dark', 'light']; const j = async (u) => (await fetch(API + u)).json(); // ---------------------------------------------------------------------------------------------------------- discover slugs const top = await j('/models?limit=3&sort=quality'); const [a, b, c] = top.items.map((m) => m.slug); const artRes = await j('/models?include=artifacts&limit=80'); // prefer an artifact whose canonical model is resolved (the page shows a prominent link + note); fall back to any artifact const artifactRow = artRes.items.find((m) => m.entity_type === 'artifact' && m.canonical) ?? artRes.items.find((m) => m.entity_type === 'artifact'); const artifact = artifactRow?.slug; const artifactHasCanonical = !!artifactRow?.canonical; const detail = await j(`/models/${encodeURIComponent(a)}`); let merged = null; for (const alias of detail.identity?.api_aliases ?? []) { if (alias.includes('/') || alias === a) continue; const d = await j(`/models/${encodeURIComponent(alias)}`); if (d.redirected_from && d.slug === a) { merged = alias; break; } } const fam = (await j('/families?limit=1&sort=models')).items[0]?.slug; const benches = (await j('/benchmarks')).items.filter((x) => Number(x.result_count) > 0); const bench = benches.find((x) => x.slug === 'gpqa-diamond')?.slug ?? benches[0]?.slug; const lic = (await j('/licenses')).items[0]?.key ?? 'Apache-2.0'; console.log(`slugs: model=${a} artifact=${artifact} merged=${merged} family=${fam} benchmark=${bench} licence=${lic}`); const PAGES = [ '/models', '/models?openness=open-weights&min_params=7B&sort=params', `/models/${a}`, `/models/${a}/diff/${b}`, `/artifacts/${artifact}`, `/compare?ids=${a},${b},${c}`, '/benchmarks', `/benchmarks/${bench}`, '/benchmarks/matrix', `/benchmarks/${bench}/cost-vs-performance`, '/families', `/families/${fam}`, '/licenses', `/licenses/${encodeURIComponent(lic)}`, ]; // 404 favicon noise and 429s from the API's per-IP rate limiter (the sweep loads a page every second) are not page bugs. const filterErrors = (errors) => errors.filter((e) => !/favicon|Failed to load resource: the server responded with a status of (404|429)/.test(e)); let failures = 0; const ok = (cond, msg) => { if (!cond) failures++; console.log(`${cond ? 'OK ' : 'FAIL'} ${msg}`); }; // ---------------------------------------------------------------------------------------------------------- redirects for (const [path, expect] of [ [`/models/${artifact}`, `/artifacts/${artifact}`], [`/models/${merged}`, `/models/${a}`], ]) { if (!path.includes('null')) { const r = await fetch(BASE + path, { redirect: 'manual' }); const loc = r.headers.get('location') ?? ''; ok((r.status === 308 || r.status === 301) && loc.endsWith(expect), `redirect ${path} → ${r.status} ${loc}`); } else console.log(`SKIP redirect (no slug discovered) ${path}`); } for (const path of ['/models/does-not-exist-xyz', '/artifacts/does-not-exist-xyz', '/families/does-not-exist-xyz', '/licenses/does-not-exist-xyz', '/benchmarks/does-not-exist-xyz']) { const r = await fetch(BASE + path); ok(r.status === 404, `404 ${path} → ${r.status}`); } for (const path of [`/models/${a}/opengraph-image`, `/artifacts/${artifact}/opengraph-image`, `/benchmarks/${bench}/opengraph-image`, `/families/${fam}/opengraph-image`, `/licenses/${encodeURIComponent(lic)}/opengraph-image`]) { const r = await fetch(BASE + path); ok(r.status === 200 && (r.headers.get('content-type') ?? '').startsWith('image/png'), `og ${path} → ${r.status} ${r.headers.get('content-type')}`); } // ---------------------------------------------------------------------------------------------------------- sweep const browser = await chromium.launch(); for (const theme of THEMES) { for (const width of WIDTHS) { const mobile = width < 768; const ctx = await browser.newContext({ viewport: { width, height: mobile ? 844 : 900 }, deviceScaleFactor: 1, isMobile: mobile, hasTouch: mobile, colorScheme: theme }); await ctx.addInitScript((t) => localStorage.setItem('aia-theme', t), theme); const page = await ctx.newPage(); for (const path of PAGES) { const errors = []; const onErr = (e) => errors.push(String(e)); const onCon = (m) => { if (m.type() === 'error') errors.push(m.text()); }; page.on('pageerror', onErr); page.on('console', onCon); const t0 = Date.now(); const res = await page.goto(BASE + path, { waitUntil: 'networkidle', timeout: 90000 }).catch((e) => ({ status: () => `ERR ${e.message.slice(0, 40)}` })); await page.waitForTimeout(400); const overflow = await page.evaluate(() => document.documentElement.scrollWidth - document.documentElement.clientWidth).catch(() => -1); const filtered = filterErrors(errors); const status = res.status(); const good = status === 200 && overflow <= 0 && filtered.length === 0; if (!good) failures++; console.log(`${good ? 'OK ' : 'FAIL'} ${theme.padEnd(5)} ${String(width).padStart(4)} ${status} ${String(Date.now() - t0).padStart(5)}ms overflow=${overflow} errors=${filtered.length} ${path}${filtered.length ? ' :: ' + filtered[0].slice(0, 160) : ''}`); if ([390, 1440].includes(width) || !good) await page.screenshot({ path: `${OUT}${theme}-${width}-${path.replace(/[^a-z0-9]+/gi, '_').replace(/^_|_$/g, '')}.png`, fullPage: false }).catch(() => undefined); page.off('pageerror', onErr); page.off('console', onCon); } await ctx.close(); } } // ---------------------------------------------------------------------------------------------------------- flows (1440 dark) { const ctx = await browser.newContext({ viewport: { width: 1440, height: 900 }, colorScheme: 'dark' }); await ctx.addInitScript(() => localStorage.setItem('aia-theme', 'dark')); const page = await ctx.newPage(); // evidence drawer from a model page value await page.goto(`${BASE}/models/${a}`, { waitUntil: 'networkidle' }); const trigger = page.locator('[data-identity-strip] [data-evidence]').first(); ok((await trigger.count()) > 0, 'model page: identity strip has evidence triggers'); await trigger.click(); const drawer = await page.waitForSelector('[data-evidence-drawer]', { timeout: 8000 }).catch(() => null); ok(!!drawer, 'model page: evidence drawer opens from an identity-strip value'); const drawerText = drawer ? await drawer.innerText() : ''; ok(/Source|Tier|Observed/i.test(drawerText), 'evidence drawer shows source · tier · observed'); await page.screenshot({ path: `${OUT}flow-evidence-drawer.png` }); await page.keyboard.press('Escape'); // section nav present with sections in order const navItems = await page.$$eval('[data-section-nav] a', (as) => as.map((x) => x.textContent.trim())); ok(navItems[0] === 'Overview' && navItems.at(-1) === 'Provenance', `section nav order: ${navItems.join(' · ')}`); // every rendered section id has a nav item const sectionIds = await page.$$eval('section[data-section]', (ss) => ss.map((s) => s.id)); const navIds = await page.$$eval('[data-section-nav] a', (as) => as.map((x) => x.getAttribute('href').slice(1))); ok(sectionIds.every((id) => navIds.includes(id)), `all ${sectionIds.length} sections are in the nav`); // models list: inspector reflects the inspected row; column chooser await page.goto(`${BASE}/models`, { waitUntil: 'networkidle' }); const second = page.locator('[data-models-table] tr[data-row]').nth(1); const secondSlug = await second.getAttribute('data-row'); await second.locator('[data-inspect]').click(); await page.waitForTimeout(200); const inspected = await page.getAttribute('aside[aria-label="Inspector"] [data-models-inspector]', 'data-inspected'); ok(inspected === secondSlug, `models inspector shows the inspected row (${inspected})`); await page.click('[data-column-chooser]'); const chooserLabels = await page.$$eval('[role="group"][aria-label="Visible columns"] label', (ls) => ls.length); ok(chooserLabels >= 6, `column chooser lists ${chooserLabels} columns`); const understood = await page.goto(`${BASE}/models?openness=open-weights&min_params=7B`, { waitUntil: 'networkidle' }).then(() => page.$$eval('[data-understood] a', (as) => as.map((x) => x.textContent.trim()))); ok(understood.some((t) => /parameters ≥ 7B/.test(t)) && understood.some((t) => /Open weights/.test(t)), `understood-as chips: ${understood.join(' | ')}`); const countText = await page.textContent('[data-models-count]'); ok(/canonical models/.test(countText ?? ''), `count label: ${countText?.trim().slice(0, 60)}`); // matrix renders cells await page.goto(`${BASE}/benchmarks/matrix`, { waitUntil: 'networkidle' }); const cells = await page.$$eval('table.heatmap td:not(.empty)', (tds) => tds.length); ok(cells >= 1, `matrix renders ${cells} heatmap cells`); // leaderboard: one row per model await page.goto(`${BASE}/benchmarks/${bench}`, { waitUntil: 'networkidle' }); const slugs = await page.$$eval('[data-leaderboard] tr[data-model]', (trs) => trs.slice(0, 50).map((t) => t.getAttribute('data-model'))); ok(slugs.length > 0 && new Set(slugs).size === slugs.length, `leaderboard: ${slugs.length} rows, ${new Set(slugs).size} distinct models in the first 50`); const lbApi = await j(`/benchmarks/${bench}/leaderboard?limit=50`); ok(slugs[0] === lbApi.items[0]?.model.slug, `leaderboard first row matches the API leader (${slugs[0]})`); // compare: hide-identical toggle + differences-only link await page.goto(`${BASE}/compare?ids=${a},${b},${c}`, { waitUntil: 'networkidle' }); const before = await page.$$eval('[data-compare-table] tr[data-dim]', (r) => r.length); await page.click('[data-hide-identical]'); await page.waitForTimeout(150); const after = await page.$$eval('[data-compare-table] tr[data-dim]', (r) => r.length); ok(after >= before, `compare: ${before} rows with identical hidden → ${after} rows shown when unhidden`); const diffOnlyHref = await page.getAttribute('[data-diff-only]', 'href'); ok((diffOnlyHref ?? '').includes('diff_only=1'), `compare: differences-only link → ${diffOnlyHref}`); const evidenceCells = await page.$$eval('[data-compare-table] [data-evidence]', (x) => x.length); ok(evidenceCells > 0, `compare: ${evidenceCells} cells open evidence`); // artifact page note + canonical link await page.goto(`${BASE}/artifacts/${artifact}`, { waitUntil: 'networkidle' }); if (artifactHasCanonical) ok((await page.locator('[data-artifact-note]').count()) === 1, 'artifact page shows the "not an independent model" note with a canonical link'); else ok((await page.locator('[data-artifact-header]').count()) === 1, `artifact page renders (canonical model unresolved for ${artifact} — honest fallback note)`); await ctx.close(); } await browser.close(); console.log(failures ? `\n${failures} failure(s)` : '\nall D1 checks OK'); process.exit(failures ? 1 : 0);