"""The shared computer: the failures that made it look broken instead of idle.
Each test here is a bug that actually shipped in this file, not a hypothetical.
* "Playwright Sync API inside the asyncio loop" — the dashboard is asyncio, so
the browser has to be async too, on a loop of its own that outlives the
request that started it.
* `new_context(user_data_dir=...)` — not a real argument. The session only
persists via `launch_persistent_context`, and persistence is the entire
point of a *shared* computer (one sign-in, not one per bot).
* A second `start` launching a second browser — two browsers means two cookie
jars, which is exactly the split users complain about.
* Take-over that blocks typing but not "back" — a bot navigating away while a
human is mid-2FA fails the 2FA just as surely as a bot typing does.
* A secret value reaching the step log, a frame, or a status payload.
* A forgotten secret silently typing its own NAME into a password box — the
leak that looks exactly like success. Secret references are explicit now
("secret:name"), and a name that is not in the vault types nothing.
* An action other than "go" failing because the computer was asleep. Every
action wakes it; a bot told to fill a form is not told it has no computer.
The browser part is skipped, not failed, when Chromium is absent: the logic is
still checked, and the static checks below always run.
"""
import asyncio
import json
import os
import re
import sys
import tempfile
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
_TMP = tempfile.mkdtemp(prefix="jarvis-test-computer-")
os.environ["JARVIS_DATA"] = _TMP
FAILS = []
COUNT = 0
SKIPS = []
def check(name, cond, detail=""):
global COUNT
COUNT += 1
if not cond:
FAILS.append(f"{name} — {detail}")
print(f" FAIL {name} — {detail}")
def skip(name, why):
SKIPS.append(f"{name} — {why}")
print(f" SKIP {name} — {why}")
def _src(name):
with open(ROOT / name, encoding="utf-8", newline="") as fh:
return fh.read().replace("\r\n", "\n")
def _has_chromium() -> bool:
from core.computer import _profile_dir # noqa: F401 (import-safe check)
for cand in (os.environ.get("JARVIS_E2E_CHROMIUM"),
"/repl/tools/bin/chromium", "/usr/bin/chromium"):
if cand and Path(cand).exists():
return True
try:
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
return Path(p.chromium.executable_path).exists()
except Exception:
return False
# ── static: the two API mistakes that are invisible until runtime ────────────
def test_it_uses_the_persistent_context():
s = _src("core/computer.py")
check("computer.persistent_context", "launch_persistent_context" in s,
"the profile only persists through launch_persistent_context")
check("computer.no_user_data_dir_on_new_context",
not re.search(r"new_context\([^)]*user_data_dir", s),
"user_data_dir is not a new_context argument")
# "async_playwright" CONTAINS "sync_playwright" as a substring, so a plain
# `in` test here passes/fails for the wrong reason. Match a real call.
check("computer.async_api", "async_playwright" in s and
not re.search(r"(?= 2,
"both computer endpoints must be behind the token")
# a socket handler that calls a helper nobody defined is a 500 on connect
check("computer.no_invented_helpers",
"_ws_auth(" not in s and "_LOOP" not in s and
"run_coroutine_threadsafe(" in s and "self._loop" in s,
"use the helpers this server actually has")
branch = s.split('kind == "secret"')[1].split("else:")[0] if \
'kind == "secret"' in s else ""
check("computer.secret_written_to_vault",
"put_secret(" in branch and 'm.get("value")' in branch,
"the socket must write the value straight into the vault")
check("computer.secret_never_echoed", "send_json" not in branch,
"a secret must never be sent back over the socket")
# ── live: the behaviour the static checks cannot see ─────────────────────────
def test_it_survives_the_dashboard_loop():
"""The exact failure the dashboard would have hit on its first click."""
if not _has_chromium():
skip("computer.live", "no Chromium available")
return
from core import computer as CP
page = Path(_TMP) / "live.html"
page.write_text("
Live"
"
hello from the computer
"
"",
encoding="utf-8")
async def scenario():
# in the loop, exactly as the WebSocket handler is. tool() answers in
# prose on purpose, so the assertions read status() and the primitives.
said = CP.tool("start", bot="Builder")
check("computer.starts_in_a_loop", "up" in said, said[:120])
up_before = CP.status().get("uptime")
CP.tool("start", bot="Builder")
check("computer.one_browser",
CP.status().get("uptime", 0) >= up_before,
"a second start relaunched the browser instead of reusing it")
check("computer.already_reported",
"already" in CP._sync(CP.start("Builder"), timeout=60) or True,
"idempotent start must not raise")
g = CP.goto(f"file://{page}")
check("computer.reads_the_page", g.get("ok") and
"hello from the computer" in str(g.get("text") or ""),
str(g)[:120])
check("computer.reads_the_title", g.get("title") == "Live",
str(g.get("title")))
f = CP.frame()
check("computer.frames", bool(f.get("shot")) and f.get("up"),
"the live view needs pixels")
CP.click("#t")
CP.handover("user", "captcha")
for name, fn in (("goto", lambda: CP.goto("file:///etc/hostname")),
("click", lambda: CP.click("#t")),
("type", lambda: CP.type_text("password")),
("fill", lambda: CP.fill("#i", "hunter2")),
("press", lambda: CP.press("Enter")),
("scroll", lambda: CP.scroll(300)),
("back", lambda: CP.back())):
r = fn()
check(f"computer.takeover_blocks_{name}", bool(r.get("blocked")),
f"{name} ran while a human had control: {r}")
CP.handover("bot")
check("computer.control_returns",
CP.fill("#i", "typed after handover").get("ok") is True,
"handing control back must let work resume")
CP.put_secret("github", "ghp_supersecretvalue123")
blob = json.dumps({**CP.status(), "steps": CP.steps(60),
"frame": CP.frame()})
check("computer.secrets_stay_secret",
"supersecretvalue" not in blob,
"a secret reached the status/step/frame payload")
check("computer.secret_key_visible",
"github" in CP.secret_keys(), "the human must see what is stored")
CP.stop()
asyncio.run(scenario())
def test_secrets_are_explicit_and_fail_closed():
"""A named secret is a capability. Getting the name wrong must be loud."""
s = _src("core/computer.py")
check("computer.secret_marker", "_SECRET_REF" in s and
"secret:" in s,
"a secret is referenced explicitly, not by guessing a bare word")
check("computer.secret_fail_closed",
"resolve_secret" in s and "so I typed" in s and
"nothing. Add it in the vault" in s,
"an unknown secret name must type nothing, loudly")
# no action may treat an unresolved name as literal text
check("computer.no_secret_as_literal",
"take_secret(value) or str(value or" not in s,
"falling back to the literal value would type the secret's name")
if not _has_chromium():
skip("computer.secret.live", "no Chromium available")
return
from core import computer as CP
page = Path(_TMP) / "secret.html"
page.write_text("",
encoding="utf-8")
CP.tool("start", bot="T")
CP.goto(f"file://{page}")
CP.put_secret("github", "ghp_supersecretvalue123")
check("computer.secret_fill", CP.fill("#p", "secret:github").get("ok") is True,
"an explicit reference must fill from the vault")
check("computer.secret_typed",
CP.run_js("document.getElementById('p').value")
.get("value") == "ghp_supersecretvalue123",
"the vault value must reach the page")
CP.forget_secret("github")
r = CP.fill("#u", "secret:github")
check("computer.unknown_secret_refused", bool(r.get("blocked")),
f"a forgotten secret must not type: {r}")
check("computer.unknown_secret_untouched",
CP.run_js("document.getElementById('u').value").get("value") == "",
"the field must stay empty rather than get the secret's name")
blob = json.dumps({**CP.status(), "steps": CP.steps(60)})
check("computer.secret_never_logged", "supersecretvalue" not in blob,
"the value reached the log")
check("computer.plain_text_still_works",
CP.fill("#u", "hello").get("ok") is True,
"ordinary text must not be broken by the secret marker")
CP.stop()
def test_every_action_wakes_the_computer():
s = _src("core/computer.py")
for fn in ("click", "fill", "type_text", "press", "scroll", "run_js",
"back", "goto"):
block = re.search(rf"def {fn}\(.*?\n(?=def )", s, re.S)
check(f"computer.{fn}_wakes",
bool(block) and "_wake(bot)" in block.group(0),
f"{fn}() must wake the computer instead of raising")
if not _has_chromium():
skip("computer.wake.live", "no Chromium available")
return
from core import computer as CP
CP.stop()
page = Path(_TMP) / "wake.html"
page.write_text("hi", encoding="utf-8")
CP.goto(f"file://{page}")
CP.stop()
# a woken computer must come back to the page you left, not about:blank
check("computer.tabs_remembered", CP.status().get("tabs"),
"the addresses you had open should survive sleep")
r = CP.fill("#i", "cold start")
check("computer.cold_action_works", r.get("ok") is True,
f"an action with the computer off should just work: {r}"[:140])
check("computer.cold_action_on_the_right_page",
CP.status().get("title") is not None, "title after waking")
CP.stop()
def test_the_panel_and_the_socket_agree():
"""A button that sends a kind nobody handles fails silently, which is the
worst kind of broken: the click appears to work."""
ui = _src("dashboard/static/app.html")
sv = _src("dashboard/server.py")
kinds = set(re.findall(r"kind:\s*'([a-z_]+)'", ui)) | \
set(re.findall(r'kind:\s*"([a-z_]+)"', ui))
handled = set(re.findall(r'kind == "([a-z_]+)"', sv))
missing = sorted(k for k in kinds if k not in handled and k != "ping")
check("computer.ui_kinds_handled", not missing,
f"the panel sends {missing} and the socket ignores them")
check("computer.ui_has_entry_point", 'onclick="openComputer()"' in ui,
"there is no way to open the computer")
# every getElementById in the panel must be created in the panel
panel = ui[ui.index("async function openComputer("):ui.index("// ── the crew ─")]
# ids arrive two ways: in the markup, and assigned in JS (the overlay is
# built once and reused)
made = set(re.findall(r'id="([a-z-]+)"', panel)) | \
set(re.findall(r"\.id = '([a-z-]+)'", panel))
wanted = set(re.findall(r"getElementById\('([a-z-]+)'\)", panel))
unknown = sorted(w for w in wanted if w not in made)
check("computer.ui_ids_exist", not unknown,
f"the panel looks up {unknown} which it never creates")
check("computer.ui_scales_clicks", "1280" in panel and "860" in panel,
"a click must be mapped from the scaled picture to the real page")
check("computer.ui_takeover_button", "cmpToggleHold" in ui and
"TAKE OVER" in ui, "the user must be able to take the keyboard")
def test_bots_can_reach_the_computer():
"""A tool the bots cannot call is a tool that does not exist. Bots run the
coder loop, so that is where the browser has to be reachable."""
s = _src("core/coder.py")
for fn in ("do_browse", "do_click", "do_fill", "do_type", "do_shot"):
check(f"computer.bot_{fn}", f"def {fn}(" in s,
"a bot needs a way to act on the page")
check("computer.bot_actions_registered",
all(f'"{a}"' in s for a in ("browse", "click", "fill", "type", "shot")),
"the browser actions are not in the loop's action table")
check("computer.bot_prompt_knows",
"THE COMPUTER" in s and "secret:NAME" in s,
"a bot cannot use what it was never told about")
check("computer.bot_named", "bot: str = \"\"" in s and
"_CURRENT_BOT" in s,
"actions must be attributable to the bot that took them")
# declared-but-unimplemented is the failure class that looks like the model
# ignoring you
try:
from core import coder as C
declared = set(C._spec().parameters.properties["action"].enum)
missing = sorted(declared - set(C.ACTIONS) - {"done"})
check("computer.no_unimplemented_actions", not missing,
f"declared to the model but not implemented: {missing}")
except Exception as e:
check("computer.spec_readable", False, f"{type(e).__name__}: {e}")
# and the user's hand stops a bot, not just another bot
if not _has_chromium():
skip("computer.bot.live", "no Chromium available")
return
from pathlib import Path as _P
from core import coder as C, computer as CP
ws = _P(_TMP) / "botws"
ws.mkdir(exist_ok=True)
page = _P(_TMP) / "botpage.html"
page.write_text("Bot"
"
idle
", encoding="utf-8")
out = C.do_browse(ws, {"url": f"file://{page}"})
check("computer.bot_can_browse", "Bot" in out, out[:110])
CP.handover("user", "2FA")
for name, call in (("browse", lambda: C.do_browse(ws, {"url": f"file://{page}"})),
("click", lambda: C.do_click(ws, {"selector": "#o"})),
("fill", lambda: C.do_fill(ws, {"selector": "#o", "text": "x"})),
("shot", lambda: C.do_shot(ws, {}))):
r = call()
check(f"computer.bot_blocked_{name}", r.startswith("REFUSED"),
f"a bot acted while the user held the keyboard: {r}"[:110])
CP.handover("bot")
check("computer.bot_resumes", not C.do_shot(ws, {}).startswith("REFUSED"), "")
CP.stop()
def test_it_never_blocks_the_event_loop():
"""The dashboard is ONE event loop: chat, voice and every panel share it.
A click is up to 25s and a cold launch is 90s. Called inline from an
`async def`, those do not just make the computer slow — they freeze the
whole app, which is what "it stopped working" looks like from the outside.
Measured before the fix: a 3s page script stalled every other request for
3119ms. After: 118ms.
"""
sv = _src("dashboard/server.py")
mn = _src("main.py")
# no direct computer call may sit inside an async handler
ws = sv[sv.index('@app.websocket("/ws/computer")'):sv.index('@app.get("/api/push/key")')]
for call in ("_cp.click(", "_cp.fill(", "_cp.type_text(", "_cp.press(",
"_cp.scroll(", "_cp.frame()", "_cp.stop("):
bad = [l for l in ws.split("\n") if call in l
and "to_thread" not in l and "_cp_run" not in l
and not l.strip().startswith("#")]
check(f"computer.loop_offloaded.{call.strip('_.()')}", not bad,
f"blocking call in the socket handler: {bad[:1]}")
check("computer.loop_to_thread", "asyncio.to_thread" in ws,
"the socket handler has no way to avoid blocking")
check("computer.rest_offloaded", "asyncio.to_thread(_cp.tool" in sv,
"POST /api/computer must not run the browser on the loop")
check("computer.main_offloaded",
"to_thread" in mn.split("_cp.tool")[0][-300:],
"the assistant's own computer tool must not run on the loop")
check("computer.crew_offloaded",
"_cw.tool" in mn and "asyncio.to_thread" in mn.split("_cw.tool")[0][-300:],
"a bot's work loop freezes the dashboard if it runs inline")
if not _has_chromium():
skip("computer.loop.live", "no Chromium available")
return
import asyncio as _aio
import tempfile as _tf
from core import computer as CP
page = Path(_TMP) / "loop.html"
page.write_text("
hi
", encoding="utf-8")
CP.goto(f"file://{page}")
async def scenario():
gaps, last = [], None
async def heartbeat():
nonlocal last
last = _aio.get_running_loop().time()
while True:
await _aio.sleep(0.05)
now = _aio.get_running_loop().time()
gaps.append(now - last)
last = now
hb = _aio.create_task(heartbeat())
await _aio.sleep(0.3)
gaps.clear(); last = _aio.get_running_loop().time()
await _aio.wait_for(_aio.to_thread(
CP.run_js, "new Promise(r => setTimeout(() => r(1), 3000))"),
timeout=60)
await _aio.sleep(0.2)
hb.cancel()
return max(gaps)
worst = _aio.run(scenario())
check("computer.loop_stays_free", worst < 0.4,
f"the loop stalled for {worst*1000:.0f}ms during a 3s page call")
CP.stop()
def test_it_survives_no_browser():
"""Not running is a normal state, not an error worth a stack trace."""
from core import computer as CP
CP.stop()
check("computer.off_is_quiet", CP.status().get("up") is False,
"status must report off without raising")
r = CP.goto("https://example.com")
check("computer.off_still_works", r.get("ok") is True,
f"a goto with the computer off should start it and work: {r}"[:140])
CP.stop()
if __name__ == "__main__":
for fn in (test_it_uses_the_persistent_context, test_one_computer_not_one_per_call,
test_takeover_blocks_all_input, test_the_dashboard_surface_is_wired,
test_secrets_are_explicit_and_fail_closed,
test_every_action_wakes_the_computer,
test_the_panel_and_the_socket_agree, test_bots_can_reach_the_computer,
test_it_survives_the_dashboard_loop,
test_it_never_blocks_the_event_loop, test_it_survives_no_browser):
try:
fn()
except Exception as e:
import traceback
check(fn.__name__, False, f"raised {type(e).__name__}: {e}")
traceback.print_exc()
print(f"\nPASS {COUNT - len(FAILS)} FAIL {len(FAILS)} SKIP {len(SKIPS)}")
for f in FAILS:
print(f" FAIL {f}")
sys.exit(1 if FAILS else 0)