From 5529e9ab430d08496f2cb186912177ea4947bb63 Mon Sep 17 00:00:00 2001 From: Kamalpreet Kaur Date: Mon, 8 Dec 2025 14:27:39 +0530 Subject: [PATCH 1/4] fix: multiRemoteBrowser support and session name & status marking --- .../src/accessibility-handler.ts | 2 +- .../src/insights-handler.ts | 2 +- .../wdio-browserstack-service/src/launcher.ts | 8 +- .../wdio-browserstack-service/src/service.ts | 8 +- .../wdio-browserstack-service/src/util.ts | 43 ++++++- .../tests/util.test.ts | 108 +++++++++++++++++- 6 files changed, 162 insertions(+), 9 deletions(-) diff --git a/packages/wdio-browserstack-service/src/accessibility-handler.ts b/packages/wdio-browserstack-service/src/accessibility-handler.ts index 6682b1d6b1c..aadb07b0158 100644 --- a/packages/wdio-browserstack-service/src/accessibility-handler.ts +++ b/packages/wdio-browserstack-service/src/accessibility-handler.ts @@ -105,7 +105,7 @@ class _AccessibilityHandler { const caps = (this._browser as WebdriverIO.Browser).capabilities as WebdriverIO.Capabilities this._platformA11yMeta = { - browser_name: caps.browserName, + browser_name: caps?.browserName, browser_version: caps?.browserVersion || (caps as Capabilities.DesiredCapabilities)?.version || 'latest', platform_name: caps?.platformName, platform_version: this._getCapabilityValue(caps, 'appium:platformVersion', 'platformVersion'), diff --git a/packages/wdio-browserstack-service/src/insights-handler.ts b/packages/wdio-browserstack-service/src/insights-handler.ts index 5e69c7e78eb..691d443b34b 100644 --- a/packages/wdio-browserstack-service/src/insights-handler.ts +++ b/packages/wdio-browserstack-service/src/insights-handler.ts @@ -68,7 +68,7 @@ class _InsightsHandler { this._options = _options this._platformMeta = { - browserName: caps.browserName, + browserName: caps?.browserName, browserVersion: caps?.browserVersion, platformName: caps?.platformName, caps: caps, diff --git a/packages/wdio-browserstack-service/src/launcher.ts b/packages/wdio-browserstack-service/src/launcher.ts index 1f161cd1e5d..1d49392b153 100644 --- a/packages/wdio-browserstack-service/src/launcher.ts +++ b/packages/wdio-browserstack-service/src/launcher.ts @@ -43,7 +43,8 @@ import { validateCapsWithNonBstackA11y, mergeChromeOptions, normalizeTestReportingConfig, - normalizeTestReportingEnvVariables + normalizeTestReportingEnvVariables, + isMultiRemoteCaps } from './util.js' import { getProductMap } from './testHub/utils.js' import CrashReporter from './crash-reporter.js' @@ -219,7 +220,10 @@ export default class BrowserstackLauncherService implements Services.ServiceInst await sendStart(this.browserStackConfig) try { - if (CLIUtils.checkCLISupportedFrameworks(config.framework)) { + // Detect if multi-remote and disable CLI for those sessions + const isMultiremote = isMultiRemoteCaps(capabilities as Capabilities.RemoteCapabilities) + + if (CLIUtils.checkCLISupportedFrameworks(config.framework) && !isMultiremote) { CLIUtils.setFrameworkDetail(WDIO_NAMING_PREFIX + config.framework, 'WebdriverIO') // TODO: make this constant const binconfig = CLIUtils.getBinConfig(config, capabilities, this._options, this._buildTag) await BrowserstackCLI.getInstance().bootstrap(this._options, config, binconfig) diff --git a/packages/wdio-browserstack-service/src/service.ts b/packages/wdio-browserstack-service/src/service.ts index d536b572589..ad8a363a375 100644 --- a/packages/wdio-browserstack-service/src/service.ts +++ b/packages/wdio-browserstack-service/src/service.ts @@ -12,7 +12,8 @@ import { shouldAddServiceVersion, isTrue, normalizeTestReportingConfig, - normalizeTestReportingEnvVariables + normalizeTestReportingEnvVariables, + isMultiRemoteCaps } from './util.js' import type { BrowserstackConfig, BrowserstackOptions, MultiRemoteAction, SessionResponse, TurboScaleSessionResponse } from './types.js' import type { Pickle, Feature, ITestCaseHookParameter, CucumberHook } from './cucumber-types.js' @@ -133,7 +134,10 @@ export default class BrowserstackService implements Services.ServiceInstance { this._config.key = config.key try { - if (CLIUtils.checkCLISupportedFrameworks(this._config.framework)) { + // Detect if multi-remote and disable CLI for those sessions + const isMultiremote = isMultiRemoteCaps(capabilities as Capabilities.RemoteCapabilities) + + if (CLIUtils.checkCLISupportedFrameworks(this._config.framework) && !isMultiremote) { // Connect to Browserstack CLI from worker await BrowserstackCLI.getInstance().bootstrap(this._options, this._config) diff --git a/packages/wdio-browserstack-service/src/util.ts b/packages/wdio-browserstack-service/src/util.ts index 0003c310f22..0c330811587 100644 --- a/packages/wdio-browserstack-service/src/util.ts +++ b/packages/wdio-browserstack-service/src/util.ts @@ -1024,7 +1024,15 @@ export function getUniqueIdentifierForCucumber(world: ITestCaseHookParameter): s } export function getCloudProvider(browser: WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser): string { - if (browser.options && browser.options.hostname && browser.options.hostname.includes('browserstack')) { + if (browser && 'instances' in browser) { + // Loop through all instances + for (const instanceName of browser.instances) { + const instance = (browser as any)[instanceName] as WebdriverIO.Browser + if (instance.options && instance.options.hostname && instance.options.hostname.includes('browserstack')) { + return 'browserstack' + } + } + } else if (browser.options && browser.options.hostname && browser.options.hostname.includes('browserstack')) { // Single browser instance return 'browserstack' } return 'unknown_grid' @@ -1793,3 +1801,36 @@ export function getMochaTestHierarchy(test: Frameworks.Test) { } return value.reverse() } + +/** + * Checks if the capabilities represent a multiremote configuration + * @param capabilities - The capabilities to check + * @returns true if capabilities represent any multiremote configuration (regular or parallel) + * + * @example + * Regular multiremote (object): + * { browserA: { capabilities: {...} }, browserB: { capabilities: {...} } } + * + * Parallel multiremote (array with nested structure): + * [{ browserA: { capabilities: {...} }, browserB: { capabilities: {...} } }] + * + * Regular capabilities (array): + * [{ browserName: 'chrome', ... }] + */ +export function isMultiRemoteCaps(capabilities: Capabilities.RemoteCapabilities): boolean { + // Regular multiremote is an object (not array) + if (!Array.isArray(capabilities)) { + return true + } + + // Empty array is not multiremote + if (capabilities.length === 0) { + return false + } + + // Parallel multiremote is an array with nested capabilities structure + return capabilities.every(cap => + Object.values(cap).length > 0 && + Object.values(cap).every(c => c !== null && typeof c === 'object' && (c as { capabilities: WebdriverIO.Capabilities }).capabilities) + ) +} diff --git a/packages/wdio-browserstack-service/tests/util.test.ts b/packages/wdio-browserstack-service/tests/util.test.ts index a1a25111b54..f042d7171b6 100644 --- a/packages/wdio-browserstack-service/tests/util.test.ts +++ b/packages/wdio-browserstack-service/tests/util.test.ts @@ -55,7 +55,8 @@ import { getAppA11yResults, getAppA11yResultsSummary, mergeDeep, - mergeChromeOptions + mergeChromeOptions, + isMultiRemoteCaps } from '../src/util.js' import * as bstackLogger from '../src/bstackLogger.js' import { BROWSERSTACK_OBSERVABILITY, TESTOPS_BUILD_COMPLETED_ENV, BROWSERSTACK_TESTHUB_JWT, BROWSERSTACK_ACCESSIBILITY } from '../src/constants.js' @@ -455,6 +456,16 @@ describe('getCloudProvider', () => { it('return Browserstack if test being run on browserstack', () => { expect(getCloudProvider({ options: { hostname: 'hub.browserstack.com' } })).toEqual('browserstack') }) + it('return Browserstack if test being run on browserstack with multiremote', () => { + const browser = { + isMultiremote: true, + instances: ['browserA'], + browserA: { + options: { hostname: 'hub.browserstack.com' } + } + } as unknown as WebdriverIO.MultiRemoteBrowser + expect(getCloudProvider(browser)).toEqual('browserstack') + }) }) describe('isBrowserstackSession', () => { @@ -2052,4 +2063,97 @@ describe('mergeChromeOptions', () => { newtab: 'https://newtab.com' }) }) -}) \ No newline at end of file +}) + +describe('isMultiRemoteCaps', () => { + it('should return true for regular multiremote capabilities (object)', () => { + const multiremoteCaps = { + browserA: { + capabilities: { + browserName: 'chrome' + } + }, + browserB: { + capabilities: { + browserName: 'firefox' + } + } + } + expect(isMultiRemoteCaps(multiremoteCaps as any)).toBe(true) + }) + + it('should return true for parallel multiremote capabilities (array with nested structure)', () => { + const parallelMultiremoteCaps = [ + { + browserA: { + capabilities: { + browserName: 'chrome' + } + }, + browserB: { + capabilities: { + browserName: 'firefox' + } + } + } + ] + expect(isMultiRemoteCaps(parallelMultiremoteCaps as any)).toBe(true) + }) + + it('should return false for regular capabilities array', () => { + const regularCaps = [ + { + browserName: 'chrome', + 'bstack:options': { + os: 'Windows' + } + } + ] + expect(isMultiRemoteCaps(regularCaps as any)).toBe(false) + }) + + it('should return true for empty array', () => { + expect(isMultiRemoteCaps([] as any)).toBe(false) + }) + + it('should return false for array with mixed structure', () => { + const mixedCaps = [ + { + browserA: { + capabilities: { + browserName: 'chrome' + } + } + }, + { + browserName: 'firefox' // This is not multiremote structure + } + ] + expect(isMultiRemoteCaps(mixedCaps as any)).toBe(false) + }) + + it('should return false for array with empty objects', () => { + const emptyCaps = [{}] + expect(isMultiRemoteCaps(emptyCaps as any)).toBe(false) + }) + + it('should handle array with objects containing non-capabilities properties', () => { + const invalidCaps = [ + { + browserA: { + somethingElse: 'value' // Missing capabilities property + } + } + ] + expect(isMultiRemoteCaps(invalidCaps as any)).toBe(false) + }) + + it('should return false for array with null values in nested structure', () => { + const capsWithNull = [ + { + browserA: null + } + ] + expect(isMultiRemoteCaps(capsWithNull as any)).toBe(false) + }) +}) From e0e8d3079ad150ed170d6556f2286d9ef7491ee1 Mon Sep 17 00:00:00 2001 From: Kamalpreet Kaur Date: Mon, 8 Dec 2025 20:07:35 +0530 Subject: [PATCH 2/4] fix: TRA reporting --- packages/wdio-browserstack-service/src/launcher.ts | 1 + packages/wdio-browserstack-service/src/service.ts | 7 ++----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/wdio-browserstack-service/src/launcher.ts b/packages/wdio-browserstack-service/src/launcher.ts index 1d49392b153..15461717cc4 100644 --- a/packages/wdio-browserstack-service/src/launcher.ts +++ b/packages/wdio-browserstack-service/src/launcher.ts @@ -222,6 +222,7 @@ export default class BrowserstackLauncherService implements Services.ServiceInst try { // Detect if multi-remote and disable CLI for those sessions const isMultiremote = isMultiRemoteCaps(capabilities as Capabilities.RemoteCapabilities) + process.env.BROWSERSTACK_IS_MULTIREMOTE = String(isMultiremote) if (CLIUtils.checkCLISupportedFrameworks(config.framework) && !isMultiremote) { CLIUtils.setFrameworkDetail(WDIO_NAMING_PREFIX + config.framework, 'WebdriverIO') // TODO: make this constant diff --git a/packages/wdio-browserstack-service/src/service.ts b/packages/wdio-browserstack-service/src/service.ts index ad8a363a375..ac2240d5bdb 100644 --- a/packages/wdio-browserstack-service/src/service.ts +++ b/packages/wdio-browserstack-service/src/service.ts @@ -12,8 +12,7 @@ import { shouldAddServiceVersion, isTrue, normalizeTestReportingConfig, - normalizeTestReportingEnvVariables, - isMultiRemoteCaps + normalizeTestReportingEnvVariables } from './util.js' import type { BrowserstackConfig, BrowserstackOptions, MultiRemoteAction, SessionResponse, TurboScaleSessionResponse } from './types.js' import type { Pickle, Feature, ITestCaseHookParameter, CucumberHook } from './cucumber-types.js' @@ -135,9 +134,7 @@ export default class BrowserstackService implements Services.ServiceInstance { try { // Detect if multi-remote and disable CLI for those sessions - const isMultiremote = isMultiRemoteCaps(capabilities as Capabilities.RemoteCapabilities) - - if (CLIUtils.checkCLISupportedFrameworks(this._config.framework) && !isMultiremote) { + if (CLIUtils.checkCLISupportedFrameworks(this._config.framework) && process.env.BROWSERSTACK_IS_MULTIREMOTE !== 'true') { // Connect to Browserstack CLI from worker await BrowserstackCLI.getInstance().bootstrap(this._options, this._config) From afec75a4854885d2e35aa9ec5359c6f243bd75fe Mon Sep 17 00:00:00 2001 From: Kamalpreet Kaur Date: Mon, 29 Dec 2025 22:20:50 +0530 Subject: [PATCH 3/4] fix: package-lock.json update for @browserstack/wdio-browserstack-service --- package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 923ea5a2fe3..3281b7c5ebc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2117,9 +2117,9 @@ } }, "node_modules/@browserstack/wdio-browserstack-service": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@browserstack/wdio-browserstack-service/-/wdio-browserstack-service-2.0.0.tgz", - "integrity": "sha512-Dqkt1DUA5AWhXfsZ5XTmY4eJ6X+PG/inhcziu6jbXl6MO2cIkiU/QxqagOhrYjOMOrg4iWF4TXMEWVPFk1z5qA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@browserstack/wdio-browserstack-service/-/wdio-browserstack-service-2.0.2.tgz", + "integrity": "sha512-G8QefAej1fAZwIxCF5FeM5bZVrqpWXTepokzd2clGmzi9tVrHbmR4jC1L8rRGTK2X88uym8Yzb+idw9FbkJztw==", "dependencies": { "@bufbuild/protobuf": "^2.5.2", "@grpc/grpc-js": "1.13.3" @@ -34353,9 +34353,9 @@ } }, "@browserstack/wdio-browserstack-service": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@browserstack/wdio-browserstack-service/-/wdio-browserstack-service-2.0.0.tgz", - "integrity": "sha512-Dqkt1DUA5AWhXfsZ5XTmY4eJ6X+PG/inhcziu6jbXl6MO2cIkiU/QxqagOhrYjOMOrg4iWF4TXMEWVPFk1z5qA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@browserstack/wdio-browserstack-service/-/wdio-browserstack-service-2.0.2.tgz", + "integrity": "sha512-G8QefAej1fAZwIxCF5FeM5bZVrqpWXTepokzd2clGmzi9tVrHbmR4jC1L8rRGTK2X88uym8Yzb+idw9FbkJztw==", "requires": { "@bufbuild/protobuf": "^2.5.2", "@grpc/grpc-js": "1.13.3" From e4eec1f2a43d8747ecc2c9b653136e29f64429d2 Mon Sep 17 00:00:00 2001 From: Kalyan SMNV Date: Tue, 30 Dec 2025 14:12:19 +0530 Subject: [PATCH 4/4] add glob dependency --- packages/wdio-browserstack-service/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/wdio-browserstack-service/package.json b/packages/wdio-browserstack-service/package.json index 5376c7c0953..2235badfef6 100644 --- a/packages/wdio-browserstack-service/package.json +++ b/packages/wdio-browserstack-service/package.json @@ -44,6 +44,7 @@ "formdata-node": "5.0.1", "git-repo-info": "^2.1.1", "gitconfiglocal": "^2.1.0", + "glob": "^10.3.10", "got": "^12.6.1", "tar": "^6.1.15", "uuid": "^10.0.0",