Summary
Inside the browser run QuickJS sandbox, response.text() returns the body's bytes joined by commas instead of the decoded text. response.json() is therefore broken for every network response.
Repro
webcmd --session <id> browser run --stdin --no-snapshot-diff <<'JS'
const pending = page.waitForResponse(r => r.url().includes('/products/1'));
await page.goto('https://dummyjson.com/products/1');
const response = await pending;
const t = await response.text();
return { type: typeof t, first80: String(t).slice(0, 80), len: String(t).length };
JS
Actual:
{
"type": "string",
"first80": "123,34,105,100,34,58,49,44,34,116,105,116,108,101,34,58,34,69,115,115,101,110,99",
"len": 5329
}
123, 34, 105, 100 are the byte values of {, ", i, d. Expected {"id":1,"title":"Essence....
Cause
src/browser/run/playwright-client/vendor/client/network.ts:732
async text(): Promise<string> {
const content = await this.body(); // a plain Uint8Array under QuickJS, not a Node Buffer
return content.toString('utf8'); // Uint8Array.toString() ignores the argument
}
Uint8Array.prototype.toString inherits Array.prototype.toString, which comma-joins the elements and discards the 'utf8' argument. There is no Node Buffer in the sandbox.
Knock-on effect
response.json() calls JSON.parse on that comma-joined string and fails with unexpected data at the end. normalizeExecutionError then matches /syntaxerror/i and reports it as BROWSER_RUN_SYNTAX_ERROR — telling the caller their program has a syntax error when it is valid JavaScript. Filed separately as #355.
Fix
The sandbox already injects a decoder for exactly this: decodeText in src/browser/run/playwright-client/quickjs-platform.ts, backed by __webcmdDecodeText in src/browser/run/runner.ts. this._platform is available on the class — see network.ts:393 for an existing use.
async text(): Promise<string> {
const content = await this.body();
return this._platform.decodeText(content);
}
The vendored client is bundled into src/browser/run/generated/playwright-client.js, so the bundle needs regenerating (see src/browser/run/playwright-client/README.md), and src/browser/run/playwright-client-build.test.ts may need updating.
Worth a regression test asserting response.text() returns decoded text for a known body.
Impact
In the internal task eval this cost roughly five wasted attempts in one scenario: the agent tried response.json(), then JSON.parse on the raw value, then several exploratory scripts, before working out that it had to decode comma-separated byte values by hand.
Summary
Inside the
browser runQuickJS sandbox,response.text()returns the body's bytes joined by commas instead of the decoded text.response.json()is therefore broken for every network response.Repro
Actual:
{ "type": "string", "first80": "123,34,105,100,34,58,49,44,34,116,105,116,108,101,34,58,34,69,115,115,101,110,99", "len": 5329 }123, 34, 105, 100are the byte values of{,",i,d. Expected{"id":1,"title":"Essence....Cause
src/browser/run/playwright-client/vendor/client/network.ts:732Uint8Array.prototype.toStringinheritsArray.prototype.toString, which comma-joins the elements and discards the'utf8'argument. There is no NodeBufferin the sandbox.Knock-on effect
response.json()callsJSON.parseon that comma-joined string and fails withunexpected data at the end.normalizeExecutionErrorthen matches/syntaxerror/iand reports it asBROWSER_RUN_SYNTAX_ERROR— telling the caller their program has a syntax error when it is valid JavaScript. Filed separately as #355.Fix
The sandbox already injects a decoder for exactly this:
decodeTextinsrc/browser/run/playwright-client/quickjs-platform.ts, backed by__webcmdDecodeTextinsrc/browser/run/runner.ts.this._platformis available on the class — seenetwork.ts:393for an existing use.The vendored client is bundled into
src/browser/run/generated/playwright-client.js, so the bundle needs regenerating (seesrc/browser/run/playwright-client/README.md), andsrc/browser/run/playwright-client-build.test.tsmay need updating.Worth a regression test asserting
response.text()returns decoded text for a known body.Impact
In the internal task eval this cost roughly five wasted attempts in one scenario: the agent tried
response.json(), thenJSON.parseon the raw value, then several exploratory scripts, before working out that it had to decode comma-separated byte values by hand.