{"record":{"id":"2aff53426f6981eb","repo":"microsoft/playwright","slug":"cannot-fulfill-with-redirect-status-response-st-2aff53","errorCode":null,"errorMessage":"Cannot fulfill with redirect status: ${response.status}","messagePattern":"Cannot fulfill with redirect status: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/playwright-core/src/server/webkit/wkInterceptableRequest.ts","lineNumber":127,"sourceCode":"  private readonly _session: WKSession;\n  private readonly _requestId: string;\n\n  constructor(session: WKSession, requestId: string) {\n    this._session = session;\n    this._requestId = requestId;\n  }\n\n  async abort(errorCode: string) {\n    const errorType = errorReasons[errorCode];\n    assert(errorType, 'Unknown error code: ' + errorCode);\n    // In certain cases, protocol will return error if the request was already canceled\n    // or the page was closed. We should tolerate these errors.\n    await this._session.sendMayFail('Network.interceptRequestWithError', { requestId: this._requestId, errorType });\n  }\n\n  async fulfill(response: types.NormalizedFulfillResponse) {\n    if (300 <= response.status && response.status < 400)\n      throw new Error('Cannot fulfill with redirect status: ' + response.status);\n\n    // In certain cases, protocol will return error if the request was already canceled\n    // or the page was closed. We should tolerate these errors.\n    let mimeType = response.isBase64 ? 'application/octet-stream' : 'text/plain';\n    const headers = headersArrayToObject(response.headers, true /* lowerCase */);\n    const contentType = headers['content-type'];\n    if (contentType)\n      mimeType = contentType.split(';')[0].trim();\n\n    await this._session.sendMayFail('Network.interceptRequestWithResponse', {\n      requestId: this._requestId,\n      status: response.status,\n      statusText: network.statusText(response.status),\n      mimeType,\n      headers,\n      base64Encoded: response.isBase64,\n      content: response.body\n    });","sourceCodeStart":109,"sourceCodeEnd":145,"githubUrl":"https://github.com/microsoft/playwright/blob/c8fc3bf8d31542d59b4d4d9eaab1df93ff541dc6/packages/playwright-core/src/server/webkit/wkInterceptableRequest.ts#L109-L145","documentation":"route.fulfill() rejects HTTP 3xx statuses because Playwright fulfills a request by injecting a synthetic response, and a redirect response cannot be synthesized client-side without a Location header negotiation the network layer does not perform. fulfill() throws on any status in the 300-399 range.","triggerScenarios":"Calling route.fulfill({ status: 301/302/307/308, headers, body }) inside a route handler. Also fulfilling with a copied response object whose status happens to be 3xx.","commonSituations":"Replaying a recorded HAR/response that contained a redirect. Attempting to 'mock' a redirect by fulfilling with 302 + Location. Migrating from Chrome DevTools-style redirect mocking.","solutions":["To mock a redirect, fulfill with the FINAL (2xx) response after following the redirect yourself, or use route.continue() with overridden url to redirect at the request layer.","Validate status<300 || status>=400 before calling fulfill; reject 3xx in your route logic.","If you only need the redirect chain's terminal body, capture and fulfill that body with status 200."],"exampleFix":"// before\nawait route.fulfill({ status: 302, headers: { location: '/x' } });\n\n// after\nawait route.continue({ url: 'https://site/x' });","handlingStrategy":"validation","validationCode":"function isFulfillableStatus(s) { return s < 300 || s >= 400; }\nif (isFulfillableStatus(response.status)) await route.fulfill(response);\nelse await route.continue({ url: response.headers.location });","typeGuard":"function isRedirectStatus(s: number): boolean { return s >= 300 && s < 400; }","tryCatchPattern":"try { await route.fulfill(resp); }\ncatch (e) {\n  if (/redirect status/.test(e.message)) await route.continue({ url: resp.headers.location });\n  else throw e;\n}","preventionTips":["Reject 3xx in route handlers before fulfill().","Mock redirects via route.continue({url}) instead of fulfill()."],"tags":["webkit","network","route","fulfill","redirect"],"backgroundTag":null,"analyzedSha":"c8fc3bf8d31542d59b4d4d9eaab1df93ff541dc6","analyzedAt":"2026-08-12T07:26:36.950Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}