{"record":{"id":"650059b0e14378f1","repo":"grafana/k6","slug":"response-body-is-unavailable-for-redirect-response","errorCode":null,"errorMessage":"response body is unavailable for redirect responses","messagePattern":"response body is unavailable for redirect responses","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"internal/js/modules/k6/browser/common/http.go","lineNumber":663,"sourceCode":"\theaders := make(map[string]string)\n\tr.extraHeadersMu.RLock()\n\tif len(r.extraHeaders) != 0 {\n\t\tfor name, values := range r.extraHeaders {\n\t\t\theaders[strings.ToLower(name)] = strings.Join(values, \"\\n\")\n\t\t}\n\t} else {\n\t\tfor name, values := range r.headers {\n\t\t\theaders[strings.ToLower(name)] = strings.Join(values, \"\\n\")\n\t\t}\n\t}\n\tr.extraHeadersMu.RUnlock()\n\treturn headers\n}\n\n// Body returns the response body as a bytes buffer.\nfunc (r *Response) Body() ([]byte, error) {\n\tif r.status >= 300 && r.status <= 399 {\n\t\treturn nil, fmt.Errorf(\"response body is unavailable for redirect responses\")\n\t}\n\tif err := r.fetchBody(); err != nil {\n\t\treturn nil, fmt.Errorf(\"getting response body: %w\", err)\n\t}\n\n\tr.bodyMu.RLock()\n\tdefer r.bodyMu.RUnlock()\n\n\treturn r.body, nil\n}\n\n// bodySize returns the size in bytes of the response body.\nfunc (r *Response) bodySize() int64 {\n\t// Skip redirect responses\n\tif r.status >= 300 && r.status <= 399 {\n\t\treturn 0\n\t}\n","sourceCodeStart":645,"sourceCodeEnd":681,"githubUrl":"https://github.com/grafana/k6/blob/93accf6570dcd306ca5e99cc44c393ee3797761b/internal/js/modules/k6/browser/common/http.go#L645-L681","documentation":"Response.Body() deliberately refuses to return bodies for HTTP 3xx responses because the browser follows redirects internally and CDP reports redirect responses with an empty body. This is a designed, deterministic guard evaluated before any fetching: if r.status is 300-399, the call fails immediately with this message.","triggerScenarios":"Calling res.body(), res.text() or res.json() on a Response whose status is in 300-399, typically the first hop of a redirect chain (302 to login, 301 http-to-https, 304 negotiation responses).","commonSituations":"Asserting on a response object that is actually a redirect hop rather than the final document; endpoints that 302 to auth pages; caching flows returning 304; following redirects without realizing the Response object represents the redirect.","solutions":["Check res.status() first and skip body reads for any 3xx","Use the final response after the browser followed redirects (e.g. the response of the last navigation), or locate the final response via the request's redirect chain","If you must inspect redirect hop bodies, intercept them with page.route() and inspect/fulfill manually"],"exampleFix":"// before\nconst res = await page.goto('http://example.com'); // 301\nconst text = await res.text(); // throws\n\n// after\nconst res = await page.goto('http://example.com');\nconst text = (res.status() >= 300 && res.status() <= 399) ? '' : await res.text();","handlingStrategy":"validation","validationCode":"function isRedirect(res) {\n  const s = res.status();\n  return s >= 300 && s <= 399;\n}\nconst text = isRedirect(res) ? null : await res.text();","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always check res.status() for 3xx before body/text/json","Remember browser Responses for followed redirects represent the final document","Use page.route to inspect redirect hop bodies when truly needed"],"tags":["browser","http","redirect","response-body","by-design"],"backgroundTag":null,"analyzedSha":"93accf6570dcd306ca5e99cc44c393ee3797761b","analyzedAt":"2026-08-15T21:23:27.118Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}