{"record":{"id":"616632ef16c72272","repo":"microsoft/playwright","slug":"another-client-is-already-routing-websockets","errorCode":null,"errorMessage":"Another client is already routing WebSockets","messagePattern":"Another client is already routing WebSockets","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/playwright-core/src/server/dispatchers/webSocketRouteDispatcher.ts","lineNumber":66,"sourceCode":"          if (frame === this._frame)\n            this._executionContextGone();\n        }),\n        eventsHelper.addEventListener(frame._page, Page.Events.FrameDetached, (frame: Frame) => {\n          if (frame === this._frame)\n            this._executionContextGone();\n        }),\n        eventsHelper.addEventListener(frame._page, Page.Events.Close, () => this._executionContextGone()),\n        eventsHelper.addEventListener(frame._page, Page.Events.Crash, () => this._executionContextGone()),\n    );\n    WebSocketRouteDispatcher._idToDispatcher.set(this._id, this);\n    (scope as any)._dispatchEvent('webSocketRoute', { webSocketRoute: this });\n  }\n\n  static async install(progress: Progress, connection: DispatcherConnection, target: Page | BrowserContext): Promise<InitScript> {\n    const context = target instanceof Page ? target.browserContext : target;\n    let data = context.getBindingClient(kBindingName) as BindingData | undefined;\n    if (data && data.connection !== connection)\n      throw new Error('Another client is already routing WebSockets');\n    if (!data) {\n      data = { counter: 0, connection, binding: null as any };\n      data.binding = await context.exposeBinding(progress, kBindingName, (source, payload: ws.BindingPayload) => {\n        if (payload.type === 'onCreate') {\n          const contextDispatcher = connection.existingDispatcher<BrowserContextDispatcher>(context);\n          const pageDispatcher = contextDispatcher ? PageDispatcher.fromNullable(contextDispatcher, source.page) : undefined;\n          let scope: PageDispatcher | BrowserContextDispatcher | undefined;\n          if (pageDispatcher && matchesPattern(pageDispatcher, context._options.baseURL, payload.url))\n            scope = pageDispatcher;\n          else if (contextDispatcher && matchesPattern(contextDispatcher, context._options.baseURL, payload.url))\n            scope = contextDispatcher;\n          if (scope) {\n            new WebSocketRouteDispatcher(scope, payload.id, payload.url, payload.protocols, source.frame);\n          } else {\n            const request: ws.PassthroughRequest = { id: payload.id, type: 'passthrough' };\n            source.frame.evaluateExpression(progress, `globalThis.__pwWebSocketDispatch(${JSON.stringify(request)})`).catch(() => {});\n          }\n          return;","sourceCodeStart":48,"sourceCodeEnd":84,"githubUrl":"https://github.com/microsoft/playwright/blob/c8fc3bf8d31542d59b4d4d9eaab1df93ff541dc6/packages/playwright-core/src/server/dispatchers/webSocketRouteDispatcher.ts#L48-L84","documentation":"WebSocket interception works by installing a single named binding (kBindingName) on a browser context. WebSocketRouteDispatcher.install records which connection registered the binding; if a different connection then tries to install it, the guard throws. Only one client may route WebSockets per context at a time.","triggerScenarios":"Two separate Playwright connections (e.g. two MCP clients, or a client plus a second test worker) both call context.routeWebSocket() on the same shared browser context; reconnecting a new client without the previous one having removed the binding.","commonSituations":"Multiple MCP sessions attached to one shared browser context for collaborative automation; a long-lived context reused across reconnects where the prior session's routeWebSocket binding persists.","solutions":["Route WebSockets from exactly one client per browser context.","Give the second client its own browser context (newContext) so it can install its own binding.","Ensure the previous connection fully disconnects (closes the context or unroutes) before a new client routes WebSockets."],"exampleFix":"// before — two clients, same context\nawait ctx1.routeWebSocket('**/ws', handler1);\nawait ctx2.routeWebSocket('**/ws', handler2); // throws if ctx1===ctx2\n// after — separate contexts\nconst ctx2 = await browser.newContext();\nawait ctx2.routeWebSocket('**/ws', handler2);","handlingStrategy":"validation","validationCode":"// Route WebSockets from a single client per context.\n// Before a second client routes, give it its own context.\nconst ctx2 = await browser.newContext();\nawait ctx2.routeWebSocket('**/ws', handler);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Route WebSockets from exactly one connection per browser context.","When sharing a browser across MCP clients, assign each its own context.","Disconnect/unroute the prior client before a new one routes WebSockets."],"tags":["websocket","routing","multi-client"],"backgroundTag":null,"analyzedSha":"c8fc3bf8d31542d59b4d4d9eaab1df93ff541dc6","analyzedAt":"2026-08-12T07:26:36.950Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}