// Wave 0 boot-integrity smoke (scratch). Visits every authed route with an injected // airshelf_token, captures console/page errors + a screenshot per route, then runs a // login-latency probe (proves the fake-delay removal + username-not-email field). import { chromium } from "playwright"; import fs from "node:fs"; import path from "node:path"; const BASE = process.env.BASE || "http://localhost:5173"; const TOKEN = process.env.TOKEN || ""; const OUT = path.resolve(process.env.OUT || "../../../_qa_shots/wave0"); fs.mkdirSync(OUT, { recursive: true }); const ROUTES = [ "/", "/dashboard", "/products", "/projects", "/messages", "/account", "/team", "/settings", "/asset-factory", "/model-photo", "/image-optimize", "/platform-cover", "/library", "/pipeline" ]; const NOISE = /favicon|net::ERR_|Failed to load resource.*(png|ico|svg|jpg|woff)|ResizeObserver/i; const browser = await chromium.launch({ headless: true }); const ctx = await browser.newContext({ viewport: { width: 1440, height: 900 }, colorScheme: "light" }); const seed = await ctx.newPage(); await seed.goto(BASE + "/", { waitUntil: "domcontentloaded" }); await seed.evaluate((t) => localStorage.setItem("airshelf_token", t), TOKEN); await seed.close(); const results = []; for (const route of ROUTES) { const page = await ctx.newPage(); const errs = []; page.on("console", (m) => { if (m.type() === "error") errs.push(m.text()); }); page.on("pageerror", (e) => errs.push("PAGEERROR: " + (e.message || e))); let ok = true; try { await page.goto(BASE + route, { waitUntil: "load", timeout: 20000 }); await page.waitForTimeout(1500); } catch (e) { ok = false; errs.push("NAV: " + e.message); } const name = route === "/" ? "root" : route.replace(/\//g, ""); await page.screenshot({ path: path.join(OUT, `${name}.png`), fullPage: false }).catch(() => {}); results.push({ route, ok, errors: errs.filter((e) => !NOISE.test(e)) }); await page.close(); } // login-latency probe (fresh context, no token) const lctx = await browser.newContext({ viewport: { width: 1440, height: 900 } }); const lp = await lctx.newPage(); let login = { error: null }; try { await lp.goto(BASE + "/login", { waitUntil: "load" }); await lp.waitForTimeout(500); const hasUser = await lp.locator("#auth-username").count(); const hasEmail = await lp.locator('input[type="email"]').count(); await lp.fill("#auth-username", "airshelf"); await lp.fill("#auth-pwd", "Restraint2026"); const t0 = Date.now(); await lp.click("button.btn-cta"); let ms = -1; try { await lp.waitForFunction(() => !location.pathname.replace(/\/+$/, "").endsWith("/login") && location.pathname !== "/login", { timeout: 15000 }); ms = Date.now() - t0; } catch { ms = -1; } await lp.waitForTimeout(800); await lp.screenshot({ path: path.join(OUT, "after-login.png") }).catch(() => {}); login = { hasUsernameField: !!hasUser, hasEmailField: !!hasEmail, clickToLeaveLoginMs: ms, landedPath: lp.url() }; } catch (e) { login = { error: e.message }; } await lctx.close(); await browser.close(); const summary = { base: BASE, routes: results, login }; fs.writeFileSync(path.join(OUT, "summary.json"), JSON.stringify(summary, null, 2)); console.log(JSON.stringify(summary, null, 2));