#!/usr/bin/env python3 """Set up a browser-interactive-testing session. Creates the output directory, inits the showboat report, starts a browser, and prints the DIR path. Automatically detects whether rodney can launch its own Chromium or falls back to a system-installed browser. """ import datetime import os import shutil import socket import subprocess import sys import time REMOTE_DEBUG_PORT = 9222 def find_system_browser(): """Return the path to a system Chrome/Chromium binary, or None.""" for name in ["chromium", "chromium-browser", "google-chrome", "google-chrome-stable"]: path = shutil.which(name) if path: return path return None def port_listening(port): """Check if something is already listening on the given port.""" with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.settimeout(1) return s.connect_ex(("localhost", port)) == 0 def try_connect(port): """Try to connect rodney to a browser on the given port. Returns True on success.""" result = subprocess.run( ["uvx", "rodney", "connect", "--local", f"localhost:{port}"], capture_output=True, text=True, ) if result.returncode == 0: print(f"Connected to existing browser on port {port}", file=sys.stderr) return True return False def launch_system_browser(browser_path): """Launch a system browser with remote debugging and wait for it to be ready.""" subprocess.Popen( [ browser_path, "--headless", "--disable-gpu", f"--remote-debugging-port={REMOTE_DEBUG_PORT}", "--no-sandbox", ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) # Wait for the browser to start listening for _ in range(20): if port_listening(REMOTE_DEBUG_PORT): return True time.sleep(0.25) return False def start_browser(): """Start a headless browser and connect rodney to it. Strategy order (fastest path first): 1. Connect to an already-running browser on the debug port. 2. Launch a system Chrome/Chromium (avoids rodney's Chromium download, which fails on some architectures like Linux ARM64). 3. Let rodney launch its own browser as a last resort. """ # Strategy 1: connect to an already-running browser if port_listening(REMOTE_DEBUG_PORT) and try_connect(REMOTE_DEBUG_PORT): return # Strategy 2: launch a system browser (most reliable on Linux) browser = find_system_browser() if browser: print(f"Launching system browser: {browser}", file=sys.stderr) if launch_system_browser(browser): if try_connect(REMOTE_DEBUG_PORT): return print("WARNING: system browser started but rodney could not connect", file=sys.stderr) else: print("WARNING: system browser did not start in time", file=sys.stderr) # Strategy 3: let rodney try its built-in launcher result = subprocess.run( ["uvx", "rodney", "start", "--local"], capture_output=True, text=True, ) if result.returncode == 0: print("Browser started via rodney", file=sys.stderr) return print( "ERROR: Could not start a browser. Tried:\n" f" - Connecting to localhost:{REMOTE_DEBUG_PORT} (no browser found)\n" f" - System browser: {browser or 'not found'}\n" " - rodney start (failed)\n" "Install chromium or google-chrome and try again.", file=sys.stderr, ) sys.exit(1) def ensure_gitignore_entry(entry): """Add entry to .gitignore if the file exists and the entry is missing.""" gitignore = ".gitignore" if not os.path.isfile(gitignore): return with open(gitignore, "r") as f: content = f.read() # Check if the entry (with or without trailing slash/newline variations) is already present lines = content.splitlines() if any(line.strip() == entry or line.strip() == entry.rstrip("/") for line in lines): return # Append the entry with open(gitignore, "a") as f: if content and not content.endswith("\n"): f.write("\n") f.write(f"{entry}\n") print(f"Added '{entry}' to .gitignore", file=sys.stderr) def main(): if len(sys.argv) < 3: print(f"Usage: {sys.argv[0]} ", file=sys.stderr) sys.exit(1) slug = sys.argv[1] title = sys.argv[2] # Create output directory d = f".agent-tests/{datetime.date.today()}-{slug}" os.makedirs(f"{d}/screenshots", exist_ok=True) # Ensure .rodney/ is in .gitignore (rodney stores session files there) ensure_gitignore_entry(".rodney/") # Init showboat report subprocess.run(["uvx", "showboat", "init", f"{d}/report.md", title], check=True) # Start browser start_browser() # Print the directory path (only real stdout, everything else goes to stderr) print(d) if __name__ == "__main__": main()