smolcode / engine /webcheck.py
seanpoyner's picture
Upload folder using huggingface_hub
daea45b verified
Raw
History Blame Contribute Delete
2.5 kB
"""Headless verification of model-built web apps (the web `run_python`).
smolbuilder's agent writes HTML/CSS/JS but, unlike the Python path, had no way
to *run* it — so it shipped broken apps and couldn't tell. This bridges to a
small Node + jsdom checker (engine/webcheck.js) that loads the page, runs its
scripts, clicks every button, and reports JavaScript errors.
Graceful degradation is deliberate: if Node or jsdom isn't available (e.g. a
minimal Space image), we return `None` ("unverifiable") rather than failing the
build — the agent/router fall back to the structural check.
"""
from __future__ import annotations
import json
import shutil
import subprocess
import tempfile
from pathlib import Path
_CHECKER = Path(__file__).with_name("webcheck.js")
def available() -> bool:
"""True if we can actually run the headless check (Node present)."""
return shutil.which("node") is not None and _CHECKER.exists()
def check_html(html: str, timeout: int = 20) -> tuple[bool | None, list[str]]:
"""Run the headless check on an HTML document.
Returns (ok, errors):
- (True, []) the app loaded and all buttons clicked without error
- (False, [...]) real JavaScript errors were found
- (None, [...]) unverifiable (Node/jsdom missing, or the checker broke)
"""
node = shutil.which("node")
if not node or not _CHECKER.exists():
return None, ["node/jsdom unavailable (skipped runtime check)"]
with tempfile.NamedTemporaryFile("w", suffix=".html", delete=False) as f:
f.write(html)
path = f.name
try:
proc = subprocess.run(
[node, str(_CHECKER), path],
capture_output=True, text=True, timeout=timeout,
)
except subprocess.TimeoutExpired:
return None, [f"runtime check timed out after {timeout}s"]
finally:
Path(path).unlink(missing_ok=True)
if proc.returncode == 3: # jsdom not installed
return None, ["jsdom not installed (skipped runtime check)"]
line = (proc.stdout or "").strip().splitlines()
if not line:
return None, [f"runtime check produced no output: {proc.stderr.strip()[:200]}"]
try:
data = json.loads(line[-1])
except json.JSONDecodeError:
return None, [f"runtime check output unparseable: {line[-1][:200]}"]
if data.get("ok") is None:
return None, [data.get("infra", "unverifiable")]
return bool(data.get("ok")), list(data.get("errors", []))