Dev.to · 5 min read

Playwright connect_over_cdp Hangs 100s? Check for One Dead Tab

Playwright connect_over_cdp Hangs 100s? Check for One Dead Tab

If connect_over_cdp hangs for 100 seconds while /json/version answers instantly, Chrome is not dead. One open tab has a hung renderer, and Playwright's auto-attach is waiting on it. Find that tab over a raw browser-level websocket, close only that tab, and connect again. Do not restart Chrome. I got this wrong the first time. The write-up of the original incident is in this post on my blog; this is the version for people who just want the diagnosis and the commands. The symptom: a 100-second hang with a healthy endpoint Chrome was running with its debugging port on 9401. My script did the usual thing: from playwright.sync_api import sync_playwright CDP = "http://127.0.0.1:9401" with sync_playwright() as p: b = p.chromium.connect_over_cdp(CDP) # sits here for 100 seconds, then times out The call did not fail fast. It stalled until the 100-second timeout. Meanwhile this returned immediately: curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:9401/json/version # 200 That combination is the whole puzzle: the HTTP side of the debugging port is fine, and the Playwright handshake is not. Why "Chrome is dead" was the wrong diagnosis My first conclusion was that Chrome had died and needed a restart. That does not fit the evidence. A 200 from /json/version means the browser process is up and its DevTools server is serving requests. A dead browser does not answer. So the question changes from "is the browser alive?" to "what is Playwright waiting for?" The real cause: auto-attach to a tab that cannot answer When connect_over_cdp connects, Playwright automatically attaches to every page that is already open. If one of those pages has a hung renderer, the attach step for that page never completes, and the whole connection is dragged along until the timeout. So one bad tab, out of however many you have open, is enough to make the entire connect call look like a browser failure. The browser is fine. The tab is not. Telling browser failure from tab failure Check three layers, from the outside in: Layer Probe What a healthy answer means HTTP endpoint /json/version Browser process and CDP server are up Browser target Target.getTargets The browser itself responds over the websocket Individual page Runtime.evaluate with a trivial expression That page's renderer is responsive If the first two answer and one page does not answer the third, the browser is not the problem. That tab is. The trick is to run these probes without going through Playwright, because Playwright's own connect is the thing that hangs. Finding and closing the dead tab Connect straight to the browser websocket URL that /json/version reports, list the page targets, and probe each one with a short timeout. Three to five seconds per page is plenty; the sketch below uses five. This sketch uses the websocket-client package: import json, urllib.request, websocket PORT = 9401 ver = json.load(urllib.request.urlopen(f"http://127.0.0.1:{PORT}/json/version", timeout=3)) ws = websocket.create_connection(ver["webSocketDebuggerUrl"], timeout=5) _id = 0 def send(method, params=None, session=None): global _id _id += 1 msg = {"id": _id, "method": method, "params": params or {}} if session: msg["sessionId"] = session ws.send(json.dumps(msg)) while True: # raises WebSocketTimeoutException if nothing answers in time reply = json.loads(ws.recv()) if reply.get("id") == _id: return reply.get("result", {}) pages = [t for t in send("Target.getTargets")["targetInfos"] if t["type"] == "page"] for t in pages: try: sid = send("Target.attachToTarget", {"targetId": t["targetId"], "flatten": True})["sessionId"] send("Runtime.evaluate", {"expression": "1+1"}, session=sid) send("Target.detachFromTarget", {"sessionId": sid}) print("ok ", t["targetId"], t["url"][:60]) except websocket.WebSocketTimeoutException: print("HUNG", t["targetId"], t["url"][:60]) Note the code is syntax-checked but I have not re-run it against a live hung tab for this write-up. Whatever line prints HUNG gives you the targetId to write down. Then close just that tab on the same browser connection: send("Target.closeTarget", {"targetId": ""}) Now call connect_over_cdp again. With the dead tab gone there is nothing for auto-attach to choke on, and the connect returns normally. If the connect works but you want a clean slate, pg.goto('about:blank') on a page and pg.close() are the Playwright-level equivalents once you are attached. Why not just restart Chrome Restarting looks simpler, but if several jobs share one Chrome profile, a restart wipes the logged-in sessions and in-progress state of all the other jobs, to fix a problem that lived in one tab. Closing a single target costs nothing to the others. If you really do need to shut the browser down, ask it to leave politely with the CDP Browser.close command, for example s.send("Browser.close") on a CDP session, rather than killing it by process name. Killing by name hits every Chrome on the machine, including ones you did not mean to touch. When this diagnosis does not apply /json/version does not respond at all. Then the browser or its debugging server really is down, and a restart is the right move. You are closing the last tab. Chrome exits when its last tab closes. Open a blank tab first. The hung tab is doing real work. Save whatever result it has before you close it, because closing discards it. A short pre-connect checklist Before pointing Playwright at a long-lived Chrome: Run the curl check and confirm you get 200. If connect still stalls, stop waiting and probe Target.getTargets over the raw websocket. Probe each page with a trivial Runtime.evaluate and a 3 to 5 second cap. Close only the page that does not answer. Reconnect. Restart the browser only if step 1 fails. The longer walk-through, including the misdiagnosis and the FAQ, is in the original post: Claude Code CDP connect hang, one tab at fault. Free tools I keep on my own site: 34 calculators and how-to guides - loans, severance pay, take-home salary, savings vs deposits. New ones go out in one email, no ads.

This is a summary aggregated from Dev.to. Read the complete article on the original site:

Read full article at Dev.to

More AI & Machine Learning News