UNPKG

protractor

Version:

Webdriver E2E test wrapper for Angular.

253 lines (234 loc) 7.83 kB
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;