run.js 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. var Walk = require('./walk.js');
  2. var TestSuite = require('./testsuite.js');
  3. var Logger = require('../util/logger.js');
  4. var Reporter = require('./reporter.js');
  5. var path = require('path');
  6. var Q = require('q');
  7. var globalResults;
  8. var currentTestSuite;
  9. var finishCallback;
  10. function processListener() {
  11. process.on('exit', function (code) {
  12. var exitCode = code;
  13. if (exitCode === 0 && globalResults && (globalResults.errors > 0 || globalResults.failed > 0)) {
  14. exitCode = 1;
  15. }
  16. process.exit(exitCode);
  17. });
  18. process.on('uncaughtException', function (err) {
  19. if (currentTestSuite) {
  20. var testCase = currentTestSuite.getCurrentTestCase();
  21. if (testCase/* && testCase.running*/) {
  22. testCase.catchHandler(err);
  23. return;
  24. }
  25. }
  26. if (finishCallback) {
  27. finishCallback(err);
  28. } else {
  29. console.log(err);
  30. }
  31. });
  32. }
  33. function Runner(testSource, opts, additionalOpts, doneCb) {
  34. this.testSource = testSource || [];
  35. this.options = opts;
  36. this.additionalOpts = additionalOpts;
  37. this.doneCb = doneCb || function() {};
  38. this.globalStartTime = new Date().getTime();
  39. this.currentTestSuite = null;
  40. globalResults = this.globalResults = {
  41. passed : 0,
  42. failed : 0,
  43. errors : 0,
  44. skipped : 0,
  45. tests : 0,
  46. errmessages : [],
  47. modules : {}
  48. };
  49. this.setOptions();
  50. }
  51. Runner.setFinishCallback = function(cb) {
  52. finishCallback = cb;
  53. };
  54. Runner.prototype.setOptions = function() {
  55. this.options.parallelMode = process.env.__NIGHTWATCH_PARALLEL_MODE == '1';
  56. if (this.options.parallelMode) {
  57. this.options.currentEnv = process.env.__NIGHTWATCH_ENV_KEY;
  58. }
  59. this.options.live_output = this.additionalOpts.live_output;
  60. this.options.detailed_output = this.additionalOpts.detailed_output;
  61. this.options.start_session = this.additionalOpts.start_session;
  62. this.options.report_prefix = '';
  63. this.options.test_worker = this.additionalOpts.test_worker;
  64. };
  65. Runner.prototype.runTestModule = function(modulePath, fullPaths) {
  66. try {
  67. currentTestSuite = this.currentTestSuite = new TestSuite(modulePath, fullPaths, this.options, this.additionalOpts, this.doneCb);
  68. } catch (err) {
  69. this.doneCb(err);
  70. return null;
  71. }
  72. var moduleKey = this.currentTestSuite.getReportKey();
  73. this.globalResults.modules[moduleKey] = {
  74. completed : {},
  75. skipped : null,
  76. time : null,
  77. timestamp : null,
  78. group : this.currentTestSuite.getGroupName()
  79. };
  80. return this.currentTestSuite
  81. .on('testcase:finished', function(results, errors, time) {
  82. var tests = this.globalResults.modules[moduleKey].completed[this.currentTestSuite.currentTest] = {
  83. passed : results.passed,
  84. failed : results.failed,
  85. errors : results.errors,
  86. skipped : results.skipped,
  87. assertions : [].concat(results.tests),
  88. timeMs : time,
  89. time : (time/1000).toPrecision(4),
  90. stackTrace : results.stackTrace
  91. };
  92. if (Array.isArray(errors) && errors.length) {
  93. this.globalResults.errmessages = this.globalResults.errmessages.concat(errors);
  94. }
  95. this.globalResults.passed += results.passed;
  96. this.globalResults.failed += results.failed;
  97. this.globalResults.errors += results.errors;
  98. this.globalResults.skipped += results.skipped;
  99. this.globalResults.tests += results.tests.length;
  100. this.globalResults.assertions = this.globalResults.tests;
  101. if (this.options.output && this.options.detailed_output &&
  102. (this.options.modulesNo > 1 || results.tests !== this.globalResults.tests || results.steps.length > 1)
  103. ) {
  104. this.currentTestSuite.printResult(time);
  105. } else if (this.options.output && !this.options.detailed_output) {
  106. var error = (results.failed > 0 || results.errors > 0) ? new Error('') : null;
  107. console.log(Reporter.getTestOutput(error, this.currentTestSuite.currentTest, time));
  108. if (error !== null) {
  109. Reporter.printAssertions(tests);
  110. }
  111. }
  112. }.bind(this))
  113. .run()
  114. .then(function onTestSuiteResolved(testResults) {
  115. var testSuiteResult = this.globalResults.modules[moduleKey];
  116. testSuiteResult.skipped = testResults.steps;
  117. testSuiteResult.timestamp = testResults.timestamp;
  118. testSuiteResult.time = (testResults.time/1000).toPrecision(4);
  119. testSuiteResult.tests = Object.keys(testSuiteResult.completed).length + (testSuiteResult.skipped && testSuiteResult.skipped.length || 0);
  120. var failures = 0;
  121. var errors = testResults.errors || 0;
  122. Object.keys(testSuiteResult.completed).forEach(function(item) {
  123. if (testSuiteResult.completed[item].failed > 0) {
  124. failures++;
  125. }
  126. });
  127. if (testResults.errmessages) {
  128. this.globalResults.errmessages = this.globalResults.errmessages.concat(testResults.errmessages);
  129. }
  130. testSuiteResult.errmessages = testResults.errmessages || [];
  131. testSuiteResult.failures = failures;
  132. testSuiteResult.errors = errors;
  133. if (typeof process.send == 'function') {
  134. process.send(JSON.stringify({
  135. type: 'testsuite_finished',
  136. itemKey: process.env.__NIGHTWATCH_ENV_LABEL,
  137. moduleKey: moduleKey,
  138. results: this.globalResults.modules[moduleKey],
  139. errmessages: testSuiteResult.errmessages,
  140. passed: this.globalResults.passed,
  141. failed: this.globalResults.failed,
  142. errors: this.globalResults.errors,
  143. skipped: this.globalResults.skipped,
  144. tests: this.globalResults.tests
  145. }));
  146. }
  147. return testResults;
  148. }.bind(this));
  149. };
  150. Runner.readPaths = function(testSource, opts, cb) {
  151. var deferred = Q.defer();
  152. cb = cb || function() {};
  153. if (typeof testSource == 'string') {
  154. testSource = [testSource];
  155. }
  156. var fullPaths = testSource.map(function (p) {
  157. if (p.indexOf(process.cwd()) === 0 || path.resolve(p) === p) {
  158. return p;
  159. }
  160. return path.join(process.cwd(), p);
  161. });
  162. if (fullPaths.length === 0) {
  163. throw new Error('No source folder defined. Check configuration.');
  164. }
  165. var errorMessage = ['No tests defined! using source folder:', fullPaths];
  166. if (opts.tag_filter) {
  167. errorMessage.push('; using tags:', opts.tag_filter);
  168. }
  169. Walk.readPaths(fullPaths, function (err, modules) {
  170. if (err) {
  171. if (err.code == 'ENOENT') {
  172. var error = new Error('Cannot read source folder: ' + err.path);
  173. cb(error, false);
  174. deferred.reject(error);
  175. return;
  176. }
  177. cb(err, false);
  178. deferred.reject(err);
  179. return;
  180. }
  181. opts.modulesNo = modules.length;
  182. if (modules.length === 0) {
  183. var error2 = new Error(errorMessage.join(' '));
  184. cb(error2);
  185. deferred.reject(error2);
  186. return;
  187. }
  188. cb(null, modules, fullPaths);
  189. deferred.resolve([modules, fullPaths]);
  190. }, opts);
  191. return deferred.promise;
  192. };
  193. Runner.prototype.run = function runner() {
  194. var deferred = Q.defer();
  195. var self = this;
  196. finishCallback = this.doneCb;
  197. Runner.readPaths(this.testSource, this.options)
  198. .spread(function(modulePaths, fullPaths) {
  199. (function runNextModule() {
  200. var modulePath = modulePaths.shift();
  201. var promise = self.runTestModule(modulePath, fullPaths);
  202. if (promise === null) {
  203. deferred.resolve();
  204. return;
  205. }
  206. promise.then(function(testResults) {
  207. if (modulePaths.length) {
  208. setImmediate(runNextModule);
  209. } else {
  210. var reporter = new Reporter(self.globalResults, testResults, self.globalStartTime, {
  211. output_folder : self.additionalOpts.output_folder,
  212. filename_prefix : self.options.report_prefix,
  213. globals : self.options.globals,
  214. reporter : self.additionalOpts.reporter,
  215. start_session : self.options.start_session
  216. });
  217. if (self.options.output) {
  218. reporter.printTotalResults(self.globalResults, testResults);
  219. }
  220. reporter.save().then(function() {
  221. reporter.globalReporter(self.options.globals)(self.globalResults, function() {
  222. try {
  223. self.doneCb(null, self.globalResults);
  224. deferred.resolve(self.globalResults);
  225. self.globalResults.errmessages.length = 0;
  226. } catch (err) {
  227. deferred.reject(err);
  228. }
  229. });
  230. }, function(err) {
  231. console.log(Logger.colors.yellow('Output folder doesn\'t exist and cannot be created.'));
  232. console.log(err.stack);
  233. self.doneCb(null);
  234. deferred.resolve();
  235. });
  236. }
  237. }, function(err) {
  238. self.doneCb(err, self.globalResults);
  239. }).catch(function(err) {
  240. deferred.reject(err);
  241. });
  242. })();
  243. })
  244. .catch(function(err) {
  245. self.doneCb(err, false);
  246. })
  247. .catch(function(err) {
  248. deferred.reject(err);
  249. });
  250. return deferred.promise;
  251. };
  252. processListener();
  253. module.exports = Runner;