From 7e1dd35c2caf0f723e648d475b173035cf49c8c4 Mon Sep 17 00:00:00 2001 From: xxshubhamxx Date: Thu, 2 Apr 2026 14:16:38 +0530 Subject: [PATCH 1/7] SDK-5540: feat: Add test plan ID handling in helper functions and update TestObservability class --- src/testObservability.js | 7 ++++ src/utils/helper.js | 72 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/src/testObservability.js b/src/testObservability.js index 4735312..82aac78 100644 --- a/src/testObservability.js +++ b/src/testObservability.js @@ -94,6 +94,7 @@ class TestObservability { this._parentSettings?.testReportingOptions || this._parentSettings?.testObservabilityOptions || {}; + const testPlanId = helper.getTestPlanId(this._bstackOptions); const accessibility = helper.isAccessibilityEnabled(this._parentSettings); const accessibilityOptions = accessibility ? this._settings.accessibilityOptions || {} : {}; this._gitMetadata = await helper.getGitMetaData(); @@ -133,6 +134,12 @@ class TestObservability { test_orchestration: this.getTestOrchestrationBuildStartData(this._parentSettings) }; + if (testPlanId) { + data.testManagementOptions = { + testPlanId + }; + } + const config = { auth: { username: this._user, diff --git a/src/utils/helper.js b/src/utils/helper.js index 3d84078..6678ea3 100644 --- a/src/utils/helper.js +++ b/src/utils/helper.js @@ -14,6 +14,7 @@ const {RERUN_FILE, DEFAULT_WAIT_TIMEOUT_FOR_PENDING_UPLOADS, DEFAULT_WAIT_INTERV const requestQueueHandler = require('./requestQueueHandler'); const Logger = require('./logger'); const LogPatcher = require('./logPatcher'); +const TEST_MANAGEMENT_TEST_PLAN_ID_ARG = 'browserstack.testManagementOptions.testPlanId'; const BSTestOpsPatcher = new LogPatcher({}); const sessions = {}; const {execSync} = require('child_process'); @@ -74,6 +75,77 @@ exports.getObservabilityKey = (config, bstackOptions={}) => { return process.env.BROWSERSTACK_ACCESS_KEY || config?.key || bstackOptions?.accessKey; }; +const normalizeTestPlanId = (testPlanId) => { + if (typeof testPlanId !== 'string') { + return undefined; + } + + const normalizedTestPlanId = testPlanId.trim(); + + return normalizedTestPlanId.length > 0 ? normalizedTestPlanId : undefined; +}; + +const readTestPlanIdFromCliArgs = (argv = process.argv) => { + const cliFlag = `--${TEST_MANAGEMENT_TEST_PLAN_ID_ARG}`; + + for (let index = 0; index < argv.length; index += 1) { + const currentArg = argv[index]; + + if (currentArg === cliFlag) { + const nextArg = argv[index + 1]; + + if (!nextArg || nextArg.startsWith('--')) { + return undefined; + } + + return nextArg; + } + + if (currentArg.startsWith(`${cliFlag}=`)) { + return currentArg.slice(cliFlag.length + 1) || undefined; + } + } + + return undefined; +}; + +const readTestPlanIdFromConfig = (bstackOptions = {}) => { + const nestedConfigTestPlanId = normalizeTestPlanId( + bstackOptions?.testManagementOptions?.testPlanId + ); + + if (nestedConfigTestPlanId) { + return nestedConfigTestPlanId; + } + + return normalizeTestPlanId(bstackOptions?.testPlanId); +}; + +/** + * Resolve the test plan id from supported Nightwatch client-side inputs. + * + * Priority order is CLI arguments, then environment variables, and finally + * BrowserStack config capabilities. + * + * @param {Record} [bstackOptions={}] BrowserStack capability options. + * @param {string[]} [argv=process.argv] CLI arguments to inspect. + * @param {NodeJS.ProcessEnv} [env=process.env] Environment variables to inspect. + * @returns {string|undefined} The resolved test plan id, if present. + */ +exports.getTestPlanId = (bstackOptions = {}, argv = process.argv, env = process.env) => { + const cliTestPlanId = normalizeTestPlanId(readTestPlanIdFromCliArgs(argv)); + if (cliTestPlanId) { + return cliTestPlanId; + } + + const envTestPlanId = normalizeTestPlanId(env.BROWSERSTACK_TEST_PLAN_ID); + if (envTestPlanId) { + return envTestPlanId; + } + + return readTestPlanIdFromConfig(bstackOptions); +}; + exports.isAppAutomate = () => { return process.env.BROWSERSTACK_APP_AUTOMATE === 'true'; }; From f590c76929a27cdb5bfa144898722808f71c18e4 Mon Sep 17 00:00:00 2001 From: Shubham Garg <74714209+xxshubhamxx@users.noreply.github.com> Date: Thu, 2 Apr 2026 19:11:31 +0530 Subject: [PATCH 2/7] test plan id format update --- src/testObservability.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/testObservability.js b/src/testObservability.js index 82aac78..2ef09c3 100644 --- a/src/testObservability.js +++ b/src/testObservability.js @@ -135,9 +135,9 @@ class TestObservability { }; if (testPlanId) { - data.testManagementOptions = { - testPlanId - }; + data["test_management"] = { + "test_plan_id": testPlanId + } } const config = { From b3c86bda444394b6bc772c1ec77dee0d6633666b Mon Sep 17 00:00:00 2001 From: YASH JAIN Date: Fri, 17 Apr 2026 18:34:04 +0530 Subject: [PATCH 3/7] Added log for test plan id --- src/testObservability.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/testObservability.js b/src/testObservability.js index 2ef09c3..5e27e02 100644 --- a/src/testObservability.js +++ b/src/testObservability.js @@ -154,6 +154,7 @@ class TestObservability { try { const response = await makeRequest('POST', 'api/v2/builds', data, config, API_URL); Logger.info('Build creation successful!'); + Logger.debug(`Test plan id sent to build start: ${testPlanId}`); process.env.BS_TESTOPS_BUILD_COMPLETED = true; const responseData = response.data || {}; From ad6c7cb04b038af74773b1e4809155721d76bbff Mon Sep 17 00:00:00 2001 From: YASH JAIN Date: Fri, 17 Apr 2026 19:52:18 +0530 Subject: [PATCH 4/7] Added log for test plan id --- src/testObservability.js | 42 ++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/testObservability.js b/src/testObservability.js index 5e27e02..27f3337 100644 --- a/src/testObservability.js +++ b/src/testObservability.js @@ -35,15 +35,15 @@ class TestObservability { process.env.BROWSERSTACK_TEST_REPORTING = 'false'; } - // Check for test_observability or test_reporting configuration + // Check for test_observability or test_reporting configuration const observabilityConfig = this._settings.test_observability || this._settings.test_reporting; const testReportingOptions = this._settings.testReportingOptions || this._settings.testObservabilityOptions; - + if (!helper.isUndefined(observabilityConfig) && !helper.isUndefined(observabilityConfig.enabled)) { process.env.BROWSERSTACK_TEST_OBSERVABILITY = observabilityConfig.enabled; process.env.BROWSERSTACK_TEST_REPORTING = observabilityConfig.enabled; } - + if (process.argv.includes('--disable-test-observability') || process.argv.includes('--disable-test-reporting')) { process.env.BROWSERSTACK_TEST_OBSERVABILITY = 'false'; process.env.BROWSERSTACK_TEST_REPORTING = 'false'; @@ -70,7 +70,7 @@ class TestObservability { CrashReporter.setCredentialsForCrashReportUpload(this._user, this._key); CrashReporter.setConfigDetails(settings); } - + // Also check for top-level testReportingOptions or testObservabilityOptions if (settings.testReportingOptions || settings.testObservabilityOptions) { const topLevelOptions = settings.testReportingOptions || settings.testObservabilityOptions; @@ -87,15 +87,15 @@ class TestObservability { async launchTestSession() { // Support both old and new configuration options at different levels - const testReportingOptions = this._settings.test_observability || - this._settings.test_reporting || - this._settings.testReportingOptions || - this._settings.testObservabilityOptions || + const testReportingOptions = this._settings.test_observability || + this._settings.test_reporting || + this._settings.testReportingOptions || + this._settings.testObservabilityOptions || this._parentSettings?.testReportingOptions || this._parentSettings?.testObservabilityOptions || {}; const testPlanId = helper.getTestPlanId(this._bstackOptions); - const accessibility = helper.isAccessibilityEnabled(this._parentSettings); + const accessibility = helper.isAccessibilityEnabled(this._parentSettings); const accessibilityOptions = accessibility ? this._settings.accessibilityOptions || {} : {}; this._gitMetadata = await helper.getGitMetaData(); const fromProduct = { @@ -154,7 +154,7 @@ class TestObservability { try { const response = await makeRequest('POST', 'api/v2/builds', data, config, API_URL); Logger.info('Build creation successful!'); - Logger.debug(`Test plan id sent to build start: ${testPlanId}`); + Logger.info(`Test plan id sent to build start: ${testPlanId}`); process.env.BS_TESTOPS_BUILD_COMPLETED = true; const responseData = response.data || {}; @@ -178,11 +178,11 @@ class TestObservability { } getTestOrchestrationBuildStartData(settings) { const orchestrationUtils = OrchestrationUtils.getInstance(settings); - + return orchestrationUtils.getBuildStartData(); } - processLaunchBuildResponse(responseData, settings) { + processLaunchBuildResponse(responseData, settings) { if (helper.isTestObservabilitySession()) { this.processTestObservabilityResponse(responseData); } @@ -250,12 +250,12 @@ class TestObservability { handleErrorForObservability(error) { process.env.BROWSERSTACK_TEST_OBSERVABILITY = 'false'; process.env.BROWSERSTACK_TEST_REPORTING = 'false'; - helper.logBuildError(error, 'Test Reporting and Analytics'); + helper.logBuildError(error, 'Test Reporting and Analytics'); } handleErrorForAccessibility(error) { process.env.BROWSERSTACK_ACCESSIBILITY = 'false'; - helper.logBuildError(error, 'Accessibility'); + helper.logBuildError(error, 'Accessibility'); } async stopBuildUpstream () { @@ -399,7 +399,7 @@ class TestObservability { } } } - + async sendSkippedTestEvent(skippedTest, testFileReport) { const testData = { uuid: uuidv4(), @@ -483,7 +483,7 @@ class TestObservability { type: 'test', name: testName, body: { - lang: 'javascript', + lang: 'javascript', code: testBody ? testBody.toString() : null }, scope: `${testMetaData.name} - ${testName}`, @@ -527,7 +527,7 @@ class TestObservability { } } } - } + } await this.processTestRunData (eventData, uuid); } @@ -711,7 +711,7 @@ class TestObservability { testData.failure = hook.failure_data; testData.failure_reason = (hook.failure_data instanceof Array) ? hook.failure_data[0]?.backtrace.join('\n') : ''; testData.failure_type = hook.failure_type; - + return testData; } } @@ -758,7 +758,7 @@ class TestObservability { const startedAt = new Date().toISOString(); const result = 'pending'; const hookTagsList = hookDetails.tagExpression ? hookDetails.tagExpression.split(' ').filter(val => val.includes('@')) : null; - + const hookEventData = { uuid: hookData.id, type: 'hook', @@ -850,7 +850,7 @@ class TestObservability { getProductMapForBuildStartCall(settings) { const product = helper.getObservabilityLinkedProductName(settings.desiredCapabilities, settings?.selenium?.host); - + const buildProductMap = { automate: product === 'automate', app_automate: product === 'app-automate', @@ -866,6 +866,6 @@ class TestObservability { getTestBody(testCaseData) { return testCaseData?.context.__module[testCaseData.testName] || null; } -} +} module.exports = TestObservability; From 17706eee303a4a43dd9b9ba246cbb74d8bd18b6d Mon Sep 17 00:00:00 2001 From: aniketxbrowserstack Date: Mon, 20 Apr 2026 01:32:50 +0530 Subject: [PATCH 5/7] fix: read testPlanId from plugin settings so nightwatch config path works MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit getTestPlanId() was being called with this._bstackOptions, which is populated from settings.desiredCapabilities['bstack:options']. But users configure testManagementOptions under the @nightwatch/browserstack block in nightwatch.conf.js, which lands on this._settings — so the config-based path was always returning undefined. CLI arg and env var paths were unaffected because they don't depend on the first arg. Pass this._settings instead so testManagementOptions.testPlanId in nightwatch.conf.js is picked up correctly. --- src/testObservability.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/testObservability.js b/src/testObservability.js index 27f3337..7dbbd27 100644 --- a/src/testObservability.js +++ b/src/testObservability.js @@ -94,7 +94,7 @@ class TestObservability { this._parentSettings?.testReportingOptions || this._parentSettings?.testObservabilityOptions || {}; - const testPlanId = helper.getTestPlanId(this._bstackOptions); + const testPlanId = helper.getTestPlanId(this._settings); const accessibility = helper.isAccessibilityEnabled(this._parentSettings); const accessibilityOptions = accessibility ? this._settings.accessibilityOptions || {} : {}; this._gitMetadata = await helper.getGitMetaData(); From b2c8bf008f68dd70f18988c37aa835c3ccd30a0e Mon Sep 17 00:00:00 2001 From: aniketxbrowserstack Date: Mon, 20 Apr 2026 01:50:00 +0530 Subject: [PATCH 6/7] fix: update readTestPlanIdFromConfig to use plugin settings instead of bstackOptions --- src/utils/helper.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/utils/helper.js b/src/utils/helper.js index 6678ea3..ffae9a5 100644 --- a/src/utils/helper.js +++ b/src/utils/helper.js @@ -109,30 +109,30 @@ const readTestPlanIdFromCliArgs = (argv = process.argv) => { return undefined; }; -const readTestPlanIdFromConfig = (bstackOptions = {}) => { +const readTestPlanIdFromConfig = (settings = {}) => { const nestedConfigTestPlanId = normalizeTestPlanId( - bstackOptions?.testManagementOptions?.testPlanId + settings?.testManagementOptions?.testPlanId ); if (nestedConfigTestPlanId) { return nestedConfigTestPlanId; } - return normalizeTestPlanId(bstackOptions?.testPlanId); + return normalizeTestPlanId(settings?.testPlanId); }; /** * Resolve the test plan id from supported Nightwatch client-side inputs. * * Priority order is CLI arguments, then environment variables, and finally - * BrowserStack config capabilities. + * the @nightwatch/browserstack plugin settings from nightwatch.conf.js. * - * @param {Record} [bstackOptions={}] BrowserStack capability options. + * @param {Record} [settings={}] Plugin settings block. * @param {string[]} [argv=process.argv] CLI arguments to inspect. * @param {NodeJS.ProcessEnv} [env=process.env] Environment variables to inspect. * @returns {string|undefined} The resolved test plan id, if present. */ -exports.getTestPlanId = (bstackOptions = {}, argv = process.argv, env = process.env) => { +exports.getTestPlanId = (settings = {}, argv = process.argv, env = process.env) => { const cliTestPlanId = normalizeTestPlanId(readTestPlanIdFromCliArgs(argv)); if (cliTestPlanId) { return cliTestPlanId; @@ -143,7 +143,7 @@ exports.getTestPlanId = (bstackOptions = {}, argv = process.argv, env = process. return envTestPlanId; } - return readTestPlanIdFromConfig(bstackOptions); + return readTestPlanIdFromConfig(settings); }; exports.isAppAutomate = () => { From 1b547a2e2d9b68ce95d6f13d5a0d46fb980e62de Mon Sep 17 00:00:00 2001 From: YASH JAIN Date: Thu, 23 Apr 2026 12:10:50 +0530 Subject: [PATCH 7/7] lint fix --- src/testObservability.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/testObservability.js b/src/testObservability.js index c3191c8..fa288cd 100644 --- a/src/testObservability.js +++ b/src/testObservability.js @@ -136,9 +136,9 @@ class TestObservability { }; if (testPlanId) { - data["test_management"] = { - "test_plan_id": testPlanId - } + data['test_management'] = { + 'test_plan_id': testPlanId + }; } const config = {