#!/usr/bin/env python3 """Comprehensive v2 browser API coverage (ported from agent-browser test themes).""" import os import sys import time import urllib.parse from pathlib import Path sys.path.insert(0, str(Path(__file__).parent)) from cmux import cmux, cmuxError SOCKET_PATH = os.environ.get("CMUX_SOCKET", "/tmp/cmux-debug.sock") def _must(cond: bool, msg: str) -> None: if not cond: raise cmuxError(msg) def _data_url(html: str) -> str: return "data:text/html;charset=utf-8," + urllib.parse.quote(html) def _wait_until(pred, timeout_s: float, label: str) -> None: deadline = time.time() + timeout_s last_exc = None while time.time() < deadline: try: if pred(): return except Exception as exc: # noqa: BLE001 last_exc = exc time.sleep(0.05) if last_exc is not None: raise cmuxError(f"Timed out waiting for {label}: {last_exc}") raise cmuxError(f"Timed out waiting for {label}") def _expect_error(label: str, fn, code_substr: str) -> None: try: fn() except cmuxError as exc: text = str(exc) if code_substr in text: return raise cmuxError(f"{label}: expected error containing {code_substr!r}, got: {text}") raise cmuxError(f"{label}: expected error containing {code_substr!r}, but call succeeded") def _expect_error_contains(label: str, fn, *needles: str) -> None: try: fn() except cmuxError as exc: text = str(exc) missing = [needle for needle in needles if needle not in text] if missing: raise cmuxError(f"{label}: missing expected substrings {missing!r} in error: {text}") return raise cmuxError(f"{label}: expected failure, but call succeeded") def _value(res: dict, key: str = "value"): return (res or {}).get(key) def _wait_with_fallback(c: cmux, surface_id: str, params: dict, pred, timeout_s: float, label: str) -> None: call_params = dict(params) call_params["surface_id"] = surface_id try: c._call("browser.wait", call_params) return except cmuxError as exc: if "timeout" not in str(exc): raise _wait_until(pred, timeout_s=timeout_s, label=f"{label} fallback") def _build_pages() -> tuple[str, str]: page1 = """ cmux-browser-comprehensive-1

Browser Comprehensive

ready
hover target
double target
not visible
styles
bottom-marker
""".strip() page2 = """ cmux-browser-comprehensive-2
page-two
""".strip() return _data_url(page1), _data_url(page2) def main() -> int: page1_url, page2_url = _build_pages() with cmux(SOCKET_PATH) as c: opened = c._call("browser.open_split", {"url": "about:blank"}) or {} sid = str(opened.get("surface_id") or "") sref = str(opened.get("surface_ref") or "") _must(bool(sid), f"browser.open_split returned no surface_id: {opened}") target = sid if sref: _ = c._call("browser.url.get", {"surface_id": sref}) probe_url = _data_url("") c._call("browser.navigate", {"surface_id": target, "url": probe_url}) _wait_with_fallback( c, target, {"selector": "#probe", "timeout_ms": 3000}, lambda: bool((c._call("browser.eval", {"surface_id": target, "script": "document.querySelector('#probe') !== null"}) or {}).get("value")), timeout_s=4.0, label="browser.wait selector #probe", ) c._call("browser.navigate", {"surface_id": target, "url": page1_url}) _wait_with_fallback( c, target, {"text_contains": "ready", "timeout_ms": 3000}, lambda: "ready" in str((c._call("browser.eval", {"surface_id": target, "script": "document.body ? (document.body.innerText || '') : ''"}) or {}).get("value") or ""), timeout_s=4.0, label="browser.wait text_contains ready", ) _wait_with_fallback( c, target, {"function": "document.querySelector('#hdr') !== null", "timeout_ms": 3000}, lambda: bool((c._call("browser.eval", {"surface_id": target, "script": "document.querySelector('#hdr') !== null"}) or {}).get("value")), timeout_s=4.0, label="browser.wait function hdr", ) _wait_with_fallback( c, target, {"load_state": "complete", "timeout_ms": 5000}, lambda: str((c._call("browser.eval", {"surface_id": target, "script": "document.readyState"}) or {}).get("value") or "").lower() == "complete", timeout_s=6.0, label="browser.wait load_state complete", ) _wait_with_fallback( c, target, {"url_contains": "data:text/html", "timeout_ms": 3000}, lambda: "data:text/html" in str((c._call("browser.url.get", {"surface_id": target}) or {}).get("url") or ""), timeout_s=4.0, label="browser.wait url_contains data:text/html", ) _wait_until( lambda: "cmux-browser-comprehensive-1" in str((c._call("browser.get.title", {"surface_id": target}) or {}).get("title") or ""), timeout_s=3.0, label="browser.get.title page1", ) url_payload = c._call("browser.url.get", {"surface_id": target}) or {} _must("data:text/html" in str(url_payload.get("url") or ""), f"Expected data URL from browser.url.get: {url_payload}") c._call("browser.fill", {"surface_id": target, "selector": "#name", "text": "cmux"}) c._call("browser.click", {"surface_id": target, "selector": "#btn"}) out_text = c._call("browser.get.text", {"surface_id": target, "selector": "#status"}) or {} _must(str(_value(out_text)) == "cmux", f"Expected status text to be cmux: {out_text}") cleared = c._call("browser.fill", {"surface_id": target, "selector": "#name", "text": "", "snapshot_after": True}) or {} _must(bool(cleared.get("post_action_snapshot")), f"Expected post_action_snapshot from fill(snapshot_after): {cleared}") cleared_value = c._call("browser.get.value", {"surface_id": target, "selector": "#name"}) or {} _must(str(_value(cleared_value)) == "", f"Expected fill with empty text to clear input: {cleared_value}") c._call("browser.fill", {"surface_id": target, "selector": "#name", "text": "cmux"}) c._call("browser.type", {"surface_id": target, "selector": "#name", "text": "-v2"}) name_val = c._call("browser.get.value", {"surface_id": target, "selector": "#name"}) or {} _must(str(_value(name_val)) == "cmux-v2", f"Expected typed suffix in input value: {name_val}") c._call("browser.focus", {"surface_id": target, "selector": "#keys"}) active = c._call( "browser.eval", {"surface_id": target, "script": "document.activeElement ? document.activeElement.id : ''"}, ) or {} _must(str(_value(active)) == "keys", f"Expected focus on #keys: {active}") c._call("browser.hover", {"surface_id": target, "selector": "#hover"}) c._call("browser.dblclick", {"surface_id": target, "selector": "#dbl"}) c._call("browser.press", {"surface_id": target, "key": "A"}) c._call("browser.keydown", {"surface_id": target, "key": "B"}) c._call("browser.keyup", {"surface_id": target, "key": "C"}) key_stats = c._call( "browser.eval", { "surface_id": target, "script": "({hover: window.__hover, dbl: window.__dbl, down: window.__keys.down, up: window.__keys.up, press: window.__keys.press})", }, ) or {} key_value = _value(key_stats) _must(isinstance(key_value, dict), f"Expected dict counters from eval: {key_stats}") _must(int(key_value.get("hover", 0)) >= 1, f"Expected hover counter >= 1: {key_stats}") _must(int(key_value.get("dbl", 0)) >= 1, f"Expected dbl counter >= 1: {key_stats}") _must(int(key_value.get("down", 0)) >= 2, f"Expected keydown counter >= 2: {key_stats}") _must(int(key_value.get("up", 0)) >= 2, f"Expected keyup counter >= 2: {key_stats}") _must(int(key_value.get("press", 0)) >= 1, f"Expected keypress counter >= 1: {key_stats}") c._call("browser.check", {"surface_id": target, "selector": "#chk"}) is_checked = c._call("browser.is.checked", {"surface_id": target, "selector": "#chk"}) or {} _must(bool(_value(is_checked)) is True, f"Expected checked=true: {is_checked}") c._call("browser.uncheck", {"surface_id": target, "selector": "#chk"}) is_unchecked = c._call("browser.is.checked", {"surface_id": target, "selector": "#chk"}) or {} _must(bool(_value(is_unchecked)) is False, f"Expected checked=false: {is_unchecked}") c._call("browser.select", {"surface_id": target, "selector": "#sel", "value": "b"}) sel_val = c._call("browser.get.value", {"surface_id": target, "selector": "#sel"}) or {} _must(str(_value(sel_val)) == "b", f"Expected selected value b: {sel_val}") html_val = c._call("browser.get.html", {"surface_id": target, "selector": "#status"}) or {} _must("id=\"status\"" in str(_value(html_val) or ""), f"Expected status HTML: {html_val}") attr_val = c._call("browser.get.attr", {"surface_id": target, "selector": "#status", "attr": "data-role"}) or {} _must(str(_value(attr_val)) == "status", f"Expected data-role=status: {attr_val}") cnt_val = c._call("browser.get.count", {"surface_id": target, "selector": "option"}) or {} _must(int((cnt_val or {}).get("count") or 0) == 2, f"Expected option count=2: {cnt_val}") box_val = c._call("browser.get.box", {"surface_id": target, "selector": "#status"}) or {} box = _value(box_val) _must(isinstance(box, dict), f"Expected box dict: {box_val}") _must(float(box.get("width") or 0.0) > 0.0, f"Expected positive box width: {box_val}") style_prop = c._call( "browser.get.styles", {"surface_id": target, "selector": "#style-target", "property": "color"}, ) or {} _must("rgb" in str(_value(style_prop) or ""), f"Expected rgb color in style property: {style_prop}") style_all = c._call("browser.get.styles", {"surface_id": target, "selector": "#style-target"}) or {} _must(isinstance(_value(style_all), dict), f"Expected style dictionary: {style_all}") _must("display" in (_value(style_all) or {}), f"Expected display in style dictionary: {style_all}") visible_status = c._call("browser.is.visible", {"surface_id": target, "selector": "#status"}) or {} visible_hidden = c._call("browser.is.visible", {"surface_id": target, "selector": "#hidden"}) or {} _must(bool(_value(visible_status)) is True, f"Expected #status visible: {visible_status}") _must(bool(_value(visible_hidden)) is False, f"Expected #hidden not visible: {visible_hidden}") enabled_btn = c._call("browser.is.enabled", {"surface_id": target, "selector": "#btn"}) or {} enabled_disabled = c._call("browser.is.enabled", {"surface_id": target, "selector": "#disabled"}) or {} _must(bool(_value(enabled_btn)) is True, f"Expected #btn enabled: {enabled_btn}") _must(bool(_value(enabled_disabled)) is False, f"Expected #disabled not enabled: {enabled_disabled}") c._call("browser.scroll", {"surface_id": target, "selector": "#scroller", "dx": 0, "dy": 160}) scrolled = c._call( "browser.eval", {"surface_id": target, "script": "document.querySelector('#scroller').scrollTop"}, ) or {} _must(float(_value(scrolled) or 0) >= 100, f"Expected scroller scrollTop >= 100: {scrolled}") c._call("browser.scroll", {"surface_id": target, "dy": 240}) c._call("browser.scroll_into_view", {"surface_id": target, "selector": "#bottom"}) in_view = c._call( "browser.eval", { "surface_id": target, "script": "(() => { const r = document.querySelector('#bottom').getBoundingClientRect(); return r.top < window.innerHeight; })()", }, ) or {} _must(bool(_value(in_view)) is True, f"Expected #bottom in viewport: {in_view}") shot = c._call("browser.screenshot", {"surface_id": target}) or {} _must(len(str((shot or {}).get("png_base64") or "")) > 100, f"Expected screenshot payload: {shot}") snap = c._call("browser.snapshot", {"surface_id": target}) or {} snapshot_text = str((snap or {}).get("snapshot") or "") _must("cmux-browser-comprehensive-1" in snapshot_text, f"Expected snapshot text for page1: {snap}") refs = (snap or {}).get("refs") or {} _must(isinstance(refs, dict), f"Expected snapshot refs dict: {snap}") _must(any(str(key).startswith("e") for key in refs.keys()), f"Expected eN refs from snapshot: {snap}") c._call("browser.navigate", {"surface_id": target, "url": page2_url}) _wait_with_fallback( c, target, {"text_contains": "page-two", "timeout_ms": 4000}, lambda: "page-two" in str((c._call("browser.eval", {"surface_id": target, "script": "document.body ? (document.body.innerText || '') : ''"}) or {}).get("value") or ""), timeout_s=5.0, label="browser.wait text_contains page-two", ) _wait_until( lambda: "cmux-browser-comprehensive-2" in str((c._call("browser.get.title", {"surface_id": target}) or {}).get("title") or ""), timeout_s=3.0, label="browser.get.title page2", ) c._call("browser.back", {"surface_id": target}) _wait_with_fallback( c, target, {"url_contains": "cmux-browser-comprehensive-1", "timeout_ms": 4000}, lambda: "cmux-browser-comprehensive-1" in str((c._call("browser.url.get", {"surface_id": target}) or {}).get("url") or ""), timeout_s=5.0, label="browser.wait url_contains page1 (history)", ) c._call("browser.forward", {"surface_id": target}) _wait_with_fallback( c, target, {"url_contains": "cmux-browser-comprehensive-2", "timeout_ms": 4000}, lambda: "cmux-browser-comprehensive-2" in str((c._call("browser.url.get", {"surface_id": target}) or {}).get("url") or ""), timeout_s=5.0, label="browser.wait url_contains page2 (history)", ) c._call("browser.reload", {"surface_id": target}) _wait_with_fallback( c, target, {"url_contains": "cmux-browser-comprehensive-2", "timeout_ms": 4000}, lambda: "cmux-browser-comprehensive-2" in str((c._call("browser.url.get", {"surface_id": target}) or {}).get("url") or ""), timeout_s=5.0, label="browser.wait url_contains page2 (reload)", ) c._call("browser.focus_webview", {"surface_id": target}) _wait_until( lambda: bool((c._call("browser.is_webview_focused", {"surface_id": target}) or {}).get("focused")), timeout_s=2.5, label="browser.is_webview_focused", ) # Negative cases adapted from agent-browser protocol/actions tests. _expect_error( "click missing selector", lambda: c._call("browser.click", {"surface_id": target}), "invalid_params", ) _expect_error_contains( "click missing element", lambda: c._call("browser.click", {"surface_id": target, "selector": "#does-not-exist"}), "not_found", "snapshot", "hint", ) _expect_error( "get.attr missing attr", lambda: c._call("browser.get.attr", {"surface_id": target, "selector": "#status"}), "invalid_params", ) _expect_error( "wait timeout", lambda: c._call("browser.wait", {"surface_id": target, "selector": "#never", "timeout_ms": 100}), "timeout", ) _expect_error( "navigate missing url", lambda: c._call("browser.navigate", {"surface_id": target}), "invalid_params", ) terminal_surface = c.new_surface(panel_type="terminal") _expect_error( "browser method on terminal surface", lambda: c._call("browser.url.get", {"surface_id": terminal_surface}), "not_found", ) print("PASS: comprehensive browser.* coverage (ported/adapted from agent-browser) is green") return 0 if __name__ == "__main__": raise SystemExit(main())