import inspect from pytest_check import check from . import console as ui class Browser: def __init__(self, agent_id, agent_state=None): self._agent_id = agent_id self._agent_state = agent_state def _call(self, action, **kwargs): import asyncio from strix.tools.browser.browser_actions import browser_action from strix.tools.context import set_current_agent_id from .conftest import _bg_loop async def _run(): set_current_agent_id(self._agent_id) return await browser_action( agent_state=self._agent_state, action=action, **kwargs, ) future = asyncio.run_coroutine_threadsafe(_run(), _bg_loop) result = future.result(timeout=120) if "screenshot" in result: result["screenshot"] = "[Image]" return result def __getattr__(self, action): def call(**kwargs): result = self._call(action, **kwargs) if "error" in result: Fail(result).error(result["error"]) return result return call def act_parallel(tasks): from concurrent.futures import ThreadPoolExecutor def _run_one(browser, kwargs): action = kwargs.pop("action") return getattr(browser, action)(**kwargs) with ThreadPoolExecutor(max_workers=len(tasks)) as pool: futures = [pool.submit(_run_one, b, dict(kw)) for b, kw in tasks] return [f.result(timeout=120) for f in futures] def _caller_test_name(): for frame in inspect.stack(): if frame.function.startswith("test_"): return frame.function return "unknown" class Fail: def __init__(self, result=None): self._result = result self._name = _caller_test_name() def expected(self, value): self._expected = value return self def got(self, value): self._emit(f"expected {self._expected!r}, got {value!r}") def error(self, msg): self._emit(msg) def _emit(self, reason): ui.log_error(f" \\[{self._name}] {reason}") ui.record_failure( self._name, self._name, reason, self._result, ) with check: check.fail(f"[{self._name}] {reason}")