83 lines
3.3 KiB
JavaScript
83 lines
3.3 KiB
JavaScript
// 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);
|
|
});
|