Spaces:
Paused
Paused
| // Headless smoke-check for a model-built web app, used by smolbuilder so the | |
| // agent can actually *test* what it builds (the web equivalent of run_python). | |
| // | |
| // Loads index.html in jsdom, runs its scripts, then clicks every <button>, and | |
| // reports any JavaScript errors. The goal is high precision: a correct app | |
| // reports zero errors; a broken one (null element refs, undefined functions, | |
| // syntax errors, exceptions on click) reports them so the agent can fix it. | |
| // | |
| // We stub the browser APIs jsdom doesn't implement (canvas 2d/webgl context, | |
| // alert/confirm/prompt, matchMedia, media play) so apps that *use* them aren't | |
| // falsely flagged — we're checking the app's own logic, not jsdom's coverage. | |
| // | |
| // Output: a single JSON line {ok, errors, buttons, clicked}. Exit 0 always | |
| // (the verdict is in the JSON); exit 3 only if jsdom itself is missing. | |
| ; | |
| let JSDOM, VirtualConsole; | |
| try { | |
| ({ JSDOM, VirtualConsole } = require('jsdom')); | |
| } catch (e) { | |
| process.stdout.write(JSON.stringify({ ok: null, infra: 'jsdom not installed' }) + '\n'); | |
| process.exit(3); | |
| } | |
| const fs = require('fs'); | |
| function makeCtx() { | |
| // A permissive 2d/webgl context stub: method calls no-op, the few methods | |
| // whose *return value* is used hand back something safe to deref. | |
| return new Proxy({}, { | |
| get(_t, p) { | |
| if (p === 'measureText') return () => ({ width: 0 }); | |
| if (p === 'getImageData') return () => ({ data: new Uint8ClampedArray(4), width: 1, height: 1 }); | |
| if (p === 'createLinearGradient' || p === 'createRadialGradient' || p === 'createPattern') | |
| return () => ({ addColorStop() {} }); | |
| if (p === 'canvas') return { width: 300, height: 150 }; | |
| return () => undefined; | |
| }, | |
| set() { return true; }, | |
| }); | |
| } | |
| function stubBrowser(window) { | |
| try { window.HTMLCanvasElement.prototype.getContext = () => makeCtx(); } catch (e) {} | |
| const noop = () => {}; | |
| window.alert = noop; | |
| window.confirm = () => true; | |
| window.prompt = () => ''; | |
| window.scrollTo = noop; | |
| window.scroll = noop; | |
| if (!window.matchMedia) | |
| window.matchMedia = () => ({ matches: false, media: '', addListener: noop, removeListener: noop, addEventListener: noop, removeEventListener: noop }); | |
| try { window.HTMLMediaElement.prototype.play = () => Promise.resolve(); } catch (e) {} | |
| try { window.HTMLMediaElement.prototype.pause = noop; } catch (e) {} | |
| } | |
| const file = process.argv[2]; | |
| const html = fs.readFileSync(file, 'utf8'); | |
| const errors = []; | |
| const push = (m) => { if (m && errors.indexOf(m) === -1) errors.push(String(m).slice(0, 400)); }; | |
| const vc = new VirtualConsole(); | |
| vc.on('jsdomError', (e) => push('script error: ' + (e && e.detail ? (e.detail.message || e.detail) : (e && e.message)))); | |
| let dom; | |
| try { | |
| dom = new JSDOM(html, { | |
| runScripts: 'dangerously', | |
| pretendToBeVisual: true, | |
| virtualConsole: vc, | |
| beforeParse(window) { | |
| stubBrowser(window); | |
| window.addEventListener('error', (ev) => push('uncaught: ' + (ev.error ? (ev.error.message || ev.error) : ev.message))); | |
| window.addEventListener('unhandledrejection', (ev) => push('promise rejection: ' + (ev.reason && ev.reason.message ? ev.reason.message : ev.reason))); | |
| }, | |
| }); | |
| } catch (e) { | |
| push('load failed: ' + e.message); | |
| process.stdout.write(JSON.stringify({ ok: false, errors, buttons: 0, clicked: 0 }) + '\n'); | |
| process.exit(0); | |
| } | |
| const { window } = dom; | |
| const doc = window.document; | |
| function clickAll() { | |
| const buttons = Array.from(doc.querySelectorAll('button, [onclick], input[type=button], input[type=submit]')); | |
| let clicked = 0; | |
| for (const el of buttons) { | |
| try { | |
| if (el.disabled) el.disabled = false; // exercise the handler regardless of initial state | |
| el.click(); | |
| clicked++; | |
| } catch (e) { | |
| push('click "' + (el.textContent || el.id || el.tagName).trim().slice(0, 30) + '": ' + e.message); | |
| } | |
| } | |
| return { n: buttons.length, clicked }; | |
| } | |
| // Let inline scripts settle, click, then let one timer tick surface late errors. | |
| setTimeout(() => { | |
| const { n, clicked } = clickAll(); | |
| setTimeout(() => { | |
| process.stdout.write(JSON.stringify({ ok: errors.length === 0, errors, buttons: n, clicked }) + '\n'); | |
| process.exit(0); | |
| }, 250); | |
| }, 50); | |