[Bug]: connectOverCDP kills the process with an uncaught assertion when an attached target has no browserContextId
Maintainers usually reply within 1 day
Assessment
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Newbie friendliness
- 68/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- node.js, typescript
- Domain
- backend, testing-qa
Research direction
Start in packages/playwright-core/src/server/chromium/crBrowser.ts at _onAttachedToTarget and read how it handles unsupported target types and sessions paused by waitForDebugger. Reproduce with the supplied mock CDP endpoint and connect.mjs, then inspect nearby Chromium browser tests for coverage. Done means a target without browserContextId no longer crashes connectOverCDP and the paused target is detached.
Written by the indexing model from the issue text.
Description
Version
1.64.1
Steps to reproduce
Playwright crashes when a browser reports a target without a profile id, a field the protocol says is optional. The crash is uncatchable, so the whole process dies, and it leaves the target frozen so every retry fails too.
package.json
{
"name": "connect-over-cdp-repro",
"private": true,
"type": "module",
"dependencies": {
"playwright-core": "1.64.1",
"ws": "^8.22.0"
}
}
mock-cdp.mjs
// A minimal fake of a Chromium remote-debugging endpoint.
// It answers every request with an empty result, except the few connectOverCDP needs,
// and reports one shared worker target that has no browserContextId.
import http from "node:http";
import { WebSocketServer } from "ws";
const PORT = 9777;
const sharedWorker = {
attached: true,
canAccessOpener: false,
targetId: "SHARED_WORKER_1",
title: "",
type: "shared_worker",
url: "blob:http://localhost:1420/2c5b7d6e-5f82-4b1e-b9e1-563036a8446e"
};
const server = http.createServer((request, response) => {
response.setHeader("content-type", "application/json");
response.end(JSON.stringify({
Browser: "Mock/1.0",
"Protocol-Version": "1.3",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Mock",
webSocketDebuggerUrl: `ws://127.0.0.1:${ PORT }/devtools/browser/mock`
}));
});
const sockets = new WebSocketServer({ server });
sockets.on("connection", (socket) => {
const send = (message) => socket.send(JSON.stringify(message));
socket.on("message", (data) => {
const { id, method, sessionId } = JSON.parse(String(data));
const reply = (result) => send({ id, result, sessionId });
switch (method) {
case "Browser.getVersion":
reply({ jsVersion: "0", product: "Mock/1.0", protocolVersion: "1.3", revision: "0", userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Mock" });
break;
case "Target.getBrowserContexts":
reply({ browserContextIds: [] });
break;
case "Target.setAutoAttach":
reply({});
send({ method: "Target.attachedToTarget", params: { sessionId: "SESSION_SW_1", targetInfo: sharedWorker, waitingForDebugger: true } });
break;
case "Target.setDiscoverTargets":
reply({});
send({ method: "Target.targetCreated", params: { targetInfo: sharedWorker } });
break;
case "Target.attachToTarget":
reply({ sessionId: "SESSION_SW_1" });
send({ method: "Target.attachedToTarget", params: { sessionId: "SESSION_SW_1", targetInfo: sharedWorker, waitingForDebugger: true } });
break;
default:
reply({});
}
});
});
server.listen(PORT, () => console.log(`mock endpoint on http://127.0.0.1:${ PORT }`));
connect.mjs
import { chromium } from "playwright-core";
try {
const browser = await chromium.connectOverCDP("http://127.0.0.1:9777");
console.log("connectOverCDP succeeded; contexts:", browser.contexts().length);
await browser.close();
}
catch (error) {
console.log("connectOverCDP failed:\n" + error.message.split("\n").slice(0, 12).join("\n"));
process.exitCode = 1;
}
Run:
npm install
node mock-cdp.mjs &
node connect.mjs
Expected behavior
connectOverCDP resolves. A target the library cannot place in a context should be detached and ignored, which is what _onAttachedToTarget already does for targets of type other, and what shared workers end up as anyway: the method's final comment says they are intentionally left alone so that a worker waiting on Runtime.runIfWaitingForDebugger is not stalled.
Actual behavior
The process dies. The try/catch in connect.mjs never runs, because the assertion fires inside the event handler:
playwright-core/lib/coreBundle.js:885
throw new Error(message || "Assertion error");
^
Error: targetInfo: {
"attached": true,
"canAccessOpener": false,
"targetId": "SHARED_WORKER_1",
"title": "",
"type": "shared_worker",
"url": "blob:http://localhost:1420/2c5b7d6e-5f82-4b1e-b9e1-563036a8446e"
}
at assert (playwright-core/lib/coreBundle.js:885:11)
at _CRBrowser._onAttachedToTarget (playwright-core/lib/coreBundle.js:39842:9)
at _CRSession.emit (node:events:509:20)
Source: packages/playwright-core/src/server/chromium/crBrowser.ts, _onAttachedToTarget, the assert(targetInfo.browserContextId, ...) line that precedes the type dispatch. Two consequences follow from its position:
- Even target types that the method would otherwise ignore (
shared_worker,worker) can crash the client, because the assertion runs before the type is examined. - Auto-attach was requested with
waitForDebugger: true. The assertion throws before the session is detached, so nothing ever resumes the target. In the real case below, the worker stayed paused andattached: truefor the life of its document, and every laterconnectOverCDPfailed on it again.
Additional context
Proposed fix
Treat a target without a browser context like an unsupported target: detach and return, before the assertion.
_onAttachedToTarget({ targetInfo, sessionId, waitingForDebugger }) {
if (targetInfo.type === 'browser')
return;
const session = this._session.createChildSession(sessionId);
- assert(targetInfo.browserContextId, 'targetInfo: ' + JSON.stringify(targetInfo, null, 2));
+ if (!targetInfo.browserContextId) {
+ session.detach().catch(() => {});
+ return;
+ }
let context = this._contexts.get(targetInfo.browserContextId) || null;
Detaching also releases a target paused by waitForDebugger, which removes the secondary effect. With this change applied to 1.64.1 as a package patch, connect.mjs prints connectOverCDP succeeded; contexts: 1 against the mock, and the WebView2 setup above connects reliably, including when the Vite ping worker appears mid-session.
If a stricter stance is preferred for page targets, the check could detach only non-page types and keep the assertion for page, but a page without a context has not been seen; the minimal change above seems sufficient.
Environment
System:
OS: Windows 11 10.0.26300
CPU: (32) x64 AMD Ryzen 9 7945HX with Radeon Graphics
Memory: 3.61 GB / 31.21 GB
Binaries:
Node: 26.3.1 - C:\Program Files\nodejs\node.EXE
Yarn: 1.22.22 - C:\Users\hugoa\AppData\Local\pnpm\yarn.CMD
npm: 9.6.7 - C:\Program Files\nodejs\npm.CMD
pnpm: 12.15.0 - C:\Users\hugoa\AppData\Local\pnpm\bin\pnpm.CMD
Languages:
Bash: 5.2.26 - C:\Program Files\Git\usr\bin\bash.EXE
npmPackages:
playwright-core: 1.64.1 => 1.64.1
- Dominant language
- TypeScript
- Stars
- 98.5k
- Forks
- 6.7k
- Avg merge
- 16h 5m
- Merged PRs (30d)
- 286
Getting set up
- No Dockerfile or Docker Compose file
- No pull request template
- Read the contributing 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.
More from microsoft/playwright
-
Difficulty 1/5 Under an hour Newbie friendliness 70/100
microsoft/playwright#43640 ·
Maintainers usually reply within 1 day
-
[Bug]: `playwright install` hangs silently for ~8 minutes when PLAYWRIGHT_BROWSERS_PATH is not writablePossibly taken @dcrousso claimed this 3 days ago. Open
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
microsoft/playwright#43606 ·
Maintainers usually reply within 1 day
-
[Feature]: Set `this.name` in `TargetClosedError` constructorMay be free again @LeonxLJX claimed this 52 days ago, and no pull request is open. OpenP3-collecting-feedback
Difficulty 1/5 Under an hour Newbie friendliness 86/100
microsoft/playwright#42522 · 7 comments ·
Maintainers usually reply within 1 day
-
needs-triage-win
Difficulty 3/5 1-2 days Newbie friendliness 58/100
microsoft/playwright#43632 · 2 comments ·
Maintainers usually reply within 1 day
-
[Bug]: Firefox delivers other pages' mouse events to a page that captured the pointerPossibly taken @dgozman claimed this 1 day ago. Openbrowser-firefox v1.65
microsoft/playwright#43631 · 1 assignee ·
Maintainers usually reply within 1 day
All issues in microsoft/playwright
Similar issues
-
enhancement
Difficulty 2/5 1-3 hours Newbie friendliness 73/100
ajv-validator/ajv#2689 ·
-
[Bug]: IME confirmation Enter can submit the SSH port-forward dialogPossibly taken @seijikohara claimed this today. Open
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
home-assistant/frontend#55323 ·
Maintainers usually reply within 1 day
-
community first-timers-only good first issue hacktoberfest help wanted low hanging fruit up-for-grabs
Difficulty 1/5 Under an hour Newbie friendliness 72/100
lingdojo/kana-dojo#33188 · 1 comment · 5 reactions ·
Maintainers usually reply within 1 day
-
self-driving
Difficulty 1/5 1-3 hours Newbie friendliness 86/100
Maintainers usually reply within 1 day