fix: attach() is broken with Playwright 1.61.0 and above - #237
Open
likemusic wants to merge 1 commit into
Open
Conversation
`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
force-pushed
the
fix/attach-remote-playwright-buffer
branch
from
July 20, 2026 00:23
07278fa to
9e758b2
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
attach()fails on Playwright 1.61.0 and above:Locator::setInputFiles()sends the attached file's local path:Playwright 1.61.0 added a server-side guard in
fileUploadUtils.ts:isClientCollocatedWithServer: trueis set only byinprocess.tsandcli/driver.ts(stdio). Every server path —createPlaywright({ isServer: true })— leaves it falsy. This plugin drivesplaywright run-serverover a websocket, so the flag is never set andlocalPathsis rejected always — regardless of machine, filesystem or host. It is not a remote-only edge case.Reproducing it in this repository
package.jsonpins^1.59.1, and 1.59.1/1.60.0 have neither the flag nor the error string — which is whytests/Browser/Webpage/AttachTest.phpis currently green. Installing the version the caret already permits is enough to break it:With this patch, both pass again on 1.61.1.
What this changes
Files are sent by value via Playwright's inline
payloadsparameter, 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:
bufferis base64. The protocol types it asbinary, which over the JSON channel means base64 — the same convention this plugin already relies on when decoding screenshots inSupport\Screenshot::save(). Sending raw bytes would corrupt binary uploads.mimeTypeis populated from the file extension viaSymfony\Component\Mime\MimeTypes(already a dependency, already used inLaravelHttpServer). Omitted, Playwright falls back toapplication/octet-streamand 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
FileTooLargeExceptionnaming the file and the limit. Missing or unreadable files throwFileNotReadableExceptionrather than failing deeper in the protocol.The logic lives in a small
Support\FilePayloadvalue object so it is directly testable, keepingLocatorthin.attach()'s public signature is unchanged.Tests
tests/Unit/Support/FilePayloadTest.php— 10 tests covering the payload shape (name/mimeType/buffer, neverlocalPaths), 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.phpis unchanged and passes on both 1.59.1 and 1.61.1.Note: the Playwright pin
I have deliberately not touched
package.jsonorpackage-lock.jsonhere, but it is worth flagging: the pin is already^1.59.1, which admits 1.61.x. Onlypackage-lock.jsonholds this at 1.59.1, and CI usesnpm ci— so CI will keep passing either way and will not exercise the failing case above. Any contributor running a plainnpm 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 onlyapplication/x-www-form-urlencodedand passes[] // @TODO files...toRequest::create(), so amultipart/form-datasubmission 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.phpvsLaravelHttpServer.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 on4.xuntil #200 lands — happy to contribute it to #200 instead.Related: #131 also replaces
localPathswith a buffer, but as a side-note inside a larger feature (currently conflicting) and without base64 encoding or a MIME type. #232 (multi-fileattach()) is orthogonal and composes with this.