Skip to content

fix: attach() is broken with Playwright 1.61.0 and above - #237

Open
likemusic wants to merge 1 commit into
pestphp:4.xfrom
likemusic:fix/attach-remote-playwright-buffer
Open

fix: attach() is broken with Playwright 1.61.0 and above#237
likemusic wants to merge 1 commit into
pestphp:4.xfrom
likemusic:fix/attach-remote-playwright-buffer

Conversation

@likemusic

@likemusic likemusic commented Jul 20, 2026

Copy link
Copy Markdown

Problem

attach() fails on Playwright 1.61.0 and above:

localPaths are not allowed when the client is not local

Locator::setInputFiles() sends the attached file's local path:

$params = ['localPaths' => [$path]];

Playwright 1.61.0 added a server-side guard in fileUploadUtils.ts:

if (localPaths && !frame.attribution.playwright.options.isClientCollocatedWithServer)
  throw new Error('localPaths are not allowed when the client is not local');

isClientCollocatedWithServer: true is set only by inprocess.ts and cli/driver.ts (stdio). Every server path — createPlaywright({ isServer: true }) — leaves it falsy. This plugin drives playwright run-server over a websocket, so the flag is never set and localPaths is rejected always — regardless of machine, filesystem or host. It is not a remote-only edge case.

Reproducing it in this repository

package.json pins ^1.59.1, and 1.59.1/1.60.0 have neither the flag nor the error string — which is why tests/Browser/Webpage/AttachTest.php is currently green. Installing the version the caret already permits is enough to break it:

npm i -D playwright@1.61.1
vendor/bin/pest tests/Browser/Webpage/AttachTest.php
FAILED  Tests\Browser\Webpage\AttachTest > it may attach a file to a file input
  localPaths are not allowed when the client is not local
FAILED  Tests\Browser\Webpage\AttachTest > it may attach a file to a file input using an id selector
  localPaths are not allowed when the client is not local

Tests: 2 failed

With this patch, both pass again on 1.61.1.

What this changes

Files are sent by value via Playwright's inline payloads parameter, which is accepted regardless of how the client is connected — so this fixes the 1.61 guard and works on older versions unchanged.

Two details that matter:

  • buffer is base64. The protocol types it as binary, which over the JSON channel means base64 — the same convention this plugin already relies on when decoding screenshots in Support\Screenshot::save(). Sending raw bytes would corrupt binary uploads.
  • mimeType is populated from the file extension via Symfony\Component\Mime\MimeTypes (already a dependency, already used in LaravelHttpServer). Omitted, Playwright falls back to application/octet-stream and the application sees a different MIME type than the user attached.

Playwright caps inline payloads at 50 MB. Exceeding it previously surfaced as an opaque protocol error; it now throws FileTooLargeException naming the file and the limit. Missing or unreadable files throw FileNotReadableException rather than failing deeper in the protocol.

The logic lives in a small Support\FilePayload value object so it is directly testable, keeping Locator thin. attach()'s public signature is unchanged.

Tests

tests/Unit/Support/FilePayloadTest.php — 10 tests covering the payload shape (name/mimeType/buffer, never localPaths), base64 encoding, MIME guessing and its fallbacks, and both failure modes. These are version-independent, so they hold the fix in place on the currently pinned Playwright.

The important one is content integrity: a file containing all 256 byte values plus invalid UTF-8 sequences and trailing whitespace round-trips with a matching SHA-256. That guards the encoding step against the same class of corruption fixed in #236.

tests/Browser/Webpage/AttachTest.php is unchanged and passes on both 1.59.1 and 1.61.1.

Note: the Playwright pin

I have deliberately not touched package.json or package-lock.json here, but it is worth flagging: the pin is already ^1.59.1, which admits 1.61.x. Only package-lock.json holds this at 1.59.1, and CI uses npm ci — so CI will keep passing either way and will not exercise the failing case above. Any contributor running a plain npm i, or a Dependabot lock bump, hits the break.

If you would like CI to actually cover this, the lockfile needs bumping to 1.61.x — happy to add that commit, but I did not want to fold a dependency decision into a bugfix PR.

Note on scope — this is one of two blockers

Getting an upload all the way into a Laravel app needs a second, independent fix: LaravelHttpServer::handleRequest() parses only application/x-www-form-urlencoded and passes [] // @TODO files... to Request::create(), so a multipart/form-data submission arrives with neither fields nor files. That half is already solved by #200 (approved, widely confirmed) — this PR deliberately does not duplicate it, and the two touch disjoint files (Locator.php vs LaravelHttpServer.php).

I verified the pair locally by merging this branch onto #200 and asserting that an attached file reaches $request->file() with matching name, size and SHA-256, alongside a sibling text field. It passes with both and fails with either alone. That end-to-end test is not included here because it would fail on 4.x until #200 lands — happy to contribute it to #200 instead.

Related: #131 also replaces localPaths with a buffer, but as a side-note inside a larger feature (currently conflicting) and without base64 encoding or a MIME type. #232 (multi-file attach()) is orthogonal and composes with this.

`Locator::setInputFiles()` sends the attached file's local path via the
`localPaths` parameter. Playwright 1.61.0 added a server-side guard that
rejects it unless `isClientCollocatedWithServer` is set — a flag only set
by the in-process and stdio drivers, never by `run-server`. Since this
plugin always connects to `run-server` over a websocket, every upload
fails with "localPaths are not allowed when the client is not local".

Files are now sent by value as an inline `payloads` entry, which is
accepted regardless of how the client is connected. The `buffer` field is
typed as binary by the protocol, which is base64 over the JSON channel —
the same encoding the plugin already assumes when decoding screenshots in
`Support\Screenshot`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@likemusic
likemusic force-pushed the fix/attach-remote-playwright-buffer branch from 07278fa to 9e758b2 Compare July 20, 2026 00:23
@likemusic likemusic changed the title fix: upload files by value so attach() works with a remote Playwright server fix: attach() is broken with Playwright 1.61.0 and above Jul 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant