{"record":{"id":"aaef1080e180618b","repo":"microsoft/playwright","slug":"target-page-context-or-browser-has-been-closed","errorCode":null,"errorMessage":"Target page, context or browser has been closed","messagePattern":"Target page, context or browser has been closed","errorType":"exception","errorClass":"TargetClosedError","httpStatus":null,"severity":"error","filePath":"packages/playwright-core/src/server/dispatchers/dispatcher.ts","lineNumber":365,"sourceCode":"      endTime: 0,\n      type: dispatcher._type,\n      method,\n      params: params || {},\n      timeout: validMetadata.timeout,\n      log: [],\n    };\n\n    const beforeController = dispatcher.createProgressController(callMetadata);\n    this._activeProgressControllers.set(callMetadata.id, beforeController);\n    // Be generous with the tracing timeout in case it wants to capture a screenshot, fail silently.\n    await beforeController.run(progress => sdkObject.instrumentation.onBeforeCall(progress, sdkObject), 3000).catch(() => {});\n    this._activeProgressControllers.delete(callMetadata.id);\n\n    const response: any = { id };\n    try {\n      // If the dispatcher has been disposed while running the instrumentation call, error out.\n      if (this._dispatcherByGuid.get(guid) !== dispatcher)\n        throw new TargetClosedError(sdkObject.closeReason());\n      const controller = dispatcher.createProgressController(callMetadata);\n      this._activeProgressControllers.set(callMetadata.id, controller);\n      const result = await controller.run(progress => (dispatcher as any)[method](validParams, progress), validMetadata.timeout);\n      this._activeProgressControllers.delete(callMetadata.id);\n      const validator = findValidator(dispatcher._type, method, 'Result');\n      response.result = validator(result, '', this._validatorToWireContext());\n      callMetadata.result = result;\n    } catch (e) {\n      if (isTargetClosedError(e)) {\n        const reason = sdkObject.closeReason();\n        if (reason)\n          rewriteErrorMessage(e, reason);\n      } else if (isProtocolError(e)) {\n        if (e.type === 'closed')\n          e = new TargetClosedError(sdkObject.closeReason(), e.browserLogMessage());\n        else if (e.type === 'crashed')\n          rewriteErrorMessage(e, 'Target crashed ' + e.browserLogMessage());\n      }","sourceCodeStart":347,"sourceCodeEnd":383,"githubUrl":"https://github.com/microsoft/playwright/blob/c8fc3bf8d31542d59b4d4d9eaab1df93ff541dc6/packages/playwright-core/src/server/dispatchers/dispatcher.ts#L347-L383","documentation":"TargetClosedError is the canonical Playwright error for 'the object you are talking to has gone away'. In dispatcher.ts:365 it is thrown at dispatch time when the target Dispatcher was disposed during the before-call instrumentation (this._dispatcherByGuid.get(guid) !== dispatcher after onBeforeCall) — i.e. the page/context/browser closed between the start of the call and the method invocation. The same class is reused for many close-related failures and its message is rewritten with the recorded closeReason when one exists.","triggerScenarios":"Issuing any RPC against a Page/Frame/Context/Browser whose Dispatcher was disposed while the call was being set up: the browser crashed, context.close()/browser.close() ran concurrently, the page navigated to a scheme that destroyed it, or a prior operation tore the target down. The dispatch loop detects the disposal and raises TargetClosedError (message overwritten with sdkObject.closeReason() if available).","commonSituations":"Calling page.something() while another branch called context.close(); browser crashed (OOM, SIGKILL in CI); page closed by the app (window.close, download, navigation); test teardown racing with the last assertion; remote browser dropped the connection.","solutions":["Serialize close vs. usage: await all pending operations before calling context/browser.close(), and don't use handles after close.","Detect the condition with isTargetClosedError(e) (playwright-core errors) in your catch block and treat it as end-of-session rather than retrying blindly.","For browser crashes, check logs/exit code and relaunch a fresh browser instead of reusing the closed one.","Guard long pipelines by checking page.isClosed() before non-trivial actions where appropriate."],"exampleFix":"// before: racing close with usage\ncontext.close();            // fires concurrently\nawait page.click('#x');    // TargetClosedError\n\n// after: close after work completes\nawait page.click('#x');\nawait context.close();\n\n// and handle gracefully\ncatch (e) {\n  if (isTargetClosedError(e)) { /* session ended, do not retry the same handle */ }\n  else throw e;\n}","handlingStrategy":"try-catch","validationCode":"// Avoid issuing calls against possibly-closed targets.\nasync function safeClick(page) {\n  if (page.isClosed?.()) throw new Error('page already closed');\n  await page.click('#x');\n}","typeGuard":"import { isTargetClosedError } from 'playwright-core/lib/server/errors'; // or client/errors\nfunction isTargetClosed(e: unknown): boolean {\n  return e instanceof Error && (e.name === 'TargetClosedError' || /has been closed/.test(e.message));\n}","tryCatchPattern":"try {\n  await page.click('#x');\n} catch (e) {\n  if (isTargetClosed(e)) {\n    // session is over: stop using this handle, optionally relaunch a fresh browser\n    return;\n  }\n  throw e;\n}","preventionTips":["Await all in-flight operations before closing context/browser; never close and use concurrently.","Check page.isClosed() before non-trivial actions in long pipelines.","Use isTargetClosedError() (Playwright's exported helper) to distinguish close-failures from real errors instead of retrying blindly.","On remote/browser crashes, recreate the browser rather than reusing the closed target."],"tags":["lifecycle","closed","race","protocol","target-closed"],"backgroundTag":null,"analyzedSha":"c8fc3bf8d31542d59b4d4d9eaab1df93ff541dc6","analyzedAt":"2026-08-12T07:26:36.950Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}