Spaces:
Paused
Paused
File size: 5,682 Bytes
daea45b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | """Subprocess runner: check a model-built web app in headless Chromium.
A Playwright/Chromium sibling of engine/browser_runner.py (Firefox/Selenium),
with the IDENTICAL JSON contract so engine/browsercheck.py can try whichever
real browser is installed. Invoked as `python engine/playwright_runner.py
<app.html>` — never imported (keeps Playwright out of the Gradio process and
isolates a browser crash).
It loads the app in the EXACT same `srcdoc` + `sandbox` wrapper as the live
preview (engine/preview.py), injects an error collector before the app's own
scripts, clicks every button, exercises the keyboard, and reports uncaught JS
errors — the hard failure signal that lets the router escalate a broken build.
Output: one JSON line {ok, errors, buttons, clicked}. Exit 3 only when Chromium
itself can't run (Playwright missing or the browser binary not downloaded), so
the caller falls back to Firefox, then jsdom.
"""
import json
import os
import re
import sys
import tempfile
PREVIEW_SANDBOX = "allow-scripts allow-same-origin allow-modals allow-popups allow-forms"
# Same collector browser_runner.py injects: catches errors thrown during load
# (the "script ran before its element / undefined function" class).
_CAPTURE = ("<script>(function(){window.__errs=[];"
"window.addEventListener('error',function(e){try{__errs.push('uncaught: '+"
"((e.error&&e.error.message)||e.message||String(e)))}catch(_){}} ,true);"
"window.addEventListener('unhandledrejection',function(e){try{__errs.push("
"'rejection: '+((e.reason&&e.reason.message)||e.reason))}catch(_){}});})();</script>")
_CLICK_SELECTOR = "button, [onclick], input[type=button], input[type=submit]"
_KEYBOARD_JS = (
"['ArrowUp','ArrowDown','ArrowLeft','ArrowRight',' '].forEach(function(k){"
"var c={key:k,keyCode:k===' '?32:({ArrowUp:38,ArrowDown:40,ArrowLeft:37,ArrowRight:39}[k]),bubbles:true};"
"document.dispatchEvent(new KeyboardEvent('keydown',c));"
"window.dispatchEvent(new KeyboardEvent('keydown',c));});")
def _escape_srcdoc(doc: str) -> str:
return doc.replace("&", "&").replace('"', """)
def _inject_capture(app_html: str) -> str:
m = re.search(r"<head[^>]*>", app_html, re.I)
if m:
return app_html[:m.end()] + _CAPTURE + app_html[m.end():]
m = re.search(r"<html[^>]*>", app_html, re.I)
if m:
return app_html[:m.end()] + _CAPTURE + app_html[m.end():]
return _CAPTURE + app_html
def _emit(obj: dict) -> None:
sys.stdout.write(json.dumps(obj) + "\n")
def main(path: str) -> int:
try:
from playwright.sync_api import sync_playwright
except Exception as e: # noqa: BLE001
_emit({"ok": None, "infra": f"playwright import failed: {e}"})
return 3
with open(path, encoding="utf-8") as f:
app_html = f.read()
host = ('<!doctype html><meta charset="utf-8"><body style="margin:0">'
f'<iframe id="app" style="width:100%;height:600px;border:0" '
f'sandbox="{PREVIEW_SANDBOX}" '
f'srcdoc="{_escape_srcdoc(_inject_capture(app_html))}"></iframe>')
host_path = os.path.join(tempfile.mkdtemp(prefix="pwhost-"), "host.html")
with open(host_path, "w", encoding="utf-8") as f:
f.write(host)
errors: list[str] = []
buttons = clicked = 0
try:
with sync_playwright() as p:
try:
browser = p.chromium.launch(
headless=True,
args=["--allow-file-access-from-files", "--no-sandbox"])
except Exception as e: # noqa: BLE001
_emit({"ok": None, "infra": f"chromium launch failed: {str(e)[:200]}"})
return 3
try:
page = browser.new_page()
page.set_default_timeout(4000)
page.goto("file://" + host_path, timeout=20000)
handle = page.wait_for_selector("#app", timeout=5000)
frame = handle.content_frame()
if frame is None:
_emit({"ok": None, "infra": "could not enter app iframe"})
return 3
page.wait_for_timeout(300) # let scripts settle
els = frame.query_selector_all(_CLICK_SELECTOR)
buttons = len(els)
for el in els[:25]:
try:
el.evaluate("e => { e.disabled = false; }")
el.click(force=True, timeout=1000)
clicked += 1
except Exception:
pass # handler errors land in __errs
try:
frame.evaluate(_KEYBOARD_JS)
except Exception:
pass
page.wait_for_timeout(300) # surface late/timer errors
try:
errors = frame.evaluate("() => window.__errs || []") or []
except Exception:
errors = []
finally:
try:
browser.close()
except Exception:
pass
except Exception as e: # noqa: BLE001
_emit({"ok": None, "infra": f"playwright run failed: {str(e)[:200]}"})
return 3
errors = [str(e)[:400] for e in errors][:20]
_emit({"ok": len(errors) == 0, "errors": errors, "buttons": buttons, "clicked": clicked})
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1]))
|