import { test, expect, type Page } from "@playwright/test"; import fs from "node:fs"; import { restoreSession } from "./helpers"; /** * Visual pass: every main screen in light + dark, desktop + mobile widths. * Asserts: no console errors, no horizontal overflow. Screenshots → qa/out/. * Requires a saved session from full-flow.spec.ts (run it first). */ const PAGES = ["/app/chat", "/app/arena", "/app/models", "/app/prompts", "/app/presets", "/app/usage", "/app/settings/account", "/app/settings/security", "/app/settings/providers", "/app/settings/appearance", "/app/settings/data", "/"]; const VIEWPORTS = { desktop: { width: 1360, height: 860 }, mobile: { width: 390, height: 844 } }; async function setTheme(page: Page, theme: "light" | "dark") { await page.addInitScript((t) => { window.localStorage.setItem("theme", t); }, theme); } for (const theme of ["dark", "light"] as const) { for (const [vp, size] of Object.entries(VIEWPORTS)) { test(`screens ${theme} ${vp}`, async ({ page }) => { test.setTimeout(240_000); const errors: string[] = []; page.on("console", (m) => { if (m.type() === "error" && !/favicon|Download the React DevTools/i.test(m.text())) errors.push(`${m.text()}`); }); page.on("pageerror", (e) => errors.push(e.message)); await page.setViewportSize(size); await setTheme(page, theme); await restoreSession(page); fs.mkdirSync("qa/out", { recursive: true }); for (const path of PAGES) { await page.goto(path, { waitUntil: "networkidle" }).catch(() => page.goto(path)); await page.waitForTimeout(400); const overflow = await page.evaluate(() => document.documentElement.scrollWidth - document.documentElement.clientWidth); expect.soft(overflow, `${path} (${theme}/${vp}) horizontal overflow ${overflow}px`).toBeLessThanOrEqual(1); await page.screenshot({ path: `qa/out/${theme}-${vp}-${path.replace(/\//g, "_") || "_home"}.png`, fullPage: vp === "desktop" }); } expect(errors, "console errors").toEqual([]); }); } }