mega-plant/scripts/audit-auth-mobile.mjs
Dave 2594dead84 feat: add user access and improve plant dashboard
Add PocketBase login, first-sign-in password changes, account settings,
and user activation, reactivation and deletion with self-removal guards.
Protect application pages and APIs and keep private pages out of PWA
caches.

Resolve plant locations from site assignments and improve map controls,
profit chart views, navigation and mobile table layouts.

Validation: production build and TypeScript passed; 28 unit tests passed.
Backend access integration tests and mobile verification are documented.
2026-10-06 04:02:08 +02:00

39 lines
3.6 KiB
JavaScript

// 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 === "/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 }));
}
}