Version
1.61.0 (also verified on 1.58.0; the code is unchanged on main)
Steps to reproduce
When Route.FulfillAsync is called with BodyBytes (or Path) and no explicit
content-length header, the auto-computed content-length is the length of the
base64-encoded body, not the byte length — i.e. ~33% too large.
src/Playwright/Core/Route.cs, NormalizeFulfillParametersAsync:
else if (bodyContent != null)
{
resultBody = Convert.ToBase64String(bodyContent);
isBase64 = true;
length = resultBody.Length; // <-- length of the BASE64 STRING
}
...
if (length > 0 && !resultHeaders.ContainsKey("content-length"))
{
resultHeaders["content-length"] = length.ToString(CultureInfo.InvariantCulture);
}
The Path branch has the same problem (length = resultBody.Length after
Convert.ToBase64String). The Node.js client computes it correctly
(packages/playwright-core/src/client/network.ts: length = buffer.length
before encoding), so this is specific to the .NET port. The server passes
the header through to the browser unchanged.
Minimal repro:
using System.Text;
using Microsoft.Playwright;
var playwright = await Playwright.CreateAsync();
var browser = await playwright.Chromium.LaunchAsync();
var page = await browser.NewPageAsync();
var bytes = Encoding.UTF8.GetBytes("window.x=1;"); // 11 bytes
await page.RouteAsync("**/a.js", r => r.FulfillAsync(new()
{
Status = 200,
BodyBytes = bytes,
ContentType = "application/javascript",
}));
var responseTask = page.WaitForResponseAsync("**/a.js");
await page.SetContentAsync("<script src=\"https://example.test/a.js\"></script>");
var response = await responseTask;
Console.WriteLine($"body bytes: {bytes.Length}");
Console.WriteLine($"content-length header seen by the browser: {response.Headers["content-length"]}");
Output:
body bytes: 11
content-length header seen by the browser: 16
16 is exactly Convert.ToBase64String length of an 11-byte body. The Node.js
client produces content-length: 11 for the equivalent code.
Expected behavior
content-length matches the decoded body byte length, as in the Node.js client.
Actual behavior
content-length is the base64 string length (~4/3 of the real size), for every
fulfill that relies on the auto-computed header.
Additional context
Suggested fix: compute the length from the byte count before encoding
(content.Length in the Path branch, bodyContent.Length in the byte-array
branch). The string-body branch likely also wants
Encoding.UTF8.GetByteCount(body) instead of body.Length for non-ASCII bodies.
Real-world impact: in our production setup (Linux container, caching and
re-fulfilling large JS bundles of a site behind a proxy), fulfilled scripts in
the tens of MB stalled the page silently and indefinitely — no exception, no
console/page error — and the stall disappeared once we supplied the correct
content-length explicitly. We could not reproduce the stall in a minimal
synthetic scenario (a 30 MB fulfilled script with the inflated header loads
fine on Windows against a routed origin), so we are not claiming the stall
mechanism here — only the header mismatch, which is unambiguous and diverges
from the Node.js client.
Environment
- Microsoft.Playwright 1.61.0 / 1.58.0
- Windows 11 (repro above); production on .NET 10, Linux container (mcr.microsoft.com/playwright/dotnet:v1.61.0-noble)
- Chromium (bundled)
Version
1.61.0 (also verified on 1.58.0; the code is unchanged on
main)Steps to reproduce
When
Route.FulfillAsyncis called withBodyBytes(orPath) and no explicitcontent-lengthheader, the auto-computedcontent-lengthis the length of thebase64-encoded body, not the byte length — i.e. ~33% too large.
src/Playwright/Core/Route.cs,NormalizeFulfillParametersAsync:The
Pathbranch has the same problem (length = resultBody.LengthafterConvert.ToBase64String). The Node.js client computes it correctly(
packages/playwright-core/src/client/network.ts:length = buffer.lengthbefore encoding), so this is specific to the .NET port. The server passes
the header through to the browser unchanged.
Minimal repro:
Output:
16 is exactly
Convert.ToBase64Stringlength of an 11-byte body. The Node.jsclient produces
content-length: 11for the equivalent code.Expected behavior
content-lengthmatches the decoded body byte length, as in the Node.js client.Actual behavior
content-lengthis the base64 string length (~4/3 of the real size), for everyfulfill that relies on the auto-computed header.
Additional context
Suggested fix: compute the length from the byte count before encoding
(
content.Lengthin thePathbranch,bodyContent.Lengthin the byte-arraybranch). The string-body branch likely also wants
Encoding.UTF8.GetByteCount(body)instead ofbody.Lengthfor non-ASCII bodies.Real-world impact: in our production setup (Linux container, caching and
re-fulfilling large JS bundles of a site behind a proxy), fulfilled scripts in
the tens of MB stalled the page silently and indefinitely — no exception, no
console/page error — and the stall disappeared once we supplied the correct
content-lengthexplicitly. We could not reproduce the stall in a minimalsynthetic scenario (a 30 MB fulfilled script with the inflated header loads
fine on Windows against a routed origin), so we are not claiming the stall
mechanism here — only the header mismatch, which is unambiguous and diverges
from the Node.js client.
Environment