mega-plant/scripts/audit-auth-mobile.mjs

41 lines
3.8 KiB
JavaScript
Raw Normal View History

// 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.left<innerWidth&&r.bottom>0&&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 }));
}
}