Spaces:
Runtime error
Runtime error
| #!/usr/bin/env python3 | |
| """Drive the Race Control dashboard in a real browser and capture screenshots. | |
| Walks the acceptance path from the project brief: load, run, watch the venue | |
| fill, see the bottleneck predicted, simulate strategies, apply the winner, | |
| watch the crowd redistribute, then switch to the Barcelona reconstruction. | |
| Any console error or failed request is reported as a failure. | |
| Run: python scripts/ui_check.py [--base http://127.0.0.1:8000] [--out shots] | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import sys | |
| import time | |
| from pathlib import Path | |
| from playwright.sync_api import sync_playwright | |
| IGNORED_CONSOLE = ("favicon", "Download the React DevTools") | |
| def main() -> int: | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--base", default="http://127.0.0.1:8000") | |
| ap.add_argument("--out", default=str(Path(__file__).resolve().parent.parent / "shots")) | |
| ap.add_argument("--keep-open", action="store_true") | |
| args = ap.parse_args() | |
| out = Path(args.out) | |
| out.mkdir(parents=True, exist_ok=True) | |
| errors: list[str] = [] | |
| shots: list[str] = [] | |
| def shot(page, name: str) -> None: | |
| path = out / f"{name}.png" | |
| page.screenshot(path=str(path)) | |
| shots.append(str(path)) | |
| print(f" · captured {name}") | |
| with sync_playwright() as pw: | |
| browser = pw.chromium.launch( | |
| executable_path="/opt/pw-browsers/chromium-1194/chrome-linux/chrome", | |
| args=["--no-sandbox", "--disable-dev-shm-usage"], | |
| ) | |
| page = browser.new_page(viewport={"width": 1680, "height": 1000}, | |
| device_scale_factor=2) | |
| page.on("console", lambda m: errors.append(f"console.{m.type}: {m.text}") | |
| if m.type == "error" and not any(s in m.text for s in IGNORED_CONSOLE) else None) | |
| page.on("pageerror", lambda e: errors.append(f"pageerror: {e}")) | |
| page.on("requestfailed", lambda r: errors.append( | |
| f"requestfailed: {r.url} ({r.failure})") if "favicon" not in r.url else None) | |
| print("1. loading dashboard") | |
| page.goto(args.base, wait_until="networkidle") | |
| page.wait_for_selector("#scenario-switch button", timeout=15000) | |
| shot(page, "01-loaded") | |
| print("2. starting Simulation 1") | |
| page.click("#btn-run") | |
| page.wait_for_function("() => document.querySelector('#conn-label').textContent === 'live'", | |
| timeout=20000) | |
| page.click("[data-speed='40']") | |
| page.wait_for_timeout(2500) | |
| shot(page, "02-crowd-moving") | |
| print("3. waiting for the bottleneck to be predicted") | |
| deadline = time.time() + 120 | |
| got_alert = False | |
| while time.time() < deadline: | |
| sev = page.evaluate( | |
| "() => { const a = document.querySelector('.alert'); " | |
| "return a ? a.className : ''; }") | |
| t = page.evaluate("() => document.querySelector('#clock').textContent") | |
| if "critical" in sev: | |
| got_alert = True | |
| print(f" critical alert at {t}") | |
| break | |
| page.wait_for_timeout(1200) | |
| if not got_alert: | |
| errors.append("no critical alert appeared within 120 s of wall clock") | |
| shot(page, "03-bottleneck-alert") | |
| print("4. simulating strategies") | |
| page.click("#btn-simulate") | |
| page.wait_for_selector("#drawer:not([hidden])", timeout=90000) | |
| page.wait_for_selector(".strategy-table tbody tr", timeout=10000) | |
| rows = page.eval_on_selector_all(".strategy-table tbody tr", "els => els.length") | |
| print(f" {rows} strategies compared") | |
| if rows < 4: | |
| errors.append(f"expected at least 4 candidate strategies, got {rows}") | |
| if not page.query_selector("tr.recommended"): | |
| errors.append("no recommended strategy highlighted") | |
| shot(page, "04-strategy-simulator") | |
| why = page.inner_text("#why-panel") | |
| if "primary bottleneck" not in why.lower(): | |
| errors.append("explainability panel is missing its primary bottleneck") | |
| page.click("#btn-drawer-collapse") | |
| print("5. applying the recommendation") | |
| page.click("#btn-apply") | |
| page.wait_for_selector(".applied-banner", timeout=20000) | |
| shot(page, "05-intervention-applied") | |
| print("6. watching the crowd redistribute") | |
| page.wait_for_timeout(6000) | |
| shot(page, "06-after-intervention") | |
| print("7. switching to the Barcelona reconstruction") | |
| page.click("#scenario-switch button:nth-child(2)") | |
| page.wait_for_function("() => document.querySelector('#conn-label').textContent === 'live'", | |
| timeout=30000) | |
| page.wait_for_timeout(1500) | |
| if page.query_selector("#provenance-panel[hidden]"): | |
| errors.append("Barcelona provenance panel did not appear") | |
| prov = page.inner_text("#provenance-panel") | |
| if "counterfactual" not in prov.lower(): | |
| errors.append("Barcelona disclaimer does not mention the counterfactual framing") | |
| page.click("[data-speed='40']") | |
| page.wait_for_timeout(4000) | |
| shot(page, "07-barcelona") | |
| print("8. prediction model accuracy") | |
| page.click("#btn-pred-detail") | |
| page.wait_for_selector("#modal:not([hidden])", timeout=5000) | |
| shot(page, "08-model-accuracy") | |
| page.click("#modal-close") | |
| if args.keep_open: | |
| input("press enter to close the browser…") | |
| browser.close() | |
| print() | |
| if errors: | |
| print(f"FAILED — {len(errors)} problem(s):") | |
| for e in errors[:25]: | |
| print(f" ✗ {e}") | |
| return 1 | |
| print(f"PASSED — {len(shots)} screenshots in {out}") | |
| return 0 | |
| if __name__ == "__main__": | |
| sys.exit(main()) | |