feat: add /tabs/:tabId/upload endpoint for file attachment - #8273
Merged
Conversation
Attach a file to an upload control without going through the native OS
file dialog. Two strategies are tried in order:
1. If an <input type="file"> is already present, call Playwright
setInputFiles on it directly (works for hidden inputs).
2. Otherwise arm a filechooser listener, activate the trigger element
(ref or selector) via keyboard (focus + Enter) with a forced click
as fallback, and setFiles on the resulting chooser. Also polls for
an in-app panel <input type=file> that mounts after activation.
The panel-input path is preferred over the native chooser so a control
that surfaces both (e.g. LinkedIn's media picker) attaches the file
exactly once rather than producing a duplicate.
Paths must be visible inside the container (e.g. a bind-mounted dir);
the route guards with fs.existsSync and returns 400 file_not_found
otherwise. Runs under the same per-user and per-tab locks as the other
interaction routes.
Includes OpenAPI documentation and unit tests (request validation +
source-contract assertions).
…eout` arg The upload route had inline millisecond literals (4000, 12000, 3000, 10000, 500, 1500) scattered through its two attach strategies. Replace them with named UPLOAD_*_MS constants declared next to the route, and expose the overall wait budget as an optional `timeout` request field. - UPLOAD_UI_TIMEOUT_MS (default 12000) backs the request's `timeout`: the budget to wait for an upload UI (panel input or native chooser). Non-numeric / <= 0 values fall back to the default. - The panel-poll window derives from that budget minus UPLOAD_PANEL_MARGIN_MS, preserving the original 10000/12000 split so a late native chooser is still caught after polling stops. - UPLOAD_INPUT/FOCUS/CLICK/REFS/POLL/SETTLE_MS name the per-call bounds. Defaults reproduce the previous behavior exactly. OpenAPI documents the new `timeout` field; tests cover timeout resolution and assert the route carries no bare millisecond literals.
Contributor
|
Thanks, @leoneparise — PR #8273, “feat: add Your original commits are preserved in merge commit Thank you for the implementation and the careful upload-strategy handling. |
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.
Summary
Adds a
POST /tabs/:tabId/uploadendpoint that attaches a file to an upload control without going through the native OS file dialog (which isn't answerable in a headless/containerized browser).How it works
Two strategies are tried in order:
<input type="file">is already present, call PlaywrightsetInputFileson it directly (works for hidden inputs, skips the OS dialog entirely).filechooserlistener, activate the trigger element (reforselector) via keyboard (focus+Enter) with a forced click as fallback, then either:setFileson the resulting native file chooser, or<input type=file>that mounts a beat after activation andsetInputFileson it.The panel-input path is preferred over the native chooser (preference order, not a race) so a control that surfaces both — e.g. LinkedIn's media picker, which A/B-tests a native chooser and a hidden panel input for the same button — attaches the file exactly once rather than producing a duplicate ("1 of 2") image.
Request
POST /tabs/:tabId/upload { "userId": "agent1", "path": "/data/photo.png", // absolute container-side path, or string[] "ref": "e36", // optional trigger ref "selector": "button.upload" // optional trigger selector }pathmust be visible inside the container (e.g. a bind-mounted directory). The route guards withfs.existsSyncand returns400 file_not_foundotherwise.ref/selectorare optional when aninput[type=file]already exists.Runs under the same per-user (
withUserLimit) and per-tab (withTabLock) locks as the other interaction routes, refreshes element refs afterward, and emits atab:uploadplugin event.Testing
tests/unit/uploadEndpoint.test.js— request-validation logic plus source-contract assertions (route registered, container file guard, both attach strategies, locking), following thetypeKeyboardMode.test.js/navigationTimeout.test.jsconvention.openapi.json, +1 path);openapi.test.js"up to date" check passes.28 passedacross the upload + openapi suites.Purely additive — no existing routes or behavior changed.