Skip to content

Commit 167612c

Browse files
Test Orchestration Integration For Nightwatch SDK (#41)
* feat: Implement Nightwatch Test Orchestration Module - Added helpers.js for utility functions related to test orchestration. - Created index.js to serve as the main entry point for the orchestration module. - Introduced orchestrationUtils.js for managing orchestration settings and operations. - Developed requestUtils.js for handling API requests to the BrowserStack orchestration API. - Implemented testOrchestrationHandler.js to manage test orchestration operations. - Added testOrchestrationIntegration.js for integrating orchestration with Nightwatch. - Created testOrderingServer.js to handle communication with the BrowserStack server for test ordering. - Updated requestHelper.js to improve error handling for API requests. * feat: Enhance test file collection and orchestration logic with improved glob pattern matching * feat: Refactor test orchestration logic to apply specific feature path for cucumber tests * feat: Update cucumber feature path for API tests in Nightwatch globals * feat: Update cucumber feature path handling to use ordered files * feat: Add framework name to build data payload in OrchestrationUtils * feat: Remove unused helper functions and debug logs from orchestration files * feat: Update framework name comment in build data payload and enhance error logging in RequestUtils * feat: Remove unused orchestration files and refactor helper functions for improved Git metadata handling * style: eslint update * feat: Remove framework name from build data payload in OrchestrationUtils
1 parent 31fdff6 commit 167612c

File tree

9 files changed

+1605
-2
lines changed

9 files changed

+1605
-2
lines changed

nightwatch/globals.js

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ const {v4: uuidv4} = require('uuid');
88
const path = require('path');
99
const AccessibilityAutomation = require('../src/accessibilityAutomation');
1010
const eventHelper = require('../src/utils/eventHelper');
11+
const OrchestrationUtils = require('../src/testorchestration/orchestrationUtils');
12+
const {type} = require('os');
1113
const localTunnel = new LocalTunnel();
1214
const testObservability = new TestObservability();
1315
const accessibilityAutomation = new AccessibilityAutomation();
@@ -270,7 +272,7 @@ module.exports = {
270272
}
271273
},
272274

273-
async before(settings) {
275+
async before(settings, testEnvSettings) {
274276
if (!settings.desiredCapabilities['bstack:options']) {
275277
settings.desiredCapabilities['bstack:options'] = {};
276278
}
@@ -322,6 +324,91 @@ module.exports = {
322324
} catch (error) {
323325
Logger.error(`Could not configure or launch test reporting and analytics - ${error}`);
324326
}
327+
328+
// Initialize and configure test orchestration
329+
try {
330+
const orchestrationUtils = OrchestrationUtils.getInstance(settings);
331+
if (orchestrationUtils && orchestrationUtils.testOrderingEnabled()) {
332+
// Apply test orchestration to reorder test files before execution
333+
const TestOrchestrationIntegration = require('../src/testorchestration/testOrchestrationIntegration');
334+
const orchestrationIntegration = TestOrchestrationIntegration.getInstance();
335+
orchestrationIntegration.configure(settings);
336+
337+
// Check if we have test files to reorder from various sources
338+
let allTestFiles = [];
339+
340+
// Checking either for Feature Path or src_folders, feature path take priority
341+
if (helper.isCucumberTestSuite(settings) && settings?.test_runner?.options?.feature_path){
342+
Logger.debug('Getting test files from feature_path configuration...');
343+
if (Array.isArray(settings.test_runner.options.feature_path)){
344+
settings.test_runner.options.feature_path.forEach(featurePath => {
345+
const files = helper.collectTestFiles(featurePath, 'feature_path config');
346+
allTestFiles = allTestFiles.concat(files);
347+
});
348+
} else if (typeof settings.test_runner.options.feature_path === 'string'){
349+
const files = helper.collectTestFiles(settings.test_runner.options.feature_path, 'feature_path config');
350+
allTestFiles = allTestFiles.concat(files);
351+
}
352+
} else if (settings.src_folders && Array.isArray(settings.src_folders) && settings.src_folders.length > 0) {
353+
Logger.debug('Getting test files from src_folders configuration...');
354+
settings.src_folders.forEach(folder => {
355+
const files = helper.collectTestFiles(folder, 'src_folders config');
356+
allTestFiles = allTestFiles.concat(files);
357+
});
358+
}
359+
360+
// Remove duplicates and ensure all paths are relative to cwd
361+
allTestFiles = [...new Set(allTestFiles)].map(file => {
362+
return path.isAbsolute(file) ? path.relative(process.cwd(), file) : file;
363+
});
364+
365+
366+
if (allTestFiles.length > 0) {
367+
Logger.info(`Applying test orchestration to reorder test files... Found ${allTestFiles.length} test files`);
368+
Logger.debug(`Test files: ${JSON.stringify(allTestFiles)}`);
369+
370+
// Apply orchestration to get ordered test files (synchronously)
371+
try {
372+
const orderedFiles = await orchestrationIntegration.applyOrchestration(allTestFiles, settings);
373+
if (orderedFiles && orderedFiles.length > 0) {
374+
Logger.info(`Test files reordered by orchestration: ${orderedFiles.length} files`);
375+
376+
Logger.info('Test orchestration recommended order change:');
377+
Logger.info(`Original: ${allTestFiles.join(', ')}`);
378+
Logger.info(`Optimized: ${orderedFiles.join(', ')}`);
379+
380+
try {
381+
if (helper.isCucumberTestSuite(settings) && settings?.test_runner?.options?.feature_path){
382+
// For cucumber, we override the feature_path option with ordered files
383+
settings.test_runner.options['feature_path'] = orderedFiles;
384+
} else {
385+
settings.src_folders = orderedFiles;
386+
for (const envName in testEnvSettings) {
387+
testEnvSettings[envName].src_folders = orderedFiles;
388+
testEnvSettings[envName].test_runner.src_folders = orderedFiles;
389+
}
390+
if (settings.test_runner && typeof settings.test_runner === 'object' && !Array.isArray(settings.test_runner)) {
391+
settings.test_runner.src_folders = orderedFiles;
392+
}
393+
}
394+
395+
} catch (reorderError) {
396+
Logger.error(`Runtime reordering failed: ${reorderError.message}`);
397+
Logger.info('Falling back to original order for current execution.');
398+
}
399+
} else {
400+
Logger.info('Split test API called - no reordering available');
401+
}
402+
} catch (error) {
403+
Logger.error(`Error applying test orchestration: ${error}`);
404+
}
405+
} else {
406+
Logger.debug('No test files found for orchestration - skipping split test API call');
407+
}
408+
}
409+
} catch (error) {
410+
Logger.error(`Could not configure test orchestration - ${error}`);
411+
}
325412

326413
try {
327414
accessibilityAutomation.configure(settings);
@@ -344,6 +431,18 @@ module.exports = {
344431

345432
async after() {
346433
localTunnel.stop();
434+
435+
// Collect build data for test orchestration if enabled
436+
try {
437+
const orchestrationUtils = OrchestrationUtils.getInstance();
438+
if (orchestrationUtils && orchestrationUtils.testOrderingEnabled()) {
439+
Logger.info('Collecting build data for test orchestration...');
440+
await orchestrationUtils.collectBuildData(this.settings || {});
441+
}
442+
} catch (error) {
443+
Logger.error(`Error collecting build data for test orchestration: ${error}`);
444+
}
445+
347446
if (helper.isTestObservabilitySession()) {
348447
process.env.NIGHTWATCH_RERUN_FAILED = nightwatchRerun;
349448
process.env.NIGHTWATCH_RERUN_REPORT_FILE = nightwatchRerunFile;
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
const {performance} = require('perf_hooks');
2+
const Logger = require('../utils/logger');
3+
const TestOrchestrationHandler = require('./testOrchestrationHandler');
4+
5+
/**
6+
* Applies test orchestration to the Nightwatch test run
7+
* This function is the main entry point for the orchestration integration
8+
*/
9+
async function applyOrchestrationIfEnabled(specs, config) {
10+
// Initialize orchestration handler
11+
const orchestrationHandler = TestOrchestrationHandler.getInstance(config);
12+
Logger.info('Orchestration handler is initialized');
13+
14+
if (!orchestrationHandler) {
15+
Logger.warn('Orchestration handler is not initialized. Skipping orchestration.');
16+
17+
return specs;
18+
}
19+
20+
// Check if runSmartSelection is enabled in config
21+
const testOrchOptions = config.testOrchestrationOptions || config['@nightwatch/browserstack']?.testOrchestrationOptions || {};
22+
const runSmartSelectionEnabled = Boolean(testOrchOptions?.runSmartSelection?.enabled);
23+
24+
if (!runSmartSelectionEnabled) {
25+
Logger.info('runSmartSelection is not enabled in config. Skipping orchestration.');
26+
27+
return specs;
28+
}
29+
30+
// Check if orchestration is enabled
31+
let testOrderingApplied = false;
32+
orchestrationHandler.addToOrderingInstrumentationData('enabled', orchestrationHandler.testOrderingEnabled());
33+
34+
const startTime = performance.now();
35+
36+
Logger.info('Test orchestration is enabled. Attempting to reorder test files.');
37+
38+
// Get the test files from the specs
39+
const testFiles = specs;
40+
testOrderingApplied = true;
41+
Logger.info(`Test files to be reordered: ${testFiles.join(', ')}`);
42+
43+
// Reorder the test files
44+
const orderedFiles = await orchestrationHandler.reorderTestFiles(testFiles);
45+
46+
if (orderedFiles && orderedFiles.length > 0) {
47+
orchestrationHandler.setTestOrderingApplied(testOrderingApplied);
48+
Logger.info(`Tests reordered using orchestration: ${orderedFiles.join(', ')}`);
49+
50+
// Return the ordered files as the new specs
51+
orchestrationHandler.addToOrderingInstrumentationData(
52+
'timeTakenToApply',
53+
Math.floor(performance.now() - startTime) // Time in milliseconds
54+
);
55+
56+
return orderedFiles;
57+
}
58+
Logger.info('No test files were reordered by orchestration.');
59+
orchestrationHandler.addToOrderingInstrumentationData(
60+
'timeTakenToApply',
61+
Math.floor(performance.now() - startTime) // Time in milliseconds
62+
);
63+
64+
65+
return specs;
66+
}
67+
68+
module.exports = {
69+
applyOrchestrationIfEnabled
70+
};

0 commit comments

Comments
 (0)