// Drives the running CRM (npm run dev) and screenshots each manifest screen in dark mode. // Prereq: in the CRM repo root run `npm run dev` (serves http://localhost:5173). import { chromium } from "playwright"; import { readFile, mkdir } from "node:fs/promises"; import { fileURLToPath } from "node:url"; import { dirname, join } from "node:path"; const __dirname = dirname(fileURLToPath(import.meta.url)); const ROOT = join(__dirname, ".."); // tab label + identifier per role (password is always "password") const ROLE_LOGIN = { owner: { tab: "Owner", id: "justin" }, agentCody: { tab: "Employee", id: "LUP-1040" }, subCarlos: { tab: "Sub-Con", id: "carlos" }, }; async function login(page, baseUrl, role) { const creds = ROLE_LOGIN[role]; if (!creds) throw new Error(`unknown role ${role}`); await page.goto(`${baseUrl}/login`, { waitUntil: "networkidle" }); // The role tab appears before the demo chip of the same name in the DOM, so .first() targets the tab. await page.getByRole("button", { name: creds.tab, exact: true }).first().click(); await page.fill("#identifier", creds.id); await page.fill("#password", "password"); await page.getByRole("button", { name: "Sign In" }).click(); // Auth lives only in React state (no localStorage), so we must leave the // /login route via client-side routing. Wait until we have left it. await page.waitForFunction(() => !location.pathname.startsWith("/login")); await page.waitForLoadState("networkidle"); } // Auth state is in-memory only, so a full page reload (page.goto) would drop the // session and bounce us to /login. Navigate inside the SPA via the History API // and a popstate event, which React Router (BrowserRouter) listens to. async function spaNavigate(page, route) { await page.evaluate((r) => { window.history.pushState({}, "", r); window.dispatchEvent(new PopStateEvent("popstate")); }, route); await page.waitForFunction((r) => location.pathname === r, route); } async function main() { const manifest = JSON.parse(await readFile(join(ROOT, "src/captures.manifest.json"), "utf8")); const outDir = join(ROOT, "public/captures"); await mkdir(outDir, { recursive: true }); const browser = await chromium.launch(); const context = await browser.newContext({ viewport: { width: manifest.viewport.width, height: manifest.viewport.height }, deviceScaleFactor: manifest.viewport.deviceScaleFactor, }); // Force dark theme before any app code runs. await context.addInitScript((theme) => { try { localStorage.setItem("theme", theme); } catch (e) {} }, manifest.theme); const page = await context.newPage(); // Group by role so we log in once per role, in manifest order. let currentRole = null; for (const cap of manifest.captures) { if (cap.role !== currentRole) { await login(page, manifest.baseUrl, cap.role); currentRole = cap.role; } await spaNavigate(page, cap.route); await page.waitForTimeout(cap.waitMs ?? 1500); const path = join(outDir, `${cap.id}.png`); await page.screenshot({ path }); // viewport-only → exact 16:9 console.log(`captured ${cap.id} -> ${path}`); } await browser.close(); console.log(`\nDone. ${manifest.captures.length} captures written to public/captures/`); } main().catch((err) => { console.error(err); process.exit(1); });