// Run only chromium (firefox+webkit already captured). Merges into the same // outputs JSON written by cross_browser_test.js. const fs = require("fs"); const path = require("path"); process.env.SKIP_TRIGGER = process.env.SKIP_TRIGGER || "0"; const OUT_JSON = path.resolve(__dirname, "..", "..", "outputs", "cross_browser_iter_1.json"); const existing = JSON.parse(fs.readFileSync(OUT_JSON, "utf8")); // Re-invoke the same logic from cross_browser_test.js but force chromium only // by monkey-patching ENGINES. Simpler: copy the relevant chunks here. const { chromium } = require("playwright"); const BASE_URL = process.env.BASE_URL || "http://localhost:3001"; const EVENT_ID = process.env.EVENT_ID || "114"; const OUT_DIR = path.resolve(__dirname, "..", "..", "outputs"); const SHOT_DIR = path.join(OUT_DIR, "screenshots"); fs.mkdirSync(SHOT_DIR, { recursive: true }); const ROUTES = [ { name: "home", path: "/" }, { name: "events", path: "/events" }, { name: `events-${EVENT_ID}`, path: `/events/${EVENT_ID}` }, { name: "leaderboard", path: "/leaderboard" }, { name: "about", path: "/about" }, ]; const VIEWPORTS = [ { name: "mobile", width: 375, height: 812 }, { name: "tablet", width: 768, height: 1024 }, { name: "desktop", width: 1280, height: 800 }, ]; async function readPaintTimings(page) { return page.evaluate(() => { const result = { FCP: null, LCP: null }; try { const paints = performance.getEntriesByType("paint"); const fcp = paints.find((p) => p.name === "first-contentful-paint"); if (fcp) result.FCP = Math.round(fcp.startTime); } catch (_) {} return new Promise((resolve) => { let lcpValue = result.LCP; try { const po = new PerformanceObserver((list) => { for (const entry of list.getEntries()) lcpValue = Math.round(entry.startTime); }); po.observe({ type: "largest-contentful-paint", buffered: true }); setTimeout(() => { try { po.disconnect(); } catch (_) {} resolve({ FCP: result.FCP, LCP: lcpValue }); }, 600); } catch (_) { resolve(result); } }); }); } async function checkCssApplied(page) { return page.evaluate(() => { const bg = getComputedStyle(document.body).backgroundColor; const m = bg.match(/(\d+),\s*(\d+),\s*(\d+)/); const sum = m ? Number(m[1]) + Number(m[2]) + Number(m[3]) : 0; return { bg, looksStyled: sum > 0 && sum < 600 }; }); } async function testRoute(browser, route, viewport) { const ctx = await browser.newContext({ viewport: { width: viewport.width, height: viewport.height }, colorScheme: "dark" }); const page = await ctx.newPage(); const consoleErrors = []; const networkFailures = []; page.on("console", (m) => m.type() === "error" && consoleErrors.push(m.text())); page.on("response", (r) => r.status() >= 400 && networkFailures.push({ url: r.url(), status: r.status() })); page.on("pageerror", (e) => consoleErrors.push(`pageerror: ${e.message}`)); const url = `${BASE_URL}${route.path}`; const startedAt = Date.now(); let pageError = null; try { await page.goto(url, { waitUntil: "domcontentloaded", timeout: 30000 }); await page.waitForLoadState("networkidle", { timeout: 15000 }).catch(() => {}); } catch (err) { pageError = err.message; } const loadMs = Date.now() - startedAt; let cssCheck = null, timings = { FCP: null, LCP: null }, title = null; if (!pageError) { cssCheck = await checkCssApplied(page); timings = await readPaintTimings(page); title = await page.title(); } const shotFile = path.join(SHOT_DIR, `xbrowser_chromium_${route.name}_${viewport.name}.png`); try { await page.screenshot({ path: shotFile, fullPage: false }); } catch (_) {} await ctx.close(); return { browser: "chromium", route: route.name, path: route.path, viewport: viewport.name, loadMs, pageError, consoleErrorCount: consoleErrors.length, consoleErrors: consoleErrors.slice(0, 5), networkFailureCount: networkFailures.length, networkFailures: networkFailures.slice(0, 5), cssApplied: cssCheck?.looksStyled ?? null, bodyBg: cssCheck?.bg ?? null, title, FCP: timings.FCP, LCP: timings.LCP, screenshot: path.relative(OUT_DIR, shotFile), }; } async function testTriggerFlow(browser) { const ctx = await browser.newContext({ viewport: { width: 1280, height: 800 }, colorScheme: "dark" }); const page = await ctx.newPage(); const consoleErrors = []; page.on("console", (m) => m.type() === "error" && consoleErrors.push(m.text())); page.on("pageerror", (e) => consoleErrors.push(`pageerror: ${e.message}`)); const out = { browser: "chromium", triggered: false, urlChanged: false, spinnerSeen: false, finalUrl: null, error: null, consoleErrors: [] }; try { await page.goto(`${BASE_URL}/`, { waitUntil: "domcontentloaded", timeout: 30000 }); await page.waitForLoadState("networkidle", { timeout: 10000 }).catch(() => {}); const btn = page.getByRole("button", { name: /trigger.*live demo/i }); await btn.waitFor({ state: "visible", timeout: 10000 }); const startUrl = page.url(); await btn.click(); out.triggered = true; try { await page.waitForSelector(".animate-spin", { timeout: 1500 }); out.spinnerSeen = true; } catch (_) {} const deadline = Date.now() + 90000; while (Date.now() < deadline) { const cur = page.url(); if (cur !== startUrl && /\/events\//.test(cur)) { out.urlChanged = true; out.finalUrl = cur; break; } await page.waitForTimeout(1000); } const shotFile = path.join(SHOT_DIR, `xbrowser_chromium_trigger_final.png`); await page.screenshot({ path: shotFile, fullPage: false }); out.screenshot = path.relative(OUT_DIR, shotFile); } catch (err) { out.error = err.message; } finally { out.consoleErrors = consoleErrors.slice(0, 5); await ctx.close(); } return out; } (async () => { const browser = await chromium.launch({ headless: true }); const engineResult = { available: true, routeViewport: [], triggerFlow: null }; for (const route of ROUTES) { const r = await testRoute(browser, route, VIEWPORTS[2]); console.log(` [desktop] ${route.name.padEnd(16)} load=${r.loadMs}ms FCP=${r.FCP} LCP=${r.LCP} err=${r.consoleErrorCount}`); engineResult.routeViewport.push(r); } for (const vp of [VIEWPORTS[0], VIEWPORTS[1]]) { const r = await testRoute(browser, ROUTES[0], vp); console.log(` [${vp.name}] home load=${r.loadMs}ms FCP=${r.FCP}`); engineResult.routeViewport.push(r); } if (process.env.SKIP_TRIGGER !== "1") { console.log(` [trigger] starting demo flow on chromium…`); engineResult.triggerFlow = await testTriggerFlow(browser); console.log(` [trigger] triggered=${engineResult.triggerFlow.triggered} urlChanged=${engineResult.triggerFlow.urlChanged} spinnerSeen=${engineResult.triggerFlow.spinnerSeen}`); } await browser.close(); existing.engines.chromium = engineResult; existing.finishedAt = new Date().toISOString(); fs.writeFileSync(OUT_JSON, JSON.stringify(existing, null, 2)); console.log(`Wrote ${OUT_JSON}`); })();