SDK-7138: add browser.uploadAttachment / uploadMedia to the WebdriverIO service - #193
SDK-7138: add browser.uploadAttachment / uploadMedia to the WebdriverIO service#193harshit-browserstack wants to merge 4 commits into
Conversation
…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.
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Central YAML (base), Organization UI (inherited), Workspace UI (inherited) Review profile: ASSERTIVE Plan: Enterprise Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
Duplicate changesets — one is truncated and would ship a broken release noteThis PR carries two changeset files describing the same two changes:
Both are Suggest deleting 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 |
What is this about?
Adds
browser.uploadAttachment(filePath)(aliasedbrowser.uploadMedia) to theWebdriverIO service. The command did not exist at all — not in published
8.48.0, noton
main— sodriver.uploadMedia(...)threwTypeError: driver.uploadMedia is not a functionand killed the caller's hook. Every sibling SDK already ships one(
BrowserStack.uploadAttachmentin Java,driver.upload_attachmentin Python,page.uploadAttachmentin Node), and the binary'swebdriveriolanguage module alreadyhandles
TEST_ATTACHMENTLogCreatedentries end to end — only the service-side entrypoint 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.
UploadAttachmentModuleregisters the command exactly asCustomTagsModuleregisterssetCustomTags— onAutomationFrameworkState.CREATE/HookState.POST, instantiatedfrom
loadModules()when the testhub pipeline is up. It resolves the level(Test / Hook / Build) plus the uuid it hangs off and emits one
TEST_ATTACHMENTLogCreatedentry. The file is not copied; the binary streams it fromfilePathwhiledraining its upload queue.
grpcClient.logCreatedEventwas droppingfileName/fileSize/filePatheven though the proto already carried them — also fixed.Two robustness changes ride along:
updateURLSForGRRno longer throws on a degeneratebin-session config (which previously aborted the whole CLI bootstrap and silently disabled
every product for that run), and
setConfigkeeps its defaults on an empty config insteadof leaving
this.configstale.Related Jira task/s
SDK-7138 (regression of SDK-3420, Closed)
Release (mandatory for every PR — required for the
ready-for-reviewlabel)Version bump: (required — tick exactly one)
Release notes type: (optional)
Release notes (customer-facing): (optional but encouraged)
browser.uploadAttachment(filePath)(also available asbrowser.uploadMedia) soWebdriverIO 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.
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)
UploadAttachmentModule(src/cli/modules/uploadAttachmentModule.ts) registersbrowser.uploadAttachment/browser.uploadMediaonAutomationFrameworkState.CREATE/HookState.POST, resolves Test/Hook/Build level plusthe uuid, and emits a single
TEST_ATTACHMENTLogCreatedentry. Rejects missing files,non-files and >100 MB (parity with the other SDKs) without throwing at the caller.
grpcClient.logCreatedEventnow forwardsfileName/fileSize/filePath, which theproto and generated types already carried but the mapper dropped.
(
UPLOAD_ATTACHMENT_ACK_TIMEOUT_MS): awaiting the binary round-trip inline stalled thefollowing a11y pre-command
executeAsyncscan on Chrome. See the caveat below — thisis reduced but not fully eliminated.
APIUtils.updateURLSForGRRis now fully optional-tolerant per field;setConfigshort-circuits on an empty
response.configinstead of throwing insideJSON.parse.tests/cli/modules/uploadAttachmentModule.test.ts(11) andtests/cli/apiUtils.test.ts(4).Checklist
PR Validations
Run Tests: Comment RUN_TESTS to trigger sanity tests.
Verification
Merge state. Branch is 67 commits behind
mainbutgit merge-treereports a cleanmerge. I built and tested the merge result locally:
mainmain+ this branchIdentical failing sets (
launcher,service,crash-reporter,util,cliUtils,cliUtils.staleBinary,funnelInstrumentation,requestUtils) — allpre-existing on
main. This branch adds 2 test files / 15 tests, all passing, andintroduces zero new failures.
npm run buildandeslint --ext .ts src testsbothclean on the merge result (node 18.20.8).
End-to-end, via BStackAutomation
test_wdio_mocha_wrapper_upload_media_tags.pywith theservice npm-linked from this branch (link provenance confirmed:
node_modules/@wdio/browserstack-service -> .../wdio-browserstack-service/packages/browserstack-service):8.48.0TypeError: driver.uploadMedia is not a functionx2 platforms[]['4194886789','4194887675','4194889268','4194891201']{"passed":0,"failed":0,...}{"passed":4,"failed":0,...}['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
uploadMediaactive and accessibility auto-scanning on, the Chrome workerintermittently stalls: every command issued after
uploadMediahangs until the 60s mochatimeout, then the hub reaps the session (
Session not started or terminated). Measuredacross 5 end-to-end runs on this branch:
uploadMediacalls removed)So ~40% of runs, uncorrelated with attachment size, and absent when the
uploadMediacalls are removed. Edge (no a11y) is never affected. This is the same failure mode
37f672dtargeted — the off-stack dispatch reduced it but did not eliminate it. Theservice-side send is genuinely fire-and-forget (
recordAttachmentnever awaits the gRPCround-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
TypeErrorit replaces — but it needs its own ticket before
uploadMediais recommended alongsideaccessibility auto-scanning.
Note: the branch carries a hand-written
.changeset/sdk-7138-upload-attachment.md, whilechangeset-from-pr.ymlalso generates.changeset/pr-<number>.mdfrom the Release sectionabove. Worth confirming that does not double-bump.
🤖 Generated with Claude Code