diff --git a/lib/configParser.ts b/lib/configParser.ts index c786a6afc..c9a1cdb8b 100644 --- a/lib/configParser.ts +++ b/lib/configParser.ts @@ -43,6 +43,20 @@ export interface Config { troubleshoot?: boolean; exclude?: Array|string; maxSessions?: number; + seleniumAddress?: string; + webDriverProxy?: string; + disableEnvironmentOverrides?: boolean; + browserstackUser?: string; + browserstackKey?: string; + firefoxPath?: string; + seleniumServerJar?: string; + seleniumPort?: number; + localSeleniumStandaloneOpts?: {args?: any; port?: any;}; + sauceAgent?: string; + sauceBuild?: string; + sauceKey?: string; + sauceSeleniumAddress?: string; + sauceUser?: string; } export class ConfigParser { diff --git a/lib/driverProviders/attachSession.js b/lib/driverProviders/attachSession.js deleted file mode 100644 index d63461fbf..000000000 --- a/lib/driverProviders/attachSession.js +++ /dev/null @@ -1,57 +0,0 @@ -/* - * This is an implementation of the Attach Session Driver Provider. - * It is responsible for setting up the account object, tearing - * it down, and setting up the driver correctly. - */ - -var util = require('util'), - q = require('q'), - DriverProvider = require('./driverProvider'), - log = require('../logger'), - WebDriver = require('selenium-webdriver').WebDriver, - executors = require('selenium-webdriver/executors'); - -var AttachedSessionDriverProvider = function(config) { - DriverProvider.call(this, config); -}; -util.inherits(AttachedSessionDriverProvider, DriverProvider); - -/** - * Configure and launch (if applicable) the object's environment. - * @public - * @return {q.promise} A promise which will resolve when the environment is - * ready to test. - */ -AttachedSessionDriverProvider.prototype.setupEnv = function() { - log.puts('Using the selenium server at ' + this.config_.seleniumAddress); - log.puts('Using session id - ' + this.config_.seleniumSessionId); - return q(undefined); -}; - - -/** - * Getting a new driver by attaching an existing session. - * - * @public - * @return {WebDriver} webdriver instance - */ -AttachedSessionDriverProvider.prototype.getNewDriver = function() { - var executor = executors.createExecutor(this.config_.seleniumAddress); - var newDriver = WebDriver - .attachToSession(executor, this.config_.seleniumSessionId); - this.drivers_.push(newDriver); - return newDriver; -}; - -/** - * Maintains the existing session and does not quit the driver. - * - * @public - */ -AttachedSessionDriverProvider.prototype.quitDriver = function() { -}; - -// new instance w/ each include -module.exports = function(config) { - return new AttachedSessionDriverProvider(config); -}; diff --git a/lib/driverProviders/attachSession.ts b/lib/driverProviders/attachSession.ts new file mode 100644 index 000000000..9b951fef6 --- /dev/null +++ b/lib/driverProviders/attachSession.ts @@ -0,0 +1,55 @@ +/* + * This is an implementation of the Attach Session Driver Provider. + * It is responsible for setting up the account object, tearing + * it down, and setting up the driver correctly. + */ +import * as q from 'q'; +import {Config} from '../configParser'; +import {DriverProvider} from './driverProvider'; +import {Logger} from '../logger2'; + +let webdriver = require('selenium-webdriver'); +let executors = require('selenium-webdriver/executors'); + +let logger = new Logger('attachSession'); + +export class AttachSession extends DriverProvider { + constructor(config: Config) { super(config); } + + /** + * Configure and launch (if applicable) the object's environment. + * @public + * @return {q.promise} A promise which will resolve when the environment is + * ready to test. + */ + setupEnv(): q.Promise { + logger.info('Using the selenium server at ' + this.config_.seleniumAddress); + logger.info('Using session id - ' + this.config_.seleniumSessionId); + return q(undefined); + } + + /** + * Getting a new driver by attaching an existing session. + * + * @public + * @return {WebDriver} webdriver instance + */ + getNewDriver(): webdriver.WebDriver { + var executor = executors.createExecutor(this.config_.seleniumAddress); + var newDriver = + webdriver.WebDriver.attachToSession(executor, this.config_.seleniumSessionId); + this.drivers_.push(newDriver); + return newDriver; + } + + /** + * Maintains the existing session and does not quit the driver. + * + * @public + */ + quitDriver(): q.Promise { + let defer = q.defer(); + defer.resolve(null); + return defer.promise; + } +} diff --git a/lib/driverProviders/browserStack.ts b/lib/driverProviders/browserStack.ts new file mode 100644 index 000000000..87690ffce --- /dev/null +++ b/lib/driverProviders/browserStack.ts @@ -0,0 +1,90 @@ +/* + * This is an implementation of the Browserstack Driver Provider. + * It is responsible for setting up the account object, tearing + * it down, and setting up the driver correctly. + */ +import * as request from 'request'; +import * as q from 'q'; +import * as util from 'util'; + +import {Config} from '../configParser'; +import {DriverProvider} from './driverProvider'; +import {Logger} from '../logger2'; + +let logger = new Logger('browserstack'); + +export class BrowserStack extends DriverProvider { + constructor(config: Config) { super(config); } + + /** + * Hook to update the BrowserStack job status. + * @public + * @param {Object} update + * @return {q.promise} A promise that will resolve when the update is complete. + */ + updateJob(update: any): q.Promise { + let deferredArray = this.drivers_.map((driver: webdriver.WebDriver) => { + let deferred = q.defer(); + driver.getSession().then((session: webdriver.Session) => { + var jobStatus = update.passed ? 'completed' : 'error'; + logger.info( + 'BrowserStack results available at ' + + 'https://www.browserstack.com/automate'); + request( + { + url: 'https://www.browserstack.com/automate/sessions/' + + session.getId() + '.json', + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Basic ' + + new Buffer( + this.config_.browserstackUser + ':' + + this.config_.browserstackKey) + .toString('base64') + }, + method: 'PUT', + form: {'status': jobStatus} + }, + (error: Error) => { + if (error) { + throw new Error( + 'Error updating BrowserStack pass/fail status: ' + + util.inspect(error)); + } + }); + deferred.resolve(); + }); + return deferred.promise; + }); + return q.all(deferredArray); + } + + /** + * Configure and launch (if applicable) the object's environment. + * @public + * @return {q.promise} A promise which will resolve when the environment is + * ready to test. + */ + setupEnv(): q.Promise { + var deferred = q.defer(); + this.config_.capabilities['browserstack.user'] = + this.config_.browserstackUser; + this.config_.capabilities['browserstack.key'] = + this.config_.browserstackKey; + this.config_.seleniumAddress = 'http://hub.browserstack.com/wd/hub'; + + // Append filename to capabilities.name so that it's easier to identify + // tests. + if (this.config_.capabilities.name && + this.config_.capabilities.shardTestFiles) { + this.config_.capabilities.name += + (':' + this.config_.specs.toString().replace(/^.*[\\\/]/, '')); + } + + logger.info( + 'Using BrowserStack selenium server at ' + + this.config_.seleniumAddress); + deferred.resolve(); + return deferred.promise; + } +} diff --git a/lib/driverProviders/browserstack.js b/lib/driverProviders/browserstack.js deleted file mode 100644 index 49d782f9b..000000000 --- a/lib/driverProviders/browserstack.js +++ /dev/null @@ -1,91 +0,0 @@ -/* - * This is an implementation of the Browserstack Driver Provider. - * It is responsible for setting up the account object, tearing - * it down, and setting up the driver correctly. - */ - -var util = require('util'), - log = require('../logger'), - request = require('request'), - q = require('q'), - DriverProvider = require('./driverProvider'); - - -var BrowserStackDriverProvider = function(config) { - DriverProvider.call(this, config); -}; -util.inherits(BrowserStackDriverProvider, DriverProvider); - - -/** - * Hook to update the BrowserStack job status. - * @public - * @param {Object} update - * @return {q.promise} A promise that will resolve when the update is complete. - */ -BrowserStackDriverProvider.prototype.updateJob = function(update) { - - var self = this; - var deferredArray = this.drivers_.map(function(driver) { - var deferred = q.defer(); - driver.getSession().then(function(session) { - var jobStatus = update.passed ? 'completed' : 'error'; - log.puts('BrowserStack results available at ' + - 'https://www.browserstack.com/automate'); - request({ - url: 'https://www.browserstack.com/automate/sessions/' + - session.getId() + '.json', - headers: { - 'Content-Type': 'application/json', - 'Authorization': 'Basic ' + new Buffer(self.config_.browserstackUser - + ':' + self.config_.browserstackKey).toString('base64') - }, - method: 'PUT', - form: { - 'status': jobStatus - } - }, function(error){ - if (error) { - throw new Error( - 'Error updating BrowserStack pass/fail status: ' + - util.inspect(error) - ); - } - }); - deferred.resolve(); - }); - return deferred.promise; - }); - return q.all(deferredArray); -}; - -/** - * Configure and launch (if applicable) the object's environment. - * @public - * @return {q.promise} A promise which will resolve when the environment is - * ready to test. - */ -BrowserStackDriverProvider.prototype.setupEnv = function() { - var deferred = q.defer(); - this.config_.capabilities['browserstack.user'] = - this.config_.browserstackUser; - this.config_.capabilities['browserstack.key'] = this.config_.browserstackKey; - this.config_.seleniumAddress = 'http://hub.browserstack.com/wd/hub'; - - // Append filename to capabilities.name so that it's easier to identify tests. - if (this.config_.capabilities.name && - this.config_.capabilities.shardTestFiles) { - this.config_.capabilities.name += ( - ':' + this.config_.specs.toString().replace(/^.*[\\\/]/, '')); - } - - log.puts('Using BrowserStack selenium server at ' + - this.config_.seleniumAddress); - deferred.resolve(); - return deferred.promise; -}; - -// new instance w/ each include -module.exports = function(config) { - return new BrowserStackDriverProvider(config); -}; diff --git a/lib/driverProviders/direct.js b/lib/driverProviders/direct.js deleted file mode 100644 index 2c7ee341d..000000000 --- a/lib/driverProviders/direct.js +++ /dev/null @@ -1,89 +0,0 @@ -/* - * This is an implementation of the Direct Driver Provider. - * It is responsible for setting up the account object, tearing - * it down, and setting up the driver correctly. - */ - -var webdriver = require('selenium-webdriver'), - chrome = require('selenium-webdriver/chrome'), - firefox = require('selenium-webdriver/firefox'), - q = require('q'), - fs = require('fs'), - path = require('path'), - util = require('util'), - DriverProvider = require('./driverProvider'), - log = require('../logger'); - -var DirectDriverProvider = function(config) { - DriverProvider.call(this, config); -}; -util.inherits(DirectDriverProvider, DriverProvider); - -/** - * Configure and launch (if applicable) the object's environment. - * @public - * @return {q.promise} A promise which will resolve when the environment is - * ready to test. - */ -DirectDriverProvider.prototype.setupEnv = function() { - switch (this.config_.capabilities.browserName) { - case 'chrome': - log.puts('Using ChromeDriver directly...'); - break; - case 'firefox': - log.puts('Using FirefoxDriver directly...'); - break; - default: - throw new Error('browserName (' + this.config_.capabilities.browserName + - ') is not supported with directConnect.'); - } - return q.fcall(function() {}); -}; - -/** - * Create a new driver. - * - * @public - * @override - * @return webdriver instance - */ -DirectDriverProvider.prototype.getNewDriver = function() { - var driver; - switch (this.config_.capabilities.browserName) { - case 'chrome': - var defaultChromeDriverPath = path.resolve(__dirname, - '../../selenium/chromedriver_' + - require('../../config.json').webdriverVersions.chromedriver); - - if (process.platform.indexOf('win') === 0) { - defaultChromeDriverPath += '.exe'; - } - - var chromeDriverFile = this.config_.chromeDriver || defaultChromeDriverPath; - - if (!fs.existsSync(chromeDriverFile)) { - throw new Error('Could not find chromedriver at ' + chromeDriverFile); - } - - var service = new chrome.ServiceBuilder(chromeDriverFile).build(); - driver = new chrome.Driver( - new webdriver.Capabilities(this.config_.capabilities), service); - break; - case 'firefox': - if (this.config_.firefoxPath) { - this.config_.capabilities.firefox_binary = this.config_.firefoxPath; - } - driver = new firefox.Driver(this.config_.capabilities); - break; - default: - throw new Error('browserName ' + this.config_.capabilities.browserName + - 'is not supported with directConnect.'); - } - this.drivers_.push(driver); - return driver; -}; - -// new instance w/ each include -module.exports = function(config) { - return new DirectDriverProvider(config); -}; diff --git a/lib/driverProviders/direct.ts b/lib/driverProviders/direct.ts new file mode 100644 index 000000000..57e86f771 --- /dev/null +++ b/lib/driverProviders/direct.ts @@ -0,0 +1,89 @@ +/* + * This is an implementation of the Direct Driver Provider. + * It is responsible for setting up the account object, tearing + * it down, and setting up the driver correctly. + */ +import * as q from 'q'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as util from 'util'; + +import {Config} from '../configParser'; +import {DriverProvider} from './driverProvider'; +import {Logger} from '../logger2'; + +let webdriver = require('selenium-webdriver'), + chrome = require('selenium-webdriver/chrome'), + firefox = require('selenium-webdriver/firefox'); + +let logger = new Logger('direct'); +export class Direct extends DriverProvider { + constructor(config: Config) { super(config); } + + /** + * Configure and launch (if applicable) the object's environment. + * @public + * @return {q.promise} A promise which will resolve when the environment is + * ready to test. + */ + setupEnv(): q.Promise { + switch (this.config_.capabilities.browserName) { + case 'chrome': + logger.info('Using ChromeDriver directly...'); + break; + case 'firefox': + logger.info('Using FirefoxDriver directly...'); + break; + default: + throw new Error( + 'browserName (' + this.config_.capabilities.browserName + + ') is not supported with directConnect.'); + } + return q.fcall(function() {}); + } + + /** + * Create a new driver. + * + * @public + * @override + * @return webdriver instance + */ + getNewDriver(): webdriver.WebDriver { + let driver: webdriver.WebDriver; + switch (this.config_.capabilities.browserName) { + case 'chrome': + let defaultChromeDriverPath = path.resolve( + __dirname, '../../selenium/chromedriver_' + + require('../../config.json').webdriverVersions.chromedriver); + + if (process.platform.indexOf('win') === 0) { + defaultChromeDriverPath += '.exe'; + } + + let chromeDriverFile = + this.config_.chromeDriver || defaultChromeDriverPath; + + if (!fs.existsSync(chromeDriverFile)) { + throw new Error('Could not find chromedriver at ' + chromeDriverFile); + } + + let service = new chrome.ServiceBuilder(chromeDriverFile).build(); + driver = new chrome.Driver( + new webdriver.Capabilities(this.config_.capabilities), service); + break; + case 'firefox': + if (this.config_.firefoxPath) { + this.config_.capabilities.firefox_binary = this.config_.firefoxPath; + } + driver = new firefox.Driver(this.config_.capabilities); + break; + default: + throw new Error( + 'browserName ' + this.config_.capabilities.browserName + + 'is not supported with directConnect.'); + } + this.drivers_.push(driver); + return driver; + } +} diff --git a/lib/driverProviders/driverProvider.js b/lib/driverProviders/driverProvider.js deleted file mode 100644 index 2715065b7..000000000 --- a/lib/driverProviders/driverProvider.js +++ /dev/null @@ -1,92 +0,0 @@ -/** - * This is a base driver provider class. - * It is responsible for setting up the account object, tearing - * it down, and setting up the driver correctly. - */ - -var webdriver = require('selenium-webdriver'), - q = require('q'); - - -var DriverProvider = function(config) { - this.config_ = config; - this.drivers_ = []; -}; - - -/** - * Get all existing drivers. - * - * @public - * @return array of webdriver instances - */ -DriverProvider.prototype.getExistingDrivers = function() { - return this.drivers_.slice(); // Create a shallow copy -}; - - -/** - * Create a new driver. - * - * @public - * @return webdriver instance - */ -DriverProvider.prototype.getNewDriver = function() { - var builder = new webdriver.Builder(). - usingServer(this.config_.seleniumAddress). - usingWebDriverProxy(this.config_.webDriverProxy). - withCapabilities(this.config_.capabilities); - if (this.config_.disableEnvironmentOverrides === true) { - builder.disableEnvironmentOverrides(); - } - var newDriver = builder.build(); - this.drivers_.push(newDriver); - return newDriver; -}; - - -/** - * Quit a driver. - * - * @public - * @param webdriver instance - */ -DriverProvider.prototype.quitDriver = function(driver) { - var driverIndex = this.drivers_.indexOf(driver); - if (driverIndex >= 0) { - this.drivers_.splice(driverIndex, 1); - } - - var deferred = q.defer(); - if (driver.getSession() === undefined) { - deferred.resolve(); - } - else { - driver.getSession().then(function(session_) { - if (session_) { - driver.quit().then(function() { - deferred.resolve(); - }); - } else { - deferred.resolve(); - } - }); - } - return deferred.promise; -}; - - -/** - * Teardown and destroy the environment and do any associated cleanup. - * Shuts down the drivers. - * - * @public - * @return {q.promise} A promise which will resolve when the environment - * is down. - */ -DriverProvider.prototype.teardownEnv = function() { - return q.all(this.drivers_.map(this.quitDriver.bind(this))); -}; - - -module.exports = DriverProvider; diff --git a/lib/driverProviders/driverProvider.ts b/lib/driverProviders/driverProvider.ts new file mode 100644 index 000000000..9d1aba2aa --- /dev/null +++ b/lib/driverProviders/driverProvider.ts @@ -0,0 +1,87 @@ +/** + * This is a base driver provider class. + * It is responsible for setting up the account object, tearing + * it down, and setting up the driver correctly. + */ +import * as q from 'q'; +import {Config} from '../configParser'; +let webdriver = require('selenium-webdriver'); + +export class DriverProvider { + drivers_: webdriver.WebDriver[]; + config_: Config; + + constructor(config: Config) { + this.config_ = config; + this.drivers_ = []; + } + + /** + * Get all existing drivers. + * + * @public + * @return array of webdriver instances + */ + getExistingDrivers() { + return this.drivers_.slice(); // Create a shallow copy + } + + /** + * Create a new driver. + * + * @public + * @return webdriver instance + */ + getNewDriver() { + let builder = new webdriver.Builder() + .usingServer(this.config_.seleniumAddress) + .usingWebDriverProxy(this.config_.webDriverProxy) + .withCapabilities(this.config_.capabilities); + if (this.config_.disableEnvironmentOverrides === true) { + builder.disableEnvironmentOverrides(); + } + let newDriver = builder.build(); + this.drivers_.push(newDriver); + return newDriver; + } + + /** + * Quit a driver. + * + * @public + * @param webdriver instance + */ + quitDriver(driver: webdriver.WebDriver): q.Promise { + let driverIndex = this.drivers_.indexOf(driver); + if (driverIndex >= 0) { + this.drivers_.splice(driverIndex, 1); + } + + let deferred = q.defer(); + if (driver.getSession() === undefined) { + deferred.resolve(); + } else { + driver.getSession().then((session_: string) => { + if (session_) { + driver.quit().then(function() { deferred.resolve(); }); + } else { + deferred.resolve(); + } + }); + } + return deferred.promise; + } + + /** + * Teardown and destroy the environment and do any associated cleanup. + * Shuts down the drivers. + * + * @public + * @return {q.promise} A promise which will resolve when the environment + * is down. + */ + teardownEnv(): q.Promise[]> { + return q.all( + this.drivers_.map(() => { return this.quitDriver.bind(this); })); + } +} diff --git a/lib/driverProviders/hosted.js b/lib/driverProviders/hosted.js deleted file mode 100644 index d87c70822..000000000 --- a/lib/driverProviders/hosted.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - * This is an implementation of the Hosted Driver Provider. - * It is responsible for setting up the account object, tearing - * it down, and setting up the driver correctly. - */ - -var util = require('util'), - q = require('q'), - DriverProvider = require('./driverProvider'), - log = require('../logger'); - -var HostedDriverProvider = function(config) { - DriverProvider.call(this, config); -}; -util.inherits(HostedDriverProvider, DriverProvider); - -/** - * Configure and launch (if applicable) the object's environment. - * @public - * @return {q.promise} A promise which will resolve when the environment is - * ready to test. - */ -HostedDriverProvider.prototype.setupEnv = function() { - log.puts('Using the selenium server at ' + - this.config_.seleniumAddress); - return q.fcall(function() {}); -}; - -// new instance w/ each include -module.exports = function(config) { - return new HostedDriverProvider(config); -}; diff --git a/lib/driverProviders/hosted.ts b/lib/driverProviders/hosted.ts new file mode 100644 index 000000000..2b17ed01b --- /dev/null +++ b/lib/driverProviders/hosted.ts @@ -0,0 +1,27 @@ +/* + * This is an implementation of the Hosted Driver Provider. + * It is responsible for setting up the account object, tearing + * it down, and setting up the driver correctly. + */ +import * as q from 'q'; +import * as util from 'util'; + +import {Config} from '../configParser'; +import {DriverProvider} from './driverProvider'; +import {Logger} from '../logger2'; + +let logger = new Logger('hosted'); +export class Hosted extends DriverProvider { + constructor(config: Config) { super(config); } + + /** + * Configure and launch (if applicable) the object's environment. + * @public + * @return {q.promise} A promise which will resolve when the environment is + * ready to test. + */ + setupEnv(): q.Promise { + logger.info('Using the selenium server at ' + this.config_.seleniumAddress); + return q.fcall(function() {}); + } +} diff --git a/lib/driverProviders/local.js b/lib/driverProviders/local.js deleted file mode 100644 index e76fad5dc..000000000 --- a/lib/driverProviders/local.js +++ /dev/null @@ -1,131 +0,0 @@ -/* - * This is an implementation of the Local Driver Provider. - * It is responsible for setting up the account object, tearing - * it down, and setting up the driver correctly. - * - * TODO - it would be nice to do this in the launcher phase, - * so that we only start the local selenium once per entire launch. - */ -var util = require('util'), - log = require('../logger'), - path = require('path'), - remote = require('selenium-webdriver/remote'), - fs = require('fs'), - q = require('q'), - DriverProvider = require('./driverProvider'); - -var LocalDriverProvider = function(config) { - DriverProvider.call(this, config); - this.server_ = null; -}; -util.inherits(LocalDriverProvider, DriverProvider); - - -/** - * Helper to locate the default jar path if none is provided by the user. - * @private - */ -LocalDriverProvider.prototype.addDefaultBinaryLocs_ = function() { - if (!this.config_.seleniumServerJar) { - log.debug('Attempting to find the SeleniumServerJar in the default ' + - 'location used by webdriver-manager'); - this.config_.seleniumServerJar = path.resolve(__dirname, - '../../selenium/selenium-server-standalone-' + - require('../../config.json').webdriverVersions.selenium + '.jar'); - } - if (!fs.existsSync(this.config_.seleniumServerJar)) { - throw new Error('No selenium server jar found at the specified ' + - 'location (' + this.config_.seleniumServerJar + - '). Check that the version number is up to date.'); - } - if (this.config_.capabilities.browserName === 'chrome') { - if (!this.config_.chromeDriver) { - log.debug('Attempting to find the chromedriver binary in the default ' + - 'location used by webdriver-manager'); - this.config_.chromeDriver = - path.resolve(__dirname, '../../selenium/chromedriver_' + - require('../../config.json').webdriverVersions.chromedriver); - } - - // Check if file exists, if not try .exe or fail accordingly - if (!fs.existsSync(this.config_.chromeDriver)) { - if (fs.existsSync(this.config_.chromeDriver + '.exe')) { - this.config_.chromeDriver += '.exe'; - } else { - throw new Error('Could not find chromedriver at ' + - this.config_.chromeDriver); - } - } - } -}; - -/** - * Configure and launch (if applicable) the object's environment. - * @public - * @return {q.promise} A promise which will resolve when the environment is - * ready to test. - */ -LocalDriverProvider.prototype.setupEnv = function() { - var deferred = q.defer(), - self = this; - - this.addDefaultBinaryLocs_(); - - log.puts('Starting selenium standalone server...'); - - var serverConf = this.config_.localSeleniumStandaloneOpts || {}; - - // If args or port is not set use seleniumArgs and seleniumPort - // for backward compatibility - if (serverConf.args === undefined) { - serverConf.args = this.config_.seleniumArgs || []; - } - if (serverConf.port === undefined) { - serverConf.port = this.config_.seleniumPort; - } - - // configure server - if (this.config_.chromeDriver) { - serverConf.args.push('-Dwebdriver.chrome.driver=' + - this.config_.chromeDriver); - } - - this.server_ = new remote.SeleniumServer(this.config_.seleniumServerJar, serverConf); - - //start local server, grab hosted address, and resolve promise - this.server_.start().then(function(url) { - log.puts('Selenium standalone server started at ' + url); - self.server_.address().then(function(address) { - self.config_.seleniumAddress = address; - deferred.resolve(); - }); - }); - - return deferred.promise; -}; - -/** - * Teardown and destroy the environment and do any associated cleanup. - * Shuts down the drivers and server. - * - * @public - * @override - * @return {q.promise} A promise which will resolve when the environment - * is down. - */ -LocalDriverProvider.prototype.teardownEnv = function() { - var self = this; - var deferred = q.defer(); - DriverProvider.prototype.teardownEnv.call(this).then(function() { - log.puts('Shutting down selenium standalone server.'); - self.server_.stop().then(function() { - deferred.resolve(); - }); - }); - return deferred.promise; -}; - -// new instance w/ each include -module.exports = function(config) { - return new LocalDriverProvider(config); -}; diff --git a/lib/driverProviders/local.ts b/lib/driverProviders/local.ts new file mode 100644 index 000000000..b39cfeaed --- /dev/null +++ b/lib/driverProviders/local.ts @@ -0,0 +1,132 @@ +/* + * This is an implementation of the Local Driver Provider. + * It is responsible for setting up the account object, tearing + * it down, and setting up the driver correctly. + * + * TODO - it would be nice to do this in the launcher phase, + * so that we only start the local selenium once per entire launch. + */ +import * as fs from 'fs'; +import * as path from 'path'; +import * as q from 'q'; +import * as util from 'util'; + +import {Config} from '../configParser'; +import {DriverProvider} from './driverProvider'; +import {Logger} from '../logger2'; + +let remote = require('selenium-webdriver/remote'); + +let logger = new Logger('local'); +export class Local extends DriverProvider { + server_: any; + constructor(config: Config) { + super(config); + this.server_ = null; + } + + /** + * Helper to locate the default jar path if none is provided by the user. + * @private + */ + addDefaultBinaryLocs_(): void { + if (!this.config_.seleniumServerJar) { + logger.debug( + 'Attempting to find the SeleniumServerJar in the default ' + + 'location used by webdriver-manager'); + this.config_.seleniumServerJar = path.resolve( + __dirname, '../../selenium/selenium-server-standalone-' + + require('../../config.json').webdriverVersions.selenium + '.jar'); + } + if (!fs.existsSync(this.config_.seleniumServerJar)) { + throw new Error( + 'No selenium server jar found at the specified ' + + 'location (' + this.config_.seleniumServerJar + + '). Check that the version number is up to date.'); + } + if (this.config_.capabilities.browserName === 'chrome') { + if (!this.config_.chromeDriver) { + logger.debug( + 'Attempting to find the chromedriver binary in the default ' + + 'location used by webdriver-manager'); + this.config_.chromeDriver = path.resolve( + __dirname, '../../selenium/chromedriver_' + + require('../../config.json').webdriverVersions.chromedriver); + } + + // Check if file exists, if not try .exe or fail accordingly + if (!fs.existsSync(this.config_.chromeDriver)) { + if (fs.existsSync(this.config_.chromeDriver + '.exe')) { + this.config_.chromeDriver += '.exe'; + } else { + throw new Error( + 'Could not find chromedriver at ' + this.config_.chromeDriver); + } + } + } + } + + /** + * Configure and launch (if applicable) the object's environment. + * @public + * @return {q.promise} A promise which will resolve when the environment is + * ready to test. + */ + setupEnv(): q.Promise { + let deferred = q.defer(); + + this.addDefaultBinaryLocs_(); + + logger.info('Starting selenium standalone server...'); + + let serverConf = this.config_.localSeleniumStandaloneOpts || {}; + + // If args or port is not set use seleniumArgs and seleniumPort + // for backward compatibility + if (serverConf.args === undefined) { + serverConf.args = this.config_.seleniumArgs || []; + } + if (serverConf.port === undefined) { + serverConf.port = this.config_.seleniumPort; + } + + // configure server + if (this.config_.chromeDriver) { + serverConf.args.push( + '-Dwebdriver.chrome.driver=' + this.config_.chromeDriver); + } + + this.server_ = + new remote.SeleniumServer(this.config_.seleniumServerJar, serverConf); + + // start local server, grab hosted address, and resolve promise + this.server_.start().then((url: string) => { + logger.info('Selenium standalone server started at ' + url); + this.server_.address().then((address: string) => { + this.config_.seleniumAddress = address; + deferred.resolve(); + }); + }); + + return deferred.promise; + } + + /** + * Teardown and destroy the environment and do any associated cleanup. + * Shuts down the drivers and server. + * + * @public + * @override + * @return {q.promise} A promise which will resolve when the environment + * is down. + */ + teardownEnv(): q.Promise { + let self = this; + let deferred = q.defer(); + DriverProvider.prototype.teardownEnv.call(this).then(() => { + logger.info('Shutting down selenium standalone server.'); + self.server_.stop().then(() => { deferred.resolve(); }); + }); + return deferred.promise; + } +} diff --git a/lib/driverProviders/mock.js b/lib/driverProviders/mock.js deleted file mode 100644 index dcf21b351..000000000 --- a/lib/driverProviders/mock.js +++ /dev/null @@ -1,58 +0,0 @@ -/* - * This is an mock implementation of the Driver Provider. - * It returns a fake webdriver and never actually contacts a selenium - * server. - */ -var webdriver = require('selenium-webdriver'), - util = require('util'), - q = require('q'), - DriverProvider = require('./driverProvider'); -/** - * @constructor - */ -var MockExecutor = function() { -}; - -/** - * An execute function that returns a promise with a test value. - */ -MockExecutor.prototype.execute = function() { - var deferred = q.defer(); - deferred.resolve({value: 'test_response'}); - return deferred.promise; -}; - -var MockDriverProvider = function(config) { - DriverProvider.call(this, config); -}; -util.inherits(MockDriverProvider, DriverProvider); - - -/** - * Configure and launch (if applicable) the object's environment. - * @public - * @return {q.promise} A promise which will resolve immediately. - */ -MockDriverProvider.prototype.setupEnv = function() { - return q.fcall(function() {}); -}; - - -/** - * Create a new driver. - * - * @public - * @override - * @return webdriver instance - */ -MockDriverProvider.prototype.getNewDriver = function() { - var mockSession = new webdriver.Session('test_session_id', {}); - var newDriver = new webdriver.WebDriver(mockSession, new MockExecutor()); - this.drivers_.push(newDriver); - return newDriver; -}; - -// new instance w/ each include -module.exports = function(config) { - return new MockDriverProvider(config); -}; diff --git a/lib/driverProviders/mock.ts b/lib/driverProviders/mock.ts new file mode 100644 index 000000000..a5dfa4a8d --- /dev/null +++ b/lib/driverProviders/mock.ts @@ -0,0 +1,47 @@ +/* + * This is an mock implementation of the Driver Provider. + * It returns a fake webdriver and never actually contacts a selenium + * server. + */ +import * as q from 'q'; +import * as util from 'util'; +import {Config} from '../configParser'; +import {DriverProvider} from './driverProvider'; + +let webdriver = require('selenium-webdriver'); + +export class Mock extends DriverProvider { + constructor(config?: Config) { super(config); } + + /** + * An execute function that returns a promise with a test value. + */ + execute(): q.Promise { + let deferred = q.defer(); + deferred.resolve({value: 'test_response'}); + return deferred.promise; + } + + /** + * Configure and launch (if applicable) the object's environment. + * @public + * @return {q.promise} A promise which will resolve immediately. + */ + setupEnv(): q.Promise { + return q.fcall(function() {}); + } + + /** + * Create a new driver. + * + * @public + * @override + * @return webdriver instance + */ + getNewDriver(): webdriver.WebDriver { + let mockSession = new webdriver.Session('test_session_id', {}); + let newDriver = new webdriver.WebDriver(mockSession, new Mock()); + this.drivers_.push(newDriver); + return newDriver; + } +} diff --git a/lib/driverProviders/sauce.js b/lib/driverProviders/sauce.js deleted file mode 100644 index fa5131f9d..000000000 --- a/lib/driverProviders/sauce.js +++ /dev/null @@ -1,87 +0,0 @@ -/* - * This is an implementation of the SauceLabs Driver Provider. - * It is responsible for setting up the account object, tearing - * it down, and setting up the driver correctly. - */ - -var util = require('util'), - log = require('../logger'), - SauceLabs = require('saucelabs'), - q = require('q'), - DriverProvider = require('./driverProvider'); - - -var SauceDriverProvider = function(config) { - DriverProvider.call(this, config); - this.sauceServer_ = {}; -}; -util.inherits(SauceDriverProvider, DriverProvider); - - -/** - * Hook to update the sauce job. - * @public - * @param {Object} update - * @return {q.promise} A promise that will resolve when the update is complete. - */ -SauceDriverProvider.prototype.updateJob = function(update) { - - var self = this; - var deferredArray = this.drivers_.map(function(driver) { - var deferred = q.defer(); - driver.getSession().then(function(session) { - log.puts('SauceLabs results available at http://saucelabs.com/jobs/' + - session.getId()); - self.sauceServer_.updateJob(session.getId(), update, function(err) { - if (err) { - throw new Error( - 'Error updating Sauce pass/fail status: ' + util.inspect(err) - ); - } - deferred.resolve(); - }); - }); - return deferred.promise; - }); - return q.all(deferredArray); -}; - -/** - * Configure and launch (if applicable) the object's environment. - * @public - * @return {q.promise} A promise which will resolve when the environment is - * ready to test. - */ -SauceDriverProvider.prototype.setupEnv = function() { - var deferred = q.defer(); - this.sauceServer_ = new SauceLabs({ - username: this.config_.sauceUser, - password: this.config_.sauceKey, - agent: this.config_.sauceAgent - }); - this.config_.capabilities.username = this.config_.sauceUser; - this.config_.capabilities.accessKey = this.config_.sauceKey; - this.config_.capabilities.build = this.config_.sauceBuild; - var auth = 'http://' + this.config_.sauceUser + ':' + - this.config_.sauceKey + '@'; - this.config_.seleniumAddress = auth + - (this.config_.sauceSeleniumAddress ? this.config_.sauceSeleniumAddress : - 'ondemand.saucelabs.com:80/wd/hub'); - - // Append filename to capabilities.name so that it's easier to identify tests. - if (this.config_.capabilities.name && - this.config_.capabilities.shardTestFiles) { - this.config_.capabilities.name += ( - ':' + this.config_.specs.toString().replace(/^.*[\\\/]/, '')); - } - - log.puts('Using SauceLabs selenium server at ' + - this.config_.seleniumAddress.replace(/\/\/.+@/, '//')); - deferred.resolve(); - return deferred.promise; -}; - -// new instance w/ each include -module.exports = function(config) { - return new SauceDriverProvider(config); -}; diff --git a/lib/driverProviders/sauce.ts b/lib/driverProviders/sauce.ts new file mode 100644 index 000000000..6a1387f69 --- /dev/null +++ b/lib/driverProviders/sauce.ts @@ -0,0 +1,85 @@ +/* + * This is an implementation of the SauceLabs Driver Provider. + * It is responsible for setting up the account object, tearing + * it down, and setting up the driver correctly. + */ + +import * as q from 'q'; +import * as util from 'util'; + +import {Config} from '../configParser'; +import {DriverProvider} from './driverProvider'; +import {Logger} from '../logger2'; + +let SauceLabs = require('saucelabs'); + +let logger = new Logger('sauce'); +export class Sauce extends DriverProvider { + sauceServer_: any; + + constructor(config: Config) { super(config); } + + /** + * Hook to update the sauce job. + * @public + * @param {Object} update + * @return {q.promise} A promise that will resolve when the update is complete. + */ + updateJob(update: any): q.Promise { + var deferredArray = this.drivers_.map((driver: webdriver.WebDriver) => { + var deferred = q.defer(); + driver.getSession().then((session: webdriver.Session) => { + logger.info( + 'SauceLabs results available at http://saucelabs.com/jobs/' + + session.getId()); + this.sauceServer_.updateJob(session.getId(), update, (err: Error) => { + if (err) { + throw new Error( + 'Error updating Sauce pass/fail status: ' + util.inspect(err)); + } + deferred.resolve(); + }); + }); + return deferred.promise; + }); + return q.all(deferredArray); + } + + /** + * Configure and launch (if applicable) the object's environment. + * @public + * @return {q.promise} A promise which will resolve when the environment is + * ready to test. + */ + setupEnv(): q.Promise { + let deferred = q.defer(); + this.sauceServer_ = new SauceLabs({ + username: this.config_.sauceUser, + password: this.config_.sauceKey, + agent: this.config_.sauceAgent + }); + this.config_.capabilities.username = this.config_.sauceUser; + this.config_.capabilities.accessKey = this.config_.sauceKey; + this.config_.capabilities.build = this.config_.sauceBuild; + let auth = + 'http://' + this.config_.sauceUser + ':' + this.config_.sauceKey + '@'; + this.config_.seleniumAddress = + auth + (this.config_.sauceSeleniumAddress ? + this.config_.sauceSeleniumAddress : + 'ondemand.saucelabs.com:80/wd/hub'); + + // Append filename to capabilities.name so that it's easier to identify + // tests. + if (this.config_.capabilities.name && + this.config_.capabilities.shardTestFiles) { + this.config_.capabilities.name += + (':' + this.config_.specs.toString().replace(/^.*[\\\/]/, '')); + } + + logger.info( + 'Using SauceLabs selenium server at ' + + this.config_.seleniumAddress.replace(/\/\/.+@/, '//')); + deferred.resolve(); + return deferred.promise; + } +} diff --git a/lib/expectedConditions.ts b/lib/expectedConditions.ts index 682bcbb15..821ee3b29 100644 --- a/lib/expectedConditions.ts +++ b/lib/expectedConditions.ts @@ -34,7 +34,8 @@ var webdriver = require('selenium-webdriver'); * // You can customize the conditions with EC.and, EC.or, and EC.not. * // Here's a condition to wait for url to change, $('abc') element to contain * // text 'bar', and button becomes clickable. - * var condition = EC.and(urlChanged, EC.textToBePresentInElement($('abc'), 'bar'), isClickable); + * var condition = EC.and(urlChanged, EC.textToBePresentInElement($('abc'), + * 'bar'), isClickable); * browser.get(URL); * browser.wait(condition, 5000); //wait for condition to be true. * button.click(); @@ -42,7 +43,6 @@ var webdriver = require('selenium-webdriver'); * @constructor */ export class ExpectedConditions { - /** * Negates the result of a promise. * @@ -58,9 +58,8 @@ export class ExpectedConditions { */ not(expectedCondition: Function): Function { return (): Function => { - return expectedCondition().then((bool: boolean): boolean => { - return !bool; - }); + return expectedCondition().then( + (bool: boolean): boolean => { return !bool; }); }; } @@ -108,9 +107,7 @@ export class ExpectedConditions { * @return {!function} An expected condition that returns a promise which * evaluates to the result of the logical and. */ - and(...args: Function[]): Function { - return this.logicalChain_(true, args); - } + and(...args: Function[]): Function { return this.logicalChain_(true, args); } /** * Chain a number of expected conditions using logical_or, short circuiting @@ -128,9 +125,7 @@ export class ExpectedConditions { * @return {!function} An expected condition that returns a promise which * evaluates to the result of the logical or. */ - or(...args: Function[]): Function { - return this.logicalChain_(false, args); - } + or(...args: Function[]): Function { return this.logicalChain_(false, args); } /** * Expect an alert to be present. @@ -145,15 +140,15 @@ export class ExpectedConditions { */ alertIsPresent(): Function { return () => { - return browser.switchTo().alert().then((): boolean => { - return true; - }, (err: any) => { - if (err.code == webdriver.error.ErrorCode.NO_SUCH_ALERT) { - return false; - } else { - throw err; - } - }); + return browser.switchTo().alert().then( + (): boolean => { return true; }, + (err: any) => { + if (err.code == webdriver.error.ErrorCode.NO_SUCH_ALERT) { + return false; + } else { + throw err; + } + }); }; } @@ -218,10 +213,10 @@ export class ExpectedConditions { */ textToBePresentInElementValue(elementFinder: any, text: string): Function { var hasText = () => { - return elementFinder.getAttribute('value') - .then((actualText: string): boolean => { - return actualText.indexOf(text) > -1; - }); + return elementFinder.getAttribute('value').then( + (actualText: string): boolean => { + return actualText.indexOf(text) > -1; + }); }; return this.and(this.presenceOf(elementFinder), hasText); } @@ -263,9 +258,8 @@ export class ExpectedConditions { */ titleIs(title: string): Function { return () => { - return browser.getTitle().then((actualTitle: string): boolean => { - return actualTitle === title; - }); + return browser.getTitle().then( + (actualTitle: string): boolean => { return actualTitle === title; }); }; } @@ -324,8 +318,8 @@ export class ExpectedConditions { */ visibilityOf(elementFinder: any): Function { return this.and( - this.presenceOf(elementFinder), - elementFinder.isDisplayed.bind(elementFinder)); + this.presenceOf(elementFinder), + elementFinder.isDisplayed.bind(elementFinder)); } /** @@ -346,7 +340,7 @@ export class ExpectedConditions { return this.not(this.visibilityOf(elementFinder)); } -/** + /** * An expectation for checking the selection is selected. * * @example @@ -361,7 +355,7 @@ export class ExpectedConditions { */ elementToBeSelected(elementFinder: any): Function { return this.and( - this.presenceOf(elementFinder), - elementFinder.isSelected.bind(elementFinder)); + this.presenceOf(elementFinder), + elementFinder.isSelected.bind(elementFinder)); } } diff --git a/lib/globals.d.ts b/lib/globals.d.ts index 4e306effb..df7d100a0 100644 --- a/lib/globals.d.ts +++ b/lib/globals.d.ts @@ -1 +1,13 @@ declare var browser: any; + +declare namespace webdriver { + class WebDriver { + getSession: Function; + quit: Function; + static attachToSession: Function; + } + + class Session { getId: Function; } +} + +declare class executors { static createExecutor: Function; } diff --git a/lib/runner.js b/lib/runner.js index c955bf012..7061fbb3f 100644 --- a/lib/runner.js +++ b/lib/runner.js @@ -5,7 +5,15 @@ var protractor = require('./protractor'), EventEmitter = require('events').EventEmitter, helper = require('./util'), log = require('./logger'), - Plugins = require('./plugins'); + Plugins = require('./plugins'), + AttachSession = require('./driverProviders/attachSession').AttachSession, + BrowserStack = require('./driverProviders/browserStack').BrowserStack, + Direct = require('./driverProviders/direct').Direct, + Hosted = require('./driverProviders/hosted').Hosted, + Local = require('./driverProviders/local').Local, + Mock = require('./driverProviders/mock').Mock, + Sauce = require('./driverProviders/sauce').Sauce; + /* * Runner is responsible for starting the execution of a test run and triggering @@ -89,28 +97,27 @@ Runner.prototype.runTestPreparer = function() { * 5) try to find the seleniumServerJar in protractor/selenium */ Runner.prototype.loadDriverProvider_ = function() { - var runnerPath; + if (this.config_.directConnect) { - runnerPath = './driverProviders/direct'; + this.driverprovider_ = new Direct(this.config_); } else if (this.config_.seleniumAddress) { if (this.config_.seleniumSessionId) { - runnerPath = './driverProviders/attachSession'; + console.log('attached the session'); + this.driverprovider_ = new AttachSession(this.config_); } else { - runnerPath = './driverProviders/hosted'; + this.driverprovider_ = new Hosted(this.config_); } } else if (this.config_.browserstackUser && this.config_.browserstackKey) { - runnerPath = './driverProviders/browserstack'; + this.driverprovider_ = new BrowserStack(this.config_); } else if (this.config_.sauceUser && this.config_.sauceKey) { - runnerPath = './driverProviders/sauce'; + this.driverprovider_ = new Sauce(this.config_); } else if (this.config_.seleniumServerJar) { - runnerPath = './driverProviders/local'; + this.driverprovider_ = new Local(this.config_); } else if (this.config_.mockSelenium) { - runnerPath = './driverProviders/mock'; + this.driverprovider_ = new Mock(this.config_); } else { - runnerPath = './driverProviders/local'; + this.driverprovider_ = new Local(this.config_); } - - this.driverprovider_ = require(runnerPath)(this.config_); }; diff --git a/typings.json b/typings.json index b5e81561f..9dd182720 100644 --- a/typings.json +++ b/typings.json @@ -1,9 +1,11 @@ { "ambientDependencies": { "chalk": "registry:dt/chalk#0.4.0+20160317120654", + "form-data": "registry:dt/form-data#0.0.0+20160316155526", "glob": "registry:dt/glob#5.0.10+20160317120654", "minimatch": "registry:dt/minimatch#2.0.8+20160317120654", "node": "registry:dt/node#4.0.0+20160412142033", - "q": "registry:dt/q#0.0.0+20160323171452" + "q": "registry:dt/q#0.0.0+20160417152954", + "request": "registry:dt/request#0.0.0+20160316155526" } }