UNPKG

cucumber-parallelly

Version:
166 lines (153 loc) 7.08 kB
'use strict'; /** * Executes the cucumber scenarios on a pool of workers parallelly * @param config {Object} The config object for the execution containing: * config.tags {String[]} The tags that will be passed to cucumber for execution * config.maxWorkerCount {number} The number of parallel threads the scenarios will be executed, by default 5 * config.maxRetryCount {number} The number of maximum retries for failed scenarios, by default 0, * config.hideOutput {boolean} The stdout/stderr for the threads can be switched off, by default they are displayed * config.reportPath {String} The path to the final report, by default './reports/report.json' * config.cucumberPath {String} The path to the cucumber executable * config.cucumberOpts {String} The options to start the cucumber framework * config.tempReportPath {String} The path to the temp reports, by default './reports/tmp/' * config.weightingTags {Object} Helper to sort scenarios by duration {pattern: '', default: 1}; * config.environment {Object} Key-value pair of environment variables to set, by default empty * @constructor */ var ScenariosOnWorkerPool = function ScenariosOnWorkerPool(config) { var maxWorkerCount = config.threads == undefined ? 5 : config.threads, maxRetryCount = config.retries == undefined ? 0 : config.retries, hideOutput = config.silent == undefined ? false : config.silent, exec = require('child_process').exec, fs = require('fs'), REPORT_FILE = config.reportPath || './reports/report.json', TEMP_REPORT_PATH = config.tempReportPath || './reports/tmp/', RETRY_STR = '_RETRY_', activeWorkers = 0, allTests = 0, failedTests = 0, passedTests = 0, tagsToGet = { tags: config.tags || [] }, arrayOfScenarioPaths = [], testResults = {}, startTime, helper = require('./helper.js'), path = require('path'), reportCreator = require('./report_creator.js'), command = "node " + config.cucumberPath + " " + config.cucumberOpts; /** * Starts to create threads to execute the scenarios. Every scenario runs in its own thread. */ function processScenarios() { while (activeWorkers < maxWorkerCount && arrayOfScenarioPaths.length > 0) { var scenario = arrayOfScenarioPaths.shift(), tempReportFile = (TEMP_REPORT_PATH + scenario.replace(/.*\/features\//, '').replace(/:/g, '_').replace('.', '_') + ".json").replace(/\\/g, '/'), execution; // Remove the string from the end of the path indicating it's a rerun if there's any scenario = scenario.replace(/_RETRY_\d+/, ''); helper.createFolderStructure(tempReportFile); activeWorkers++; allTests++; execution = createExecution(scenario, tempReportFile); if (!hideOutput) { execution.stdout.pipe(process.stdout); execution.stderr.pipe(process.stderr); } } } /** * Creates the child process that will execute the scenario. The callback function for the execution will recursively * create scenarios as long as there are new ones to be executed. * The last one will call a function to show summary and terminate the execution. * @param scenario The 'path:line' format location of the scenario that cucumber will execute * @param tempReportFile The location of the temporary report file that will be attached to the report of the whole execution */ function createExecution(scenario, tempReportFile) { scenario = path.relative('./', scenario); // on windows cucumber doesn't work well with absolute path (reads all scenarios) return exec(command + " --format json:" + tempReportFile + " " + scenario, { maxBuffer: 1024 * 1000 }, function (err) { if (err) { testResults[scenario] = false; failedTests++; // If retry is enabled and the execution was the first one for the scenario if (tempReportFile.indexOf(RETRY_STR) == -1 && maxRetryCount > 0) { arrayOfScenarioPaths.push(scenario + RETRY_STR + 1); // If the maximum retries were not exceeded yet for the scenario } else if (tempReportFile.indexOf(RETRY_STR) != -1 && helper.getRetryCountFromPath(tempReportFile) < maxRetryCount) { var count = helper.getRetryCountFromPath(tempReportFile) + 1; arrayOfScenarioPaths.push(scenario + RETRY_STR + count); } } else { // If it had undefined step then mark the scenario as failed, otherwise it passed if (helper.hadUndefinedStep(tempReportFile)) { testResults[scenario] = false; failedTests++; } else { testResults[scenario] = true; passedTests++; } } activeWorkers--; reportCreator.extendReport(tempReportFile); // If there are more tests to execute, or there are other active threads, call the parent function, otherwise finish if (arrayOfScenarioPaths.length === 0 && activeWorkers === 0) { return result(); } else { return processScenarios(); } }); } /** * Creates an empty report file for the final report, and sets environment variables listed in config.environment if * there is any. */ function initialize() { startTime = new Date(); return helper.getScenarios(tagsToGet, config.weightingTags, config.featuresPaths).then(function (scens) { arrayOfScenarioPaths = scens; helper.setEnvironmentVariables(config.environment); reportCreator.init(REPORT_FILE); }); } /** * Prints the summary to the console, and exits reflecting the outcome of the test execution. */ function result() { reportCreator.finalizeReport(); var overallSuccess = 0, failedAfterRetry = 0, executionDuration = new Date() - startTime, min = Math.floor(executionDuration / 1000 / 60), sec = Math.floor((executionDuration / 1000).toFixed(2) - min * 60); console.log('All tests: ' + allTests); console.log('Passed tests: ' + passedTests); console.log('Failed tests: ' + failedTests); Object.keys(testResults).forEach(function (scenario) { if (!testResults[scenario]) { overallSuccess = 1; failedAfterRetry++; console.log(scenario + ' has failed'); } }); console.log('Failed tests (retry): ' + failedAfterRetry); console.info("Execution time (" + executionDuration + "ms): " + min + "m " + sec + "s"); if (config.exit) { process.exit(overallSuccess); } else { return overallSuccess; } } /** * This function is responsible for the execution. * It will collect scenarios, execute them, create the report file and terminate the execution at the end. */ this.execute = function () { return initialize().then(function () { if (arrayOfScenarioPaths.length == 0) { return result(); } else { return processScenarios(); } }); }; }; module.exports = ScenariosOnWorkerPool;