var q = require('q'); /** * Execute the Runner's test cases through Mocha. * * @param {Runner} runner The current Protractor Runner. * @param {Array} specs Array of Directory Path Strings. * @return {q.Promise} Promise resolved with the test results */ exports.run = function(runner, specs) { var Mocha = require('mocha'), mocha = new Mocha(runner.getConfig().mochaOpts); // Add hooks for afterEach require('./setupAfterEach').setup(runner, specs); var deferred = q.defer(); // Mocha doesn't set up the ui until the pre-require event, so // wait until then to load mocha-webdriver adapters as well. mocha.suite.on('pre-require', function() { try { // We need to re-wrap all of the global functions, which `selenium-webdriver/testing` only // does when it is required. So first we must remove it from the cache. delete require.cache[require.resolve('selenium-webdriver/testing')]; var seleniumAdapter = require('selenium-webdriver/testing'); // Save unwrapped version var unwrappedFns = {}; ['after', 'afterEach', 'before', 'beforeEach', 'it', 'xit', 'iit'].forEach(function(fnName) { unwrappedFns[fnName] = global[fnName] || Mocha[fnName]; }); var wrapFn = function(seleniumWrappedFn, opt_fnName) { // This does not work on functions that can be nested (e.g. `describe`) return function() { // Set globals to unwrapped version to avoid circular reference var wrappedFns = {}; for (var fnName in unwrappedFns) { wrappedFns[fnName] = global[fnName]; global[fnName] = unwrappedFns[fnName]; } var args = arguments; // Allow before/after hooks to use names if (opt_fnName && (arguments.length > 1) && (seleniumWrappedFn.length < 2)) { global[opt_fnName] = global[opt_fnName].bind(this, args[0]); args = Array.prototype.slice.call(arguments, 1); } try { seleniumWrappedFn.apply(this, args); } finally { // Restore wrapped version for (fnName in wrappedFns) { global[fnName] = wrappedFns[fnName]; } } }; }; // Wrap functions global.after = wrapFn(seleniumAdapter.after, 'after'); global.afterEach = wrapFn(seleniumAdapter.afterEach, 'afterEach'); global.before = wrapFn(seleniumAdapter.before, 'before'); global.beforeEach = wrapFn(seleniumAdapter.beforeEach, 'beforeEach'); global.it = wrapFn(seleniumAdapter.it); global.iit = wrapFn(seleniumAdapter.it.only); global.xit = wrapFn(seleniumAdapter.xit); global.it.only = wrapFn(seleniumAdapter.it.only); global.it.skip = wrapFn(seleniumAdapter.it.skip); } catch (err) { deferred.reject(err); } }); mocha.loadFiles(); runner.runTestPreparer().then(function() { specs.forEach(function(file) { mocha.addFile(file); }); var testResult = []; var mochaRunner = mocha.run(function(failures) { try { var completed = q(); if (runner.getConfig().onComplete) { completed = q(runner.getConfig().onComplete()); } completed.then(function() { deferred.resolve({ failedCount: failures, specResults: testResult }); }); } catch (err) { deferred.reject(err); } }); mochaRunner.on('pass', function(test) { var testInfo = { name: test.title, category: test.fullTitle().slice(0, -test.title.length).trim() }; runner.emit('testPass', testInfo); testResult.push({ description: test.title, assertions: [{ passed: true }], duration: test.duration }); }); mochaRunner.on('fail', function(test) { var testInfo = { name: test.title, category: test.fullTitle().slice(0, -test.title.length).trim() }; runner.emit('testFail', testInfo); testResult.push({ description: test.title, assertions: [{ passed: false, errorMsg: test.err.message, stackTrace: test.err.stack }], duration: test.duration }); }); }).catch (function(reason) { deferred.reject(reason); }); return deferred.promise; };