# CDP Browser Automation (Windows) Reusable Chrome DevTools Protocol pattern for browser automation on Windows. Uses `aiohttp` WebSocket + `subprocess` Chrome launch. Avoids `nodriver`/`playwright` greenlet issues on Python 3.14. ## CDP Client Class ```python class CDP: def __init__(self): self.ws = None; self.s = None; self._id = 0 async def connect_browser(self, ws_url): """Browser-level connection (for Target.createTarget etc).""" self.s = aiohttp.ClientSession() self.ws = await self.s.ws_connect(ws_url, max_msg_size=0) async def connect_page(self, ws_url): """Page-level connection (for Page.*, Runtime.*).""" self.s = aiohttp.ClientSession() self.ws = await self.s.ws_connect(ws_url, max_msg_size=0) await self.cmd("Page.enable"); await self.cmd("Runtime.enable") async def cmd(self, method, params=None): self._id += 1 await self.ws.send_json({"id":self._id,"method":method,"params":params or {}}) while True: r = await self.ws.receive_json() if r.get("id") == self._id: if "error" in r: return None return r.get("result",{}) async def js(self, expr, await_promise=False): r = await self.cmd("Runtime.evaluate", { "expression":expr,"returnByValue":True,"awaitPromise":await_promise }) if not r or r.get("exceptionDetails"): return None return r.get("result",{}).get("value") ``` ## Chrome Launch (Windows) ```python CHROME = r"C:\Program Files\Google\Chrome\Application\chrome.exe" CDP_PORT = 9229 PROFILE = r"C:\path\to\browser_profile" def launch_chrome(): # Kill existing chrome on port subprocess.run(f'for /f "tokens=5" %a in (\'netstat -ano ^| findstr :{CDP_PORT}\') do taskkill /F /PID %a', shell=True, capture_output=True, timeout=5) subprocess.run('taskkill /F /IM chrome.exe 2>nul', shell=True, capture_output=True, timeout=5) time.sleep(2) proc = subprocess.Popen([ CHROME, f"--remote-debugging-port={CDP_PORT}", f"--user-data-dir={PROFILE}", "--no-sandbox","--disable-gpu", "--no-first-run","--no-default-browser-check", "--no-restore-last-session", # CRITICAL: prevents extension page restore "--disable-blink-features=AutomationControlled", "--window-size=1280,900", "--new-window","about:blank", ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) time.sleep(4) return proc ``` ## Connect & Create Tab ```python # Browser-level async with aiohttp.ClientSession() as s: async with s.get(f"http://127.0.0.1:{CDP_PORT}/json/version") as r: b_ws = (await r.json())["webSocketDebuggerUrl"] b_cdp = CDP() await b_cdp.connect_browser(b_ws) tgt = await b_cdp.cmd("Target.createTarget", {"url":"about:blank"}) tid = tgt["targetId"] await b_cdp.close() # Page-level async with aiohttp.ClientSession() as s: async with s.get(f"http://127.0.0.1:{CDP_PORT}/json") as r: pages = await r.json() p_ws = next(pg["webSocketDebuggerUrl"] for pg in pages if pg.get("id") == tid) cdp = CDP() await cdp.connect_page(p_ws) ``` ## Common Operations ```python # Navigate await cdp.cmd("Page.navigate", {"url": url}) # Wait for load while await cdp.js("document.readyState") != "complete": await asyncio.sleep(0.5) # Click element by text await cdp.js(f""" (function(){{ var els=document.querySelectorAll('button,a,span'); for(var i=0;idbs.forEach(db=>indexedDB.deleteDatabase(db.name)));" "}catch(e){}") ```