Skip to content

route.fulfill() ignores isBase64, so binary responses arrive base64-encoded #161

Description

@ker0x

Route::fulfill() sends isBase64 through to route.fulfill(), but that option does not exist in Playwright's JS API — so every binary response served through a fulfilled route reaches the browser as the base64 string, not the decoded bytes.

Where

playwright-symfony base64-encodes binary bodies and flags them, in two places:

// src/Client/ResponseConverter.php:92
$options['body'] = base64_encode($body);
$options['isBase64'] = true;

(same in src/Client/Interception/AssetServer.php:124)

The Node server passes the options straight through:

// bin/playwright-server.js:124
fulfill: () => route.fulfill(command.options),

But route.fulfill() accepts only body (string|Buffer), contentType, headers, json, path, response, statusisBase64 appears nowhere in playwright-core@1.62.1's types.d.ts. It is a protocol-level field the Python/Java/.NET bindings set, not a public JS option. So it is dropped and the base64 text becomes the response body.

Reproduction

Fetch any font or image through an intercepted route:

const r = await fetch('/assets/…/inter-latin-wght-normal.woff2');
const b = new Uint8Array(await r.arrayBuffer());
String.fromCharCode(...b.slice(0, 8)); // "d09GMgAB"  ← base64 of "wOF2"
b.length;                              // 64344 for a 48256-byte file (4/3)

The browser then reports:

Failed to decode downloaded font: http://localhost/assets/…/inter-latin-wght-normal.woff2
OTS parsing error: invalid sfntVersion: 1680881991

1680881991 is 0x64336137, i.e. the ASCII "d3a7" — base64 text where the wOF2 magic should be.

Text types are unaffected: ResponseConverter::isBinaryContentType() excludes text/*, JSON, JS, XML and SVG, which is why HTML/CSS/JS pages work and only fonts and raster images break.

Suggested fix

Decode in the server before handing the body to Playwright:

fulfill: () => route.fulfill(
  command.options?.isBase64
    ? {...command.options, body: Buffer.from(command.options.body, 'base64'), isBase64: undefined}
    : command.options
),

Impact

In-process E2E tests render with fallback fonts and broken images, so anything asserting on rendered typography, image pixels, or layout that depends on web-font metrics is untestable. It fails silently — the request is a 200 and only the browser console shows the decode error.

Found while moving a Symfony app's E2E suite onto zenstruck/browser's PlaywrightBrowser; reproduced identically through both ResponseConverter and AssetServer, on playwright-php/playwright v1.4.0 + playwright-symfony v0.10.0.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions