var Walk = require('./walk.js'); var TestSuite = require('./testsuite.js'); var Logger = require('../util/logger.js'); var Reporter = require('./reporter.js'); var path = require('path'); var Q = require('q'); var globalResults; var currentTestSuite; var finishCallback; function processListener() { process.on('exit', function (code) { var exitCode = code; if (exitCode === 0 && globalResults && (globalResults.errors > 0 || globalResults.failed > 0)) { exitCode = 1; } process.exit(exitCode); }); process.on('uncaughtException', function (err) { if (currentTestSuite) { var testCase = currentTestSuite.getCurrentTestCase(); if (testCase/* && testCase.running*/) { testCase.catchHandler(err); return; } } if (finishCallback) { finishCallback(err); } }); } function Runner(testSource, opts, additionalOpts, doneCb) { this.testSource = testSource || []; this.options = opts; this.additionalOpts = additionalOpts; this.doneCb = doneCb || function() {}; this.globalStartTime = new Date().getTime(); this.currentTestSuite = null; globalResults = this.globalResults = { passed : 0, failed : 0, errors : 0, skipped : 0, tests : 0, errmessages : [], modules : {} }; this.setOptions(); } Runner.prototype.setOptions = function() { this.options.parallelMode = process.env.__NIGHTWATCH_PARALLEL_MODE == '1'; if (this.options.parallelMode) { this.options.currentEnv = process.env.__NIGHTWATCH_ENV_KEY; } this.options.live_output = this.additionalOpts.live_output; this.options.detailed_output = this.additionalOpts.detailed_output; this.options.start_session = this.additionalOpts.start_session; this.options.report_prefix = ''; this.options.test_worker = this.additionalOpts.test_worker; }; Runner.prototype.runTestModule = function(modulePath, fullPaths) { try { currentTestSuite = this.currentTestSuite = new TestSuite(modulePath, fullPaths, this.options, this.additionalOpts, this.doneCb); } catch (err) { this.doneCb(err); return null; } var moduleKey = this.currentTestSuite.getReportKey(); this.globalResults.modules[moduleKey] = { completed : {}, skipped : null, time : null, timestamp : null, group : this.currentTestSuite.getGroupName() }; return this.currentTestSuite .on('testcase:finished', function(results, errors, time) { this.globalResults.modules[moduleKey].completed[this.currentTestSuite.currentTest] = { passed : results.passed, failed : results.failed, errors : results.errors, skipped : results.skipped, assertions : [].concat(results.tests), timeMs : time, time : (time/1000).toPrecision(4), stackTrace : results.stackTrace }; if (Array.isArray(errors) && errors.length) { this.globalResults.errmessages = this.globalResults.errmessages.concat(errors); } this.globalResults.passed += results.passed; this.globalResults.failed += results.failed; this.globalResults.errors += results.errors; this.globalResults.skipped += results.skipped; this.globalResults.tests += results.tests.length; this.globalResults.assertions = this.globalResults.tests; if (this.options.output && this.options.detailed_output && (this.options.modulesNo > 1 || results.tests !== this.globalResults.tests || results.steps.length > 1) ) { this.currentTestSuite.printResult(time); } else if (this.options.output && !this.options.detailed_output) { var error = (results.failed > 0 || results.errors > 0) ? new Error('') : null; console.log(Reporter.getTestOutput(error, this.currentTestSuite.currentTest, time)); if (error !== null) { Reporter.printAssertions(results.tests); } } }.bind(this)) .run() .then(function onTestSuiteResolved(testResults) { var testSuiteResult = this.globalResults.modules[moduleKey]; testSuiteResult.skipped = testResults.steps; testSuiteResult.timestamp = testResults.timestamp; testSuiteResult.time = (testResults.time/1000).toPrecision(4); testSuiteResult.tests = Object.keys(testSuiteResult.completed).length + (testSuiteResult.skipped && testSuiteResult.skipped.length || 0); var failures = 0; var errors = testResults.errors || 0; Object.keys(testSuiteResult.completed).forEach(function(item) { if (testSuiteResult.completed[item].failed > 0) { failures++; } }); testSuiteResult.errmessages = testResults.errmessages || []; testSuiteResult.failures = failures; testSuiteResult.errors = errors; if (typeof process.send == 'function') { process.send(JSON.stringify({ type: 'testsuite_finished', itemKey: process.env.__NIGHTWATCH_ENV_LABEL, moduleKey: moduleKey, results: this.globalResults.modules[moduleKey], errmessages: testSuiteResult.errmessages, passed: this.globalResults.passed, failed: this.globalResults.failed, errors: this.globalResults.errors, skipped: this.globalResults.skipped, tests: this.globalResults.tests })); } return testResults; }.bind(this)); }; Runner.readPaths = function(testSource, opts, cb) { var deferred = Q.defer(); cb = cb || function() {}; if (typeof testSource == 'string') { testSource = [testSource]; } var fullPaths = testSource.map(function (p) { if (p.indexOf(process.cwd()) === 0 || path.resolve(p) === p) { return p; } return path.join(process.cwd(), p); }); if (fullPaths.length === 0) { throw new Error('No source folder defined. Check configuration.'); } var errorMessage = ['No tests defined! using source folder:', fullPaths]; if (opts.tag_filter) { errorMessage.push('; using tags:', opts.tag_filter); } Walk.readPaths(fullPaths, function (err, modules) { if (err) { if (err.code == 'ENOENT') { var error = new Error('Cannot read source folder: ' + err.path); cb(error, false); deferred.reject(error); return; } cb(err, false); deferred.reject(err); return; } opts.modulesNo = modules.length; if (modules.length === 0) { var error2 = new Error(errorMessage.join(' ')); cb(error2); deferred.reject(error2); return; } cb(null, modules, fullPaths); deferred.resolve([modules, fullPaths]); }, opts); return deferred.promise; }; Runner.prototype.run = function runner() { var deferred = Q.defer(); var self = this; finishCallback = this.doneCb; Runner.readPaths(this.testSource, this.options) .spread(function(modulePaths, fullPaths) { (function runNextModule() { var modulePath = modulePaths.shift(); var promise = self.runTestModule(modulePath, fullPaths); if (promise === null) { deferred.resolve(); return; } promise.then(function(testResults) { if (modulePaths.length) { setImmediate(runNextModule); } else { var reporter = new Reporter(self.globalResults, testResults, self.globalStartTime, { output_folder : self.additionalOpts.output_folder, filename_prefix : self.options.report_prefix, globals : self.options.globals, reporter : self.additionalOpts.reporter, start_session : self.options.start_session }); if (self.options.output) { reporter.printTotalResults(self.globalResults, testResults); } reporter.save().then(function() { reporter.globalReporter(self.options.globals)(self.globalResults, function() { try { self.doneCb(null, self.globalResults); deferred.resolve(self.globalResults); } catch (err) { deferred.reject(err); } }); }, function(err) { console.log(Logger.colors.yellow('Output folder doesn\'t exist and cannot be created.')); console.log(err.stack); self.doneCb(null); deferred.resolve(); }); } }, function(err) { self.doneCb(err, self.globalResults); }).catch(function(err) { deferred.reject(err); }); })(); }) .catch(function(err) { self.doneCb(err, false); }) .catch(function(err) { deferred.reject(err); }); return deferred.promise; }; processListener(); module.exports = Runner;