webdriverio-workflo
Version:
This is a customized version of webdriverio for use with workflo framework.
1,261 lines (1,032 loc) • 75 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray');
var _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2);
var _slicedToArray2 = require('babel-runtime/helpers/slicedToArray');
var _slicedToArray3 = _interopRequireDefault(_slicedToArray2);
var _regenerator = require('babel-runtime/regenerator');
var _regenerator2 = _interopRequireDefault(_regenerator);
var _promise = require('babel-runtime/core-js/promise');
var _promise2 = _interopRequireDefault(_promise);
var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
var _assign = require('babel-runtime/core-js/object/assign');
var _assign2 = _interopRequireDefault(_assign);
var _keys = require('babel-runtime/core-js/object/keys');
var _keys2 = _interopRequireDefault(_keys);
var _getIterator2 = require('babel-runtime/core-js/get-iterator');
var _getIterator3 = _interopRequireDefault(_getIterator2);
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
var _createClass2 = require('babel-runtime/helpers/createClass');
var _createClass3 = _interopRequireDefault(_createClass2);
var _path = require('path');
var _path2 = _interopRequireDefault(_path);
var _child_process = require('child_process');
var _child_process2 = _interopRequireDefault(_child_process);
var _jsonfile = require('jsonfile');
var jsonfile = _interopRequireWildcard(_jsonfile);
var _fs = require('fs');
var fs = _interopRequireWildcard(_fs);
var _ConfigParser = require('./utils/ConfigParser');
var _ConfigParser2 = _interopRequireDefault(_ConfigParser);
var _BaseReporter = require('./utils/BaseReporter');
var _BaseReporter2 = _interopRequireDefault(_BaseReporter);
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var Launcher = function () {
function Launcher(configFile, argv) {
(0, _classCallCheck3.default)(this, Launcher);
this.configParser = new _ConfigParser2.default();
this.configParser.addConfigFile(configFile);
this.configParser.merge(argv);
this.reporters = this.initReporters();
this.argv = argv;
this.configFile = configFile;
this.exitCode = 0;
this.hasTriggeredExitRoutine = false;
this.hasStartedAnyProcess = false;
this.processes = [];
this.schedule = [];
this.rid = [];
this.processesStarted = 0;
this.runnerFailed = 0;
this.argv.screenshotId = 0;
// store validation results
this.finishedTests = false;
this.validationResults = {};
this.validationStates = {};
this.stepStates = {};
// this.uidStore = {}
this.cidProcesses = {};
this.stackStore = {};
this.stackStoreLocks = {};
this.containStackFilter = {
'at new Promise': true,
'/node_modules/': true,
'runMicrotasksCallback': true,
'\\node_modules\\': true
};
this.stackFilter = {
'at <anonymous>': true,
'at new Promise (<anonymous>)': true,
'at Promise (<anonymous>)': true,
'Error': true
};
this.stepErrors = {};
this.waitUntilErrors = {};
this.endHandlerRunFunc = this.runTestcases;
}
/**
* check if multiremote or wdio test
*/
(0, _createClass3.default)(Launcher, [{
key: 'isMultiremote',
value: function isMultiremote() {
var caps = this.configParser.getCapabilities();
return !Array.isArray(caps);
}
/**
* initialise reporters
*/
}, {
key: 'initReporters',
value: function initReporters() {
var config = this.configParser.getConfig();
var reporter = new _BaseReporter2.default(config);
/**
* if no reporter is set or config property is in a wrong format
* just use the dot reporter
*/
if (!config.reporters || !Array.isArray(config.reporters) || !config.reporters.length) {
config.reporters = ['dot'];
}
var reporters = {};
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = (0, _getIterator3.default)(config.reporters), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var reporterName = _step.value;
var Reporter = void 0;
if (typeof reporterName === 'function') {
Reporter = reporterName;
if (!Reporter.reporterName) {
throw new Error('Custom reporters must export a unique \'reporterName\' property');
}
reporters[Reporter.reporterName] = Reporter;
} else if (typeof reporterName === 'string') {
try {
var pkgName = reporterName.startsWith('@') ? reporterName : `wdio-${reporterName}-reporter`;
Reporter = require(pkgName);
} catch (e) {
throw new Error(`reporter "wdio-${reporterName}-reporter" is not installed. Error: ${e.stack}`);
}
reporters[reporterName] = Reporter;
}
if (!Reporter) {
throw new Error(`config.reporters must be an array of strings or functions, but got '${typeof reporterName}': ${reporterName}`);
}
}
/**
* if no reporter options are set or property is in a wrong format default to
* empty object
*/
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
if (!config.reporterOptions || typeof config.reporterOptions !== 'object') {
config.reporterOptions = {};
}
for (var _reporterName in reporters) {
var Reporter = reporters[_reporterName];
var reporterOptions = {};
var _iteratorNormalCompletion2 = true;
var _didIteratorError2 = false;
var _iteratorError2 = undefined;
try {
for (var _iterator2 = (0, _getIterator3.default)((0, _keys2.default)(config.reporterOptions)), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
var option = _step2.value;
if (option === _reporterName && typeof config.reporterOptions[_reporterName] === 'object') {
// Copy over options specifically for this reporter type
reporterOptions = (0, _assign2.default)(reporterOptions, config.reporterOptions[_reporterName]);
} else if (reporters[option]) {
// Don't copy options for other reporters
continue;
} else {
// Copy over generic options
reporterOptions[option] = config.reporterOptions[option];
}
}
} catch (err) {
_didIteratorError2 = true;
_iteratorError2 = err;
} finally {
try {
if (!_iteratorNormalCompletion2 && _iterator2.return) {
_iterator2.return();
}
} finally {
if (_didIteratorError2) {
throw _iteratorError2;
}
}
}
reporter.add(new Reporter(reporter, config, reporterOptions));
}
return reporter;
}
/**
* run sequence
* @return {Promise} that only gets resolves with either an exitCode or an error
*/
}, {
key: 'run',
value: function () {
var _ref = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee() {
var _this = this;
var config, caps, launcher, _exitCode, cid, _iteratorNormalCompletion3, _didIteratorError3, _iteratorError3, _iterator3, _step3, capabilities, exitCode, _iteratorNormalCompletion4, _didIteratorError4, _iteratorError4, _iterator4, _step4, _capabilities;
return _regenerator2.default.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
config = this.configParser.getConfig();
caps = this.configParser.getCapabilities();
launcher = this.getLauncher(config);
this.reporters.handleEvent('start', {
isMultiremote: this.isMultiremote(),
capabilities: caps,
config
});
// this.importUidStore(config)
/**
* run onPrepare hook
*/
_context.next = 6;
return config.onPrepare(config, caps);
case 6:
_context.next = 8;
return this.runServiceHook(launcher, 'onPrepare', config, caps);
case 8:
if (!this.isMultiremote()) {
_context.next = 17;
break;
}
_context.next = 11;
return new _promise2.default(function (resolve) {
_this.resolve = resolve;
_this.startRunnerInstance(_this.configParser.getTestcases(), caps, 0);
});
case 11:
_exitCode = _context.sent;
_context.next = 14;
return this.runServiceHook(launcher, 'onComplete', _exitCode, config, caps);
case 14:
_context.next = 16;
return config.onComplete(_exitCode, config, caps);
case 16:
return _context.abrupt('return', _exitCode);
case 17:
/**
* schedule test runs
*/
cid = 0;
_iteratorNormalCompletion3 = true;
_didIteratorError3 = false;
_iteratorError3 = undefined;
_context.prev = 21;
for (_iterator3 = (0, _getIterator3.default)(caps); !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
capabilities = _step3.value;
this.schedule.push({
cid: cid++,
caps: capabilities,
testcases: this.configParser.getTestcases(capabilities.testcases, capabilities.exclude),
availableInstances: capabilities.maxInstances || config.maxInstancesPerCapability,
runningInstances: 0,
seleniumServer: { host: config.host, port: config.port, protocol: config.protocol }
});
}
/**
* catches ctrl+c event
*/
_context.next = 29;
break;
case 25:
_context.prev = 25;
_context.t0 = _context['catch'](21);
_didIteratorError3 = true;
_iteratorError3 = _context.t0;
case 29:
_context.prev = 29;
_context.prev = 30;
if (!_iteratorNormalCompletion3 && _iterator3.return) {
_iterator3.return();
}
case 32:
_context.prev = 32;
if (!_didIteratorError3) {
_context.next = 35;
break;
}
throw _iteratorError3;
case 35:
return _context.finish(32);
case 36:
return _context.finish(29);
case 37:
process.on('SIGINT', this.exitHandler.bind(this));
/**
* make sure the program will not close instantly
*/
if (process.stdin.isPaused()) {
process.stdin.resume();
}
_context.next = 41;
return new _promise2.default(function (resolve) {
_this.resolve = resolve;
/**
* return immediately if no spec was run
*/
if (_this.runTestcases()) {
resolve(0);
}
});
case 41:
exitCode = _context.sent;
// this.exportUidStore(config)
// run specs
this.finishedTests = true;
cid = 0;
this.schedule = [];
_iteratorNormalCompletion4 = true;
_didIteratorError4 = false;
_iteratorError4 = undefined;
_context.prev = 48;
for (_iterator4 = (0, _getIterator3.default)(caps); !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) {
_capabilities = _step4.value;
this.schedule.push({
cid: cid++,
caps: _capabilities,
specs: this.configParser.getSpecs(_capabilities.specs, _capabilities.exclude),
availableInstances: _capabilities.maxInstances || config.maxInstancesPerCapability,
runningInstances: 0,
seleniumServer: { host: config.host, port: config.port, protocol: config.protocol }
});
}
_context.next = 56;
break;
case 52:
_context.prev = 52;
_context.t1 = _context['catch'](48);
_didIteratorError4 = true;
_iteratorError4 = _context.t1;
case 56:
_context.prev = 56;
_context.prev = 57;
if (!_iteratorNormalCompletion4 && _iterator4.return) {
_iterator4.return();
}
case 59:
_context.prev = 59;
if (!_didIteratorError4) {
_context.next = 62;
break;
}
throw _iteratorError4;
case 62:
return _context.finish(59);
case 63:
return _context.finish(56);
case 64:
this.endHandlerRunFunc = this.runSpecs;
this.mergeManualTestcaseResults();
_context.next = 68;
return new _promise2.default(function (resolve) {
_this.resolve = resolve;
/**
* return immediatelly if no spec was run
*/
if (_this.runSpecs()) {
resolve(0);
}
});
case 68:
_context.next = 70;
return this.runServiceHook(launcher, 'onComplete', exitCode, config, caps);
case 70:
_context.next = 72;
return config.onComplete(exitCode, config, caps);
case 72:
return _context.abrupt('return', exitCode);
case 73:
case 'end':
return _context.stop();
}
}
}, _callee, this, [[21, 25, 29, 37], [30,, 32, 36], [48, 52, 56, 64], [57,, 59, 63]]);
}));
function run() {
return _ref.apply(this, arguments);
}
return run;
}()
}, {
key: 'importUidStore',
value: function importUidStore(config) {
if (fs.existsSync(config.uidStorePath)) {
this.uidStore = jsonfile.readFileSync(config.uidStorePath);
}
}
}, {
key: 'exportUidStore',
value: function exportUidStore(config) {
if (fs.existsSync(config.uidStorePath)) {
fs.unlinkSync(config.uidStorePath);
}
jsonfile.writeFileSync(config.uidStorePath, this.uidStore);
}
}, {
key: 'mergeManualTestcaseResults',
value: function mergeManualTestcaseResults() {
var manualResults = this.configParser.getManualResults();
var cid = '0-0';
if (manualResults) {
var _iteratorNormalCompletion5 = true;
var _didIteratorError5 = false;
var _iteratorError5 = undefined;
try {
for (var _iterator5 = (0, _getIterator3.default)(manualResults), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) {
var filename = _step5.value;
var manualTestcase = require(filename).default;
// how to treat different capabilities???
if (!(this.getCidGroup(cid) in this.validationResults)) {
this.validationResults[this.getCidGroup(cid)] = {};
}
for (var story in manualTestcase) {
if (!(story in this.validationResults[this.getCidGroup(cid)])) {
this.validationResults[this.getCidGroup(cid)][story] = {
successes: {},
failures: {},
arguments: {},
screenshots: {}
};
}
for (var criteria in manualTestcase[story]) {
if (manualTestcase[story][criteria].result === true) {
if (!(criteria in this.validationResults[this.getCidGroup(cid)][story])) {
this.validationResults[this.getCidGroup(cid)][story].successes[criteria] = [];
}
this.validationResults[this.getCidGroup(cid)][story].successes[criteria].push({});
} else {
if (!(criteria in this.validationResults[this.getCidGroup(cid)][story])) {
this.validationResults[this.getCidGroup(cid)][story].failures[criteria] = [];
}
this.validationResults[this.getCidGroup(cid)][story].failures[criteria].push({
message: `Spec ${story}: Then ${criteria} failed manual tests!`,
stack: ''
});
}
this.validationResults[this.getCidGroup(cid)][story].arguments[criteria] = {
date: {
caption: 'Last manual execution',
value: manualTestcase[story][criteria].date
},
comment: {
caption: 'Comment',
value: manualTestcase[story][criteria].comment
}
};
}
}
}
} catch (err) {
_didIteratorError5 = true;
_iteratorError5 = err;
} finally {
try {
if (!_iteratorNormalCompletion5 && _iterator5.return) {
_iterator5.return();
}
} finally {
if (_didIteratorError5) {
throw _iteratorError5;
}
}
}
}
}
// need to figure out multiple capabilities...
}, {
key: 'getValidationResults',
value: function getValidationResults() {
return this.validationResults['0'];
}
/**
* run service launch sequences
*/
}, {
key: 'runServiceHook',
value: function () {
var _ref2 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee2(launcher, hookName) {
for (var _len = arguments.length, args = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
args[_key - 2] = arguments[_key];
}
return _regenerator2.default.wrap(function _callee2$(_context2) {
while (1) {
switch (_context2.prev = _context2.next) {
case 0:
_context2.prev = 0;
_context2.next = 3;
return _promise2.default.all(launcher.map(function (service) {
if (typeof service[hookName] === 'function') {
return service[hookName].apply(service, args);
}
}));
case 3:
return _context2.abrupt('return', _context2.sent);
case 6:
_context2.prev = 6;
_context2.t0 = _context2['catch'](0);
console.error(`A service failed in the '${hookName}' hook\n${_context2.t0.stack}\n\nContinue...`);
case 9:
case 'end':
return _context2.stop();
}
}
}, _callee2, this, [[0, 6]]);
}));
function runServiceHook(_x, _x2) {
return _ref2.apply(this, arguments);
}
return runServiceHook;
}()
/**
* run multiple single remote tests
* @return {Boolean} true if all specs have been run and all instances have finished
*/
}, {
key: 'runTestcases',
value: function runTestcases() {
var _this2 = this;
var config = this.configParser.getConfig();
/**
* stop spawning new processes when CTRL+C was triggered
*/
if (this.hasTriggeredExitRoutine) {
return true;
}
while (this.getNumberOfRunningInstances() < config.maxInstances) {
var schedulableCaps = this.schedule
/**
* bail if number of errors exceeds allowed
*/
.filter(function () {
if (_this2.argv.bailErrors && config.bail && _this2.argv.bailErrors <= config.bail) {
_this2.argv.bailRunner = true;
}
return true;
})
/**
* make sure complete number of running instances is not higher than general maxInstances number
*/
.filter(function (a) {
return _this2.getNumberOfRunningInstances() < config.maxInstances;
})
/**
* make sure the capabiltiy has available capacities
*/
.filter(function (a) {
return a.availableInstances > 0;
})
/**
* make sure capabiltiy has still caps to run
*/
.filter(function (a) {
return a.testcases.length > 0;
})
/**
* make sure we are running caps with less running instances first
*/
.sort(function (a, b) {
return a.runningInstances > b.runningInstances;
});
/**
* continue if no capabiltiy were schedulable
*/
if (schedulableCaps.length === 0) {
break;
}
this.startRunnerInstance([schedulableCaps[0].testcases.shift()], schedulableCaps[0].caps, schedulableCaps[0].cid, schedulableCaps[0].seleniumServer);
schedulableCaps[0].availableInstances--;
schedulableCaps[0].runningInstances++;
}
return this.getNumberOfRunningInstances() === 0 && this.getNumberOfTestcasesLeft() === 0;
}
}, {
key: 'runSpecs',
value: function runSpecs() {
var _this3 = this;
var config = this.configParser.getConfig();
/**
* stop spawning new processes when CTRL+C was triggered
*/
if (this.hasTriggeredExitRoutine) {
return true;
}
while (this.getNumberOfRunningInstances() < config.maxInstances) {
var schedulableCaps = this.schedule
/**
* make sure complete number of running instances is not higher than general maxInstances number
*/
.filter(function (a) {
return _this3.getNumberOfRunningInstances() < config.maxInstances;
})
/**
* make sure the capability has available capacities
*/
.filter(function (a) {
return a.availableInstances > 0;
})
/**
* make sure capability has still caps to run
*/
.filter(function (a) {
return a.specs.length > 0;
})
/**
* make sure we are running caps with less running instances first
*/
.sort(function (a, b) {
return a.runningInstances > b.runningInstances;
});
/**
* continue if no capability were schedulable
*/
if (schedulableCaps.length === 0) {
break;
}
this.startValidatorInstance([schedulableCaps[0].specs.shift()], schedulableCaps[0].caps, schedulableCaps[0].cid);
schedulableCaps[0].availableInstances--;
schedulableCaps[0].runningInstances++;
}
return this.getNumberOfRunningInstances() === 0 && this.getNumberOfSpecsLeft() === 0;
}
/**
* gets number of all running instances
* @return {number} number of running instances
*/
}, {
key: 'getNumberOfRunningInstances',
value: function getNumberOfRunningInstances() {
return this.schedule.map(function (a) {
return a.runningInstances;
}).reduce(function (a, b) {
return a + b;
}, 0);
}
/**
* get number of total testcases left to complete whole suites
* @return {number} testcases left to complete suite
*/
// TODO: formulate differently -> testcases are only indirectly linked to spec suites!
}, {
key: 'getNumberOfTestcasesLeft',
value: function getNumberOfTestcasesLeft() {
return this.schedule.map(function (a) {
return a.testcases.length;
}).reduce(function (a, b) {
return a + b;
});
}
}, {
key: 'getNumberOfSpecsLeft',
value: function getNumberOfSpecsLeft() {
return this.schedule.map(function (a) {
return a.specs.length;
}).reduce(function (a, b) {
return a + b;
}, 0);
}
/**
* Start instance in a child process.
* @param {Array} specs Specs to run
* @param {Number} cid Capabilities ID
*/
}, {
key: 'startRunnerInstance',
value: function startRunnerInstance(specs, caps, cid, server) {
var config = this.configParser.getConfig();
cid = this.getRunnerId(cid);
var processNumber = this.processesStarted + 1;
// process.debugPort defaults to 5858 and is set even when process
// is not being debugged.
var debugArgs = [];
var debugType = void 0;
var debugHost = '';
var debugPort = process.debugPort;
for (var i in process.execArgv) {
var _debugArgs = process.execArgv[i].match('--(debug|inspect)(?:-brk)?(?:=(.*):)?');
if (_debugArgs) {
var _debugArgs2 = (0, _slicedToArray3.default)(_debugArgs, 3),
type = _debugArgs2[1],
host = _debugArgs2[2];
if (type) {
debugType = type;
}
if (host) {
debugHost = `${host}:`;
}
}
}
if (debugType) {
debugArgs.push(`--${debugType}=${debugHost}${debugPort + processNumber}`);
}
// if you would like to add --debug-brk, use a different port, etc...
var capExecArgs = [].concat((0, _toConsumableArray3.default)(config.execArgv || []), (0, _toConsumableArray3.default)(caps.execArgv || []));
// The default value for child.fork execArgs is process.execArgs,
// so continue to use this unless another value is specified in config.
var defaultArgs = capExecArgs.length === 0 ? process.execArgv : [];
// If an arg appears multiple times the last occurrence is used
var execArgv = [].concat((0, _toConsumableArray3.default)(defaultArgs), debugArgs, (0, _toConsumableArray3.default)(capExecArgs));
var childProcess = _child_process2.default.fork(_path2.default.join(__dirname, '/runner.js'), process.argv.slice(2), {
cwd: process.cwd(),
execArgv
});
/**
* increase framework timeout to 10m when debugging is enabled
*/
var _iteratorNormalCompletion6 = true;
var _didIteratorError6 = false;
var _iteratorError6 = undefined;
try {
for (var _iterator6 = (0, _getIterator3.default)(execArgv), _step6; !(_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done); _iteratorNormalCompletion6 = true) {
var argv = _step6.value;
if (argv !== '--inspect') {
continue;
}
var frameworkOptsKey = config.framework === 'mocha' ? 'mochaOpts' : config.framework === 'jasmine' ? 'jasmineNodeOpts' : 'cucumberOpts';
var frameworkTimeoutKey = config.framework === 'jasmine' ? 'defaultTimeoutInterval' : 'timeout';
if (!this.argv[frameworkOptsKey]) {
this.argv[frameworkOptsKey] = {};
}
this.argv[frameworkOptsKey][frameworkTimeoutKey] = 10 * 60 * 1000;
}
} catch (err) {
_didIteratorError6 = true;
_iteratorError6 = err;
} finally {
try {
if (!_iteratorNormalCompletion6 && _iterator6.return) {
_iterator6.return();
}
} finally {
if (_didIteratorError6) {
throw _iteratorError6;
}
}
}
this.processes.push(childProcess);
childProcess.on('message', this.messageHandler.bind(this, cid)).on('exit', this.endHandler.bind(this, cid));
childProcess.send({
cid,
command: 'run',
configFile: this.configFile,
argv: this.argv,
caps,
processNumber,
specs,
server,
isMultiremote: this.isMultiremote()
});
this.processesStarted++;
}
/**
* Start instance in a child process.
* @param {Array} specs Specs to run
* @param {Number} cid Capabilities ID
*/
}, {
key: 'startValidatorInstance',
value: function startValidatorInstance(specs, caps, cid, server) {
var config = this.configParser.getConfig();
var debug = caps.debug || config.debug;
cid = this.getRunnerId(cid);
var processNumber = this.processesStarted + 1;
if (config.execArgv) {
config.execArgv = config.execArgv.filter(function (param) {
return param.indexOf('--inspect') === -1;
});
}
// process.debugPort defaults to 5858 and is set even when process
// is not being debugged.
var debugArgs = debug ? [`--debug=${process.debugPort + processNumber}`] : [];
// if you would like to add --debug-brk, use a different port, etc...
var capExecArgs = [].concat((0, _toConsumableArray3.default)(config.execArgv || []), (0, _toConsumableArray3.default)(caps.execArgv || []));
// The default value for child.fork execArgs is process.execArgs,
// so continue to use this unless another value is specified in config.
var defaultArgs = capExecArgs.length ? process.execArgv : [];
// If an arg appears multiple times the last occurence is used
var execArgv = [].concat((0, _toConsumableArray3.default)(defaultArgs), debugArgs, (0, _toConsumableArray3.default)(capExecArgs));
var childProcess = _child_process2.default.fork(_path2.default.join(__dirname, '/validator.js'), process.argv.slice(2), {
cwd: process.cwd(),
execArgv
});
this.processes.push(childProcess);
this.cidProcesses[cid] = childProcess;
childProcess.on('message', this.messageHandler.bind(this, cid)).on('exit', this.endHandler.bind(this, cid));
var validationResults = this.getValidationResults();
this.reporters.handleEvent('startSpecs', { event: 'startSpecs', cid: cid });
childProcess.send({
cid,
command: 'run',
configFile: this.configFile,
argv: this.argv,
caps,
processNumber,
specs,
validationResults,
isMultiremote: this.isMultiremote()
});
this.processesStarted++;
}
/**
* generates a runner id
* @param {Number} cid capability id (unique identifier for a capability)
* @return {String} runner id (combination of cid and test id e.g. 0a, 0b, 1a, 1b ...)
*/
}, {
key: 'getRunnerId',
value: function getRunnerId(cid) {
if (!this.rid[cid]) {
this.rid[cid] = 0;
}
return `${cid}-${this.rid[cid]++}`;
}
}, {
key: 'getCidGroup',
value: function getCidGroup(cid) {
return cid.split('-')[0];
}
}, {
key: 'getPatchedStack',
value: function getPatchedStack(source, cid) {
var self = this;
var combinedStack = source.stack;
if (source.errorType && this.stackStore[cid] && combinedStack.indexOf('at testcase (') < 0) {
combinedStack += '\n' + this.stackStore[cid];
}
return combinedStack.split('\n').filter(function (line) {
var isFiltered = false;
var trimmedLine = line;
if (typeof line === 'string') {
trimmedLine = line.trim();
if (trimmedLine.length === 0) {
return false;
}
}
if (self.config.cleanStackTraces) {
(0, _keys2.default)(self.containStackFilter).forEach(function (filterLine) {
if (trimmedLine.indexOf(filterLine) > 0) {
isFiltered = true;
}
});
(0, _keys2.default)(self.stackFilter).forEach(function (filterLine) {
if (filterLine === trimmedLine) {
isFiltered = true;
}
});
}
return !isFiltered;
}).join('\n');
}
}, {
key: 'fillValidationResults',
value: function fillValidationResults(assertion, m) {
var _iteratorNormalCompletion7 = true;
var _didIteratorError7 = false;
var _iteratorError7 = undefined;
try {
for (var _iterator7 = (0, _getIterator3.default)((0, _keys2.default)(assertion.specObj)), _step7; !(_iteratorNormalCompletion7 = (_step7 = _iterator7.next()).done); _iteratorNormalCompletion7 = true) {
var key = _step7.value;
if (!(key in this.validationResults[this.getCidGroup(m.cid)])) {
this.validationResults[this.getCidGroup(m.cid)][key] = {
successes: {},
failures: {},
arguments: {},
screenshots: {}
};
}
var _iteratorNormalCompletion8 = true;
var _didIteratorError8 = false;
var _iteratorError8 = undefined;
try {
for (var _iterator8 = (0, _getIterator3.default)(assertion.specObj[key]), _step8; !(_iteratorNormalCompletion8 = (_step8 = _iterator8.next()).done); _iteratorNormalCompletion8 = true) {
var criteria = _step8.value;
if (!(criteria in this.validationResults[this.getCidGroup(m.cid)][key].failures)) {
this.validationResults[this.getCidGroup(m.cid)][key].failures[criteria] = [];
}
this.validationResults[this.getCidGroup(m.cid)][key].failures[criteria].push({
matcherName: assertion.matcherName,
message: assertion.message,
stack: assertion.stack,
screenshotId: assertion.screenshotId,
screenshotFilename: assertion.screenshotFilename
});
}
} catch (err) {
_didIteratorError8 = true;
_iteratorError8 = err;
} finally {
try {
if (!_iteratorNormalCompletion8 && _iterator8.return) {
_iterator8.return();
}
} finally {
if (_didIteratorError8) {
throw _iteratorError8;
}
}
}
}
} catch (err) {
_didIteratorError7 = true;
_iteratorError7 = err;
} finally {
try {
if (!_iteratorNormalCompletion7 && _iterator7.return) {
_iterator7.return();
}
} finally {
if (_didIteratorError7) {
throw _iteratorError7;
}
}
}
}
}, {
key: 'handleAssertion',
value: function handleAssertion(assertion, m) {
if (assertion.stack.trim().indexOf('at') !== 0) {
var stacklines = assertion.stack.split('\n');
stacklines.shift();
assertion.stack = stacklines.join('\n');
}
if (assertion.specObj) {
this.fillValidationResults(assertion, m);
}
m.err.message += assertion.message + '\n\n';
// for allure reports
if (assertion.stack.trim().indexOf('at') === 0) {
m.err.stack += assertion.message + '\n';
}
m.err.stack += assertion.stack + '\n\n';
m.errs.push(assertion);
}
/**
* emit event from child process to reporter
* @param {String} cid
* @param {Object} m event object
*/
}, {
key: 'messageHandler',
value: function messageHandler(cid, m) {
if (!this.config) {
this.config = this.configParser.getConfig();
}
this.hasStartedAnyProcess = true;
if (!m.cid) {
m.cid = cid;
}
if (m.event === 'runner:error') {
this.reporters.handleEvent('error', m);
}
if (m.event === 'uid:request') {
if (!(m.id in this.uidStore)) {
this.uidStore[m.id] = 0;
}
this.cidProcesses[m.cid].send({
event: 'uid:response',
id: `${m.id}_${++this.uidStore[m.id]}`,
cid: m.cid
});
}
if (m.event.indexOf('test:') === 0) {
if (m.title) {
var testData = JSON.parse(m.title);
m.title = testData.title;
m.metadata = testData.metadata;
}
}
if (this.finishedTests) {
m.finishedTests = true;
switch (m.event) {
case 'test:start':
m.spec = m.specs[0];
break;
case 'step:end':
m.status = 'passed';
if (m.validate) {
if (!this.validationStates[this.getCidGroup(m.cid)]) {
this.validationStates[this.getCidGroup(m.cid)] = {};
}
this.validationStates[this.getCidGroup(m.cid)].storyId = m.validate.storyId;
this.validationStates[this.getCidGroup(m.cid)].criteriaId = m.validate.criteriaId;
var _status = this.evaluateValidations(m.validate.storyId, m.validate.criteriaId, m);
m.event = `step:${_status}`;
}
break;
case 'test:pass':
var storyId = this.validationStates[this.getCidGroup(m.cid)].storyId;
var criteriaId = this.validationStates[this.getCidGroup(m.cid)].criteriaId;
var status = this.evaluateValidations(storyId, criteriaId, m);
m.event = `test:${status}`;
this.addTestArguments(storyId, criteriaId, m);
break;
}
this.reporters.handleEvent(m.event, m);
} else {
if (m.event === 'runner:currentStack') {
if (!this.stackStoreLocks[m.cid]) {
this.stackStore[m.cid] = m.stack;
if (m.name === 'waitUntil') {
this.waitUntilErrors[m.cid] = m.stack;
}
}
}
if (m.event === 'runner:aftercommand') {
if (m.err) {
this.stackStoreLocks[m.cid] = true;
if (m.command === 'waitUntil') {
this.stackStore[m.cid] = this.waitUntilErrors[m.cid];
} else if (this.waitUntilErrors[m.cid]) {
this.stackStore[m.cid] = this.waitUntilErrors[m.cid];
}
} else if (m.command === 'waitUntil') {
delete this.waitUntilErrors[m.cid];
}
}
if (m.event === 'test:start') {
m.testcase = m.specs[0];
this.validationStates[this.getCidGroup(m.cid)] = {};
this.stackStoreLocks[m.cid] = false;
delete this.stackStore[m.cid];
delete this.waitUntilErrors[m.cid];
this.stepErrors[this.getCidGroup(m.cid)] = {
failed: [],
broken: []
};
}
i