clawdie-ai/scripts/browser-jail-validation/render.mjs
Operator & Codex 6c549e7ad0 Rename browser validation assets
---
Build: pass | Tests: pass — 2383 passed (175 files)
2026-05-11 17:32:22 +02:00

102 lines
3.5 KiB
JavaScript

// Render fixtures to PNG + extract target bounding boxes from the DOM.
// Node v22+, requires: npm install puppeteer-core
// Connects to a pre-launched headless Chromium (mirrors the FreeBSD jail path
// where puppeteer-core talks to a system-pkg Chromium over CDP).
//
// Usage:
// 1) launch: chromium --headless=new --remote-debugging-port=9222 \
// --user-data-dir=/tmp/validation-profile about:blank &
// 2) render: node render.mjs
//
// Produces:
// screenshots/<fixture>.png
// results/<fixture>.json { viewport, targets: [{label, bbox, center}] }
//
// Targets per fixture (DOM ids -> human labels):
import { connect } from 'puppeteer-core';
import { readFileSync, writeFileSync, mkdirSync } from 'fs';
import path from 'path';
import { pathToFileURL } from 'url';
const FIXTURES = [
{
name: '01-login',
viewport: { width: 1024, height: 768 },
targets: [
{ id: 'u', label: 'Email input field' },
{ id: 'p', label: 'Password input field' },
{ id: 'submit', label: 'Sign in button' },
{ id: 'cancel', label: 'Cancel button' },
{ id: 'forgot', label: 'Forgot password link' },
],
},
{
name: '02-dashboard',
viewport: { width: 1024, height: 768 },
targets: [
{ id: 't-deploy', label: 'Deploy tile' },
{ id: 't-stop', label: 'Stop service tile' },
{ id: 't-health', label: 'Health tile' },
{ id: 't-settings', label: 'Settings tile' },
{ id: 'b-refresh', label: 'Refresh button' },
{ id: 'b-export', label: 'Export CSV button' },
{ id: 'b-new', label: 'New project button' },
],
},
{
name: '03-modal',
viewport: { width: 1024, height: 768 },
targets: [
{ id: 'm-close', label: 'Modal close (X) button' },
{ id: 'm-cancel', label: 'Cancel button in modal' },
{ id: 'm-confirm', label: 'Delete tenant button' },
{ id: 'm-confirm-input', label: 'Tenant name confirmation input' },
{ id: 'bg-action-1', label: 'Pause tenant button (behind modal)' },
],
},
];
const ROOT = path.resolve(import.meta.dirname);
mkdirSync(path.join(ROOT, 'screenshots'), { recursive: true });
mkdirSync(path.join(ROOT, 'results'), { recursive: true });
const browser = await connect({ browserURL: 'http://127.0.0.1:9222' });
for (const f of FIXTURES) {
const page = await browser.newPage();
await page.setViewport(f.viewport);
const fileUrl = pathToFileURL(path.join(ROOT, 'fixtures', `${f.name}.html`)).href;
await page.goto(fileUrl, { waitUntil: 'networkidle0' });
const targets = await page.evaluate((wanted) => {
return wanted.map(({ id, label }) => {
const el = document.getElementById(id);
if (!el) return { id, label, missing: true };
const r = el.getBoundingClientRect();
const bbox = {
x: Math.round(r.left),
y: Math.round(r.top),
w: Math.round(r.width),
h: Math.round(r.height),
};
const center = {
x: Math.round(r.left + r.width / 2),
y: Math.round(r.top + r.height / 2),
};
return { id, label, bbox, center };
});
}, f.targets);
const pngPath = path.join(ROOT, 'screenshots', `${f.name}.png`);
await page.screenshot({ path: pngPath, fullPage: false });
const meta = { fixture: f.name, viewport: f.viewport, targets };
writeFileSync(path.join(ROOT, 'results', `${f.name}.json`), JSON.stringify(meta, null, 2));
console.log(`[${f.name}] ${pngPath} (${targets.length} targets)`);
await page.close();
}
await browser.disconnect();
console.log('done');