/** * Admin panel QA: wrong token → error; right token (deploy/.admin-token) → cookie → overview/coverage/runs/issues/raw * render; "Clear cache" server action round-trips; screenshots at 390 + 1440 → qa/screens/explore/admin_*.png. * Needs CA_ADMIN_TOKEN on the web process (and ADMIN_API_URL → an API instance with the same token). * Run: node qa/admin-explore.mjs [BASE_URL] */ import { chromium } from '/Users/simon-pierreboucher/Desktop/uqo-eval/node_modules/playwright/index.mjs'; import { readFileSync } from 'node:fs'; const BASE = process.argv[2] ?? 'http://localhost:8290'; const OUT = new URL('./screens/explore/', import.meta.url).pathname; const TOKEN = readFileSync(new URL('../../../deploy/.admin-token', import.meta.url), 'utf8').trim(); const browser = await chromium.launch(); for (const width of [390, 1440]) { const mobile = width < 768; const ctx = await browser.newContext({ viewport: { width, height: mobile ? 844 : 900 }, isMobile: mobile, hasTouch: mobile }); const page = await ctx.newPage(); const errors = []; page.on('pageerror', (e) => errors.push(String(e))); page.on('console', (m) => m.type() === 'error' && errors.push(m.text())); // unauthenticated → redirect to login let res = await page.goto(BASE + '/admin/coverage', { waitUntil: 'networkidle' }); console.log(width, 'gate:', page.url().endsWith('/admin/login') ? 'redirected to login OK' : `FAIL ${page.url()}`, res?.status()); // wrong token await page.fill('input[name=token]', 'nope'); await page.click('button[type=submit]'); await page.waitForURL(/error=1/, { timeout: 60_000 }).catch(() => {}); await page.waitForLoadState('networkidle'); const err = await page.getByText('Invalid token.').count(); const stillNoCookie = !(await ctx.cookies()).some((k) => k.name === 'ca_admin'); console.log(width, 'wrong token:', err && stillNoCookie ? 'error shown, no cookie OK' : `FAIL url=${page.url()} err=${err} noCookie=${stillNoCookie}`); // right token await page.fill('input[name=token]', TOKEN); await page.click('button[type=submit]'); await page.waitForURL(/\/admin(\?.*)?$/, { timeout: 60_000 }); await page.waitForLoadState('networkidle'); const cookies = await ctx.cookies(); const c = cookies.find((k) => k.name === 'ca_admin'); console.log(width, 'cookie:', c ? `httpOnly=${c.httpOnly} sameSite=${c.sameSite} path=${c.path} value≠token=${c.value !== TOKEN}` : 'MISSING'); for (const p of ['/admin', '/admin/coverage', '/admin/runs', '/admin/issues', '/admin/raw']) { res = await page.goto(BASE + p, { waitUntil: 'networkidle', timeout: 120_000 }); const h = await page.locator('h2').first().textContent().catch(() => ''); const overflow = await page.evaluate(() => document.documentElement.scrollWidth - document.documentElement.clientWidth); await page.screenshot({ path: `${OUT}admin${p.replace(/\//g, '_')}-${width}.png`, fullPage: true }); console.log(width, p.padEnd(16), res?.status(), `h2="${h?.trim()}"`, overflow > 0 ? `OVERFLOW +${overflow}` : 'ok'); } // raw files for the latest run id from the runs page await page.goto(BASE + '/admin/runs', { waitUntil: 'networkidle' }); const runId = await page.locator('a[href*="run_id="]').first().textContent(); await page.goto(BASE + `/admin/raw?run_id=${runId?.trim()}`, { waitUntil: 'networkidle' }); const files = await page.locator('table').count(); console.log(width, 'raw run', runId?.trim(), files ? `${files} tables OK` : 'no table'); // clear cache action await page.goto(BASE + '/admin', { waitUntil: 'networkidle' }); await page.click('button:has-text("Clear cache")'); await page.waitForURL(/[?&](ok|err)=/, { timeout: 60_000 }); console.log(width, 'clear cache:', decodeURIComponent(page.url().split('?')[1] ?? '')); // refresh now (expected 409 → err=: no scheduler pid on this dev machine) await page.goto(BASE + '/admin', { waitUntil: 'networkidle' }); await page.click('button:has-text("Refresh now")'); await page.waitForURL(/[?&](ok|err)=/, { timeout: 60_000 }); console.log(width, 'refresh now:', decodeURIComponent(page.url().split('?')[1] ?? '').slice(0, 120)); // logout await page.click('button:has-text("Sign out")'); await page.waitForURL(/\/admin\/login/, { timeout: 30_000 }); console.log(width, 'logout OK', errors.length ? `console errors: ${errors.slice(0, 3).join(' | ')}` : 'no console errors'); await ctx.close(); } await browser.close();