protractor
Version:
Webdriver E2E test wrapper for Angular.
267 lines (244 loc) • 8.88 kB
JavaScript
/**
* The launcher is responsible for parsing the capabilities from the
* input configuration and launching test runners.
*/
;
var ConfigParser = require('./configParser'),
TaskScheduler = require('./taskScheduler'),
helper = require('./util'),
log = require('./logger'),
q = require('q'),
TaskRunner = require('./taskRunner');
var logPrefix = '[launcher]';
var RUNNERS_FAILED_EXIT_CODE = 100;
/**
* Custom console.log proxy
* @param {*} [stuff...] Any value to log
* @private
*/
var log_ = function() {
var args = [logPrefix].concat([].slice.call(arguments));
log.puts.apply(log, args);
};
/**
* Keeps track of a list of task results. Provides method to add a new
* result, aggregate the results into a summary, count failures,
* and save results into a JSON file.
*/
var taskResults_ = {
results_: [],
add: function(result) {
this.results_.push(result);
},
totalSpecFailures: function() {
var specFailures = 0;
this.results_.forEach(function(result) {
specFailures += result.failedCount;
});
return specFailures;
},
totalProcessFailures: function() {
var processFailures = 0;
this.results_.forEach(function(result) {
if (!result.failedCount && result.exitCode !== 0) {
processFailures += 1;
}
});
return processFailures;
},
saveResults: function(filepath) {
var jsonOutput = [];
this.results_.forEach(function(result) {
jsonOutput = jsonOutput.concat(result.specResults);
});
var json = JSON.stringify(jsonOutput, null, ' ');
var fs = require('fs');
fs.writeFileSync(filepath, json);
},
reportSummary: function() {
var specFailures = this.totalSpecFailures();
var processFailures = this.totalProcessFailures();
this.results_.forEach(function(result) {
var capabilities = result.capabilities;
var shortName = (capabilities.browserName) ? capabilities.browserName : '';
shortName += (capabilities.version) ? capabilities.version : '';
shortName += (' #' + result.taskId);
if (result.failedCount) {
log_(shortName + ' failed ' + result.failedCount + ' test(s)');
} else if (result.exitCode !== 0) {
log_(shortName + ' failed with exit code: ' + result.exitCode);
} else {
log_(shortName + ' passed');
}
});
if (specFailures && processFailures) {
log_('overall: ' + specFailures + ' failed spec(s) and ' +
processFailures + ' process(es) failed to complete');
} else if (specFailures) {
log_('overall: ' + specFailures + ' failed spec(s)');
} else if (processFailures) {
log_('overall: ' + processFailures + ' process(es) failed to complete');
}
}
};
/**
* Initialize and run the tests.
* Exits with 1 on test failure, and RUNNERS_FAILED_EXIT_CODE on unexpected
* failures.
*
* @param {string=} configFile
* @param {Object=} additionalConfig
*/
var init = function(configFile, additionalConfig) {
var configParser = new ConfigParser();
if (configFile) {
configParser.addFileConfig(configFile);
}
if (additionalConfig) {
configParser.addConfig(additionalConfig);
}
var config = configParser.getConfig();
log.set(config);
log.debug('Running with --troubleshoot');
log.debug('Protractor version: ' + require('../package.json').version);
log.debug('Your base url for tests is ' + config.baseUrl);
// Run beforeLaunch
helper.runFilenameOrFn_(config.configDir, config.beforeLaunch).then(function() {
return q.promise(function(resolve) {
// 1) If getMultiCapabilities is set, resolve that as `multiCapabilities`.
if (config.getMultiCapabilities &&
typeof config.getMultiCapabilities === 'function') {
if (config.multiCapabilities.length || config.capabilities) {
log.warn('getMultiCapabilities() will override both capabilites ' +
'and multiCapabilities');
}
// If getMultiCapabilities is defined and a function, use this.
q.when(config.getMultiCapabilities(), function(multiCapabilities) {
config.multiCapabilities = multiCapabilities;
config.capabilities = null;
}).then(resolve);
} else {
resolve();
}
}).then(function() {
// 2) Set `multicapabilities` using `capabilities`, `multicapabilites`,
// or default
if (config.capabilities) {
if (config.multiCapabilities.length) {
log.warn('You have specified both capabilites and ' +
'multiCapabilities. This will result in capabilities being ' +
'ignored');
} else {
// Use capabilities if multiCapabilities is empty.
config.multiCapabilities = [config.capabilities];
}
} else if (!config.multiCapabilities.length) {
// Default to chrome if no capabilities given
config.multiCapabilities = [{
browserName: 'chrome'
}];
}
});
}).then(function() {
// 3) If we're in `elementExplorer` mode, run only that.
if (config.elementExplorer || config.framework === 'explorer') {
if (config.multiCapabilities.length != 1) {
throw new Error('Must specify only 1 browser while using elementExplorer');
} else {
config.capabilities = config.multiCapabilities[0];
}
config.framework = 'explorer';
var Runner = require('./runner');
var runner = new Runner(config);
return runner.run().then(function(exitCode) {
process.exit(exitCode);
}, function(err) {
log_(err);
process.exit(1);
});
}
}).then(function() {
// 4) Run tests.
var scheduler = new TaskScheduler(config);
process.on('exit', function(code) {
if (code) {
log_('Process exited with error code ' + code);
} else if (scheduler.numTasksOutstanding() > 0) {
log_('BUG: launcher exited with ' +
scheduler.numTasksOutstanding() + ' tasks remaining');
process.exit(RUNNERS_FAILED_EXIT_CODE);
}
});
// Run afterlaunch and exit
var cleanUpAndExit = function(exitCode) {
return helper.runFilenameOrFn_(
config.configDir, config.afterLaunch, [exitCode]).
then(function(returned) {
if (typeof returned === 'number') {
process.exit(returned);
} else {
process.exit(exitCode);
}
}, function(err) {
log_('Error:', err);
process.exit(1);
});
};
var totalTasks = scheduler.numTasksOutstanding();
var forkProcess = false;
if (totalTasks > 1) { // Start new processes only if there are >1 tasks.
forkProcess = true;
if (config.debug) {
throw new Error('Cannot run in debug mode with ' +
'multiCapabilities, count > 1, or sharding');
}
}
var deferred = q.defer(); // Resolved when all tasks are completed
var createNextTaskRunner = function() {
var task = scheduler.nextTask();
if (task) {
var taskRunner = new TaskRunner(configFile, additionalConfig, task, forkProcess);
taskRunner.run().then(function(result) {
if (result.exitCode && !result.failedCount) {
log_('Runner process exited unexpectedly with error code: ' + result.exitCode);
}
taskResults_.add(result);
task.done();
createNextTaskRunner();
// If all tasks are finished
if (scheduler.numTasksOutstanding() === 0) {
deferred.fulfill();
}
log_(scheduler.countActiveTasks() +
' instance(s) of WebDriver still running');
}).catch (function(err) {
log_('Error:', err.stack || err.message || err);
cleanUpAndExit(RUNNERS_FAILED_EXIT_CODE);
});
}
};
// Start `scheduler.maxConcurrentTasks()` workers for handling tasks in
// the beginning. As a worker finishes a task, it will pick up the next task
// from the scheduler's queue until all tasks are gone.
for (var i = 0; i < scheduler.maxConcurrentTasks(); ++i) {
createNextTaskRunner();
}
log_('Running ' + scheduler.countActiveTasks() + ' instances of WebDriver');
// By now all runners have completed.
deferred.promise.then(function() {
// Save results if desired
if (config.resultJsonOutputFile) {
taskResults_.saveResults(config.resultJsonOutputFile);
}
taskResults_.reportSummary();
var exitCode = 0;
if (taskResults_.totalProcessFailures() > 0) {
exitCode = RUNNERS_FAILED_EXIT_CODE;
} else if (taskResults_.totalSpecFailures() > 0) {
exitCode = 1;
}
return cleanUpAndExit(exitCode);
}).done();
}).done();
};
exports.init = init;