Files
Octop/tests/integration/test_browser_api.py
2209cdc90f chore(deps): switch runtime packages to octop-* 1.0.0 (#1136)
Replace orcakit-harness-agent / harness-* with octop-harness, octop-gateway, octop-memory, and octop-browser, and align docs, UI copy, and generated paths.

Co-authored-by: jubaoliang <jubaoliang@tencent.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-25 06:44:24 +08:00

241 lines
7.1 KiB
Python

"""tests/integration/test_browser_api.py — remote browser endpoints.
We deliberately don't spawn a real Chrome in the test suite. These
cases drive env-status, ``_probe_env``, install SSE, harness-sessions,
and shutdown gates. Live screencast is covered by dashboard + WS unit
tests.
"""
from __future__ import annotations
import os
from pathlib import Path
from types import SimpleNamespace
from typing import Any
from unittest.mock import AsyncMock, patch
# --- env-status -------------------------------------------------------------
async def test_env_status_returns_shape(env: Any) -> None:
c, _srv, auth = env
r = await c.get("/api/browser/env-status", headers=auth)
assert r.status_code == 200
body = r.json()
assert "playwright" in body
assert "browsers_ok" in body
assert isinstance(body["playwright"], bool)
assert isinstance(body["browsers_ok"], bool)
async def test_env_status_requires_auth(env: Any) -> None:
c, _srv, _auth = env
r = await c.get("/api/browser/env-status")
assert r.status_code == 401
def test_probe_env_when_playwright_missing() -> None:
"""``_probe_env`` should not raise even with playwright import broken."""
import builtins
from octop.api.routers.browser import env as br
original_import = builtins.__import__
def fake_import(name: str, *args: Any, **kw: Any) -> Any:
if name == "playwright":
raise ImportError("simulated")
return original_import(name, *args, **kw)
with patch("builtins.__import__", side_effect=fake_import):
out = br._probe_env()
assert out["playwright"] is False
if not out.get("harness_browser"):
assert out["browsers_ok"] is False
assert out["error"]
def test_probe_env_harness_browser_without_chromium() -> None:
"""``browsers_ok`` must stay false when no Chrome/Chromium is available."""
from octop.api.routers.browser import env as br
try:
import octop_browser # noqa: F401
except ImportError:
return
with patch(
"octop_browser.cdp.launcher.find_chrome",
return_value=None,
):
out = br._probe_env()
assert out["harness_browser"] is True
assert out["browsers_ok"] is False
assert out["error"]
def test_probe_env_accepts_system_chrome() -> None:
"""System Chrome via find_chrome is enough for browsers_ok (same as launch)."""
from octop.api.routers.browser import env as br
try:
import octop_browser # noqa: F401
except ImportError:
return
with (
patch(
"octop_browser.cdp.launcher.find_chrome",
return_value="/usr/bin/google-chrome",
),
patch(
"octop.infra.browser.setup.playwright_chromium_installed",
return_value=False,
),
patch(
"octop.infra.browser.setup.chrome_source_for_path",
return_value="system",
),
):
out = br._probe_env()
assert out["harness_browser"] is True
assert out["browsers_ok"] is True
assert out["chrome_path"] == "/usr/bin/google-chrome"
assert out["chrome_source"] == "system"
assert out["playwright_chromium"] is False
def test_verify_browser_binary_ok(tmp_path: Path) -> None:
from octop.api.routers.browser import env as br
if os.name == "nt":
# Windows cannot exec a shebang script; use a .bat that ignores args.
exe = tmp_path / "fake-chrome.bat"
exe.write_text("@echo Chrome 1.0\r\n", encoding="utf-8")
else:
exe = tmp_path / "fake-chrome"
exe.write_text("#!/bin/sh\necho 'Chrome 1.0'\n", encoding="utf-8")
exe.chmod(0o755)
ok, msg = br._verify_browser_binary(str(exe))
assert ok is True
assert "Chrome" in msg
async def test_legacy_playwright_session_routes_removed(env: Any) -> None:
"""In-process Playwright CRUD is gone; the product path is harness-sessions."""
c, _srv, auth = env
r = await c.get("/api/browser/sessions", headers=auth)
assert r.status_code in (404, 405)
r = await c.post("/api/browser/sessions", headers=auth)
assert r.status_code in (404, 405)
assert r.status_code != 201
async def test_harness_sessions_shape(env: Any) -> None:
c, _srv, auth = env
r = await c.get("/api/browser/harness-sessions", headers=auth)
assert r.status_code == 200
body = r.json()
assert body["ok"] is True
assert "sessions" in body
assert isinstance(body["sessions"], list)
async def test_harness_list_tabs_uses_sticky_target_id(monkeypatch) -> None:
import aiohttp
from octop.api.routers.browser import harness as harness_router
class FakeProfile:
cdp_port = 9222
def load_target(self) -> str:
return "TAB-2"
class FakeInternal:
_cfg = SimpleNamespace(cdp_host="localhost")
_profile = FakeProfile()
class FakeSession:
_internal = FakeInternal()
class FakeResponse:
async def __aenter__(self):
return self
async def __aexit__(self, *exc):
return False
async def json(self, content_type=None):
return [
{
"type": "page",
"id": "TAB-1",
"url": "https://www.bilibili.com/",
"title": "B站",
},
{
"type": "page",
"id": "TAB-2",
"url": "https://weibo.com/",
"title": "微博",
},
]
class FakeClientSession:
async def __aenter__(self):
return self
async def __aexit__(self, *exc):
return False
def get(self, _url: str, timeout=None):
return FakeResponse()
monkeypatch.setattr(aiohttp, "ClientSession", lambda: FakeClientSession())
tabs = await harness_router.harness_list_tabs(FakeSession())
assert [tab["active"] for tab in tabs] == [False, True]
async def test_shutdown_requires_auth(env: Any) -> None:
c, _srv, _auth = env
r = await c.post("/api/browser/shutdown")
assert r.status_code == 401
async def test_shutdown_ignores_client_profile(env: Any) -> None:
c, _srv, auth = env
result = SimpleNamespace(success=True, error=None)
with patch(
"octop_browser.tool_interface.browser_tool",
new=AsyncMock(return_value=result),
) as tool:
r = await c.post("/api/browser/shutdown?profile=work", headers=auth)
assert r.status_code == 200
assert r.json() == {"ok": True, "profile": "user-1"}
tool.assert_awaited_once_with(
action="close_session",
profile="user-1",
kill=True,
)
# --- install spawn ---------------------------------------------------------
async def test_install_returns_pid(env: Any) -> None:
c, _srv, auth = env
async def fake_stream():
yield {"log": "downloading chromium"}
yield {"done": True, "success": True}
with patch(
"octop_browser.install_chromium_stream",
side_effect=lambda: fake_stream(),
):
r = await c.post("/api/browser/install", headers=auth)
assert r.status_code in (200, 202)