Download tests/test_browser_runtime.py from Madras1/Lancer: direct link, hf CLI and curl.
- Browser
- Download file 31 kB
-
https://huggingface.co/spaces/Madras1/Lancer/resolve/main/tests/test_browser_runtime.py
- Command line
-
hf download hf://spaces/Madras1/Lancer/tests/test_browser_runtime.py
-
curl -L -o test_browser_runtime.py https://huggingface.co/spaces/Madras1/Lancer/resolve/main/tests/test_browser_runtime.py
31 kB
| import json | |
| import os | |
| import sys | |
| import tempfile | |
| import threading | |
| import types | |
| import unittest | |
| import asyncio | |
| from pathlib import Path | |
| from types import SimpleNamespace | |
| from unittest.mock import AsyncMock, patch | |
| from app.agents.browser_use_bridge import ( | |
| BROWSER_USE_VERSION, | |
| CAMOUFOX_SETUP_COMMAND, | |
| CAMOUFOX_VERSION, | |
| CDP_PROXY_COMMAND, | |
| REMOTE_CDP_PROXY_PATH, | |
| REMOTE_STEALTH_SERVER_PATH, | |
| _create_e2b_desktop, | |
| _start_local_worker, | |
| _wait_for_external_cdp, | |
| _worker_environment, | |
| build_chrome_command, | |
| build_e2b_sandbox_kwargs, | |
| build_public_result, | |
| build_worker_config, | |
| cdp_proxy_script_source, | |
| run_browser_use_agent, | |
| translate_worker_event, | |
| worker_script_source, | |
| ) | |
| from app.agents.browser_use_worker import ( | |
| _browser_search_urls, | |
| _normalize_browser_links, | |
| _normalize_stealth_links, | |
| _playwright_cookies, | |
| ) | |
| import app.agents.cdp_proxy as cdp_proxy | |
| from app.agents.cdp_proxy import _authorized_and_sanitized | |
| MODEL = "nvidia/nemotron-3-ultra-550b-a55b:free" | |
| class _FakeFiles: | |
| def __init__(self): | |
| self.data = {} | |
| def write(self, path, data): | |
| self.data[path] = data | |
| return SimpleNamespace(path=path) | |
| class _FakeRemoteHandle: | |
| def __init__(self): | |
| self.killed = False | |
| def kill(self): | |
| self.killed = True | |
| return True | |
| class _FakeCommands: | |
| def __init__(self): | |
| self.calls = [] | |
| self.handles = [] | |
| def run(self, command, **kwargs): | |
| self.calls.append((command, kwargs)) | |
| handle = _FakeRemoteHandle() | |
| self.handles.append(handle) | |
| return handle | |
| class _FakeDesktop: | |
| def __init__(self): | |
| self.files = _FakeFiles() | |
| self.commands = _FakeCommands() | |
| self.stream = _FakeStream() | |
| self.killed = False | |
| def get_host(self, port): | |
| return f"{port}-sandbox.e2b.app" | |
| def kill(self): | |
| self.killed = True | |
| class _FakeStream: | |
| def __init__(self): | |
| self.started = False | |
| self.stopped = False | |
| def start(self, **kwargs): | |
| self.started = True | |
| def get_auth_key(self): | |
| return "visual-auth-key" | |
| def get_url(self, **kwargs): | |
| return "https://visual.example/session" | |
| def stop(self): | |
| self.stopped = True | |
| class _FakeSandbox: | |
| desktop = None | |
| create_kwargs = None | |
| def create(cls, **kwargs): | |
| cls.create_kwargs = kwargs | |
| cls.desktop = _FakeDesktop() | |
| return cls.desktop | |
| class _FakeLocalProcess: | |
| def __init__(self, exit_code=0): | |
| self.returncode = exit_code | |
| self.terminated = False | |
| self.killed = False | |
| async def wait(self): | |
| return self.returncode | |
| def terminate(self): | |
| self.terminated = True | |
| self.returncode = -15 | |
| def kill(self): | |
| self.killed = True | |
| self.returncode = -9 | |
| def _successful_events_and_result(): | |
| events = [ | |
| { | |
| "type": "step", | |
| "step": 1, | |
| "url": "https://duckduckgo.com/?q=lulinha", | |
| "title": "Search", | |
| "actions": [{"navigate": {"url": "https://source-one.example/profile"}}], | |
| "next_goal": "Open the first direct source", | |
| }, | |
| { | |
| "type": "evidence", | |
| "record": { | |
| "claim": "Lulinha is Fábio Luís", | |
| "exact_quote": "Fábio Luís, conhecido como Lulinha", | |
| "url": "https://source-one.example/profile", | |
| "title": "Profile", | |
| "hostname": "source-one.example", | |
| }, | |
| }, | |
| { | |
| "type": "evidence", | |
| "record": { | |
| "claim": "A second source identifies the same person", | |
| "exact_quote": "Fábio Luís Lula da Silva, o Lulinha", | |
| "url": "https://source-two.example/story", | |
| "title": "Story", | |
| "hostname": "source-two.example", | |
| }, | |
| }, | |
| ] | |
| result = { | |
| "ok": True, | |
| "answer": "Lulinha is Fábio Luís. Sources are linked below.", | |
| "agent_success": True, | |
| "is_done": True, | |
| "urls": [ | |
| "https://duckduckgo.com/?q=lulinha", | |
| "https://source-one.example/profile", | |
| "https://source-two.example/story", | |
| ], | |
| "steps": 3, | |
| "duration_seconds": 9.2, | |
| "evidence": [item["record"] for item in events if item["type"] == "evidence"], | |
| "model": MODEL, | |
| } | |
| return events, result | |
| class _FakeResponse: | |
| status_code = 200 | |
| def json(self): | |
| return { | |
| "webSocketDebuggerUrl": ( | |
| "ws://127.0.0.1:9222/devtools/browser/internal-browser-id" | |
| ) | |
| } | |
| class _FakeHttpClient: | |
| calls = [] | |
| def __init__(self, *args, **kwargs): | |
| pass | |
| async def __aenter__(self): | |
| return self | |
| async def __aexit__(self, *args): | |
| return False | |
| async def get(self, url, headers): | |
| self.calls.append((url, headers)) | |
| return _FakeResponse() | |
| class BrowserUseBridgeTests(unittest.IsolatedAsyncioTestCase): | |
| def test_host_worker_is_standalone_remote_cdp_code(self): | |
| source = worker_script_source() | |
| compile(source, "<browser-use-worker>", "exec") | |
| self.assertIn("from browser_use import", source) | |
| self.assertIn("record_evidence", source) | |
| self.assertIn("final_response_after_failure=True", source) | |
| self.assertNotIn(' "done",', source) | |
| self.assertNotIn("finish_research", source) | |
| self.assertIn("LANCER_CDP_URL", source) | |
| self.assertIn("x-lancer-cdp-token", source) | |
| self.assertIn("BrowserProfile", source) | |
| self.assertIn("block_ip_addresses=True", source) | |
| self.assertIn("MAX_PAGE_TEXT_CHARS", source) | |
| self.assertIn(".slice(0, limit)", source) | |
| self.assertIn("browser_search", source) | |
| self.assertIn("open_source", source) | |
| self.assertIn("_inspect_search_page", source) | |
| self.assertIn('"navigate",', source) | |
| self.assertIn('"search",', source) | |
| self.assertIn("enable_planning=False", source) | |
| self.assertIn("captcha_solver=True", source) | |
| self.assertIn('llm_kwargs["reasoning_effort"] = "low"', source) | |
| self.assertIn('llm_kwargs["reasoning_models"] = [worker_model]', source) | |
| self.assertIn('"max_retries": 3', source) | |
| self.assertIn("use_thinking=False", source) | |
| self.assertNotIn("SearXNG", source) | |
| self.assertNotIn("searxng", source.casefold()) | |
| self.assertIn("raw_page_text = await page.evaluate", source) | |
| self.assertIn("quote_normalized in page_text_normalized", source) | |
| self.assertNotIn("Fuzzy fallback", source) | |
| self.assertNotIn("match_ratio", source) | |
| self.assertNotIn("executable_path=", source) | |
| self.assertIn("Camoufox", source) | |
| self.assertIn("stealth_gateway_url", source) | |
| self.assertIn("captcha_solved", source) | |
| def test_camoufox_snapshot_links_and_cookies_are_sanitized(self): | |
| links = _normalize_stealth_links( | |
| { | |
| "elements": [ | |
| { | |
| "text": "Search link", | |
| "href": "https://search.brave.com/help", | |
| }, | |
| { | |
| "text": "Direct source", | |
| "href": "https://news.example.org/article", | |
| }, | |
| ] | |
| } | |
| ) | |
| self.assertEqual( | |
| links, | |
| [{"title": "Direct source", "url": "https://news.example.org/article"}], | |
| ) | |
| cookies = _playwright_cookies( | |
| [ | |
| { | |
| "name": "clearance", | |
| "value": "ok", | |
| "domain": ".example.org", | |
| "path": "/", | |
| "sameSite": "lax", | |
| "unsupported": "discard-me", | |
| } | |
| ] | |
| ) | |
| self.assertEqual(cookies[0]["sameSite"], "Lax") | |
| self.assertNotIn("unsupported", cookies[0]) | |
| def test_live_browser_search_filters_serp_links_and_unwraps_targets(self): | |
| engines = dict(_browser_search_urls("quem é lulinha?")) | |
| self.assertIn("search.brave.com", engines["Brave Search"]) | |
| self.assertNotIn("google.com", " ".join(engines.values())) | |
| links = _normalize_browser_links( | |
| [ | |
| {"title": "Search navigation", "url": "https://search.brave.com/help"}, | |
| { | |
| "title": "Direct article", | |
| "url": "https://noticias.example.org/reportagem", | |
| }, | |
| { | |
| "title": "Wrapped article", | |
| "url": ( | |
| "https://duckduckgo.com/l/?uddg=" | |
| "https%3A%2F%2Fsecond.example.com%2Fprofile" | |
| ), | |
| }, | |
| { | |
| "title": "Yahoo wrapped article", | |
| "url": ( | |
| "https://r.search.yahoo.com/_ylt=x/RV=2/RU=" | |
| "https%3A%2F%2Fthird.example.net%2Fbio/RK=2/RS=x" | |
| ), | |
| }, | |
| ] | |
| ) | |
| self.assertEqual( | |
| [item["url"] for item in links], | |
| [ | |
| "https://noticias.example.org/reportagem", | |
| "https://second.example.com/profile", | |
| "https://third.example.net/bio", | |
| ], | |
| ) | |
| def test_proxy_is_dependency_free_authenticated_and_rewrites_host(self): | |
| source = cdp_proxy_script_source() | |
| compile(source, "<cdp-proxy>", "exec") | |
| request = ( | |
| b"GET /json/version HTTP/1.1\r\n" | |
| b"Host: 9223-public.e2b.app\r\n" | |
| b"X-Lancer-CDP-Token: correct-token\r\n" | |
| b"Connection: Upgrade\r\n\r\n" | |
| ) | |
| sanitized = _authorized_and_sanitized(request, "correct-token") | |
| self.assertIsNotNone(sanitized) | |
| self.assertIn(b"Host: 127.0.0.1:9222", sanitized) | |
| self.assertNotIn(b"correct-token", sanitized) | |
| self.assertIsNone(_authorized_and_sanitized(request, "wrong-token")) | |
| async def test_proxy_tunnels_websocket_bytes_after_authenticated_upgrade(self): | |
| captured = {} | |
| upstream_done = asyncio.Event() | |
| proxy_done = asyncio.Event() | |
| async def fake_chrome(reader, writer): | |
| try: | |
| captured["headers"] = await reader.readuntil(b"\r\n\r\n") | |
| writer.write( | |
| b"HTTP/1.1 101 Switching Protocols\r\n" | |
| b"Upgrade: websocket\r\n" | |
| b"Connection: Upgrade\r\n\r\n" | |
| ) | |
| await writer.drain() | |
| payload = await reader.readexactly(4) | |
| writer.write(payload.upper()) | |
| await writer.drain() | |
| finally: | |
| writer.close() | |
| await writer.wait_closed() | |
| upstream_done.set() | |
| async def proxy_connection(reader, writer): | |
| try: | |
| await cdp_proxy._handle_client(reader, writer) | |
| finally: | |
| proxy_done.set() | |
| upstream = await asyncio.start_server(fake_chrome, "127.0.0.1", 0) | |
| upstream_port = upstream.sockets[0].getsockname()[1] | |
| with ( | |
| patch.object(cdp_proxy, "TOKEN", "correct-token"), | |
| patch.object(cdp_proxy, "UPSTREAM_PORT", upstream_port), | |
| ): | |
| proxy = await asyncio.start_server( | |
| proxy_connection, | |
| "127.0.0.1", | |
| 0, | |
| ) | |
| proxy_port = proxy.sockets[0].getsockname()[1] | |
| reader, writer = await asyncio.open_connection("127.0.0.1", proxy_port) | |
| writer.write( | |
| b"GET /devtools/browser/id HTTP/1.1\r\n" | |
| b"Host: public.example\r\n" | |
| b"Upgrade: websocket\r\n" | |
| b"Connection: Upgrade\r\n" | |
| b"X-Lancer-CDP-Token: correct-token\r\n\r\n" | |
| ) | |
| await writer.drain() | |
| response = await reader.readuntil(b"\r\n\r\n") | |
| self.assertIn(b"101 Switching Protocols", response) | |
| writer.write(b"ping") | |
| await writer.drain() | |
| self.assertEqual(await reader.readexactly(4), b"PING") | |
| writer.close() | |
| await writer.wait_closed() | |
| await asyncio.wait_for(upstream_done.wait(), timeout=2) | |
| await asyncio.wait_for(proxy_done.wait(), timeout=2) | |
| proxy.close() | |
| await proxy.wait_closed() | |
| upstream.close() | |
| await upstream.wait_closed() | |
| self.assertIn(f"Host: 127.0.0.1:{upstream_port}".encode(), captured["headers"]) | |
| self.assertNotIn(b"correct-token", captured["headers"]) | |
| def test_chrome_launch_is_remote_debuggable_and_headless_is_explicit(self): | |
| visual = build_chrome_command(visual=True) | |
| headless = build_chrome_command(visual=False) | |
| self.assertIn("--remote-debugging-port=9222", visual) | |
| self.assertIn("--remote-allow-origins=*", visual) | |
| self.assertNotIn("--headless=new", visual) | |
| self.assertIn("--headless=new", headless) | |
| def test_config_is_non_secret_and_uses_current_normal_depth_preset(self): | |
| config = build_worker_config( | |
| task="quem é lulinha?", | |
| url=None, | |
| visual=False, | |
| model=MODEL, | |
| ) | |
| serialized = json.dumps(config) | |
| self.assertEqual(config["minimum_sources"], 2) | |
| self.assertEqual(config["min_evidence_to_finish"], 2) | |
| self.assertTrue(config["headless"]) | |
| self.assertEqual(config["max_steps"], 18) | |
| self.assertEqual(config["llm_timeout"], 120) | |
| self.assertEqual(config["step_timeout"], 150) | |
| self.assertEqual(config["worker_timeout"], 600) | |
| self.assertNotIn("api_key", serialized.casefold()) | |
| self.assertNotIn("secret", serialized.casefold()) | |
| def test_prebuilt_e2b_template_is_optional_and_explicit(self): | |
| default_settings = SimpleNamespace(e2b_api_key="key") | |
| template_settings = SimpleNamespace( | |
| e2b_api_key="key", | |
| e2b_browser_template="lancer-browser-v1", | |
| ) | |
| self.assertNotIn("template", build_e2b_sandbox_kwargs(default_settings)) | |
| self.assertEqual( | |
| build_e2b_sandbox_kwargs(template_settings)["template"], | |
| "lancer-browser-v1", | |
| ) | |
| def test_worker_environment_does_not_copy_unrelated_host_secrets(self): | |
| with patch.dict( | |
| os.environ, | |
| { | |
| "E2B_API_KEY": "must-not-cross", | |
| "AWS_SECRET_ACCESS_KEY": "must-not-cross-either", | |
| }, | |
| ): | |
| environment = _worker_environment( | |
| openrouter_api_key="openrouter-key", | |
| cdp_url="wss://9223-sandbox.e2b.app/devtools/browser/id", | |
| cdp_token="cdp-token", | |
| runtime_dir=Path("/tmp/test-runtime"), | |
| ) | |
| self.assertNotIn("E2B_API_KEY", environment) | |
| self.assertNotIn("AWS_SECRET_ACCESS_KEY", environment) | |
| self.assertEqual(environment["OPENROUTER_API_KEY"], "openrouter-key") | |
| self.assertEqual(environment["LANCER_CDP_TOKEN"], "cdp-token") | |
| self.assertEqual(environment["PYTHON_DOTENV_DISABLED"], "1") | |
| self.assertEqual(environment["PYTHONNOUSERSITE"], "1") | |
| self.assertEqual(environment["HOME"], str(Path("/tmp/test-runtime") / "home")) | |
| self.assertNotIn("PYTHONPATH", environment) | |
| async def test_local_worker_starts_in_its_private_runtime_directory(self): | |
| fake_process = _FakeLocalProcess() | |
| with tempfile.TemporaryDirectory() as temp_dir: | |
| runtime = Path(temp_dir) | |
| config_path = runtime / "config.json" | |
| config_path.write_text("{}", encoding="utf-8") | |
| with patch( | |
| "app.agents.browser_use_bridge.asyncio.create_subprocess_exec", | |
| new=AsyncMock(return_value=fake_process), | |
| ) as spawn: | |
| await _start_local_worker( | |
| config_path=config_path, | |
| events_path=runtime / "events.jsonl", | |
| result_path=runtime / "result.json", | |
| log_path=runtime / "worker.log", | |
| environment={"PATH": os.environ.get("PATH", "")}, | |
| ) | |
| self.assertEqual(spawn.await_args.kwargs["cwd"], str(runtime)) | |
| async def test_cancelled_creation_kills_late_e2b_sandbox(self): | |
| started = threading.Event() | |
| release = threading.Event() | |
| created = _FakeDesktop() | |
| class SlowSandbox: | |
| def create(cls, **kwargs): | |
| started.set() | |
| release.wait(timeout=2) | |
| return created | |
| task = asyncio.create_task(_create_e2b_desktop(SlowSandbox, timeout=720)) | |
| self.assertTrue(await asyncio.to_thread(started.wait, 1)) | |
| task.cancel() | |
| release.set() | |
| with self.assertRaises(asyncio.CancelledError): | |
| await task | |
| self.assertTrue(created.killed) | |
| async def test_external_cdp_probe_converts_internal_ws_to_public_wss(self): | |
| _FakeHttpClient.calls = [] | |
| desktop = _FakeDesktop() | |
| with patch( | |
| "app.agents.browser_use_bridge.httpx.AsyncClient", | |
| _FakeHttpClient, | |
| ): | |
| url = await _wait_for_external_cdp(desktop, "per-run-token") | |
| self.assertEqual( | |
| url, | |
| "wss://9223-sandbox.e2b.app/devtools/browser/internal-browser-id", | |
| ) | |
| request_url, headers = _FakeHttpClient.calls[0] | |
| self.assertEqual(request_url, "https://9223-sandbox.e2b.app/json/version") | |
| self.assertEqual(headers["x-lancer-cdp-token"], "per-run-token") | |
| def test_translation_exposes_only_accepted_evidence_as_facts(self): | |
| progress = {"evidence": [], "next_goals": [], "queries": []} | |
| events = translate_worker_event( | |
| { | |
| "type": "step", | |
| "step": 2, | |
| "url": "https://example.com", | |
| "title": "Example", | |
| "actions": [{"browser_search": {"query": "focused query"}}], | |
| "memory": "Unverified model memory must not appear as evidence", | |
| "next_goal": "Open the original source", | |
| }, | |
| progress, | |
| ) | |
| public_progress = next(item for item in events if item["type"] == "progress") | |
| self.assertEqual(public_progress["known_facts"], []) | |
| self.assertEqual(public_progress["last_queries"], ["focused query"]) | |
| self.assertEqual(public_progress["missing_points"], ["Open the original source"]) | |
| def test_public_result_never_promotes_search_or_visited_urls(self): | |
| config = build_worker_config( | |
| task="quem é alguém?", | |
| url=None, | |
| visual=False, | |
| model="test-model", | |
| ) | |
| public = build_public_result( | |
| { | |
| "ok": True, | |
| "answer": "Useful partial answer", | |
| "agent_success": True, | |
| "urls": [ | |
| "https://duckduckgo.com/?q=someone", | |
| "https://unverified.example/page", | |
| "https://verified.example/page", | |
| ], | |
| "evidence": [ | |
| { | |
| "claim": "Verified claim", | |
| "exact_quote": "A sufficiently long exact quotation", | |
| "url": "https://verified.example/page", | |
| }, | |
| { | |
| "claim": "Snippet claim", | |
| "exact_quote": "A search snippet must never count", | |
| "url": "https://duckduckgo.com/?q=someone", | |
| }, | |
| ], | |
| }, | |
| config, | |
| ) | |
| self.assertEqual(public["links"], ["https://verified.example/page"]) | |
| self.assertFalse(public["success"]) | |
| self.assertIn("1/2", public["content"]) | |
| self.assertIn("https://unverified.example/page", public["visited_links"]) | |
| self.assertEqual(public["verification_status"], "partial") | |
| def test_public_result_without_recorded_evidence_is_never_verified(self): | |
| config = build_worker_config( | |
| task="research a person", | |
| url=None, | |
| visual=False, | |
| model="test-model", | |
| ) | |
| public = build_public_result( | |
| { | |
| "ok": True, | |
| "answer": "A useful evaluator draft", | |
| "agent_success": True, | |
| "urls": ["https://visited.example/profile"], | |
| "evidence": [], | |
| }, | |
| config, | |
| ) | |
| self.assertFalse(public["success"]) | |
| self.assertEqual(public["links"], []) | |
| self.assertEqual(public["evidence"], []) | |
| self.assertEqual(public["verification_status"], "partial") | |
| self.assertIn("0/1", public["content"]) | |
| self.assertNotIn("Citação validada pelo Evaluator", worker_script_source()) | |
| def test_subdomains_of_one_publisher_are_not_independent_sources(self): | |
| config = build_worker_config( | |
| task="quem é alguém?", | |
| url=None, | |
| visual=False, | |
| model="test-model", | |
| ) | |
| public = build_public_result( | |
| { | |
| "ok": True, | |
| "answer": "Answer", | |
| "agent_success": True, | |
| "evidence": [ | |
| { | |
| "claim": "Claim one", | |
| "exact_quote": "A sufficiently long literal quote one", | |
| "url": "https://pt.wikipedia.org/wiki/Person", | |
| }, | |
| { | |
| "claim": "Claim two", | |
| "exact_quote": "A sufficiently long literal quote two", | |
| "url": "https://en.wikipedia.org/wiki/Person", | |
| }, | |
| ], | |
| }, | |
| config, | |
| ) | |
| self.assertEqual(public["verified_sources"], 1) | |
| self.assertFalse(public["success"]) | |
| async def test_full_bridge_runs_host_browser_use_with_camoufox_in_e2b(self): | |
| fake_module = types.ModuleType("e2b_desktop") | |
| fake_module.Sandbox = _FakeSandbox | |
| settings = SimpleNamespace( | |
| e2b_api_key="e2b-test-key", | |
| openrouter_api_key="openrouter-test-key", | |
| llm_model=MODEL, | |
| ) | |
| captured = {} | |
| async def fake_start_local_worker(**kwargs): | |
| captured["environment"] = dict(kwargs["environment"]) | |
| captured["config"] = kwargs["config_path"].read_text(encoding="utf-8") | |
| events, result = _successful_events_and_result() | |
| kwargs["events_path"].write_text( | |
| "\n".join(json.dumps(item) for item in events) + "\n", | |
| encoding="utf-8", | |
| ) | |
| kwargs["result_path"].write_text(json.dumps(result), encoding="utf-8") | |
| return _FakeLocalProcess() | |
| _FakeSandbox.desktop = None | |
| with ( | |
| patch.dict(sys.modules, {"e2b_desktop": fake_module}), | |
| patch("app.agents.browser_use_bridge.get_settings", return_value=settings), | |
| patch( | |
| "app.agents.browser_use_bridge.installed_browser_use_version", | |
| return_value=BROWSER_USE_VERSION, | |
| ), | |
| patch( | |
| "app.agents.browser_use_bridge._wait_for_external_cdp", | |
| new=AsyncMock( | |
| return_value=( | |
| "wss://9223-sandbox.e2b.app/devtools/browser/test-browser" | |
| ) | |
| ), | |
| ), | |
| patch( | |
| "app.agents.browser_use_bridge._wait_for_external_stealth_browser", | |
| new=AsyncMock(return_value="https://8766-sandbox.e2b.app"), | |
| ), | |
| patch( | |
| "app.agents.browser_use_bridge._start_local_worker", | |
| side_effect=fake_start_local_worker, | |
| ), | |
| ): | |
| events = [ | |
| event | |
| async for event in run_browser_use_agent( | |
| "quem é lulinha?", | |
| visual=False, | |
| ) | |
| ] | |
| desktop = _FakeSandbox.desktop | |
| commands = [command for command, _ in desktop.commands.calls] | |
| self.assertTrue(any("google-chrome" in command for command in commands)) | |
| self.assertIn(CDP_PROXY_COMMAND, commands) | |
| self.assertIn(CAMOUFOX_SETUP_COMMAND, commands) | |
| self.assertTrue(any(f"camoufox=={CAMOUFOX_VERSION}" in command for command in commands)) | |
| self.assertFalse(any("browser-use==" in command for command in commands)) | |
| self.assertNotIn("openrouter-test-key", " ".join(commands)) | |
| self.assertIn(REMOTE_CDP_PROXY_PATH, desktop.files.data) | |
| self.assertIn(REMOTE_STEALTH_SERVER_PATH, desktop.files.data) | |
| self.assertTrue(desktop.killed) | |
| background_handles = [ | |
| handle | |
| for (_, kwargs), handle in zip( | |
| desktop.commands.calls, | |
| desktop.commands.handles, | |
| ) | |
| if kwargs.get("background") | |
| ] | |
| self.assertTrue(background_handles) | |
| self.assertTrue(all(handle.killed for handle in background_handles)) | |
| # Regression: E2B rejects several mathematically valid reserved CIDRs | |
| # (including 0.0.0.0/8) with HTTP 400. Network policy must not prevent | |
| # the browser sandbox from being created. | |
| self.assertNotIn("network", _FakeSandbox.create_kwargs) | |
| self.assertNotIn("template", _FakeSandbox.create_kwargs) | |
| config_text = captured["config"] | |
| worker_environment = captured["environment"] | |
| self.assertNotIn("openrouter-test-key", config_text) | |
| self.assertNotIn("e2b-test-key", config_text) | |
| self.assertIn("https://8766-sandbox.e2b.app", config_text) | |
| self.assertEqual(worker_environment["OPENROUTER_API_KEY"], "openrouter-test-key") | |
| self.assertNotIn("E2B_API_KEY", worker_environment) | |
| result = next(item for item in events if item["type"] == "result") | |
| self.assertTrue(result["success"]) | |
| self.assertEqual(result["verified_sources"], 2) | |
| self.assertNotIn("https://duckduckgo.com/?q=lulinha", result["links"]) | |
| async def test_missing_pinned_host_runtime_fails_before_creating_e2b(self): | |
| settings = SimpleNamespace( | |
| e2b_api_key="e2b-test-key", | |
| openrouter_api_key="openrouter-test-key", | |
| llm_model=MODEL, | |
| ) | |
| _FakeSandbox.desktop = None | |
| with ( | |
| patch("app.agents.browser_use_bridge.get_settings", return_value=settings), | |
| patch( | |
| "app.agents.browser_use_bridge.installed_browser_use_version", | |
| return_value="", | |
| ), | |
| ): | |
| events = [ | |
| event | |
| async for event in run_browser_use_agent("research", visual=False) | |
| ] | |
| self.assertEqual(len(events), 1) | |
| self.assertEqual(events[0]["type"], "error") | |
| self.assertIn(f"browser-use=={BROWSER_USE_VERSION}", events[0]["message"]) | |
| self.assertIsNone(_FakeSandbox.desktop) | |
| async def test_runtime_failure_redacts_every_request_credential(self): | |
| fake_module = types.ModuleType("e2b_desktop") | |
| fake_module.Sandbox = _FakeSandbox | |
| settings = SimpleNamespace( | |
| e2b_api_key="e2b-test-key", | |
| openrouter_api_key="openrouter-test-key", | |
| llm_model=MODEL, | |
| ) | |
| failure = RuntimeError( | |
| "gateway rejected cdp-secret-token openrouter-test-key e2b-test-key" | |
| ) | |
| _FakeSandbox.desktop = None | |
| with ( | |
| patch.dict(sys.modules, {"e2b_desktop": fake_module}), | |
| patch("app.agents.browser_use_bridge.get_settings", return_value=settings), | |
| patch( | |
| "app.agents.browser_use_bridge.installed_browser_use_version", | |
| return_value=BROWSER_USE_VERSION, | |
| ), | |
| patch( | |
| "app.agents.browser_use_bridge.secrets.token_urlsafe", | |
| return_value="cdp-secret-token", | |
| ), | |
| patch( | |
| "app.agents.browser_use_bridge._wait_for_external_cdp", | |
| new=AsyncMock(side_effect=failure), | |
| ), | |
| self.assertLogs("app.agents.browser_use_bridge", level="ERROR") as logs, | |
| ): | |
| events = [ | |
| event | |
| async for event in run_browser_use_agent("research", visual=False) | |
| ] | |
| combined = json.dumps(events, ensure_ascii=False) + "\n" + "\n".join(logs.output) | |
| self.assertIn("[REDACTED]", combined) | |
| self.assertNotIn("cdp-secret-token", combined) | |
| self.assertNotIn("openrouter-test-key", combined) | |
| self.assertNotIn("e2b-test-key", combined) | |
| self.assertTrue(_FakeSandbox.desktop.killed) | |
| self.assertTrue( | |
| all(handle.killed for handle in _FakeSandbox.desktop.commands.handles) | |
| ) | |
| async def test_visual_failure_ends_stream_before_reporting_error(self): | |
| fake_module = types.ModuleType("e2b_desktop") | |
| fake_module.Sandbox = _FakeSandbox | |
| settings = SimpleNamespace( | |
| e2b_api_key="e2b-test-key", | |
| openrouter_api_key="openrouter-test-key", | |
| llm_model=MODEL, | |
| ) | |
| _FakeSandbox.desktop = None | |
| with ( | |
| patch.dict(sys.modules, {"e2b_desktop": fake_module}), | |
| patch("app.agents.browser_use_bridge.get_settings", return_value=settings), | |
| patch( | |
| "app.agents.browser_use_bridge.installed_browser_use_version", | |
| return_value=BROWSER_USE_VERSION, | |
| ), | |
| patch( | |
| "app.agents.browser_use_bridge._wait_for_external_cdp", | |
| new=AsyncMock( | |
| return_value="wss://9223-sandbox.e2b.app/devtools/browser/id" | |
| ), | |
| ), | |
| patch( | |
| "app.agents.browser_use_bridge._start_local_worker", | |
| new=AsyncMock(side_effect=RuntimeError("worker startup failed")), | |
| ), | |
| ): | |
| events = [ | |
| event | |
| async for event in run_browser_use_agent("research", visual=True) | |
| ] | |
| event_types = [event["type"] for event in events] | |
| self.assertIn("stream", event_types) | |
| self.assertLess(event_types.index("stream_end"), event_types.index("error")) | |
| self.assertTrue(_FakeSandbox.desktop.stream.stopped) | |
| if __name__ == "__main__": | |
| unittest.main() | |