/** * Screenshot the mobile nav open state for QA review. * Usage: node scripts/screenshot.mjs */ import puppeteer from "puppeteer-core"; import { readFileSync } from "fs"; const PB_URL = process.env.POCKETBASE_URL || "http://pocketbase-zinlbpsext2ro0lgb2558vfy.169.239.180.130.sslip.io"; const APP_URL = "http://localhost:4004"; const CHROME = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"; async function getToken() { const res = await fetch(`${PB_URL}/api/collections/users/auth-with-password`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ identity: "david@rjbulk.local", password: "ChangeMe123!" }), }); const data = await res.json(); return data.token; } async function shoot(url, name, viewport = { width: 390, height: 844 }, action) { const browser = await puppeteer.launch({ executablePath: CHROME, headless: "new", args: ["--no-sandbox", "--disable-gpu", "--disable-dev-shm-usage"], }); try { const page = await browser.newPage(); await page.setViewport(viewport); await page.setCookie({ name: "pb_auth", value: await getToken(), domain: "localhost", path: "/", }); await page.goto(`${APP_URL}${url}`, { waitUntil: "networkidle2", timeout: 30000 }); if (action) { await page.evaluate(action); await new Promise((r) => setTimeout(r, 500)); } await page.screenshot({ path: `/tmp/rjbulk-screens/${name}.png`, fullPage: false }); console.log(` ✓ ${name}.png`); } catch (e) { console.error(` ✗ ${name}.png — ${e.message}`); } finally { await browser.close(); } } async function main() { // Desktop dashboard for reference await shoot("/dashboard", "10-desktop-dashboard", { width: 1440, height: 900 }); // Mobile dashboard, closed menu await shoot("/dashboard", "11-mobile-dashboard"); // Mobile menu open await shoot("/dashboard", "12-mobile-menu-open", { width: 390, height: 844 }, async () => { const btn = document.querySelector('button[aria-label="Open navigation"]'); if (btn) btn.click(); }); // Mobile menu open, on a form page (where it was "behind forms") await shoot("/customers/new", "13-mobile-menu-on-form", { width: 390, height: 844 }, async () => { const btn = document.querySelector('button[aria-label="Open navigation"]'); if (btn) btn.click(); }); // Mobile menu open, on a page with a select (dropdown might be in front) await shoot("/delivery-notes/new", "14-mobile-menu-on-delivery", { width: 390, height: 844 }, async () => { const btn = document.querySelector('button[aria-label="Open navigation"]'); if (btn) btn.click(); }); // Mobile menu open, then click a nav item (close behaviour) await shoot("/dashboard", "15-mobile-menu-then-tap-dashboard", { width: 390, height: 844 }, async () => { const btn = document.querySelector('button[aria-label="Open navigation"]'); if (btn) btn.click(); await new Promise((r) => setTimeout(r, 300)); const link = document.querySelector('a[href="/dashboard"]'); // Actually click a DIFFERENT link to see if it navigates const customersLink = Array.from(document.querySelectorAll("a")).find((a) => a.getAttribute("href") === "/customers"); if (customersLink) customersLink.click(); }); // Mobile menu open on settings (which has the Email settings + SMTP password fields) await shoot("/settings", "16-mobile-menu-on-settings", { width: 390, height: 844 }, async () => { const btn = document.querySelector('button[aria-label="Open navigation"]'); if (btn) btn.click(); }); } main().catch((e) => { console.error(e); process.exit(1); });