microsoft/playwright · error · Error
Cannot fulfill with redirect status: ${response.status}
Error message
Cannot fulfill with redirect status: ${response.status} What it means
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.
Source
Thrown at packages/playwright-core/src/server/webkit/wkInterceptableRequest.ts:127
private readonly _session: WKSession;
private readonly _requestId: string;
constructor(session: WKSession, requestId: string) {
this._session = session;
this._requestId = requestId;
}
async abort(errorCode: string) {
const errorType = errorReasons[errorCode];
assert(errorType, 'Unknown error code: ' + errorCode);
// In certain cases, protocol will return error if the request was already canceled
// or the page was closed. We should tolerate these errors.
await this._session.sendMayFail('Network.interceptRequestWithError', { requestId: this._requestId, errorType });
}
async fulfill(response: types.NormalizedFulfillResponse) {
if (300 <= response.status && response.status < 400)
throw new Error('Cannot fulfill with redirect status: ' + response.status);
// In certain cases, protocol will return error if the request was already canceled
// or the page was closed. We should tolerate these errors.
let mimeType = response.isBase64 ? 'application/octet-stream' : 'text/plain';
const headers = headersArrayToObject(response.headers, true /* lowerCase */);
const contentType = headers['content-type'];
if (contentType)
mimeType = contentType.split(';')[0].trim();
await this._session.sendMayFail('Network.interceptRequestWithResponse', {
requestId: this._requestId,
status: response.status,
statusText: network.statusText(response.status),
mimeType,
headers,
base64Encoded: response.isBase64,
content: response.body
});View on GitHub (pinned to c8fc3bf8d3)
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.
Example fix
// before
await route.fulfill({ status: 302, headers: { location: '/x' } });
// after
await route.continue({ url: 'https://site/x' }); Defensive patterns
Strategy: validation
Validate before calling
function isFulfillableStatus(s) { return s < 300 || s >= 400; }
if (isFulfillableStatus(response.status)) await route.fulfill(response);
else await route.continue({ url: response.headers.location }); Type guard
function isRedirectStatus(s: number): boolean { return s >= 300 && s < 400; } Try / catch
try { await route.fulfill(resp); }
catch (e) {
if (/redirect status/.test(e.message)) await route.continue({ url: resp.headers.location });
else throw e;
} Prevention
- Reject 3xx in route handlers before fulfill().
- Mock redirects via route.continue({url}) instead of fulfill().
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Response body is unavailable
- New URL must have same protocol as overridden URL
- Response body is unavailable for redirect responses
- Timeout ${options.timeout}ms exceeded
- Malformed endpoint. Did you use BrowserType.launchServer met
AI-assisted analysis of microsoft/playwright@c8fc3bf8d3 (2026-08-12).
Data as JSON: /api/errors/2aff53426f6981eb.
Report an issue: GitHub.