protractor
Version:
Webdriver E2E test wrapper for Angular.
253 lines (234 loc) • 7.83 kB
JavaScript
var webdriver = require('selenium-webdriver'),
q = require('q'),
ConfigParser = require('./configParser'),
log = require('./logger');
var PROMISE_TYPE = {
Q: 0,
WEBDRIVER: 1
};
/**
* The plugin API for Protractor. Note that this API is unstable. See
* plugins/README.md for more information.
*
* @constructor
* @param {Object} config parsed from the config file
*/
var Plugins = function(config) {
var self = this;
this.pluginConfs = config.plugins || [];
this.pluginObjs = [];
this.assertions = {};
this.resultsReported = false;
this.pluginConfs.forEach(function(pluginConf, i) {
var path;
if (pluginConf.path) {
path = ConfigParser.resolveFilePatterns(pluginConf.path, true,
config.configDir)[0];
} else {
path = pluginConf.package;
}
var pluginObj;
if (path) {
pluginObj = require(path);
} else if (pluginConf.inline) {
pluginObj = pluginConf.inline;
} else {
throw new Error('Plugin configuration did not contain a valid path or ' +
'inline definition.');
}
annotatePluginObj(self, pluginObj, pluginConf, i);
log.debug('Plugin "' + pluginObj.name + '" loaded.');
self.pluginObjs.push(pluginObj);
});
};
/**
* Adds properties to a plugin's object
*
* @see docs/plugins.md#provided-properties-and-functions
*/
function annotatePluginObj(self, obj, conf, i) {
function addAssertion(info, passed, message) {
if (self.resultsReported) {
throw new Error('Cannot add new tests results, since they were already ' +
'reported.');
}
info = info || {};
var specName = info.specName || (obj.name + ' Plugin Tests');
var assertion = {passed: passed};
if (!passed) {
assertion.errorMsg = message;
if (info.stackTrace) {
assertion.stackTrace = info.stackTrace;
}
}
self.assertions[specName] = self.assertions[specName] || [];
self.assertions[specName].push(assertion);
}
obj.name = obj.name || conf.name || conf.path || conf.package ||
('Plugin #' + i);
obj.config = conf;
obj.addFailure = function(message, options) {
addAssertion(options, false, message);
};
obj.addSuccess = function(options) {
addAssertion(options, true);
};
obj.addWarning = function(message, options) {
options = options || {};
log.puts('Warning ' + (options.specName ? 'in ' + options.specName :
'from "' + obj.name + '" plugin') +
': ' + message);
};
}
function printPluginResults(specResults) {
var green = '\x1b[32m';
var red = '\x1b[31m';
var normalColor = '\x1b[39m';
var printResult = function(message, pass) {
log.puts(pass ? green : red,
'\t', pass ? 'Pass: ' : 'Fail: ', message, normalColor);
};
for (var j = 0; j < specResults.length; j++) {
var specResult = specResults[j];
var passed = specResult.assertions.map(function(x) {
return x.passed;
}).reduce(function(x, y) {
return x && y;
}, true);
printResult(specResult.description, passed);
if (!passed) {
for (var k = 0; k < specResult.assertions.length; k++) {
var assertion = specResult.assertions[k];
if (!assertion.passed) {
log.puts('\t\t' + assertion.errorMsg);
if (assertion.stackTrace) {
log.puts('\t\t' + assertion.stackTrace.replace(/\n/g, '\n\t\t'));
}
}
}
}
}
}
/**
* Gets the tests results generated by any plugins
*
* @see lib/frameworks/README.md#requirements for a complete description of what
* the results object must look like
*
* @return {Object} The results object
*/
Plugins.prototype.getResults = function() {
var results = {
failedCount: 0,
specResults: []
};
for (var specName in this.assertions) {
results.specResults.push({
description: specName,
assertions: this.assertions[specName]
});
results.failedCount += this.assertions[specName].filter(
function(assertion) {return !assertion.passed;}).length;
}
printPluginResults(results.specResults);
this.resultsReported = true;
return results;
};
/**
* Calls a function from a plugin safely. If the plugin's function throws an
* exception or returns a rejected promise, that failure will be logged as a
* failed test result instead of crashing protractor. If the tests results have
* already been reported, the failure will be logged to the console.
*
* @param {Object} pluginObj The plugin object containing the function to be run
* @param {string} funName The name of the function we want to run
* @param {*[]} args The arguments we want to invoke the function with
* @param {PROMISE_TYPE} promiseType The type of promise (WebDriver or Q) that
* should be used
* @param {boolean} resultsReported If the results have already been reported
* @param {*} failReturnVal The value to return if the function fails
*
* @return {webdriver.promise.Promise} A promise which resolves to the
* function's return value
*/
function safeCallPluginFun(pluginObj, funName, args, promiseType,
resultsReported, failReturnVal) {
var deferred = promiseType == PROMISE_TYPE.Q ? q.defer() :
webdriver.promise.defer();
var logError = function(e) {
if (resultsReported) {
printPluginResults([{
description: pluginObj.name + ' Runtime',
assertions: [{
passed: false,
errorMsg: 'Failure during ' + funName + ': ' + (e.message || e),
stackTrace: e.stack
}]
}]);
} else {
pluginObj.addFailure('Failure during ' + funName + ': ' +
e.message || e, {stackTrace: e.stack});
}
deferred.fulfill(failReturnVal);
};
try {
var result = pluginObj[funName].apply(pluginObj, args);
if (webdriver.promise.isPromise(result)) {
result.then(function() {
deferred.fulfill.apply(deferred, arguments);
}, function(e) {
logError(e);
});
} else {
deferred.fulfill(result);
}
} catch(e) {
logError(e);
}
return deferred.promise;
}
/**
* Generates the handler for a plugin function (e.g. the setup() function)
*
* @param {string} funName The name of the function to make a handler for
* @param {PROMISE_TYPE} promiseType The type of promise (WebDriver or Q) that
* should be used
* @param {boolean=} failReturnVal The value that the function should return if
* the plugin crashes
*
* @return {Function} The handler
*/
function pluginFunFactory(funName, promiseType, failReturnVal) {
return function() {
var promises = [];
var self = this;
var args = arguments;
this.pluginObjs.forEach(function(pluginObj) {
if (pluginObj[funName]) {
promises.push(safeCallPluginFun(pluginObj, funName, args, promiseType,
self.resultsReported, failReturnVal));
}
});
if (promiseType == PROMISE_TYPE.Q) {
return q.all(promises);
} else {
return webdriver.promise.all(promises);
}
};
}
/**
* @see docs/plugins.md#writing-plugins for information on these functions
*/
Plugins.prototype.setup = pluginFunFactory('setup', PROMISE_TYPE.Q);
Plugins.prototype.teardown = pluginFunFactory('teardown', PROMISE_TYPE.Q);
Plugins.prototype.postResults = pluginFunFactory('postResults', PROMISE_TYPE.Q);
Plugins.prototype.postTest = pluginFunFactory('postTest', PROMISE_TYPE.Q);
Plugins.prototype.onPageLoad = pluginFunFactory('onPageLoad',
PROMISE_TYPE.WEBDRIVER);
Plugins.prototype.onPageStable = pluginFunFactory('onPageStable',
PROMISE_TYPE.WEBDRIVER);
Plugins.prototype.waitForPromise = pluginFunFactory('waitForPromise',
PROMISE_TYPE.WEBDRIVER);
Plugins.prototype.waitForCondition = pluginFunFactory('waitForCondition',
PROMISE_TYPE.WEBDRIVER, true);
module.exports = Plugins;