From 024cd0ab8ac283e4eb5fa00c154852ff37693eec Mon Sep 17 00:00:00 2001 From: manish-bothra Date: Fri, 30 Oct 2026 12:04:42 +0530 Subject: [PATCH 1/5] Forward the Test Observability collector URL to the remote reporter The reporter that sends TestRunStarted/TestRunFinished runs on the BrowserStack machine from its own install of this package, which has the prod collector hard-coded. Against any other collector (staging environments) those events never arrive, so TestHub keeps nameless test cases and Accessibility collapses every test in a spec into one. Make API_URL overridable via BROWSERSTACK_TEST_OBSERVABILITY_API_URL and forward the launching cli's effective collector in system_env_vars, the same channel BS_TESTOPS_JWT already uses. Prod behaviour is unchanged. Jira: A11Y-13909 Co-Authored-By: Claude Fable 5.1 --- bin/helpers/utils.js | 4 ++- bin/testObservability/helper/constants.js | 3 ++- test/unit/bin/helpers/utils.js | 30 +++++++++++++++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/bin/helpers/utils.js b/bin/helpers/utils.js index a6903720..7acaa4a1 100644 --- a/bin/helpers/utils.js +++ b/bin/helpers/utils.js @@ -31,7 +31,7 @@ const usageReporting = require("./usageReporting"), pkg = require('../../package.json'), transports = require('./logger').transports, o11yHelpers = require('../testObservability/helper/helper'), - { OBSERVABILITY_ENV_VARS, TEST_OBSERVABILITY_REPORTER } = require('../testObservability/helper/constants'); + { OBSERVABILITY_ENV_VARS, TEST_OBSERVABILITY_REPORTER, API_URL: OBSERVABILITY_API_URL } = require('../testObservability/helper/constants'); const { default: axios } = require("axios"); const { shouldProcessEventForTesthub } = require("../testhub/utils"); @@ -614,6 +614,8 @@ exports.setSystemEnvs = (bsConfig) => { OBSERVABILITY_ENV_VARS.forEach(key => { envKeys[key] = process.env[key]; }); + // Remote reporter must post to the collector this build was created on, not its package default + envKeys['BROWSERSTACK_TEST_OBSERVABILITY_API_URL'] = OBSERVABILITY_API_URL; let gitConfigPath = o11yHelpers.findGitConfig(process.cwd()); if(!o11yHelpers.isBrowserstackInfra()) process.env.OBSERVABILITY_GIT_CONFIG_PATH_LOCAL = gitConfigPath; diff --git a/bin/testObservability/helper/constants.js b/bin/testObservability/helper/constants.js index d3013f40..40475f5f 100644 --- a/bin/testObservability/helper/constants.js +++ b/bin/testObservability/helper/constants.js @@ -3,7 +3,8 @@ const path = require('path'); exports.consoleHolder = Object.assign({},console); exports.BATCH_SIZE = 1000; exports.BATCH_INTERVAL = 2000; -exports.API_URL = 'https://collector-observability.browserstack.com'; +// Overridable so the remote reporter (own install of this package) can target the launching cli's collector +exports.API_URL = process.env.BROWSERSTACK_TEST_OBSERVABILITY_API_URL || 'https://collector-observability.browserstack.com'; exports.IPC_EVENTS = { LOG: 'testObservability:cypressLog', diff --git a/test/unit/bin/helpers/utils.js b/test/unit/bin/helpers/utils.js index 480cb5f4..7429a43a 100644 --- a/test/unit/bin/helpers/utils.js +++ b/test/unit/bin/helpers/utils.js @@ -961,6 +961,36 @@ describe('utils', () => { delete process.env.CYPRESS_TEST_1; delete process.env.cypress_test_2; }); + + it('forwards the observability collector url for the remote reporter', () => { + let bsConfig = { run_settings: { env: null } }; + + utils.setSystemEnvs(bsConfig); + expect(bsConfig.run_settings.system_env_vars) + .to.be.an('array') + .that.includes('BROWSERSTACK_TEST_OBSERVABILITY_API_URL=https://collector-observability.browserstack.com'); + }); + + it('forwards an overridden observability collector url', () => { + const constantsPath = require.resolve('../../../../bin/testObservability/helper/constants'); + const utilsPath = require.resolve('../../../../bin/helpers/utils'); + process.env.BROWSERSTACK_TEST_OBSERVABILITY_API_URL = 'https://collector-example.bsstag.com'; + delete require.cache[constantsPath]; + delete require.cache[utilsPath]; + try { + const freshUtils = require('../../../../bin/helpers/utils'); + let bsConfig = { run_settings: { env: null } }; + freshUtils.setSystemEnvs(bsConfig); + expect(require(constantsPath).API_URL).to.equal('https://collector-example.bsstag.com'); + expect(bsConfig.run_settings.system_env_vars) + .to.be.an('array') + .that.includes('BROWSERSTACK_TEST_OBSERVABILITY_API_URL=https://collector-example.bsstag.com'); + } finally { + delete process.env.BROWSERSTACK_TEST_OBSERVABILITY_API_URL; + delete require.cache[constantsPath]; + delete require.cache[utilsPath]; + } + }); }); describe('fixCommaSeparatedString', () => { From bf87243ae85deee0fafee88cf80e24c1098ec0ff Mon Sep 17 00:00:00 2001 From: manish-bothra Date: Fri, 30 Oct 2026 12:51:54 +0530 Subject: [PATCH 2/5] Allow a separate collector for the remote reporter On staging the collector the cli talks to is a private ingress the BrowserStack machine cannot reach, while the machine-facing public ingress is a different host. BROWSERSTACK_TEST_OBSERVABILITY_REMOTE_API_URL, when set, is what gets forwarded to the machine; otherwise the cli's own collector is forwarded as before. Read at call time so the tests need no module reloads. Jira: A11Y-13909 Co-Authored-By: Claude Fable 5.1 --- bin/helpers/utils.js | 6 ++--- bin/testObservability/helper/constants.js | 6 ++++- test/unit/bin/helpers/utils.js | 29 +++++++++++++++-------- 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/bin/helpers/utils.js b/bin/helpers/utils.js index 7acaa4a1..c1601846 100644 --- a/bin/helpers/utils.js +++ b/bin/helpers/utils.js @@ -31,7 +31,7 @@ const usageReporting = require("./usageReporting"), pkg = require('../../package.json'), transports = require('./logger').transports, o11yHelpers = require('../testObservability/helper/helper'), - { OBSERVABILITY_ENV_VARS, TEST_OBSERVABILITY_REPORTER, API_URL: OBSERVABILITY_API_URL } = require('../testObservability/helper/constants'); + { OBSERVABILITY_ENV_VARS, TEST_OBSERVABILITY_REPORTER, remoteReporterApiUrl } = require('../testObservability/helper/constants'); const { default: axios } = require("axios"); const { shouldProcessEventForTesthub } = require("../testhub/utils"); @@ -614,8 +614,8 @@ exports.setSystemEnvs = (bsConfig) => { OBSERVABILITY_ENV_VARS.forEach(key => { envKeys[key] = process.env[key]; }); - // Remote reporter must post to the collector this build was created on, not its package default - envKeys['BROWSERSTACK_TEST_OBSERVABILITY_API_URL'] = OBSERVABILITY_API_URL; + // Remote reporter must post to this build's collector, not its package default + envKeys['BROWSERSTACK_TEST_OBSERVABILITY_API_URL'] = remoteReporterApiUrl(); let gitConfigPath = o11yHelpers.findGitConfig(process.cwd()); if(!o11yHelpers.isBrowserstackInfra()) process.env.OBSERVABILITY_GIT_CONFIG_PATH_LOCAL = gitConfigPath; diff --git a/bin/testObservability/helper/constants.js b/bin/testObservability/helper/constants.js index 40475f5f..040503bb 100644 --- a/bin/testObservability/helper/constants.js +++ b/bin/testObservability/helper/constants.js @@ -3,8 +3,12 @@ const path = require('path'); exports.consoleHolder = Object.assign({},console); exports.BATCH_SIZE = 1000; exports.BATCH_INTERVAL = 2000; +const DEFAULT_API_URL = 'https://collector-observability.browserstack.com'; // Overridable so the remote reporter (own install of this package) can target the launching cli's collector -exports.API_URL = process.env.BROWSERSTACK_TEST_OBSERVABILITY_API_URL || 'https://collector-observability.browserstack.com'; +exports.API_URL = process.env.BROWSERSTACK_TEST_OBSERVABILITY_API_URL || DEFAULT_API_URL; +// Collector handed to the reporter on the remote machine; separate override for when it cannot reach API_URL +exports.remoteReporterApiUrl = () => process.env.BROWSERSTACK_TEST_OBSERVABILITY_REMOTE_API_URL + || process.env.BROWSERSTACK_TEST_OBSERVABILITY_API_URL || DEFAULT_API_URL; exports.IPC_EVENTS = { LOG: 'testObservability:cypressLog', diff --git a/test/unit/bin/helpers/utils.js b/test/unit/bin/helpers/utils.js index 7429a43a..41e0fe38 100644 --- a/test/unit/bin/helpers/utils.js +++ b/test/unit/bin/helpers/utils.js @@ -972,23 +972,32 @@ describe('utils', () => { }); it('forwards an overridden observability collector url', () => { - const constantsPath = require.resolve('../../../../bin/testObservability/helper/constants'); - const utilsPath = require.resolve('../../../../bin/helpers/utils'); process.env.BROWSERSTACK_TEST_OBSERVABILITY_API_URL = 'https://collector-example.bsstag.com'; - delete require.cache[constantsPath]; - delete require.cache[utilsPath]; + let bsConfig = { run_settings: { env: null } }; + try { - const freshUtils = require('../../../../bin/helpers/utils'); - let bsConfig = { run_settings: { env: null } }; - freshUtils.setSystemEnvs(bsConfig); - expect(require(constantsPath).API_URL).to.equal('https://collector-example.bsstag.com'); + utils.setSystemEnvs(bsConfig); expect(bsConfig.run_settings.system_env_vars) .to.be.an('array') .that.includes('BROWSERSTACK_TEST_OBSERVABILITY_API_URL=https://collector-example.bsstag.com'); } finally { delete process.env.BROWSERSTACK_TEST_OBSERVABILITY_API_URL; - delete require.cache[constantsPath]; - delete require.cache[utilsPath]; + } + }); + + it('prefers a separate remote collector url for the remote reporter', () => { + process.env.BROWSERSTACK_TEST_OBSERVABILITY_API_URL = 'https://collector-private.bsstag.com'; + process.env.BROWSERSTACK_TEST_OBSERVABILITY_REMOTE_API_URL = 'https://collector-public.bsstag.com'; + let bsConfig = { run_settings: { env: null } }; + + try { + utils.setSystemEnvs(bsConfig); + expect(bsConfig.run_settings.system_env_vars) + .to.be.an('array') + .that.includes('BROWSERSTACK_TEST_OBSERVABILITY_API_URL=https://collector-public.bsstag.com'); + } finally { + delete process.env.BROWSERSTACK_TEST_OBSERVABILITY_API_URL; + delete process.env.BROWSERSTACK_TEST_OBSERVABILITY_REMOTE_API_URL; } }); }); From 0166a03a9ceb26a8d48a4288cfff79a062b16e16 Mon Sep 17 00:00:00 2001 From: manish-bothra Date: Fri, 30 Oct 2026 20:02:47 +0530 Subject: [PATCH 3/5] Require https for the collector overrides and isolate the env vars in tests Review feedback on #1210. Collector requests carry BS_TESTOPS_JWT, so an http:// override would send it in cleartext: reject such a value instead of falling back to the default collector. The assignment moves out of the try block in setSystemEnvs because that block swallows errors, which would have turned the rejection into a silent fallback. Tests now clear both URL variables before each case and restore the originals after. Jira: A11Y-13909 Co-Authored-By: Claude Opus 5 (1M context) --- bin/helpers/utils.js | 6 ++- bin/testObservability/helper/constants.js | 18 ++++++-- test/unit/bin/helpers/utils.js | 55 ++++++++++++++++------- 3 files changed, 57 insertions(+), 22 deletions(-) diff --git a/bin/helpers/utils.js b/bin/helpers/utils.js index c1601846..c15cc69d 100644 --- a/bin/helpers/utils.js +++ b/bin/helpers/utils.js @@ -610,12 +610,14 @@ exports.setSystemEnvs = (bsConfig) => { logger.error(`Error in adding accessibility configs ${error}`) } + // Remote reporter must post to this build's collector, not its package default. Kept out of the + // try below, which swallows errors: a rejected override must fail rather than fall back silently. + envKeys['BROWSERSTACK_TEST_OBSERVABILITY_API_URL'] = remoteReporterApiUrl(); + try { OBSERVABILITY_ENV_VARS.forEach(key => { envKeys[key] = process.env[key]; }); - // Remote reporter must post to this build's collector, not its package default - envKeys['BROWSERSTACK_TEST_OBSERVABILITY_API_URL'] = remoteReporterApiUrl(); let gitConfigPath = o11yHelpers.findGitConfig(process.cwd()); if(!o11yHelpers.isBrowserstackInfra()) process.env.OBSERVABILITY_GIT_CONFIG_PATH_LOCAL = gitConfigPath; diff --git a/bin/testObservability/helper/constants.js b/bin/testObservability/helper/constants.js index 040503bb..da6ebe18 100644 --- a/bin/testObservability/helper/constants.js +++ b/bin/testObservability/helper/constants.js @@ -4,11 +4,23 @@ exports.consoleHolder = Object.assign({},console); exports.BATCH_SIZE = 1000; exports.BATCH_INTERVAL = 2000; const DEFAULT_API_URL = 'https://collector-observability.browserstack.com'; + +// Requests to the collector carry BS_TESTOPS_JWT, so an http:// override would put the build's token +// on the wire in cleartext. Reject it instead of quietly using the default collector. +const httpsOverride = (name) => { + const value = process.env[name]; + if (!value) return undefined; + if (!value.startsWith('https://')) { + throw new Error(`${name} must be an https:// URL, got "${value}"`); + } + return value; +}; + // Overridable so the remote reporter (own install of this package) can target the launching cli's collector -exports.API_URL = process.env.BROWSERSTACK_TEST_OBSERVABILITY_API_URL || DEFAULT_API_URL; +exports.API_URL = httpsOverride('BROWSERSTACK_TEST_OBSERVABILITY_API_URL') || DEFAULT_API_URL; // Collector handed to the reporter on the remote machine; separate override for when it cannot reach API_URL -exports.remoteReporterApiUrl = () => process.env.BROWSERSTACK_TEST_OBSERVABILITY_REMOTE_API_URL - || process.env.BROWSERSTACK_TEST_OBSERVABILITY_API_URL || DEFAULT_API_URL; +exports.remoteReporterApiUrl = () => httpsOverride('BROWSERSTACK_TEST_OBSERVABILITY_REMOTE_API_URL') + || httpsOverride('BROWSERSTACK_TEST_OBSERVABILITY_API_URL') || DEFAULT_API_URL; exports.IPC_EVENTS = { LOG: 'testObservability:cypressLog', diff --git a/test/unit/bin/helpers/utils.js b/test/unit/bin/helpers/utils.js index 41e0fe38..d83ebc4f 100644 --- a/test/unit/bin/helpers/utils.js +++ b/test/unit/bin/helpers/utils.js @@ -962,6 +962,29 @@ describe('utils', () => { delete process.env.cypress_test_2; }); + const URL_ENV_VARS = [ + 'BROWSERSTACK_TEST_OBSERVABILITY_API_URL', + 'BROWSERSTACK_TEST_OBSERVABILITY_REMOTE_API_URL', + ]; + let savedUrlEnvs = {}; + + beforeEach(() => { + URL_ENV_VARS.forEach((key) => { + savedUrlEnvs[key] = process.env[key]; + delete process.env[key]; + }); + }); + + afterEach(() => { + URL_ENV_VARS.forEach((key) => { + if (savedUrlEnvs[key] === undefined) { + delete process.env[key]; + } else { + process.env[key] = savedUrlEnvs[key]; + } + }); + }); + it('forwards the observability collector url for the remote reporter', () => { let bsConfig = { run_settings: { env: null } }; @@ -975,14 +998,10 @@ describe('utils', () => { process.env.BROWSERSTACK_TEST_OBSERVABILITY_API_URL = 'https://collector-example.bsstag.com'; let bsConfig = { run_settings: { env: null } }; - try { - utils.setSystemEnvs(bsConfig); - expect(bsConfig.run_settings.system_env_vars) - .to.be.an('array') - .that.includes('BROWSERSTACK_TEST_OBSERVABILITY_API_URL=https://collector-example.bsstag.com'); - } finally { - delete process.env.BROWSERSTACK_TEST_OBSERVABILITY_API_URL; - } + utils.setSystemEnvs(bsConfig); + expect(bsConfig.run_settings.system_env_vars) + .to.be.an('array') + .that.includes('BROWSERSTACK_TEST_OBSERVABILITY_API_URL=https://collector-example.bsstag.com'); }); it('prefers a separate remote collector url for the remote reporter', () => { @@ -990,15 +1009,17 @@ describe('utils', () => { process.env.BROWSERSTACK_TEST_OBSERVABILITY_REMOTE_API_URL = 'https://collector-public.bsstag.com'; let bsConfig = { run_settings: { env: null } }; - try { - utils.setSystemEnvs(bsConfig); - expect(bsConfig.run_settings.system_env_vars) - .to.be.an('array') - .that.includes('BROWSERSTACK_TEST_OBSERVABILITY_API_URL=https://collector-public.bsstag.com'); - } finally { - delete process.env.BROWSERSTACK_TEST_OBSERVABILITY_API_URL; - delete process.env.BROWSERSTACK_TEST_OBSERVABILITY_REMOTE_API_URL; - } + utils.setSystemEnvs(bsConfig); + expect(bsConfig.run_settings.system_env_vars) + .to.be.an('array') + .that.includes('BROWSERSTACK_TEST_OBSERVABILITY_API_URL=https://collector-public.bsstag.com'); + }); + + it('rejects a collector url that is not https', () => { + process.env.BROWSERSTACK_TEST_OBSERVABILITY_REMOTE_API_URL = 'http://collector-public.bsstag.com'; + let bsConfig = { run_settings: { env: null } }; + + expect(() => utils.setSystemEnvs(bsConfig)).to.throw(/must be an https:\/\/ URL/); }); }); From e7d01ac3f63647e806ac9c70e58b0d60974c2ccc Mon Sep 17 00:00:00 2001 From: manish-bothra Date: Fri, 30 Oct 2026 20:31:22 +0530 Subject: [PATCH 4/5] Parse the collector override as a URL and reject a query or fragment Request urls are built as `${API_URL}/${path}`, so an override carrying a query or fragment would swallow the endpoint path and the upload would hit / instead. Parse with URL, require an https protocol and a hostname, reject search and hash, and trim a trailing slash so paths are not doubled. Jira: A11Y-13909 Co-Authored-By: Claude Opus 5 (1M context) --- bin/testObservability/helper/constants.js | 16 +++++++++++++--- test/unit/bin/helpers/utils.js | 17 +++++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/bin/testObservability/helper/constants.js b/bin/testObservability/helper/constants.js index da6ebe18..ee09c153 100644 --- a/bin/testObservability/helper/constants.js +++ b/bin/testObservability/helper/constants.js @@ -6,14 +6,24 @@ exports.BATCH_INTERVAL = 2000; const DEFAULT_API_URL = 'https://collector-observability.browserstack.com'; // Requests to the collector carry BS_TESTOPS_JWT, so an http:// override would put the build's token -// on the wire in cleartext. Reject it instead of quietly using the default collector. +// on the wire in cleartext. Request urls are also built as `${base}/${path}`, so a query or fragment +// would swallow the endpoint. Reject such values instead of quietly using the default collector. const httpsOverride = (name) => { const value = process.env[name]; if (!value) return undefined; - if (!value.startsWith('https://')) { + let parsed; + try { + parsed = new URL(value); + } catch (err) { + throw new Error(`${name} must be a valid https:// URL, got "${value}"`); + } + if (parsed.protocol !== 'https:' || !parsed.hostname) { throw new Error(`${name} must be an https:// URL, got "${value}"`); } - return value; + if (parsed.search || parsed.hash) { + throw new Error(`${name} must not contain a query or fragment, got "${value}"`); + } + return value.replace(/\/+$/, ''); }; // Overridable so the remote reporter (own install of this package) can target the launching cli's collector diff --git a/test/unit/bin/helpers/utils.js b/test/unit/bin/helpers/utils.js index d83ebc4f..7b11e92f 100644 --- a/test/unit/bin/helpers/utils.js +++ b/test/unit/bin/helpers/utils.js @@ -1021,6 +1021,23 @@ describe('utils', () => { expect(() => utils.setSystemEnvs(bsConfig)).to.throw(/must be an https:\/\/ URL/); }); + + it('rejects a collector url carrying a query or fragment', () => { + process.env.BROWSERSTACK_TEST_OBSERVABILITY_REMOTE_API_URL = 'https://collector-public.bsstag.com/#staging'; + let bsConfig = { run_settings: { env: null } }; + + expect(() => utils.setSystemEnvs(bsConfig)).to.throw(/must not contain a query or fragment/); + }); + + it('trims a trailing slash so endpoint paths are not doubled', () => { + process.env.BROWSERSTACK_TEST_OBSERVABILITY_REMOTE_API_URL = 'https://collector-public.bsstag.com/'; + let bsConfig = { run_settings: { env: null } }; + + utils.setSystemEnvs(bsConfig); + expect(bsConfig.run_settings.system_env_vars) + .to.be.an('array') + .that.includes('BROWSERSTACK_TEST_OBSERVABILITY_API_URL=https://collector-public.bsstag.com'); + }); }); describe('fixCommaSeparatedString', () => { From fc37d26fbe351428b346c448fd8323dfed3815b1 Mon Sep 17 00:00:00 2001 From: manish-bothra Date: Mon, 2 Nov 2026 09:17:05 +0530 Subject: [PATCH 5/5] Normalise the collector override and keep its value out of errors A bare trailing ? or # leaves URL.search and URL.hash empty, so the value passed validation and the appended endpoint became a query or fragment; return origin + pathname with no trailing separator instead. The override can also carry credentials and callers log the error, so the messages no longer echo the value. Jira: A11Y-13909 Co-Authored-By: Claude Opus 5 (1M context) --- bin/testObservability/helper/constants.js | 11 +++++++---- test/unit/bin/helpers/utils.js | 10 ++++++++++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/bin/testObservability/helper/constants.js b/bin/testObservability/helper/constants.js index ee09c153..c2cb0c8f 100644 --- a/bin/testObservability/helper/constants.js +++ b/bin/testObservability/helper/constants.js @@ -12,18 +12,21 @@ const httpsOverride = (name) => { const value = process.env[name]; if (!value) return undefined; let parsed; + // The value can carry credentials, and callers log the error, so never echo it back. try { parsed = new URL(value); } catch (err) { - throw new Error(`${name} must be a valid https:// URL, got "${value}"`); + throw new Error(`${name} must be a valid https:// URL`); } if (parsed.protocol !== 'https:' || !parsed.hostname) { - throw new Error(`${name} must be an https:// URL, got "${value}"`); + throw new Error(`${name} must be an https:// URL`); } if (parsed.search || parsed.hash) { - throw new Error(`${name} must not contain a query or fragment, got "${value}"`); + throw new Error(`${name} must not contain a query or fragment`); } - return value.replace(/\/+$/, ''); + // Normalise to origin + path with no trailing separator: a stray `/`, `?` or `#` would otherwise + // turn the appended endpoint into a query or fragment. + return `${parsed.origin}${parsed.pathname}`.replace(/\/+$/, ''); }; // Overridable so the remote reporter (own install of this package) can target the launching cli's collector diff --git a/test/unit/bin/helpers/utils.js b/test/unit/bin/helpers/utils.js index 7b11e92f..cac8faf5 100644 --- a/test/unit/bin/helpers/utils.js +++ b/test/unit/bin/helpers/utils.js @@ -1029,6 +1029,16 @@ describe('utils', () => { expect(() => utils.setSystemEnvs(bsConfig)).to.throw(/must not contain a query or fragment/); }); + it('normalises a bare query or fragment delimiter', () => { + process.env.BROWSERSTACK_TEST_OBSERVABILITY_REMOTE_API_URL = 'https://collector-public.bsstag.com?'; + let bsConfig = { run_settings: { env: null } }; + + utils.setSystemEnvs(bsConfig); + expect(bsConfig.run_settings.system_env_vars) + .to.be.an('array') + .that.includes('BROWSERSTACK_TEST_OBSERVABILITY_API_URL=https://collector-public.bsstag.com'); + }); + it('trims a trailing slash so endpoint paths are not doubled', () => { process.env.BROWSERSTACK_TEST_OBSERVABILITY_REMOTE_API_URL = 'https://collector-public.bsstag.com/'; let bsConfig = { run_settings: { env: null } };