protractor
Version:
Webdriver E2E test wrapper for Angular.
332 lines (299 loc) • 9.63 kB
JavaScript
var protractor = require('./protractor'),
webdriver = require('selenium-webdriver'),
util = require('util'),
q = require('q'),
EventEmitter = require('events').EventEmitter,
helper = require('./util'),
log = require('./logger'),
Plugins = require('./plugins');
/*
* Runner is responsible for starting the execution of a test run and triggering
* setup, teardown, managing config, etc through its various dependencies.
*
* The Protractor Runner is a node EventEmitter with the following events:
* - testPass
* - testFail
* - testsDone
*
* @param {Object} config
* @constructor
*/
var Runner = function(config) {
log.set(config);
this.preparer_ = null;
this.driverprovider_ = null;
this.config_ = config;
this.drivers_ = [];
if (config.v8Debug) {
// Call this private function instead of sending SIGUSR1 because Windows.
process._debugProcess(process.pid);
}
if (config.nodeDebug) {
process._debugProcess(process.pid);
var flow = webdriver.promise.controlFlow();
flow.execute(function() {
var nodedebug = require('child_process').fork('debug', ['localhost:5858']);
process.on('exit', function() {
nodedebug.kill('SIGTERM');
});
nodedebug.on('exit', function() {
process.exit('1');
});
}, 'start the node debugger');
flow.timeout(1000, 'waiting for debugger to attach');
}
this.loadDriverProvider_(config);
this.setTestPreparer(config.onPrepare);
};
util.inherits(Runner, EventEmitter);
/**
* Registrar for testPreparers - executed right before tests run.
* @public
* @param {string/Fn} filenameOrFn
*/
Runner.prototype.setTestPreparer = function(filenameOrFn) {
this.preparer_ = filenameOrFn;
};
/**
* Executor of testPreparer
* @public
* @return {q.Promise} A promise that will resolve when the test preparers
* are finished.
*/
Runner.prototype.runTestPreparer = function() {
return helper.runFilenameOrFn_(this.config_.configDir, this.preparer_);
};
/**
* Grab driver provider based on type
* @private
*
* Priority
* 1) if directConnect is true, use that
* 2) if seleniumAddress is given, use that
* 3) if a Sauce Labs account is given, use that
* 4) if a seleniumServerJar is specified, use that
* 5) try to find the seleniumServerJar in protractor/selenium
*/
Runner.prototype.loadDriverProvider_ = function() {
var runnerPath;
if (this.config_.directConnect) {
runnerPath = './driverProviders/direct';
} else if (this.config_.seleniumAddress) {
runnerPath = './driverProviders/hosted';
} else if (this.config_.sauceUser && this.config_.sauceKey) {
runnerPath = './driverProviders/sauce';
} else if (this.config_.seleniumServerJar) {
runnerPath = './driverProviders/local';
} else if (this.config_.mockSelenium) {
runnerPath = './driverProviders/mock';
} else {
runnerPath = './driverProviders/local';
}
this.driverprovider_ = require(runnerPath)(this.config_);
};
/**
* Responsible for cleaning up test run and exiting the process.
* @private
* @param {int} Standard unix exit code
*/
Runner.prototype.exit_ = function(exitCode) {
return helper.runFilenameOrFn_(
this.config_.configDir, this.config_.onCleanUp, [exitCode]).
then(function(returned) {
if (typeof returned === 'number') {
return returned;
} else {
return exitCode;
}
});
};
/**
* Getter for the Runner config object
* @public
* @return {Object} config
*/
Runner.prototype.getConfig = function() {
return this.config_;
};
/**
* Get the control flow used by this runner.
* @return {Object} WebDriver control flow.
*/
Runner.prototype.controlFlow = function() {
return webdriver.promise.controlFlow();
};
/**
* Sets up convenience globals for test specs
* @private
*/
Runner.prototype.setupGlobals_ = function(browser) {
// Export protractor to the global namespace to be used in tests.
global.protractor = protractor;
global.browser = browser;
global.$ = browser.$;
global.$$ = browser.$$;
global.element = browser.element;
global.by = global.By = protractor.By;
// Enable sourcemap support for stack traces.
require('source-map-support').install();
// Required by dart2js machinery.
// https://code.google.com/p/dart/source/browse/branches/bleeding_edge/dart/sdk/lib/js/dart2js/js_dart2js.dart?spec=svn32943&r=32943#487
global.DartObject = function(o) { this.o = o; };
};
/**
* Create a new driver from a driverProvider. Then set up a
* new protractor instance using this driver.
* This is used to set up the initial protractor instances and any
* future ones.
*
* @return {Protractor} a protractor instance.
* @public
*/
Runner.prototype.createBrowser = function() {
var config = this.config_;
var driver = this.driverprovider_.getNewDriver();
this.drivers_.push(driver);
driver.manage().timeouts().setScriptTimeout(config.allScriptsTimeout);
var browser = protractor.wrapDriver(driver,
config.baseUrl, config.rootElement);
browser.params = config.params;
if (config.getPageTimeout) {
browser.getPageTimeout = config.getPageTimeout;
}
var self = this;
/**
* Fork another instance of protractor for use in interactive tests.
*
* @param {boolean} opt_useSameUrl Whether to navigate to current url on creation
* @param {boolean} opt_copyMockModules Whether to apply same mock modules on creation
* @return {Protractor} a protractor instance.
*/
browser.forkNewDriverInstance = function(opt_useSameUrl, opt_copyMockModules) {
var newBrowser = self.createBrowser();
if (opt_copyMockModules) {
newBrowser.mockModules_ = browser.mockModules_;
}
if (opt_useSameUrl) {
browser.driver.getCurrentUrl().then(function(url) {
newBrowser.get(url);
});
}
return newBrowser;
};
return browser;
};
/**
* Final cleanup on exiting the runner.
*
* @return {q.Promise} A promise which resolves on finish.
* @private
*/
Runner.prototype.shutdown_ = function() {
var deferredArr = this.drivers_.map(function(driver) {
var deferred = q.defer();
driver.getSession().then(function(session_) {
if (session_) {
driver.quit().then(function() {
deferred.resolve();
});
} else {
deferred.resolve();
}
});
return deferred.promise;
});
return q.all(deferredArr);
};
/**
* The primary workhorse interface. Kicks off the test running process.
*
* @return {q.Promise} A promise which resolves to the exit code of the tests.
* @public
*/
Runner.prototype.run = function() {
var self = this,
testPassed,
plugins;
if (!this.config_.specs.length) {
throw new Error('Spec patterns did not match any files.');
}
// 1) Setup environment
//noinspection JSValidateTypes
return this.driverprovider_.setupEnv().then(function() {
// Resolve capabilities first, so it can be a promise
return q(self.config_.capabilities).then(function(capabilities) {
self.config_.capabilities = capabilities;
});
// 2) Create a browser and setup globals
}).then(function() {
var browser = self.createBrowser();
self.setupGlobals_(browser);
return browser.getSession().then(function(session) {
log.debug('WebDriver session successfully started with capabilities ' +
util.inspect(session.getCapabilities()));
}, function(err) {
log.error('Unable to start a WebDriver session.');
throw err;
});
// 3) Setup plugins
}).then(function() {
plugins = new Plugins(self.config_);
return plugins.setup();
// 4) Execute test cases
}).then(function(pluginSetupResults) {
// Do the framework setup here so that jasmine and mocha globals are
// available to the onPrepare function.
var frameworkPath = '';
if (self.config_.framework === 'jasmine') {
frameworkPath = './frameworks/jasmine.js';
} else if (self.config_.framework === 'mocha') {
frameworkPath = './frameworks/mocha.js';
} else if (self.config_.framework === 'cucumber') {
frameworkPath = './frameworks/cucumber.js';
} else if (self.config_.framework === 'debugprint') {
frameworkPath = './frameworks/debugprint.js';
} else {
throw new Error('config.framework (' + self.config_.framework +
') is not a valid framework.');
}
return require(frameworkPath).run(self, self.config_.specs).
then(function(testResults) {
return helper.joinTestLogs(pluginSetupResults, testResults);
});
// 5) Teardown plugins
}).then(function(testResults) {
var deferred = q.defer();
plugins.teardown().then(function(pluginTeardownResults) {
deferred.resolve(helper.joinTestLogs(testResults, pluginTeardownResults));
}, function(error) {
deferred.reject(error);
});
return deferred.promise;
// 6) Teardown
}).then(function(result) {
self.emit('testsDone', result);
testPassed = result.failedCount === 0;
if (self.driverprovider_.updateJob) {
return self.driverprovider_.updateJob({
'passed': testPassed
}).then(function() {
return self.driverprovider_.teardownEnv();
});
} else {
return self.driverprovider_.teardownEnv();
}
// 7) Exit process
}).then(function() {
var exitCode = testPassed ? 0 : 1;
return self.exit_(exitCode);
}).fin(function() {
return self.shutdown_();
});
};
/**
* Creates and returns a Runner instance
*
* @public
* @param {Object} config
*/
module.exports = Runner;