// Uses an already authenticated agent-browser session. Run again after signing in // to screenshot every dashboard route. Evidence defaults to a temporary directory. import { execFileSync } from "node:child_process"; import { mkdirSync, readFileSync, writeFileSync, existsSync } from "node:fs"; import { join } from "node:path"; const session = process.env.MEGA_BROWSER_SESSION || "mega-login"; const output = process.env.MEGA_AUDIT_DIR || "/tmp/mega-auth-mobile"; mkdirSync(output, { recursive: true }); const browser = (...args) => execFileSync("agent-browser", ["--session", session, ...args], { encoding: "utf8", timeout: 45000 }); const routes = process.argv.slice(2); if (!routes.length) throw new Error("Pass one or more route paths to audit."); const evaluate = `(() => { const shown = (el) => { const r=el.getBoundingClientRect(),s=getComputedStyle(el);return r.width>0&&r.height>0&&r.right>0&&r.left0&&s.visibility!=='hidden'&&s.display!=='none'; }; const taps=[...document.querySelectorAll('button,a,input,select,textarea,summary,[role="button"]')].filter(shown); const smallTaps=taps.filter(el=>{const r=el.getBoundingClientRect();return r.width<43.99||r.height<43.99}).map(el=>({tag:el.tagName,label:(el.getAttribute('aria-label')||el.textContent||el.getAttribute('placeholder')||el.id).trim().slice(0,70),width:el.getBoundingClientRect().width,height:el.getBoundingClientRect().height})); const walker=document.createTreeWalker(document.body,4);const smallText=[];let node; while(node=walker.nextNode()){const el=node.parentElement;if(el&&!['SCRIPT','STYLE','OPTION'].includes(el.tagName)&&node.textContent.trim()&&shown(el)&&parseFloat(getComputedStyle(el).fontSize)<11.99)smallText.push({text:node.textContent.trim().slice(0,70),size:getComputedStyle(el).fontSize});} const wideTables=[...document.querySelectorAll('table')].filter(shown).filter(table=>table.scrollWidth>table.parentElement.clientWidth).map(table=>({contained:['auto','scroll'].includes(getComputedStyle(table.parentElement).overflowX),stickyFirstColumn:getComputedStyle(table.querySelector('tbody tr td')).position==='sticky',touchScroll:/-webkit-overflow-scrolling:\\s*touch/.test(table.parentElement.getAttribute('style')||''),scrollAffordance:!!table.parentElement.parentElement.querySelector('[aria-hidden="true"]')})); return {route:location.pathname,width:innerWidth,height:innerHeight,overflow:document.documentElement.scrollWidth>document.documentElement.clientWidth,tapCount:taps.length,smallTaps,smallText,wideTables}; })()`; const file = join(output, "results.json"); const results = existsSync(file) ? JSON.parse(readFileSync(file, "utf8")) : []; for (const [width, height] of [[375, 812], [768, 1024]]) { browser("set", "viewport", String(width), String(height)); for (const route of routes) { browser("open", `http://localhost:6007${route}`); browser("wait", "--load", "networkidle"); if (route === "/clock") browser("wait", "--fn", "!!document.querySelector('.plant-clock-page') && !document.querySelector('.plant-clock-page').textContent.includes('--:--')"); if (route === "/plant/map") browser("wait", "--fn", "!!document.querySelector('.leaflet-container')"); const raw = execFileSync("agent-browser", ["--session", session, "eval", "--stdin"], { input: evaluate, encoding: "utf8", timeout: 45000 }); const result = JSON.parse(raw); const filename = `${route === "/" ? "dashboard" : route.slice(1).replaceAll("/", "-")}-${width}.png`; browser("screenshot", "--full", join(output, filename)); const index = results.findIndex((entry) => entry.route === result.route && entry.width === width); if (index >= 0) results[index] = result; else results.push(result); writeFileSync(file, JSON.stringify(results, null, 2)); console.log(JSON.stringify({ requestedRoute: route, ...result })); } }