{"record":{"id":"17bd2486c73f8fba","repo":"microsoft/playwright","slug":"this-closereason","errorCode":null,"errorMessage":"${this._closeReason}","messagePattern":"\\$\\{this\\._closeReason\\}","errorType":"exception","errorClass":"TargetClosedError","httpStatus":null,"severity":"error","filePath":"packages/playwright-core/src/client/fetch.ts","lineNumber":182,"sourceCode":"  }\n\n  async put(url: string, options?: RequestWithBodyOptions): Promise<APIResponse> {\n    return await this.fetch(url, {\n      ...options,\n      method: 'PUT',\n    });\n  }\n\n  async fetch(urlOrRequest: string | api.Request, options: FetchOptions = {}): Promise<APIResponse> {\n    const url = isString(urlOrRequest) ? urlOrRequest : undefined;\n    const request = isString(urlOrRequest) ? undefined : urlOrRequest;\n    return await this._innerFetch({ url, request, ...options });\n  }\n\n  async _innerFetch(options: FetchOptions & { url?: string, request?: api.Request } = {}): Promise<APIResponse> {\n    return await this._wrapApiCall(async () => {\n      if (this._closeReason)\n        throw new TargetClosedError(this._closeReason);\n      assert(options.request || typeof options.url === 'string', 'First argument must be either URL string or Request');\n      assert((options.data === undefined ? 0 : 1) + (options.form === undefined ? 0 : 1) + (options.multipart === undefined ? 0 : 1) <= 1, `Only one of 'data', 'form' or 'multipart' can be specified`);\n      assert(options.maxRedirects === undefined || options.maxRedirects >= 0, `'maxRedirects' must be greater than or equal to '0'`);\n      assert(options.maxRetries === undefined || options.maxRetries >= 0, `'maxRetries' must be greater than or equal to '0'`);\n      const url = options.url !== undefined ? options.url : options.request!.url();\n      const method = options.method || options.request?.method();\n      let encodedParams = undefined;\n      if (typeof options.params === 'string')\n        encodedParams = options.params;\n      else if (options.params instanceof URLSearchParams)\n        encodedParams = options.params.toString();\n      // Cannot call allHeaders() here as the request may be paused inside route handler.\n      const headersObj = options.headers || options.request?.headers();\n      const headers = headersObj ? headersObjectToArray(headersObj) : undefined;\n      let jsonData: any;\n      let formData: channels.NameValue[] | undefined;\n      let multipartData: channels.FormField[] | undefined;\n      let postDataBuffer: Buffer | undefined;","sourceCodeStart":164,"sourceCodeEnd":200,"githubUrl":"https://github.com/microsoft/playwright/blob/c8fc3bf8d31542d59b4d4d9eaab1df93ff541dc6/packages/playwright-core/src/client/fetch.ts#L164-L200","documentation":"Thrown by APIRequestContext._innerFetch (wrapped in _wrapApiCall) when this._closeReason is truthy at call entry. _closeReason is set by dispose({ reason }), so any request issued after the APIRequestContext was disposed fails with a TargetClosedError whose message is the user-supplied (or default) reason. The browser/page/context objects carry the same pattern.","triggerScenarios":"Calling request.get/post/... (or apiRequestContext.fetch) after request.dispose([...]) has run, after the owning BrowserContext was closed, or after the test fixture tore down the APIRequestContext. The check runs before any URL/argument validation, so the close reason is the first thing surfaced.","commonSituations":"Using a shared APIRequestContext across tests and disposing it early; issuing requests in afterAll/teardown after context cleanup; leak of a context reference past page.close(); reusing a context whose underlying browser disconnected.","solutions":["Do not call fetch on a disposed APIRequestContext — create a fresh one via request.newContext() or the fixture.","Ensure ordering: perform all requests before context.dispose()/browser.close().","If you need post-close requests, allocate a new APIRequestContext tied to a still-live browser.","Check request._closeReason (or wrap calls) before issuing requests in shared/long-lived code."],"exampleFix":"// before\nconst request = await playwright.request.newContext();\nawait request.dispose();\nawait request.get('https://example.com/api'); // TargetClosedError\n\n// after\nconst request = await playwright.request.newContext();\nawait request.get('https://example.com/api');\nawait request.dispose();","handlingStrategy":"type-guard","validationCode":"// Guard before issuing requests on a shared/long-lived APIRequestContext.\nfunction isRequestContextClosed(ctx) {\n  // No public flag; emulate by tracking dispose() yourself.\n  return ctx.__disposed === true;\n}\nasync function safeFetch(ctx, url, init) {\n  if (isRequestContextClosed(ctx)) throw new Error('APIRequestContext disposed; create a new one.');\n  return ctx.fetch(url, init);\n}\n// Wrap dispose to mark:\nconst origDispose = ctx.dispose.bind(ctx);\nctx.dispose = async (o) => { ctx.__disposed = true; return origDispose(o); };","typeGuard":"import type { APIRequestContext } from 'playwright-core';\n// Track lifecycle at the type level via a branded wrapper.\ntype Live<T> = T & { __alive?: true };\ntype Disposed = { __alive?: false };\nfunction assertLive(ctx: APIRequestContext & Partial<{ __alive?: boolean }>): asserts ctx is Live<APIRequestContext> {\n  if (ctx.__alive === false) throw new Error('APIRequestContext is disposed');\n}","tryCatchPattern":"try {\n  await request.get(url);\n} catch (e) {\n  if (e?.name === 'TargetClosedError' || /closed|disposed/i.test(String(e?.message))) {\n    request = await playwright.request.newContext();\n    return request.get(url);\n  }\n  throw e;\n}","preventionTips":["Perform all requests before request.dispose()/context.close().","Don't share an APIRequestContext past its owning context's lifetime.","In tests, rely on the request fixture rather than managing lifecycle by hand.","Track disposed state explicitly if you long-live a context."],"tags":["lifecycle","fetch","api-request","closed"],"backgroundTag":null,"analyzedSha":"c8fc3bf8d31542d59b4d4d9eaab1df93ff541dc6","analyzedAt":"2026-08-12T07:26:36.950Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}