refactor app directory structure and add tests

This commit is contained in:
s2
2016-11-10 16:27:26 +01:00
parent 204834ce28
commit dd88218c0e
1844 changed files with 263520 additions and 0 deletions

View File

@@ -0,0 +1,144 @@
var child_process = require('child_process');
var util = require('util');
var events = require('events');
var Logger = require('../../util/logger.js');
function ChildProcess(environment, index, env_output, settings, args) {
events.EventEmitter.call(this);
this.env_output = env_output || [];
this.mainModule = process.mainModule.filename;
this.index = index;
this.itemKey = this.getChildProcessEnvKey(environment);
this.startDelay = settings.parallel_process_delay || ChildProcess.defaultStartDelay;
this.environment = environment;
this.settings = settings;
this.child = null;
this.globalExitCode = 0;
this.processRunning = false;
this.env_label = '';
this.args = args || [];
}
util.inherits(ChildProcess, events.EventEmitter);
ChildProcess.prevIndex = 0;
ChildProcess.defaultStartDelay = 10;
ChildProcess.prototype.setLabel = function(label) {
this.env_itemKey = label;
this.env_label = this.settings.disable_colors ?
(' ' + label + ' ') : Logger.colors.yellow(' ' + label + ' ', Logger.colors.background.black);
return this;
};
ChildProcess.prototype.run = function(colors, done) {
this.availColors = colors;
var cliArgs = this.getArgs();
var env = {};
Object.keys(process.env).forEach(function(key) {
env[key] = process.env[key];
});
var self = this;
setTimeout(function() {
env.__NIGHTWATCH_PARALLEL_MODE = 1;
env.__NIGHTWATCH_ENV = self.environment;
env.__NIGHTWATCH_ENV_KEY = self.itemKey;
env.__NIGHTWATCH_ENV_LABEL = self.env_itemKey;
self.child = child_process.spawn(process.execPath, cliArgs, {
cwd: process.cwd(),
encoding: 'utf8',
env: env,
stdio: [null, null, null, 'ipc']
});
self.child.on('message', function(data) {
self.emit('result', JSON.parse(data));
});
self.processRunning = true;
if (self.settings.output) {
console.log('Started child process for:' + self.env_label);
}
self.child.stdout.on('data', function (data) {
self.writeToStdout(data);
});
self.child.stderr.on('data', function (data) {
self.writeToStdout(data);
});
self.child.on('exit', function (code) {
if (self.settings.output) {
console.log('\n >>' + self.env_label + 'finished. ', '\n');
}
if (code) {
self.globalExitCode = 2;
}
self.processRunning = false;
done(self.env_output, code);
});
}, this.index * this.startDelay);
};
ChildProcess.prototype.getChildProcessEnvKey = function(env) {
return env + '_' + (this.index+1);
};
/**
* Returns an array of cli arguments to be passed to the child process,
* based on the args passed to the main process
* @returns {Array}
*/
ChildProcess.prototype.getArgs = function() {
var args = [this.mainModule];
args.push.apply(args, this.args);
args.push('--parallel-mode');
return args;
};
ChildProcess.prototype.writeToStdout = function(data) {
data = data.toString().trim();
var color_pair = this.availColors[this.index%4];
var output = '';
if (ChildProcess.prevIndex !== this.index) {
ChildProcess.prevIndex = this.index;
if (this.settings.live_output) {
output += '\n';
}
}
if (this.settings.output) {
if (this.settings.disable_colors) {
output += ' ' + this.environment + ' ';
} else {
output += Logger.colors[color_pair[1]](' ' + this.environment + ' ',
Logger.colors.background[color_pair[0]]);
}
output += ' ';
}
output += data;
if (this.settings.live_output) {
process.stdout.write(output + '\n');
} else {
this.env_output.push(output);
}
};
module.exports = ChildProcess;

174
tests/node_modules/nightwatch/lib/runner/cli/cli.js generated vendored Normal file
View File

@@ -0,0 +1,174 @@
/**
* Module dependencies
*/
var opt = require('optimist');
module.exports = new (function() {
var _DEFAULTS_ = {};
var _COMMANDS_ = {};
function Command(name) {
this.name = name;
_DEFAULTS_[this.name] = {};
}
Command.prototype.demand = function(value) {
if (!value && _DEFAULTS_[this.name].demand) {
return _DEFAULTS_[this.name].demand;
}
_DEFAULTS_[this.name].demand = value;
return this;
};
Command.prototype.description = function(value) {
if (!value && _DEFAULTS_[this.name].description) {
return _DEFAULTS_[this.name].alias;
}
_DEFAULTS_[this.name].description = value;
return this;
};
Command.prototype.alias = function(value) {
if (!value && _DEFAULTS_[this.name].alias) {
return _DEFAULTS_[this.name].alias;
}
_DEFAULTS_[this.name].alias = value;
return this;
};
Command.prototype.defaults = function(value) {
if (!value && _DEFAULTS_[this.name]['default']) {
return _DEFAULTS_[this.name]['default'];
}
_DEFAULTS_[this.name]['default'] = value;
return this;
};
Command.prototype.isDefault = function(value) {
return _DEFAULTS_[this.name]['default'] === value;
};
this.showHelp = function() {
return opt.showHelp();
};
this.command = function(name) {
if (_COMMANDS_[name]) {
return _COMMANDS_[name];
}
_COMMANDS_[name] = new Command(name);
return _COMMANDS_[name];
};
this.init = function() {
var argv = opt.usage('Usage: $0 [source] [options]')
.option('source', {
string : true
})
.options(_DEFAULTS_).argv;
return argv;
};
this.setup = function() {
// CLI definitions
// $ nightwatch -c
// $ nightwatch --config
this.command('config')
.demand(true)
.description('Path to configuration file')
.alias('c')
.defaults('./nightwatch.json');
// $ nightwatch -o
// $ nightwatch --output
this.command('output')
.description('Where to save the (JUnit XML) test reports.')
.alias('o')
.defaults('tests_output');
// $ nightwatch -r
// $ nightwatch --reporter
this.command('reporter')
.description('Name of a predefined reporter (e.g. junit) or path to a custom reporter file to use.')
.alias('r')
.defaults('junit');
// $ nightwatch -e
// $ nightwatch --env saucelabs
this.command('env')
.description('Testing environment to use.')
.alias('e')
.defaults('default');
// $ nightwatch --verbose
this.command('verbose')
.description('Turns on selenium command logging during the session.');
// $ nightwatch -t
// $ nightwatch --test
this.command('test')
.description('Runs a single test.')
.alias('t');
// $ nightwatch --testcase
this.command('testcase')
.description('Used only together with --test. Runs the specified testcase from the current suite/module.');
// $ nightwatch -g
// $ nightwatch --group
this.command('group')
.description('Runs a group of tests (i.e. a folder)')
.alias('g');
// $ nightwatch -s
// $ nightwatch --skipgroup
this.command('skipgroup')
.description('Skips one or several (comma separated) group of tests.')
.alias('s');
// $ nightwatch -f
// $ nightwatch --filter
this.command('filter')
.description('Specify a filter (glob expression) as the file name format to use when loading the files.')
.defaults('')
.alias('f');
// $ nightwatch -a
// $ nightwatch --tag
this.command('tag')
.description('Only run tests with the given tag.')
.defaults('')
.alias('a');
// $ nightwatch --skiptags
this.command('skiptags')
.description('Skips tests that have the specified tag or tags (comma separated).');
// $ nightwatch --retries
this.command('retries')
.description('Retries failed or errored testcases up <n> times.');
// $ nightwatch --suiteRetries
this.command('suiteRetries')
.description('Retries failed or errored testsuites up <n> times.');
// $ nightwatch -h
// $ nightwatch --help
this.command('help')
.description('Shows this help.')
.alias('h');
// $ nightwatch -v
// $ nightwatch --version
this.command('version')
.alias('v')
.description('Shows version information.');
return this;
};
})();

View File

@@ -0,0 +1,977 @@
var fs = require('fs');
var path = require('path');
var Q = require('q');
var clone = require('lodash.clone');
var defaults = require('lodash.defaultsdeep');
var Runner = require('../run.js');
var Logger = require('../../util/logger.js');
var Utils = require('../../util/utils.js');
var Selenium = require('../selenium.js');
var ChildProcess = require('./child-process.js');
var ErrorHandler = require('./errorhandler.js');
var SETTINGS_DEPRECTED_VAL = './settings.json';
var SETTINGS_JS_FILE = './nightwatch.conf.js';
function CliRunner(argv) {
this.settings = null;
this.argv = argv;
this.test_settings = null;
this.output_folder = '';
this.parallelMode = false;
this.runningProcesses = {};
this.cli = require('./cli.js');
}
CliRunner.prototype = {
/**
* @param {function} [done]
* @deprecated - use .setup instead
* @returns {CliRunner}
*/
init : function(done) {
this
.readSettings()
.parseTestSettings({}, done);
return this;
},
/**
* @param {object} [settings]
* @param {function} [done]
* @returns {CliRunner}
*/
setup : function(settings, done) {
this
.readSettings()
.parseTestSettings(settings, done);
return this;
},
/**
* Read the provided config json file; defaults to settings.json if one isn't provided
*/
readSettings : function() {
// use default nightwatch.json file if we haven't received another value
if (this.cli.command('config').isDefault(this.argv.config)) {
var defaultValue = this.cli.command('config').defaults();
var localJsValue = path.resolve(SETTINGS_JS_FILE);
if (Utils.fileExistsSync(SETTINGS_JS_FILE)) {
this.argv.config = localJsValue;
} else if (Utils.fileExistsSync(defaultValue)) {
this.argv.config = path.join(path.resolve('./'), this.argv.config);
} else if (Utils.fileExistsSync(SETTINGS_DEPRECTED_VAL)) {
this.argv.config = path.join(path.resolve('./'), SETTINGS_DEPRECTED_VAL);
} else {
var binFolder = path.resolve(__dirname + '/../../../bin');
var defaultFile = path.join(binFolder, this.argv.config);
if (Utils.fileExistsSync(defaultFile)) {
this.argv.config = defaultFile;
} else {
this.argv.config = path.join(binFolder, SETTINGS_DEPRECTED_VAL);
}
}
} else {
this.argv.config = path.resolve(this.argv.config);
}
this.argv.env = typeof this.argv.env == 'string' ? this.argv.env : 'default';
// reading the settings file
try {
this.settings = require(this.argv.config);
this.replaceEnvVariables();
this.manageSelenium = !this.isParallelMode() && this.settings.selenium &&
this.settings.selenium.start_process || false;
if (typeof this.settings.src_folders == 'string') {
this.settings.src_folders = [this.settings.src_folders];
}
this.settings.output = this.settings.output || typeof this.settings.output == 'undefined';
this.settings.detailed_output = this.settings.detailed_output || typeof this.settings.detailed_output == 'undefined';
} catch (ex) {
Logger.error(ex.stack);
this.settings = {};
}
return this;
},
/**
* Looks for pattern ${VAR_NAME} in settings
* @param {Object} [target]
*/
replaceEnvVariables : function(target) {
target = target || this.settings;
for (var key in target) {
switch(typeof target[key]) {
case 'object':
this.replaceEnvVariables(target[key]);
break;
case 'string':
target[key] = target[key].replace(/\$\{(\w+)\}/g, function(match, varName) {
return process.env[varName] || '${' + varName + '}';
});
break;
}
}
return this;
},
/**
* Reads globals from an external js or json file set in the settings file
* @return {CliRunner}
*/
readExternalGlobals : function () {
if (!this.settings.globals_path) {
return this;
}
var fullPath = path.resolve(this.settings.globals_path);
try {
var externalGlobals = require(fullPath);
if (!externalGlobals) {
return this;
}
// if we already have globals, make a copy of them
var globals = this.test_settings.globals ? clone(this.test_settings.globals, true) : {};
if (externalGlobals.hasOwnProperty(this.argv.env)) {
for (var prop in externalGlobals[this.argv.env]) {
externalGlobals[prop] = externalGlobals[this.argv.env][prop];
}
}
defaults(globals, externalGlobals);
this.test_settings.globals = globals;
return this;
} catch (err) {
var originalMsg = Logger.colors.red(err.name + ': ' + err.message);
err.name = 'Error reading external global file failed';
err.message = 'using '+ fullPath + ':\n' + originalMsg;
throw err;
}
},
/**
* @returns {CliRunner}
*/
setOutputFolder : function() {
var isDisabled = this.settings.output_folder === false && typeof(this.test_settings.output_folder) == 'undefined' ||
this.test_settings.output_folder === false;
var isDefault = this.cli.command('output').isDefault(this.argv.output);
var folder = this.test_settings.output_folder || this.settings.output_folder;
this.output_folder = isDisabled ? false : (isDefault && folder || this.argv.output);
return this;
},
singleTestRun: function () {
return typeof this.argv.test == 'string';
},
singleSourceFile: function() {
if (this.singleTestRun()) {
return fs.statSync(this.argv.test).isFile();
}
return (Array.isArray(this.argv._source) && this.argv._source.length == 1) && fs.statSync(this.argv._source[0]).isFile();
},
getTestSourceForSingle: function(targetPath) {
var testsource;
if (targetPath && path.resolve(targetPath) === targetPath) {
testsource = targetPath;
} else {
testsource = path.join(process.cwd(), targetPath);
}
if (testsource.substr(-3) != '.js') {
testsource += '.js';
}
return testsource;
},
/**
* Returns the path where the tests are located
* @returns {*}
*/
getTestSource : function() {
var testsource;
if (this.isTestWorker() || this.singleSourceFile()) {
testsource = this.getTestSourceForSingle(this.argv.test || this.argv._source[0]);
} else {
if (this.argv.testcase) {
this.argv.testcase = null;
if (this.test_settings.output) {
ErrorHandler.logWarning('Option --testcase used without --test is ignored.');
}
}
if (Array.isArray(this.argv._source) && (this.argv._source.length > 0)) {
testsource = this.argv._source;
} else if (this.argv.group) {
// add support for multiple groups
if (typeof this.argv.group == 'string') {
this.argv.group = this.argv.group.split(',');
}
var groupTestsource = this.findGroupPathMultiple(this.argv.group);
// If a group does not exist in the multiple src folder case, it is removed
// from the test path list.
if (this.settings.src_folders.length === 1) {
// any incorrect path here will result in a run error
testsource = groupTestsource;
} else {
// only when all groups fail to match will there be a run error
testsource = groupTestsource.filter(Utils.dirExistsSync);
if (this.argv.verbose) {
var ignoredSource = groupTestsource.filter(function(path) {
return testsource.indexOf(path) === -1;
});
if (ignoredSource.length) {
Logger.warn('The following group paths were not found and will be excluded from the run:\n - ' + ignoredSource.join('\n - '));
}
}
}
} else {
testsource = this.settings.src_folders;
}
}
return testsource;
},
/**
* Gets test paths from each of the src folders for a single group.
*
* @param {string} groupName
* @return {Array}
*/
findGroupPath : function(groupName) {
var fullGroupPath = path.resolve(groupName);
// for each src folder, append the group to the path
// to resolve the full test path
return this.settings.src_folders.map(function(srcFolder) {
var fullSrcFolder = path.resolve(srcFolder);
if (fullGroupPath.indexOf(fullSrcFolder) === 0) {
return groupName;
}
return path.join(srcFolder, groupName);
});
},
/**
* Gets test paths for tests from any number of groups.
*
* @param {Array} groups
*/
findGroupPathMultiple : function(groups) {
var paths = [];
groups.forEach(function(groupName) {
// findGroupPath will return an array of paths
paths = paths.concat(this.findGroupPath(groupName));
}.bind(this));
return paths;
},
/**
* Starts the selenium server process
* @param {function} [callback]
* @returns {CliRunner}
*/
startSelenium : function(callback) {
callback = callback || function() {};
if (this.isTestWorker()) {
callback();
return this;
}
var globalsContext = this.test_settings && this.test_settings.globals || null;
// adding a link to the test_settings object on the globals context
if (globalsContext) {
globalsContext.test_settings = this.test_settings;
}
var beforeGlobal = Utils.checkFunction('before', globalsContext) || function(done) {done();};
var self = this;
if (!this.manageSelenium) {
beforeGlobal.call(globalsContext, function() {
callback();
});
return this;
}
this.settings.parallelMode = this.parallelMode;
beforeGlobal.call(globalsContext, function() {
Selenium.startServer(self.settings, function(error, child, error_out, exitcode) {
if (error) {
console.error('There was an error while starting the Selenium server:');
ErrorHandler.handle({
message : error_out
});
return;
}
callback();
});
});
return this;
},
/**
* Stops the selenium server if it is running
* @return {*|promise}
*/
stopSelenium : function() {
var deferred = Q.defer();
if (this.manageSelenium) {
Selenium.stopServer(function() {
deferred.resolve();
});
} else {
deferred.resolve();
}
return deferred.promise;
},
/**
* Starts the test runner
*
* @param {function} [finished] optional callback
* @returns {CliRunner}
*/
runTests : function(finished) {
if (this.parallelMode) {
return this;
}
var self = this;
var handleError = ErrorHandler.handle;
this.startSelenium(function() {
self.runner(function(err, results) {
self.stopSelenium().then(function() {
if (self.isTestWorker()) {
handleError(err, results, finished);
return;
}
self.runGlobalAfter().then(function (ex) {
handleError(ex || err, results, finished);
});
});
});
});
return this;
},
runner : function(fn) {
var testRunner = this.settings.test_runner || 'default';
var testRunnerType = testRunner.type ? testRunner.type : testRunner;
// getTestSource() will throw on an error, so we need
// to wrap and pass along any error that does occur
// to the callback fn
var source;
try {
source = this.getTestSource();
} catch (err) {
fn(err, {});
return;
}
switch (testRunnerType) {
case 'default':
var runner = new Runner(source, this.test_settings, {
output_folder : this.output_folder,
src_folders : this.settings.src_folders,
live_output : this.settings.live_output,
detailed_output : this.settings.detailed_output,
start_session: this.startSession,
reporter : this.argv.reporter,
testcase : this.argv.testcase,
end_session_on_fail : this.endSessionOnFail,
retries : this.argv.retries,
test_worker : this.isTestWorker(),
suite_retries : this.argv.suiteRetries
}, fn);
return runner.run();
case 'mocha':
var MochaNightwatch = require('mocha-nightwatch');
var mochaOpts = testRunner.options || {
ui : 'bdd'
};
var mocha = new MochaNightwatch(mochaOpts);
var nightwatch = require('../../index.js');
Runner.readPaths(source, this.test_settings, function(error, modulePaths) {
if (error) {
fn(error, {});
return;
}
modulePaths.forEach(function(file) {
mocha.addFile(file);
});
mocha.run(nightwatch, this.test_settings, function(failures) {
fn(null, {
failed : failures
});
if (failures) {
process.exit(10);
}
});
}.bind(this));
return mocha;
}
},
inheritFromDefaultEnv : function() {
if (this.argv.env == 'default') {
return;
}
var defaultEnv = this.settings.test_settings['default'] || {};
defaults(this.test_settings, defaultEnv);
return this;
},
runGlobalAfter : function() {
var deferred = Q.defer();
var globalsContext = this.test_settings && this.test_settings.globals || null;
var afterGlobal = Utils.checkFunction('after', globalsContext) || function(done) {done();};
try {
afterGlobal.call(globalsContext, function done() {
deferred.resolve(null);
});
} catch (ex) {
deferred.resolve(ex);
}
return deferred.promise;
},
/**
* Validates and parses the test settings
* @param {object} [settings]
* @param {function} [done]
* @returns {CliRunner}
*/
parseTestSettings : function(settings, done) {
// checking if the env passed is valid
if (!this.settings.test_settings) {
throw new Error('No testing environment specified.');
}
var envs = this.argv.env.split(',');
for (var i = 0; i < envs.length; i++) {
if (!(envs[i] in this.settings.test_settings)) {
throw new Error('Invalid testing environment specified: ' + envs[i]);
}
}
if (envs.length > 1) {
this.setupParallelMode(envs, done);
return this;
}
this.initTestSettings(this.argv.env, settings);
if (this.parallelModeWorkers()) {
this.setupParallelMode(null, done);
}
return this;
},
setGlobalOutputOptions: function () {
this.test_settings.output = this.test_settings.output || (this.settings.output && typeof this.test_settings.output == 'undefined');
this.test_settings.silent = this.test_settings.silent || typeof this.test_settings.silent == 'undefined';
if (this.argv.verbose) {
this.test_settings.silent = false;
}
return this;
},
/**
* Sets the specific test settings for the specified environment
* @param {string} [env]
* @param {object} [settings]
* @returns {CliRunner}
*/
initTestSettings : function(env, settings) {
// picking the environment specific test settings
this.test_settings = env && this.settings.test_settings[env] || {};
if (env) {
this.test_settings.custom_commands_path = this.settings.custom_commands_path || '';
this.test_settings.custom_assertions_path = this.settings.custom_assertions_path || '';
this.test_settings.page_objects_path = this.settings.page_objects_path || '';
this.inheritFromDefaultEnv();
this.updateTestSettings(settings || {});
this.readExternalGlobals();
}
this.setOutputFolder();
this.setGlobalOutputOptions();
if (typeof this.test_settings.test_workers != 'undefined') {
this.settings.test_workers = this.test_settings.test_workers;
} else if (typeof this.settings.test_workers != 'undefined') {
this.test_settings.test_workers = this.settings.test_workers;
}
if (typeof this.test_settings.test_runner != 'undefined') {
this.settings.test_runner = this.test_settings.test_runner;
}
if (typeof this.test_settings.detailed_output != 'undefined') {
this.settings.detailed_output = this.test_settings.detailed_output;
}
if (typeof this.argv.skipgroup == 'string') {
this.test_settings.skipgroup = this.argv.skipgroup.split(',');
}
if (this.argv.filter) {
this.test_settings.filename_filter = this.argv.filter;
}
if (this.argv.tag) {
this.test_settings.tag_filter = this.argv.tag;
}
if (typeof this.argv.skiptags == 'string') {
this.test_settings.skiptags = this.argv.skiptags.split(',');
}
return this;
},
/**
*
* @param {Object} [test_settings]
* @returns {CliRunner}
*/
updateTestSettings : function(test_settings) {
if (this.parallelMode && !this.parallelModeWorkers()) {
return this;
}
if (test_settings && typeof test_settings == 'object') {
for (var key in test_settings) {
this.test_settings[key] = test_settings[key];
}
}
this.settings.selenium = this.settings.selenium || {};
// overwrite selenium settings per environment
if (Utils.isObject(this.test_settings.selenium)) {
for (var prop in this.test_settings.selenium) {
this.settings.selenium[prop] = this.test_settings.selenium[prop];
}
}
this.manageSelenium = !this.isParallelMode() && this.settings.selenium.start_process || false;
this.startSession = this.settings.selenium.start_session || typeof this.settings.selenium.start_session == 'undefined';
this.mergeSeleniumOptions();
this.disableCliColorsIfNeeded();
this.endSessionOnFail = typeof this.settings.end_session_on_fail == 'undefined' || this.settings.end_session_on_fail;
if (typeof this.test_settings.end_session_on_fail != 'undefined') {
this.endSessionOnFail = this.test_settings.end_session_on_fail;
}
return this;
},
disableCliColorsIfNeeded : function() {
if (this.settings.disable_colors || this.test_settings.disable_colors) {
Logger.disableColors();
}
return this;
},
/**
* Backwards compatible method which attempts to merge deprecated driver specific options for selenium
* @returns {CliRunner}
*/
mergeSeleniumOptions : function() {
if (!this.manageSelenium) {
return this;
}
this.settings.selenium.cli_args = this.settings.selenium.cli_args || {};
this.mergeCliArgs();
var deprecationNotice = function(propertyName, newSettingName) {
ErrorHandler.logWarning('DEPRECATION NOTICE: Property ' + propertyName + ' is deprecated since v0.5. Please' +
' use the "cli_args" object on the "selenium" property to define "' + newSettingName + '". E.g.:');
var demoObj = '{\n' +
' "cli_args": {\n' +
' "'+ Logger.colors.yellow(newSettingName) +'": "<VALUE>"\n' +
' }\n' +
'}';
console.log(demoObj, '\n');
};
if (this.test_settings.firefox_profile) {
deprecationNotice('firefox_profile', 'webdriver.firefox.profile');
this.settings.selenium.cli_args['webdriver.firefox.profile'] = this.test_settings.firefox_profile;
}
if (this.test_settings.chrome_driver) {
deprecationNotice('chrome_driver', 'webdriver.chrome.driver');
this.settings.selenium.cli_args['webdriver.chrome.driver'] = this.test_settings.chrome_driver;
}
if (this.test_settings.ie_driver) {
deprecationNotice('ie_driver', 'webdriver.ie.driver');
this.settings.selenium.cli_args['webdriver.ie.driver'] = this.test_settings.ie_driver;
}
return this;
},
/**
* Merge current environment specific cli_args into the main cli_args object to be passed to selenium
*
* @returns {CliRunner}
*/
mergeCliArgs : function() {
if (Utils.isObject(this.test_settings.cli_args)) {
for (var prop in this.test_settings.cli_args) {
if (this.test_settings.cli_args.hasOwnProperty(prop)) {
this.settings.selenium.cli_args[prop] = this.test_settings.cli_args[prop];
}
}
}
return this;
},
////////////////////////////////////////////////////////////////////////////////////
// Parallelism related
////////////////////////////////////////////////////////////////////////////////////
isParallelMode : function() {
return process.env.__NIGHTWATCH_PARALLEL_MODE === '1';
},
isTestWorker : function() {
return this.isParallelMode() && this.argv['test-worker'];
},
parallelModeWorkers: function () {
return !this.isParallelMode() && !this.singleSourceFile() && (this.settings.test_workers === true ||
Utils.isObject(this.settings.test_workers) && this.settings.test_workers.enabled);
},
/**
* Enables parallel execution mode
* @param {Array} envs
* @param {function} [done]
* @returns {CliRunner}
*/
setupParallelMode : function(envs, done) {
this.parallelMode = true;
var self = this;
this.startSelenium(function() {
self.startChildProcesses(envs, function(code) {
self.stopSelenium().then(function() {
if (self.parallelModeWorkers()) {
return self.runGlobalAfter();
}
return true;
}).then(function() {
if (done) {
try {
done(self.childProcessOutput, code);
} catch (err) {
done(err);
}
}
if (code) {
process.exit(code);
}
});
});
});
return this;
},
getAvailableColors : function () {
var availColors = [
['red', 'light_gray'],
['green', 'black'],
['blue', 'light_gray'],
['magenta', 'light_gray']
];
var currentIndex = availColors.length, temporaryValue, randomIndex;
// While there remain elements to shuffle...
while (0 !== currentIndex) {
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex -= 1;
// And swap it with the current element.
temporaryValue = availColors[currentIndex];
availColors[currentIndex] = availColors[randomIndex];
availColors[randomIndex] = temporaryValue;
}
return availColors;
},
/**
* Start a new child process for each environment
* @param {Array} envs
* @param {function} doneCallback
*/
startChildProcesses : function(envs, doneCallback) {
doneCallback = doneCallback || function() {};
var availColors = this.getAvailableColors();
this.childProcessOutput = {};
ChildProcess.prevIndex = 0;
var args = this.getChildProcessArgs(envs);
if (envs === null) {
this.startTestWorkers(availColors, args, doneCallback);
return this;
}
this.startEnvChildren(envs, availColors, args, doneCallback);
},
startEnvChildren : function(envs, availColors, args, doneCallback) {
var self = this;
var globalExitCode = 0;
envs.forEach(function(environment, index) {
self.childProcessOutput[environment] = [];
var childArgs = args.slice();
childArgs.push('--env', environment);
var child = new ChildProcess(environment, index, self.childProcessOutput[environment], self.settings, childArgs);
child.setLabel(environment + ' environment');
self.runningProcesses[child.itemKey] = child;
self.runningProcesses[child.itemKey].run(availColors, function(output, exitCode) {
if (exitCode > 0) {
globalExitCode = exitCode;
}
if (self.processesRunning() === 0) {
if (!self.settings.live_output) {
self.printChildProcessOutput();
}
doneCallback(globalExitCode);
}
});
});
},
getChildProcessArgs : function(envs) {
var childProcessArgs = [];
var arg;
for (var i = 2; i < process.argv.length; i++) {
arg = process.argv[i];
if (envs && (arg == '-e' || arg == '--env')) {
i++;
} else {
childProcessArgs.push(arg);
}
}
return childProcessArgs;
},
startTestWorkers : function(availColors, args, doneCallback) {
var workerCount = this.getTestWorkersCount();
var source = this.getTestSource();
var self = this;
var globalExitCode = 0;
var globalStartTime = new Date().getTime();
var globalResults = {
errmessages : [],
modules : {},
passed : 0,
failed : 0,
errors : 0,
skipped : 0,
tests : 0
};
var Reporter = require('../../runner/reporter.js');
var globalReporter = new Reporter(globalResults, {}, globalStartTime, {start_session: this.startSession});
Runner.readPaths(source, this.test_settings, function(error, modulePaths) {
if (error) {
ErrorHandler.handle(error, null, doneCallback);
return;
}
var remaining = modulePaths.length;
Utils.processAsyncQueue(workerCount, modulePaths, function(modulePath, index, next) {
var outputLabel = Utils.getModuleKey(modulePath, self.settings.src_folders, modulePaths);
var childOutput = self.childProcessOutput[outputLabel] = [];
// arguments to the new child process, essentially running a single test suite
var childArgs = args.slice();
childArgs.push('--test', modulePath, '--test-worker');
var settings = clone(self.settings);
settings.output = settings.output && settings.detailed_output;
var child = new ChildProcess(outputLabel, index, childOutput, settings, childArgs);
child.setLabel(outputLabel);
child.on('result', function(childResult) {
switch (childResult.type) {
case 'testsuite_finished':
globalResults.modules[childResult.moduleKey] = childResult.results;
if (childResult.errmessages.length) {
globalResults.errmessages.concat(childResult.errmessages);
}
globalResults.passed += childResult.passed;
globalResults.failed += childResult.failed;
globalResults.errors += childResult.errors;
globalResults.skipped += childResult.skipped;
globalResults.tests += childResult.tests;
self.printChildProcessOutput(childResult.itemKey);
break;
case 'testsuite_started':
break;
}
});
self.runningProcesses[child.itemKey] = child;
self.runningProcesses[child.itemKey].run(availColors, function (output, exitCode) {
if (exitCode > 0) {
globalExitCode = exitCode;
}
remaining -=1;
if (remaining > 0) {
next();
} else {
if (!self.settings.live_output) {
globalReporter.printTotalResults();
}
doneCallback(globalExitCode);
}
});
});
});
},
printChildProcessOutput : function(label) {
var self = this;
if (label) {
self.childProcessOutput[label] = self.childProcessOutput[label].filter(function(item) {
return item !== '';
}).map(function(item) {
if (item == '\\n') {
item = '\n';
}
return item;
});
self.childProcessOutput[label].forEach(function(output) {
process.stdout.write(output + '\n');
});
self.childProcessOutput[label].length = 0;
return;
}
Object.keys(this.childProcessOutput).forEach(function(environment) {
self.printChildProcessOutput(environment);
});
},
getTestWorkersCount : function() {
var workers = 1;
if (this.settings.test_workers === true || this.settings.test_workers.workers === 'auto') {
workers = require('os').cpus().length;
} else if ('number' === typeof this.settings.test_workers.workers) {
workers = this.settings.test_workers.workers;
}
return workers;
},
processesRunning : function() {
var running = 0;
for (var item in this.runningProcesses) {
if (this.runningProcesses.hasOwnProperty(item) && this.runningProcesses[item].processRunning) {
running += 1;
}
}
return running;
}
};
module.exports = CliRunner;

View File

@@ -0,0 +1,70 @@
var Logger = require('../../util/logger.js');
var ErrorHandler = module.exports = {
handle : function(err, results, finished) {
finished = finished || function() {};
if (results && results.errors) {
console.log(results.errmessages.join('\n'));
}
if (err) {
Logger.enable();
if (!err.message) {
err.message = 'There was an error while running the test.';
}
ErrorHandler.logError(err);
finished(false);
process.exit(1);
} else {
var result = true;
if (results.failed || results.errors) {
result = false;
}
finished(result);
}
},
logWarning : function(message) {
console.warn(Logger.colors.brown(message));
},
logError : function(err) {
if (!err) {
return;
}
var util = require('util');
console.error('');
var stackTrace = err && err.stack;
if (!stackTrace) {
var data;
if (err.message) {
data = err.data;
err = err.message;
}
if (typeof err == 'string') {
process.stderr.write(Logger.colors.red(err));
if (data) {
if (typeof data == 'object' && Object.keys(data).length > 0) {
data = util.inspect(data);
}
process.stderr.write(Logger.colors.stack_trace(data) + '\n');
}
process.stderr.write('\n');
} else {
console.error(err);
}
return;
}
var parts = stackTrace.split('\n');
process.stderr.write(Logger.colors.red(parts.shift()) + '\n');
process.stderr.write(parts.join('\n') + '\n\n');
}
};