From 907fff475e34bf453887655335d42dbad0b191da Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Wed, 3 Dec 2025 09:28:03 -0800 Subject: [PATCH] Patch anti-bot-detection/stealth-mode into playwright browser --- examples/browser_tool_example.py | 24 +++++++++++++ libs/python/agent/agent/tools/browser_tool.py | 19 ++++++++++ .../computer_server/browser.py | 35 +++++++++++++++++++ libs/xfce/Dockerfile | 2 +- libs/xfce/Dockerfile.dev | 2 +- 5 files changed, 80 insertions(+), 2 deletions(-) diff --git a/examples/browser_tool_example.py b/examples/browser_tool_example.py index 70055ff9..e5767f27 100644 --- a/examples/browser_tool_example.py +++ b/examples/browser_tool_example.py @@ -48,6 +48,14 @@ async def test_browser_tool(): logger.info("Testing Browser Tool...") try: + # Test 0: Take a screenshot (pre-init) + logger.info("Test 0: Taking a screenshot...") + screenshot_bytes = await browser.screenshot() + screenshot_path = Path(__file__).parent / "browser_screenshot_init.png" + with open(screenshot_path, "wb") as f: + f.write(screenshot_bytes) + logger.info(f"Screenshot captured: {len(screenshot_bytes)} bytes") + # Test 1: Visit a URL logger.info("Test 1: Visiting a URL...") result = await browser.visit_url("https://www.trycua.com") @@ -56,6 +64,22 @@ async def test_browser_tool(): # Wait a bit for the page to load await asyncio.sleep(2) + # Test 2: Take a screenshot + logger.info("Test 2: Taking a screenshot...") + screenshot_bytes = await browser.screenshot() + screenshot_path = Path(__file__).parent / "browser_screenshot.png" + with open(screenshot_path, "wb") as f: + f.write(screenshot_bytes) + logger.info(f"Screenshot captured: {len(screenshot_bytes)} bytes") + + # Wait a bit + await asyncio.sleep(1) + + # Test 3: Visit bot detector + logger.info("Test 3: Visiting bot detector...") + result = await browser.visit_url("https://bot-detector.rebrowser.net/") + logger.info(f"Visit URL result: {result}") + # Test 2: Web search logger.info("Test 2: Performing a web search...") result = await browser.web_search("Python programming") diff --git a/libs/python/agent/agent/tools/browser_tool.py b/libs/python/agent/agent/tools/browser_tool.py index 85b6ba23..a1bf2090 100644 --- a/libs/python/agent/agent/tools/browser_tool.py +++ b/libs/python/agent/agent/tools/browser_tool.py @@ -114,3 +114,22 @@ class BrowserTool: Response dictionary with success status and current URL """ return await self._execute_command("web_search", {"query": query}) + + async def screenshot(self) -> bytes: + """ + Take a screenshot of the current browser page. + + Returns: + Screenshot image data as bytes (PNG format) + """ + import base64 + + result = await self._execute_command("screenshot", {}) + if result.get("success") and result.get("screenshot"): + # Decode base64 screenshot to bytes + screenshot_b64 = result["screenshot"] + screenshot_bytes = base64.b64decode(screenshot_b64) + return screenshot_bytes + else: + error = result.get("error", "Unknown error") + raise RuntimeError(f"Failed to take screenshot: {error}") diff --git a/libs/python/computer-server/computer_server/browser.py b/libs/python/computer-server/computer_server/browser.py index bd0f3f7e..9789abf7 100644 --- a/libs/python/computer-server/computer_server/browser.py +++ b/libs/python/computer-server/computer_server/browser.py @@ -109,6 +109,33 @@ class BrowserManager: # Removed --kiosk to allow desktop visibility ) + # Add init script to make the browser less detectable + await self.context.add_init_script( + """const defaultGetter = Object.getOwnPropertyDescriptor( + Navigator.prototype, + "webdriver" + ).get; + defaultGetter.apply(navigator); + defaultGetter.toString(); + Object.defineProperty(Navigator.prototype, "webdriver", { + set: undefined, + enumerable: true, + configurable: true, + get: new Proxy(defaultGetter, { + apply: (target, thisArg, args) => { + Reflect.apply(target, thisArg, args); + return false; + }, + }), + }); + const patchedGetter = Object.getOwnPropertyDescriptor( + Navigator.prototype, + "webdriver" + ).get; + patchedGetter.apply(navigator); + patchedGetter.toString();""" + ) + # Get the first page or create one pages = self.context.pages if pages: @@ -167,6 +194,14 @@ class BrowserManager: await self.page.goto(search_url, wait_until="domcontentloaded", timeout=30000) return {"success": True, "url": self.page.url} + elif cmd == "screenshot": + # Take a screenshot and return as base64 + import base64 + + screenshot_bytes = await self.page.screenshot(type="png") + screenshot_b64 = base64.b64encode(screenshot_bytes).decode("utf-8") + return {"success": True, "screenshot": screenshot_b64} + else: return {"success": False, "error": f"Unknown command: {cmd}"} diff --git a/libs/xfce/Dockerfile b/libs/xfce/Dockerfile index f1605181..2b687940 100644 --- a/libs/xfce/Dockerfile +++ b/libs/xfce/Dockerfile @@ -108,7 +108,7 @@ RUN mkdir -p /home/cua/.cache && \ RUN python3.12 -m pip install cua-computer-server # Install playwright and Firefox dependencies -RUN python3.12 -m pip install playwright && \ +RUN python3.12 -m pip install rebrowser-playwright && \ python3.12 -m playwright install --with-deps firefox # Fix any cache files created by pip diff --git a/libs/xfce/Dockerfile.dev b/libs/xfce/Dockerfile.dev index c24efaf9..2eec430e 100644 --- a/libs/xfce/Dockerfile.dev +++ b/libs/xfce/Dockerfile.dev @@ -110,7 +110,7 @@ RUN python3.12 -m pip install /tmp/computer-server && \ rm -rf /tmp/computer-server # Install playwright and Firefox dependencies -RUN python3.12 -m pip install playwright && \ +RUN python3.12 -m pip install rebrowser-playwright && \ python3.12 -m playwright install --with-deps firefox # Fix any cache files created by pip