microsoft / microsoft/playwright
[Bug]: connectOverCDP never completes when a pre-existing page target stops answering CDP commands
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 96.3k
- Forks
- 6.5k
- Avg merge
- 1d 6h
- Merged PRs (30d)
- 180
Description
### Version
playwright-core `1.63.0-alpha` (bundled with `@playwright/cli` 0.1.18 and 0.1.19). Same symptom reported earlier on 1.60.0 in #41093.
### Steps to reproduce
Reproduces with any Chromium/Edge started with a debugging port — no special page or proxy needed. The trigger is a page target that exists at connect time but no longer answers page-level CDP commands (e.g. an Edge sleeping tab or a hung renderer), so I suspend one renderer to make that state deterministic:
```bash
# 1. start a browser with remote debugging
msedge --headless=new --remote-debugging-port=9333 --user-data-dir=/tmp/repro about:blank &
# 2. suspend the renderer of ONE tab (Chromium: one renderer process per tab)
ps -eo pid,args | grep repro | grep "type=renderer" # pick one pid
kill -STOP # Windows: pssuspend
# 3. this call never returns (unpatched client)
node -e "const{chromium}=require('playwright');chromium.connectOverCDP('http://127.0.0.1:9333',{timeout:15000}).then(b=>console.log('ok'))"
# => Timeout 15000ms exceeded; call log stops at
# 4. resume that renderer
kill -CONT
# => connectOverCDP returns in ~30 ms
```
Note `TargetInfo.pid` is not usable to locate the victim (it reports 0 in headless mode / before the renderer spawns), hence the process table lookup.
### Expected behavior
`connectOverCDP` should return a browser object. A page that cannot be initialized should be reported as-is or skipped; at worst connect should fail with a clear error. It should not hang indefinitely.
### Actual behavior
`connectOverCDP` never resolves (or expires when an explicit timeout is passed). The call log ends at ``, while browser-level CDP commands still answer when sent manually, which makes it look like a protocol/handshake problem.
### Additional context
**Root cause.** Connect auto-attaches to every existing target and then awaits `CRBrowser._waitForAllPagesToBeInitialized()`, i.e. `Promise.all([..._crPages].map(p => p._page.waitForInitializedOrError()))`. For the frozen page, `Page.enable`, `Page.getFrameTree`, `Log.enable`, `Runtime.enable` and `Runtime.runIfWaitingForDebugger` are sent but never answered, so that promise never settles.
**Diagnostic signature.** Pairing sent/answered CDP ids on the browser session: page-level commands for the stuck target get no reply for 30 s+, while browser-level commands answer immediately.
**One bad tab poisons the whole connect** (3-tab browser, 4 renderer processes, 1 suspended):
| check | result |
|---|---|
| `Target.getTargets` (browser-level) | OK, 3 targets |
| `Runtime.evaluate` on the suspended tab | TIMEOUT |
| `Runtime.evaluate` on the other two tabs | `"tabA\|1+1=2"`, `"tabB\|1+1=2"` |
| `connectOverCDP` (unpatched) | FAILED after 15015 ms |
| `connectOverCDP` after `kill -CONT` | CONNECTED in 31 ms |
**Related, but different await.** #41093 shows the identical symptom on Edge 148 (WS connects, then hangs) and was closed as NOT_PLANNED for lack of a reliable repro; that reporter mentioned tabs "stuck due to loading errors or had gone into sleep mode" and that reloading every tab fixed it. PR #41128 addressed a page stuck on its *first navigation commit*, which is not the await that hangs here. #41397 (prerender activation) is a different mechanism again.
**Why it matters for `attach`.** In the everyday case — attaching to a user's own browser with many tabs — a single sleeping/hung tab makes every subsequent `playwright-cli attach` fail with `Daemon process exited with code 1`, until the user manually reloads/closes that tab.
**Local workaround** (not proposed as the fix): after the auto-attach sweep, drop `_crPages` entries whose targetId is absent from `Target.getTargets`, and race the initialization wait with a timeout. Connect then succeeds at the cost of a fixed 10 s delay; a targeted "skip/flag unresponsive page targets" approach would be better.
### Environment
```
System:
OS: Linux 7.0 openSUSE Tumbleweed 20260430
CPU: (20) x64 AMD Ryzen AI 9 H 365 w/ Radeon 880M
Memory: 13.23 GB / 27.02 GB
Binaries:
Node: 24.14.1 - /usr/bin/node
npm: 11.11.0 - /usr/bin/npm
npmPackages:
@playwright/cli: 0.1.18 (unpatched) / 0.1.19 (patched workaround tested)
playwright-core: 1.63.0-alpha (bundled)
Browsers:
Microsoft Edge 148.0.3967.54 launched with --remote-debugging-port=9333
```
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start at chromium.connectOverCDP and CRBrowser._waitForAllPagesToBeInitialized, then reproduce with the suspended renderer commands in the issue. Trace the auto-attach initialization wait and determine how an unresponsive page target is handled. Done means connectOverCDP returns or fails clearly despite one stuck target, with coverage for the regression.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- testing-qa
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100