Skip to content
48 changes: 28 additions & 20 deletions src/testObservability.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,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';
Expand All @@ -71,7 +71,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;
Expand All @@ -88,14 +88,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 accessibility = helper.isAccessibilityEnabled(this._parentSettings);
const testPlanId = helper.getTestPlanId(this._settings);
const accessibility = helper.isAccessibilityEnabled(this._parentSettings);
const accessibilityOptions = accessibility ? this._settings.accessibilityOptions || {} : {};
this._gitMetadata = await helper.getGitMetaData();
const fromProduct = {
Expand Down Expand Up @@ -134,6 +135,12 @@ class TestObservability {
test_orchestration: this.getTestOrchestrationBuildStartData(this._parentSettings)
};

if (testPlanId) {
data['test_management'] = {
'test_plan_id': testPlanId
};
}

const config = {
auth: {
username: this._user,
Expand All @@ -148,6 +155,7 @@ class TestObservability {
try {
const response = await makeRequest('POST', 'api/v2/builds', data, config, API_URL);
Logger.info('Build creation successful!');
Logger.info(`Test plan id sent to build start: ${testPlanId}`);
process.env.BS_TESTOPS_BUILD_COMPLETED = true;

const responseData = response.data || {};
Expand All @@ -171,11 +179,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);
}
Expand Down Expand Up @@ -243,12 +251,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 () {
Expand Down Expand Up @@ -396,7 +404,7 @@ class TestObservability {
}
}
}

async sendSkippedTestEvent(skippedTest, testFileReport) {
const testData = {
uuid: uuidv4(),
Expand Down Expand Up @@ -480,7 +488,7 @@ class TestObservability {
type: 'test',
name: testName,
body: {
lang: 'javascript',
lang: 'javascript',
code: testBody ? testBody.toString() : null
},
scope: `${testMetaData.name} - ${testName}`,
Expand Down Expand Up @@ -524,7 +532,7 @@ class TestObservability {
}
}
}
}
}
await this.processTestRunData (eventData, uuid);
}

Expand Down Expand Up @@ -726,7 +734,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;
}
}
Expand Down Expand Up @@ -773,7 +781,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',
Expand Down Expand Up @@ -865,7 +873,7 @@ class TestObservability {

getProductMapForBuildStartCall(settings) {
const product = helper.getObservabilityLinkedProductName(settings.desiredCapabilities, settings?.selenium?.host);

const buildProductMap = {
automate: product === 'automate',
app_automate: product === 'app-automate',
Expand All @@ -881,6 +889,6 @@ class TestObservability {
getTestBody(testCaseData) {
return testCaseData?.context.__module[testCaseData.testName] || null;
}
}
}

module.exports = TestObservability;
72 changes: 72 additions & 0 deletions src/utils/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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 = (settings = {}) => {
const nestedConfigTestPlanId = normalizeTestPlanId(
settings?.testManagementOptions?.testPlanId
);

if (nestedConfigTestPlanId) {
return nestedConfigTestPlanId;
}

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
* the @nightwatch/browserstack plugin settings from nightwatch.conf.js.
*
* @param {Record<string, unknown>} [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 = (settings = {}, 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(settings);
};

exports.isAppAutomate = () => {
return process.env.BROWSERSTACK_APP_AUTOMATE === 'true';
};
Expand Down
Loading