Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
4eb12b3
feat(browserstack-service): add browser.uploadAttachment / uploadMedi…
shivamku-BS Aug 17, 2026
ee0a40a
fix(browserstack-service): bound the uploadAttachment ack wait (SDK-7…
shivamku-BS Aug 17, 2026
37f672d
fix(browserstack-service): dispatch the attachment event off the call…
shivamku-BS Aug 17, 2026
817d036
chore(changeset): auto-generate from PR template (minor)
github-actions[bot] Sep 9, 2026
b462687
Merge branch 'fix/SDK-7138-wdio-upload-attachment' onto main
harshit-browserstack Sep 29, 2026
fc2d81e
fix(SDK-7138): address PR review findings
harshit-browserstack Sep 29, 2026
10bc360
chore(changeset): auto-generate from PR template (minor)
github-actions[bot] Sep 29, 2026
ba987e2
chore(changeset): auto-generate from PR template (minor)
github-actions[bot] Sep 29, 2026
54cfbbd
chore(changeset): auto-generate from PR template (patch)
github-actions[bot] Sep 29, 2026
e4dc5cd
fix(SDK-7138): snapshot attachments, never-throwing uploadMedia, unre…
harshit-browserstack Sep 29, 2026
8274fc6
fix(deps): raise chalk floor to ^5.6.2 to exclude malicious 5.6.1
Sep 29, 2026
706da2a
chore(changeset): auto-generate from PR template (patch)
github-actions[bot] Sep 29, 2026
a5fb1d8
fix(SDK-7138): per-worker, per-run attachment snapshot folder
harshit-browserstack Sep 30, 2026
c844aac
Merge pull request #260 from browserstack/fix/SDK-7138-wdio-upload-at…
bsautomation Sep 30, 2026
85b672b
Merge pull request #259 from browserstack/security/chalk-mal-2025-46969
rahulpsq Sep 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/pr-193.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@wdio/browserstack-service": patch
---

- 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.
5 changes: 5 additions & 0 deletions .changeset/pr-255.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@wdio/browserstack-service": patch
---

- Raised the minimum `chalk` version to 5.6.2 so installs can never resolve the compromised `chalk@5.6.1` (GHSA-2v46-p5h4-248w).
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/browserstack-service/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
"@wdio/reporter": "^9.0.0",
"@wdio/types": "^9.0.0",
"browserstack-local": "^1.5.1",
"chalk": "^5.3.0",
"chalk": "^5.6.2",
"csv-writer": "^1.6.0",
"git-repo-info": "^2.1.1",
"gitconfiglocal": "^2.1.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,13 @@
declare namespace WebdriverIO {
interface Browser {
setCustomTags: (key: string, value: string) => Promise<void>
uploadAttachment: (filePath: string, options?: { buildAttachment?: boolean }) => Promise<void>
uploadMedia: (filePath: string, options?: { buildAttachment?: boolean }) => Promise<void>
}

interface MultiRemoteBrowser {
setCustomTags: (key: string, value: string) => Promise<void>
uploadAttachment: (filePath: string, options?: { buildAttachment?: boolean }) => Promise<void>
uploadMedia: (filePath: string, options?: { buildAttachment?: boolean }) => Promise<void>
}
}
62 changes: 51 additions & 11 deletions packages/browserstack-service/src/cli/apiUtils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { BStackLogger } from './cliLogger.js'

export default class APIUtils {
static FUNNEL_INSTRUMENTATION_URL = 'https://api.browserstack.com/sdk/v1/event'
static BROWSERSTACK_AUTOMATE_API_URL = 'https://api.browserstack.com'
Expand All @@ -10,16 +12,54 @@ export default class APIUtils {
static UPLOAD_LOGS_ADDRESS = 'https://upload-observability.browserstack.com'
static EDS_URL = 'https://eds.browserstack.com'

static updateURLSForGRR(apis: GRRUrls) {
this.FUNNEL_INSTRUMENTATION_URL = `${apis.automate.api}/sdk/v1/event`
this.BROWSERSTACK_AUTOMATE_API_URL = apis.automate.api
this.BROWSERSTACK_AA_API_URL = apis.appAutomate.api
this.BROWSERSTACK_PERCY_API_URL = apis.percy.api
this.BROWSERSTACK_AUTOMATE_API_CLOUD_URL = apis.automate.upload
this.BROWSERSTACK_AA_API_CLOUD_URL = apis.appAutomate.upload
this.APP_ALLY_ENDPOINT = `${apis.appAccessibility.api}/automate`
this.DATA_ENDPOINT = apis.observability.api
this.UPLOAD_LOGS_ADDRESS = apis.observability.upload
this.EDS_URL = apis.edsInstrumentation.api
/**
* Overlay the binary-supplied GRR endpoints onto the public defaults. Every field is
* optional: a degenerate StartBinSession/ConnectBinSession config (auth failure, empty
* payload) used to throw here and abort the whole CLI bootstrap, taking every product
* module with it. Missing entries now just leave the corresponding default in place.
*/
static updateURLSForGRR(apis?: GRRUrls) {
if (!apis) {
BStackLogger.debug('updateURLSForGRR: no apis in the bin-session config; keeping default endpoints')
return
}
const missing = [
['automate.api', apis.automate?.api], ['automate.upload', apis.automate?.upload],
['appAutomate.api', apis.appAutomate?.api], ['appAutomate.upload', apis.appAutomate?.upload],
['percy.api', apis.percy?.api], ['appAccessibility.api', apis.appAccessibility?.api],
['observability.api', apis.observability?.api], ['observability.upload', apis.observability?.upload],
['edsInstrumentation.api', apis.edsInstrumentation?.api]
].filter(([, value]) => !value).map(([name]) => name)
if (missing.length) {
BStackLogger.debug(`updateURLSForGRR: keeping default endpoints for ${missing.join(', ')}`)
}
if (apis.automate?.api) {
this.FUNNEL_INSTRUMENTATION_URL = `${apis.automate.api}/sdk/v1/event`
this.BROWSERSTACK_AUTOMATE_API_URL = apis.automate.api
}
if (apis.automate?.upload) {
this.BROWSERSTACK_AUTOMATE_API_CLOUD_URL = apis.automate.upload
}
if (apis.appAutomate?.api) {
this.BROWSERSTACK_AA_API_URL = apis.appAutomate.api
}
if (apis.appAutomate?.upload) {
this.BROWSERSTACK_AA_API_CLOUD_URL = apis.appAutomate.upload
}
if (apis.percy?.api) {
this.BROWSERSTACK_PERCY_API_URL = apis.percy.api
}
if (apis.appAccessibility?.api) {
this.APP_ALLY_ENDPOINT = `${apis.appAccessibility.api}/automate`
}
if (apis.observability?.api) {
this.DATA_ENDPOINT = apis.observability.api
}
if (apis.observability?.upload) {
this.UPLOAD_LOGS_ADDRESS = apis.observability.upload
}
if (apis.edsInstrumentation?.api) {
this.EDS_URL = apis.edsInstrumentation.api
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,6 @@ export const TestFrameworkConstants = {
DEFAULT_HOOK_RESULT : 'pending',
KIND_SCREENSHOT : 'TEST_SCREENSHOT',
KIND_LOG : 'TEST_LOG',
KIND_ATTACHMENT : 'TEST_ATTACHMENT',
HOOK_REGEX : '^(BEFORE_|AFTER_)',
}
13 changes: 13 additions & 0 deletions packages/browserstack-service/src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import WdioAutomationFramework from './frameworks/wdioAutomationFramework.js'
import WebdriverIOModule from './modules/webdriverIOModule.js'
import AccessibilityModule from './modules/accessibilityModule.js'
import CustomTagsModule from './modules/customTagsModule.js'
import UploadAttachmentModule from './modules/uploadAttachmentModule.js'
import { isTurboScale, processAccessibilityResponse, shouldAddServiceVersion } from '../util.js'
import ObservabilityModule from './modules/observabilityModule.js'
import type { BrowserstackConfig, BrowserstackOptions, LaunchResponse } from '../types.js'
Expand Down Expand Up @@ -184,6 +185,10 @@ export class BrowserstackCLI {
// to TestHub, so it is gated on the testhub pipeline being active.
this.modules[CustomTagsModule.MODULE_NAME] = new CustomTagsModule()

// Attachments ride a TEST_ATTACHMENT LogCreated event keyed on the test /
// hook uuid, so they are gated on the same pipeline.
this.modules[UploadAttachmentModule.MODULE_NAME] = new UploadAttachmentModule()

if (startBinResponse.accessibility?.success){
process.env[BROWSERSTACK_ACCESSIBILITY] = 'true'
const options = this.options as BrowserstackConfig & BrowserstackOptions
Expand Down Expand Up @@ -529,6 +534,14 @@ export class BrowserstackCLI {
*/
setConfig(response: StartBinSessionResponse) {
try {
// A degenerate bin-session response (auth failure, races on a parallel worker's
// ConnectBinSession) carries an empty config. JSON.parse would throw, leaving
// this.config on its previous value and the error indistinguishable from a
// malformed payload — keep the empty default and say so.
if (!response.config || !response.config.trim()) {
this.logger.warn('setConfig: bin session returned an empty config; continuing with defaults')
return
}
this.config = JSON.parse(response.config)
// Binary now nests apis under config.sessionData; prefer it, fall back to the flat config.apis (SDK-6821 Phase 3)
const sessionData = this.config.sessionData as { apis?: unknown } | undefined
Expand Down
Loading
Loading