Skip to content

SDK-7138: add browser.uploadAttachment / uploadMedia to the WebdriverIO service - #193

Open
harshit-browserstack wants to merge 4 commits into
mainfrom
fix/SDK-7138-wdio-upload-attachment
Open

SDK-7138: add browser.uploadAttachment / uploadMedia to the WebdriverIO service#193
harshit-browserstack wants to merge 4 commits into
mainfrom
fix/SDK-7138-wdio-upload-attachment

Conversation

@harshit-browserstack

Copy link
Copy Markdown
Collaborator

What is this about?

Adds browser.uploadAttachment(filePath) (aliased browser.uploadMedia) to the
WebdriverIO service. The command did not exist at all — not in published 8.48.0, not
on main — so driver.uploadMedia(...) threw TypeError: driver.uploadMedia is not a function and killed the caller's hook. Every sibling SDK already ships one
(BrowserStack.uploadAttachment in Java, driver.upload_attachment in Python,
page.uploadAttachment in Node), and the binary's webdriverio language module already
handles TEST_ATTACHMENT LogCreated entries end to end — only the service-side entry
point was missing.

Commits authored by Shivam Kumar (branch pushed 2026-08-17). This PR opens them for
review and adds independent verification; I have not modified the branch.

UploadAttachmentModule registers the command exactly as CustomTagsModule registers
setCustomTags — on AutomationFrameworkState.CREATE / HookState.POST, instantiated
from loadModules() when the testhub pipeline is up. It resolves the level
(Test / Hook / Build) plus the uuid it hangs off and emits one TEST_ATTACHMENT
LogCreated entry. The file is not copied; the binary streams it from filePath while
draining its upload queue. grpcClient.logCreatedEvent was dropping
fileName/fileSize/filePath even though the proto already carried them — also fixed.

Two robustness changes ride along: updateURLSForGRR no longer throws on a degenerate
bin-session config (which previously aborted the whole CLI bootstrap and silently disabled
every product for that run), and setConfig keeps its defaults on an empty config instead
of leaving this.config stale.

Related Jira task/s

SDK-7138 (regression of SDK-3420, Closed)

Release (mandatory for every PR — required for the ready-for-review label)

Version bump: (required — tick exactly one)

  • minor (backwards-compatible feature)
  • patch (bug fix or other small change)

Release notes type: (optional)

  • New Feature
  • Bug Fix
  • Other Improvement

Release notes (customer-facing): (optional but encouraged)

  • Added browser.uploadAttachment(filePath) (also available as browser.uploadMedia) so
    WebdriverIO tests can attach files to a test, hook, or build in Test Reporting — the same
    capability the Java, Python and Node SDKs already offer. Pass { buildAttachment: true }
    to attach to the build instead of the current test.
  • Made BrowserStack session bootstrap tolerant of an incomplete configuration response.
    Previously an empty or partial response aborted the whole bootstrap, which silently
    disabled every BrowserStack feature for that run — including custom tags and Test
    Reporting — and could leave the build with no test results.

Release notes (internal): (required — engineer-facing; what actually changed / why)

  • New UploadAttachmentModule (src/cli/modules/uploadAttachmentModule.ts) registers
    browser.uploadAttachment / browser.uploadMedia on
    AutomationFrameworkState.CREATE / HookState.POST, resolves Test/Hook/Build level plus
    the uuid, and emits a single TEST_ATTACHMENT LogCreated entry. Rejects missing files,
    non-files and >100 MB (parity with the other SDKs) without throwing at the caller.
  • grpcClient.logCreatedEvent now forwards fileName / fileSize / filePath, which the
    proto and generated types already carried but the mapper dropped.
  • The event is dispatched off the caller's stack with a bounded 10s ack observation
    (UPLOAD_ATTACHMENT_ACK_TIMEOUT_MS): awaiting the binary round-trip inline stalled the
    following a11y pre-command executeAsync scan on Chrome. See the caveat below — this
    is reduced but not fully eliminated.
  • APIUtils.updateURLSForGRR is now fully optional-tolerant per field; setConfig
    short-circuits on an empty response.config instead of throwing inside JSON.parse.
  • New unit tests: tests/cli/modules/uploadAttachmentModule.test.ts (11) and
    tests/cli/apiUtils.test.ts (4).

Checklist

  • Ready to review
  • Has it been tested locally?

PR Validations

Run Tests: Comment RUN_TESTS to trigger sanity tests.


Verification

Merge state. Branch is 67 commits behind main but git merge-tree reports a clean
merge. I built and tested the merge result locally:

test files result
main 55 8 failed, 47 passed
main + this branch 57 8 failed, 49 passed

Identical failing sets (launcher, service, crash-reporter, util,
cliUtils, cliUtils.staleBinary, funnelInstrumentation, requestUtils) — all
pre-existing on main. This branch adds 2 test files / 15 tests, all passing, and
introduces zero new failures. npm run build and eslint --ext .ts src tests both
clean on the merge result (node 18.20.8).

End-to-end, via BStackAutomation test_wdio_mocha_wrapper_upload_media_tags.py with the
service npm-linked from this branch (link provenance confirmed:
node_modules/@wdio/browserstack-service -> .../wdio-browserstack-service/packages/browserstack-service):

published 8.48.0 this branch
hooks TypeError: driver.uploadMedia is not a function x2 platforms clean
spec files 0 passed, 2 failed 2 passed, 0 failed
O11Y test runs [] ['4194886789','4194887675','4194889268','4194891201']
O11Y rollup {"passed":0,"failed":0,...} {"passed":4,"failed":0,...}
session marking ['unmarked','unmarked'] ['passed','passed']

O11Y build t39ilc2tdvh20gtjtgj9yoqi3shzzzfjlit1tjht. Companion test-side PR:
browserstack/BStackAutomation#83493.

Known caveat — intermittent Chrome stall (not fixed by this PR)

With uploadMedia active and accessibility auto-scanning on, the Chrome worker
intermittently stalls: every command issued after uploadMedia hangs until the 60s mocha
timeout, then the hub reaps the session (Session not started or terminated). Measured
across 5 end-to-end runs on this branch:

run attachment size Chrome 60s timeouts
1 0 B 2
2 (control — uploadMedia calls removed) 0
3 48 B 0
4 48 B 2
5 0 B 0

So ~40% of runs, uncorrelated with attachment size, and absent when the uploadMedia
calls are removed. Edge (no a11y) is never affected. This is the same failure mode
37f672d targeted — the off-stack dispatch reduced it but did not eliminate it. The
service-side send is genuinely fire-and-forget (recordAttachment never awaits the gRPC
round-trip), so the remaining stall most likely sits on the binary side, where the
attachment upload and the a11y scan path meet. I did not root-cause it further and I do
not think it should block this PR — the command is strictly better than the TypeError
it replaces — but it needs its own ticket before uploadMedia is recommended alongside
accessibility auto-scanning.

Note: the branch carries a hand-written .changeset/sdk-7138-upload-attachment.md, while
changeset-from-pr.yml also generates .changeset/pr-<number>.md from the Release section
above. Worth confirming that does not double-bump.

🤖 Generated with Claude Code

…a (SDK-7138)

WebdriverIO had no way to attach a file to a test, hook or build in Test
Reporting. Every sibling SDK ships one (BrowserStack.uploadAttachment in Java,
driver.upload_attachment in Python, page.uploadAttachment in Node), and the
binary's webdriverio language module already handles TEST_ATTACHMENT LogCreated
entries end to end -- only the service-side entry point was missing, so
driver.uploadMedia(...) threw "is not a function" and killed the customer's
hook.

UploadAttachmentModule registers the command the same way CustomTagsModule
registers setCustomTags: on AutomationFrameworkState.CREATE / HookState.POST,
instantiated from loadModules() when the testhub pipeline is up. It resolves the
level (Test / Hook / Build) plus the uuid it hangs off, and emits one
TEST_ATTACHMENT LogCreated entry. The file is not copied -- the binary streams
it from filePath while draining its upload queue, which can outlive this
process.

grpcClient.logCreatedEvent was dropping fileName / fileSize / filePath on the
floor even though the proto and generated types already carry them; without
that the binary has nothing to stream.

Also hardens CLI bootstrap against a degenerate bin-session response, observed
on parallel workers alongside this bug: an empty config made JSON.parse throw in
setConfig, and updateURLSForGRR then dereferenced the undefined config and threw
out of loadModules. That aborted the entire bootstrap, so no module loaded --
custom tags, observability and the rest silently went away and the build
recorded no test results. Both sites now degrade to defaults instead.

Verified against the SDK-7138 reproduction (wdio_mocha upload-media/custom-tags
spec, @wdio/browserstack-service built from main): uploadMedia and
uploadAttachment both register, before-all/after-all hooks and the first test
run clean where they previously died in "before all".
…138)

uploadAttachment runs inside the customer's test body and awaited the binary's
LogCreated ack with no bound, so a wedged binary would stall the calling test
until the framework's own timeout fired. Race the ack against a 10s budget: the
event is already on the wire when the timer wins, so nothing is dropped.

Also re-arm the logCreatedEvent mock per test — afterEach's restoreAllMocks
drops the implementation, so every test after the first was getting a
non-promise back from the ack.
…er's stack (SDK-7138)

uploadAttachment is called from the customer's test body and the next statement is
usually a browser command that the accessibility module wraps with a pre-command scan.
Awaiting the binary round-trip on that stack stalled the following executeAsync scan
under load: chrome sessions issued the scan and then no further WebDriver request,
until the framework timeout fired and the hub reaped the session (reproduced 4/4 in
BStackAutomation at logLevel warn; absent 2/2 with the uploadMedia calls removed).

The ack carries nothing the caller can act on — the binary streams the file from
filePath while draining its own upload queue — so the event is written and its ack
observed off-stack, still bounded so a wedged binary cannot leak a pending timer.
@coderabbitai

coderabbitai Bot commented Sep 9, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Central YAML (base), Organization UI (inherited), Workspace UI (inherited)

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: f71a8b43-e01f-4330-83bb-0b7070fbf5ff

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

@harshit-browserstack

Copy link
Copy Markdown
Collaborator Author

Duplicate changesets — one is truncated and would ship a broken release note

This PR carries two changeset files describing the same two changes:

  • .changeset/sdk-7138-upload-attachment.md — complete
  • .changeset/pr-193.md — a partial copy whose first bullet is cut off mid-sentence:
- Added `browser.uploadAttachment(filePath)` (also available as `browser.uploadMedia`) so
- Made BrowserStack session bootstrap tolerant of an incomplete configuration response.

Both are minor, so the version bump itself is fine — changesets takes the highest bump rather than summing. The problem is the changelog: both entries get emitted, so the published notes would carry the description twice, once ending at the word "so".

Suggest deleting .changeset/pr-193.md and keeping sdk-7138-upload-attachment.md, which has the full text for both bullets.

Worth noting the second bullet is doing real work and deserves to survive intact — "an empty or partial response aborted the whole bootstrap, which silently disabled every BrowserStack feature for that run — including custom tags and Test Reporting" is a meaningful robustness fix that a reader shouldn't have to infer from a duplicate.

🤖 Generated with Claude Code

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.

2 participants