Skip to content

Add setInputFiles - #2559

Merged
monadoid merged 10 commits into
v4-spikefrom
add-setInputFiles
Aug 4, 2026
Merged

Add setInputFiles#2559
monadoid merged 10 commits into
v4-spikefrom
add-setInputFiles

Conversation

@monadoid

@monadoid monadoid commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Summary

add file input upload support to all three SDKs.

// same syntax as v3
await page.locator("input[type=file]").setInputFiles("./resume.pdf");
await page.locator("input[type=file]").set_input_files("./resume.pdf")
err := page.Locator(`input[type=file]`).SetInputFiles(
    ctx,
    stagehand.FilePath("./resume.pdf"),
)

Supports local paths, multiple files, in-memory payloads, and clearing the selection. Also adds runnable, model-free examples and end-to-end coverage.


Summary by cubic

Adds cross-SDK file uploads on locators: setInputFiles (TS), set_input_files (Python), and SetInputFiles (Go). Supports local paths, in-memory payloads, multiple files, and clearing; the protocol enforces a strict per-file 50 MiB decoded limit.

  • New Features
    • TypeScript: locator.setInputFiles(files) with FileInput/FilePayload normalization (base64 transport, size checks, non-negative lastModified); types exported; example, integration, and unit tests plus a Browserbase smoke test for remote upload and clearing.
    • Python: locator.set_input_files(files) with FileInput/FilePayload dataclass normalization; omits unset metadata in RPC; example and tests added; types exported from stagehand.
    • Go: locator.SetInputFiles(ctx, files...) with FilePath/FileData; reads, validates, and base64-encodes files; example and tests added.
    • Protocol/Server: New RPC locator.set_input_files; InputFilePayload (base64 data, optional mimeType/lastModified) with decoded-size enforcement; router/controller/runtime wiring added; server decodes to bytes and forwards to the runtime locator; empty arrays clear via payload injection.
    • Docs: Updated v4 locator reference (TS and Python) with parameter shapes, limits, and examples.

Written for commit ef13797. Summary will update on new commits.

Review in cubic

@changeset-bot

changeset-bot Bot commented Aug 3, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: ef13797

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@monadoid

monadoid commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

@cubic-dev-ai

@cubic-dev-ai

cubic-dev-ai Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai

@monadoid I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 30 files

Architecture diagram
sequenceDiagram
    participant SDK as SDK Client (TS/Py/Go)
    participant FileSys as Local Filesystem
    participant Normalizer as FileNormalizer
    participant RPC as RPC Client
    participant Server as Stagehand Server
    participant Runtime as StagehandRuntime
    participant Understudy as Understudy Locator
    participant Browser as Browser (CDP Session)

    Note over SDK,Browser: File Upload Flow via locator.setInputFiles

    SDK->>SDK: User calls setInputFiles(pathOrPayload)
    SDK->>FileSys: Read file (stat, readFile) for path inputs
    FileSys-->>SDK: file buffer + metadata
    SDK->>Normalizer: normalizeFileInput()
    Normalizer->>Normalizer: Convert to InputFilePayload (base64 data, name, mimeType, lastModified)
    alt Empty array passed
        Normalizer->>Normalizer: Return empty payload list (clear selection)
    end
    Normalizer-->>SDK: InputFilePayload[]

    SDK->>RPC: send("locator.set_input_files", {page_id, selector, files: [...]})
    RPC->>Server: JSON-RPC request (method: "locator.set_input_files")

    Server->>Runtime: locatorSetInputFiles(params)
    Runtime->>Runtime: Resolve locator from pageId + selector
    Runtime->>Runtime: Decode base64 → Uint8Array buffer
    Runtime->>Understudy: setInputFiles([{name, mimeType, buffer, lastModified}])

    Understudy->>Browser: Resolve objectId for input selector
    Understudy->>Browser: CDP Input.dispatchFileInput / setFileInputFiles
    Browser-->>Understudy: File(s) attached to input element

    Understudy-->>Runtime: void (success)
    Runtime-->>Server: { set: true }
    Server-->>RPC: JSON-RPC response
    RPC-->>SDK: { set: true }
    SDK-->>SDK: Resolve Promise/await

    Note over Understudy,Browser: For empty files: clear selection via CDP
    Understudy->>Browser: DOM.setFileInputFiles with empty array (or file payload injection)
Loading

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/server/runtime.ts Outdated
Comment thread packages/sdk-python/src/stagehand/_generated/models.py
Comment thread packages/server/rpcRouter.ts
Comment thread packages/docs/v4/reference/locator.mdx Outdated
Comment thread packages/sdk-ts/examples/file-upload.ts
Comment thread packages/protocol/schemas.ts Outdated
Comment thread packages/sdk-ts/src/fileUpload.ts
Comment thread packages/sdk-python/src/stagehand/file_upload.py Outdated
Comment thread packages/sdk-go/locator.go
Comment thread packages/sdk-python/src/stagehand/file_upload.py Outdated
@monadoid
monadoid marked this pull request as ready for review August 3, 2026 22:06
@monadoid
monadoid requested a review from a team as a code owner August 3, 2026 22:06

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

13 issues found across 33 files

Confidence score: 3/5

  • The highest-risk gap is around instrumentation coverage for the new upload API: locator.set_input_files appears not to be fully enforced by the flow-logger contract in packages/protocol/tests/protocol/schema-registry.test-d.ts, so calls could bypass expected tracing/observability and make debugging or policy enforcement harder — instrument all newly exposed page/locator methods and lock it with schema-registry checks.
  • Behavior changed in packages/server/understudy/locator.ts without integration coverage, and packages/server/tests/stagehand-clients.test.ts currently misses locator.nth(...).setInputFiles(...) visibility, so routing/clearing regressions can slip through undetected — add server integration tests for the new clearing semantics and nth-path call assertions.
  • Large upload paths in packages/sdk-ts/src/fileUpload.ts and packages/server/runtime.ts do avoidable parallel read/encode/decode work, which can spike memory/CPU and increase timeout/OOM risk on big payloads — switch to sequential or bounded-concurrency reads and avoid redundant base64 transforms across RPC/runtime boundaries.
  • Cross-SDK edge handling is still inconsistent: packages/sdk-go/locator.go can bypass the 50 MiB limit if files change after Stat, packages/sdk-python/src/stagehand/file_upload.py can leak raw OS read errors, and packages/sdk-ts/src/fileUpload.ts can emit invalid negative lastModified for pre-1970 files — add post-read size validation, normalize timestamps, and map local file-read failures to stable SDK error types.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/server/understudy/locator.ts">

<violation number="1" location="packages/server/understudy/locator.ts:99">
P2: Custom agent: **Any breaking changes to Stagehand REST API client / server implementation must be covered by an integration test under packages/server/test**

This PR changes the server-side clearing behavior for `locator.setInputFiles` (empty array case) to route through `assignFilesViaPayloadInjection`. That changed code path should be covered by a server integration test in `packages/server/tests/` so regressions in the clearing branch are caught at the RPC/runtime boundary. Consider adding an empty-array variant to the existing `locator.set_input_files` handler tests.</violation>
</file>

<file name="packages/server/tests/stagehand-clients.test.ts">

<violation number="1" location="packages/server/tests/stagehand-clients.test.ts:562">
P3: Calls through `locator.nth(...).setInputFiles(...)` are not observable by this fake, so an nth-specific routing regression can pass without being detected. Sharing the call recorder across `nth()` clones or adding an assertion against the returned clone would make this new test double cover the supported nth path.</violation>
</file>

<file name="packages/sdk-ts/src/fileUpload.ts">

<violation number="1" location="packages/sdk-ts/src/fileUpload.ts:18">
P2: Large multi-file uploads can cause avoidable memory spikes because all files are read and encoded concurrently. Processing entries sequentially (or with bounded concurrency) keeps resource usage predictable.</violation>

<violation number="2" location="packages/sdk-ts/src/fileUpload.ts:36">
P3: Local files with a pre-1970 modification time cannot be uploaded: `Math.trunc(fileStat.mtimeMs)` can remain negative, while the `InputFilePayload` protocol rejects negative `lastModified` values. Normalizing this generated metadata to the protocol's valid range (or omitting it when invalid) would keep path uploads working.</violation>

<violation number="3" location="packages/sdk-ts/src/fileUpload.ts:41">
P3: The `instanceof Uint8Array` check is redundant because both branches produce the same value. Simplifying to a single `Buffer.from(file.buffer)` keeps this conversion path easier to read.</violation>
</file>

<file name="packages/protocol/tests/protocol/schema-registry.test-d.ts">

<violation number="1" location="packages/protocol/tests/protocol/schema-registry.test-d.ts:89">
P2: Custom agent: **Ensure all public methods added to the stagehand class, agent, or understudy (page, locator, etc.) interfaces are properly instrumented with the flowLogger**

The new `locator.set_input_files` browser action is currently untracked. Both the public TypeScript `Locator.setInputFiles` wrapper and the server understudy locator implementation perform the upload without a flowLogger decorator or manual flowLogger call, even though Rule 3 requires significant `locator.*` actions to be traced. Adding the equivalent flowLogger instrumentation to the new locator method would keep file uploads visible in logging and span tracking.</violation>
</file>

<file name="packages/protocol/schemas.ts">

<violation number="1" location="packages/protocol/schemas.ts:1788">
P3: The new 50 MiB decoded-size guard is not covered by the protocol tests: `object-model-protocol.test.ts` only exercises three small padding cases and malformed base64. A regression in the encoded-length calculation or the refine boundary could therefore accept an oversized payload (or reject a valid 50 MiB payload) without failing CI. Focused cases for exactly 50 MiB and 50 MiB + 1 byte would make this contract executable.

(Based on your team's feedback about unit tests for new behavior.)</violation>
</file>

<file name="packages/sdk-go/locator.go">

<violation number="1" location="packages/sdk-go/locator.go:245">
P2: The 50 MiB client guard can be bypassed when a file grows between `Stat` and `os.ReadFile`, because size is only checked pre-read. A post-read length check (or bounded read) would keep limit enforcement consistent.</violation>

<violation number="2" location="packages/sdk-go/locator.go:249">
P3: Uploading a valid local file with a pre-1970 modification time fails at the RPC boundary because `UnixMilli()` can produce a negative `last_modified`, while the protocol only accepts nonnegative values. Treat this optional metadata as absent (or return a local validation error) when it is negative, and apply the same validation to `FileInput.LastModified`.</violation>
</file>

<file name="packages/docs/v4/reference/locator.mdx">

<violation number="1" location="packages/docs/v4/reference/locator.mdx:332">
P2: Custom agent: **Stagehand docs prose guide**

The new `setInputFiles()` docs use passive voice ('Relative paths are resolved...' and 'Files are serialized...'), which violates the active-voice requirement in the Stagehand docs prose guide. Please rewrite these sentences with the actor performing the action. For example:

- 'Relative paths are resolved on the SDK caller's machine.' → 'The SDK resolves relative paths on the caller's machine.'
- 'Files are serialized in memory and limited to 50 MiB each.' → 'The SDK serializes each file in memory and limits it to 50 MiB.'

The same passive wording should also be updated in the Python `set_input_files()` section.</violation>
</file>

<file name="packages/sdk-python/src/stagehand/file_upload.py">

<violation number="1" location="packages/sdk-python/src/stagehand/file_upload.py:36">
P2: Unreadable or race-deleted files can currently bubble raw OS exceptions from file reads, which makes `set_input_files()` error behavior inconsistent for callers. Wrapping local-file stat/read in `OSError` handling and re-raising the existing `ValueError` message would keep failures deterministic.</violation>

<violation number="2" location="packages/sdk-python/src/stagehand/file_upload.py:45">
P3: Invalid sequence entries currently fail with `AttributeError` when `_normalize_file` accesses `file.name`, which is hard to diagnose from API usage. An explicit `FilePayload` type check before field access would return a clear `ValueError` for unsupported item types.</violation>
</file>

<file name="packages/server/runtime.ts">

<violation number="1" location="packages/server/runtime.ts:725">
P2: Large uploads do extra CPU/memory work because RPC base64 payloads are decoded in runtime and then encoded again in locator injection. Consider passing already-encoded payloads through this path (or adding a base64-aware locator helper) to avoid the double conversion for up-to-50 MiB files.</violation>
</file>
Architecture diagram
sequenceDiagram
    participant User as User Code (TS/Py/Go)
    participant SDK as SDK (Normalization)
    participant FS as Local Filesystem
    participant Protocol as JSON-RPC (Protocol)
    participant Server as Stagehand Server
    participant Browser as Browser (Understudy)

    Note over User,SDK: File Preparation Phase
    User->>SDK: NEW: setInputFiles(files)
    
    alt If input is file path
        SDK->>FS: resolve() & stat()
        FS-->>SDK: file metadata (mtime, size)
        opt File > 50MiB
            SDK-->>User: Throw Range/Value Error
        end
        SDK->>FS: readFile()
        FS-->>SDK: bytes
    else If input is payload/buffer
        SDK->>SDK: Validate buffer size
    end

    Note over SDK,Protocol: Serialization Phase
    SDK->>SDK: NEW: Base64 encode data
    SDK->>Protocol: Request: locator.set_input_files
    Note right of Protocol: Payload: name, mimeType, data (b64), lastModified

    Note over Protocol,Server: Server Routing Phase
    Protocol->>Server: RPCRouter: route request
    Server->>Server: locatorController: setInputFiles()
    
    Note over Server,Browser: Execution Phase
    Server->>Server: NEW: StagehandRuntime: Decode Base64 to bytes
    
    alt Files array not empty
        Server->>Browser: UnderstudyLocator: setInputFiles(normalized)
        Browser->>Browser: CDP: DOM.setFileInputFiles
    else Files array empty (Clear)
        Server->>Browser: UnderstudyLocator: setInputFiles([])
        Browser->>Browser: NEW: Payload Injection (Clear Selection)
    end
    
    Browser-->>Server: void
    Server-->>Protocol: Response: { "set": true }
    Protocol-->>SDK: Response
    SDK-->>User: Resolve/Success
Loading

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/sdk-ts/src/locator.ts
Comment thread packages/docs/tests/sdk-reference.test.ts Outdated
Comment thread packages/sdk-python/src/stagehand/file_upload.py Outdated
Comment thread packages/sdk-go/locator.go Outdated
Comment thread packages/sdk-ts/src/fileUpload.ts Outdated
Comment thread packages/sdk-ts/src/fileUpload.ts Outdated
Comment thread packages/sdk-python/src/stagehand/file_upload.py
Comment thread packages/sdk-ts/src/fileUpload.ts Outdated
Comment thread packages/protocol/schemas.ts
Comment thread packages/sdk-go/locator.go
# Conflicts:
#	packages/sdk-go/internal/extensionassets/stagehand-extension.zip
#	packages/sdk-python/tests/test_rpc_client.py

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 14 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/sdk-python/src/stagehand/file_upload.py Outdated
Comment thread packages/sdk-ts/src/fileUpload.ts Outdated
# Conflicts:
#	packages/sdk-go/internal/extensionassets/stagehand-extension.zip

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 4 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread packages/sdk-python/src/stagehand/file_upload.py
# Conflicts:
#	packages/sdk-go/internal/extensionassets/stagehand-extension.zip
# Conflicts:
#	packages/docs/tests/sdk-reference.test.ts
#	packages/protocol/schema-registry.ts
#	packages/protocol/stagehand.v4.json
#	packages/sdk-go/internal/extensionassets/stagehand-extension.zip
@seanmcguire12

Copy link
Copy Markdown
Member

@monadoid could we add one bb smoke test for this? just to confirm there is no remote browser weirdness. maybe in packages/sdk-ts/tests/browser-runtime/stagehand-browserbase-smoke.test.ts

@monadoid
monadoid merged commit 27dae6d into v4-spike Aug 4, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants