From adef9b208fcba2a9d60347bda38a3fe3fac6bf50 Mon Sep 17 00:00:00 2001 From: Julie Ralph Date: Tue, 7 Oct 2014 18:55:36 -0700 Subject: [PATCH] feat(runner): add a new method of getting browser drivers - directConnect directConnect as an option on the configuration will replace chromeOnly. Now, WebDriverJS allows Firefox to be used directly as well, so directConnect will work for Chrome and Firefox, and throw an error if another browser is used. This change deprecates but does not remove the chromeOnly option. --- docs/browser-setup.md | 9 --- docs/referenceConf.js | 45 +++++++++------ docs/server-setup.md | 17 +++--- example/chromeOnlyConf.js | 21 ------- example/conf.js | 3 +- lib/configParser.js | 18 ++++-- lib/driverProviders/chrome.js | 76 ------------------------- lib/driverProviders/direct.js | 101 ++++++++++++++++++++++++++++++++++ lib/runner.js | 8 +-- lib/taskScheduler.js | 32 ++++------- lib/util.js | 4 +- spec/basicConf.js | 2 - spec/driverprovider_test.js | 18 +++++- spec/multiConf.js | 2 - spec/shardingConf.js | 1 - spec/suitesConf.js | 2 - website/protractor.conf.js | 2 +- 17 files changed, 187 insertions(+), 174 deletions(-) delete mode 100644 example/chromeOnlyConf.js delete mode 100644 lib/driverProviders/chrome.js create mode 100644 lib/driverProviders/direct.js diff --git a/docs/browser-setup.md b/docs/browser-setup.md index 031a8e165..dc03935a3 100644 --- a/docs/browser-setup.md +++ b/docs/browser-setup.md @@ -57,7 +57,6 @@ capabilities: { } }, ``` -If running with `chromeOnly` and `chromeOptions` together, chromeOptions.args and chromeOptions.extensions are required due to [Issue 6627](https://code.google.com/p/selenium/issues/detail?id=6627&thanks=6627&ts=1385488060) of selenium-webdriver currently(@2.37.0). So in order to avoid the issue, you may simply set them(or one of them) to an empty array. Testing Against Multiple Browsers --------------------------------- @@ -170,8 +169,6 @@ exports.config = { 'basic/*_spec.js' ], - chromeOnly: false, - capabilities: { device: 'android', 'browserName': '', @@ -225,8 +222,6 @@ exports.config = { 'basic/*_spec.js' ], - chromeOnly: false, - capabilities: { browserName: '', device: 'iPhone', @@ -246,8 +241,6 @@ exports.config = { 'basic/*_spec.js' ], - chromeOnly: false, - capabilities: { browserName: '', device: 'iPad', @@ -321,8 +314,6 @@ exports.config = { 'basic/*_spec.js' ], - chromeOnly: false, - capabilities: { 'browserName': 'android' }, diff --git a/docs/referenceConf.js b/docs/referenceConf.js index ae775b19c..a84588aa2 100644 --- a/docs/referenceConf.js +++ b/docs/referenceConf.js @@ -6,48 +6,47 @@ exports.config = { // --------------------------------------------------------------------------- - // ----- How to setup Selenium ----------------------------------------------- + // ----- How to connect to Browser Drivers ----------------------------------- // --------------------------------------------------------------------------- // - // There are three ways to use the Selenium Server. Specify one of the - // following: + // Protractor needs to know how to connect to Drivers for the browsers + // it is testing on. This is usually done through a Selenium Server. + // There are four options - specify one of the following: // // 1. seleniumServerJar - to start a standalone Selenium Server locally. // 2. seleniumAddress - to connect to a Selenium Server which is already // running. // 3. sauceUser/sauceKey - to use remote Selenium Servers via Sauce Labs. - // - // You can bypass a Selenium Server if you only want to test using Chrome. - // Set chromeOnly to true and ChromeDriver will be used directly (from the - // location specified in chromeDriver). + // 4. directConnect - to connect directly to the browser Drivers. + // This option is only available for Firefox and Chrome. + // ---- 1. To start a standalone Selenium Server locally --------------------- // The location of the standalone Selenium Server jar file, relative // to the location of this config. If no other method of starting Selenium // Server is found, this will default to // node_modules/protractor/selenium/selenium-server... seleniumServerJar: null, // The port to start the Selenium Server on, or null if the server should - // find its own unused port. + // find its own unused port. Ignored if seleniumServerJar is null. seleniumPort: null, // Additional command line options to pass to selenium. For example, // if you need to change the browser timeout, use - // seleniumArgs: ['-browserTimeout=60'], + // seleniumArgs: ['-browserTimeout=60'] + // Ignored if seleniumServerJar is null. seleniumArgs: [], - // ChromeDriver location is used to help the standalone Selenium Server - // find the chromedriver binary. This will be passed to the Selenium jar as - // the system property webdriver.chrome.driver. If null, Selenium will + // ChromeDriver location is used to help find the chromedriver binary. + // This will be passed to the Selenium jar as the system property + // webdriver.chrome.driver. If null, Selenium will // attempt to find ChromeDriver using PATH. chromeDriver: './selenium/chromedriver', - // If true, only ChromeDriver will be started, not a Selenium Server. - // Tests for browsers other than Chrome will not run. - chromeOnly: false, - + // ---- 2. To connect to a Selenium Server which is already running ---------- // The address of a running Selenium Server. If specified, Protractor will // connect to an already running instance of Selenium. This usually looks like // seleniumAddress: 'http://localhost:4444/wd/hub' seleniumAddress: null, + // ---- 3. To use remote browsers via Sauce Labs ----------------------------- // If sauceUser and sauceKey are specified, seleniumServerJar will be ignored. // The tests will be run remotely using Sauce Labs. sauceUser: null, @@ -58,6 +57,20 @@ exports.config = { // ondemand.saucelabs.com:80/wd/hub sauceSeleniumAddress: null, + // ---- 4. To connect directly to Drivers ------------------------------------ + // Boolean. If true, Protractor will connect directly to the browser Drivers + // at the locations specified by chromeDriver and firefoxPath. Only Chrome + // and Firefox are supported for direct connect. + directConnect: false, + // Path to the firefox application binary. If null, will attempt to find + // firefox in the default locations. + firefoxPath: null, + + // **DEPRECATED** + // If true, only ChromeDriver will be started, not a Selenium Server. + // This should be replaced with directConnect. + chromeOnly: false, + // --------------------------------------------------------------------------- // ----- What tests to run --------------------------------------------------- // --------------------------------------------------------------------------- diff --git a/docs/server-setup.md b/docs/server-setup.md index 83cf62cf8..ca0e2adf9 100644 --- a/docs/server-setup.md +++ b/docs/server-setup.md @@ -1,9 +1,9 @@ Setting Up the Selenium Server ============================== -When working with Protractor you will most likely use the Selenium Server. The server acts as proxy between your test script (written with the WebDriver API) and the browser driver (controlled by the WebDriver protocols). +When working with Protractor, you need to specify how to connect to the browser drivers which will start up and control the browsers you are testing on. You will most likely use the Selenium Server. The server acts as proxy between your test script (written with the WebDriver API) and the browser driver (controlled by the WebDriver protocols). -The server forwards commands from your script to the driver and returns responses from the driver to your script. The server can handle multiple scripts in different languages. The server can startup and manage multiple browsers in different versions and implementations. +The server forwards commands from your script to the driver and returns responses from the driver to your script. The server can handle multiple scripts in different languages. The server can startup and manage multiple browsers in different versions and implementations. [Test Scripts] < ------------ > [Selenium Server] < ------------ > [Browser Drivers] @@ -35,6 +35,9 @@ To install and start the standalone Selenium Server manually, use the webdriver- 3. Leave the server running while you conduct your test sessions. +4. In your config file, set `seleniumAddress` to the address of the running server. This defaults to + `http://localhost:4444/wd/hub`. + **Starting the Server from a Test Script** @@ -67,13 +70,11 @@ In your config file, set these options: Please note that if you set `sauceUser` and `sauceKey`, the settings for `seleniumServerJar`, `seleniumPort` and `seleniumArgs` will be ignored. -Selenium Server and the Chrome Browser +Connecting Directly to Browser Drivers -------------------------------------- -The Selenium Server is optional when you test against the Chrome browser. In your config file, you can set the chromeOnly option to true or false: - - - `chromeOnly: false` - Your test script communicates with the Selenium Server (running locally or remotely). This is the default setting. +Protractor can test directly against Chrome and Firefox without using a Selenium Server. To use this, in your config file set `directConnect: true`. - - `chromeOnly: true` - Your test script communicates directly with the ChromeDriver. The Selenium Server (running locally or remotely) will be ignored. + - `directConnect: true` - Your test script communicates directly Chrome Driver or Firefox Driver, bypassing any Selenium Server. If this is true, settings for `seleniumAddress` and `seleniumServerJar` will be ignored. If you attempt to use a browser other than Chrome or Firefox an error will be thrown. -The advantage of running only with Chrome is that your test scripts will start up and run faster. For more detailed information about chromeOnly, see the [chrome.js source code](https://code.google.com/p/selenium/source/browse/javascript/node/selenium-webdriver/chrome.js). +The advantage of directly connecting to browser drivers is that your test scripts may start up and run faster. diff --git a/example/chromeOnlyConf.js b/example/chromeOnlyConf.js deleted file mode 100644 index 5c59c56a5..000000000 --- a/example/chromeOnlyConf.js +++ /dev/null @@ -1,21 +0,0 @@ -// An example configuration file. -exports.config = { - // Do not start a Selenium Standalone sever - only run this using chrome. - chromeOnly: true, - chromeDriver: '../selenium/chromedriver', - - // Capabilities to be passed to the webdriver instance. - capabilities: { - 'browserName': 'chrome' - }, - - // Spec patterns are relative to the current working directly when - // protractor is called. - specs: ['example_spec.js'], - - // Options to be passed to Jasmine-node. - jasmineNodeOpts: { - showColors: true, - defaultTimeoutInterval: 30000 - } -}; diff --git a/example/conf.js b/example/conf.js index 4831ecdbc..045b26f25 100644 --- a/example/conf.js +++ b/example/conf.js @@ -1,7 +1,6 @@ // An example configuration file. exports.config = { - // The address of a running selenium server. - seleniumAddress: 'http://localhost:4444/wd/hub', + directConnect: true, // Capabilities to be passed to the webdriver instance. capabilities: { diff --git a/lib/configParser.js b/lib/configParser.js index 98e024eb4..763bbba59 100644 --- a/lib/configParser.js +++ b/lib/configParser.js @@ -141,11 +141,13 @@ ConfigParser.getSpecs = function(config) { ConfigParser.prototype.addConfig_ = function(additionalConfig, relativeTo) { // All filepaths should be kept relative to the current config location. // This will not affect absolute paths. - ['seleniumServerJar', 'chromeDriver', 'onPrepare'].forEach(function(name) { - if (additionalConfig[name] && typeof additionalConfig[name] === 'string') { - additionalConfig[name] = - path.resolve(relativeTo, additionalConfig[name]); - } + ['seleniumServerJar', 'chromeDriver', 'onPrepare', 'firefoxPath']. + forEach(function(name) { + if (additionalConfig[name] && + typeof additionalConfig[name] === 'string') { + additionalConfig[name] = + path.resolve(relativeTo, additionalConfig[name]); + } }); // Make sure they're not trying to add in deprecated config vals. @@ -154,6 +156,12 @@ ConfigParser.prototype.addConfig_ = function(additionalConfig, relativeTo) { throw new Error('Using config.jasmineNodeOpts.specFolders is deprecated ' + 'since Protractor 0.6.0. Please switch to config.specs.'); } + + // chromeOnly is deprecated, use directConnect instead. + if (additionalConfig.chromeOnly) { + console.log('Warning: chromeOnly is deprecated. Use directConnect'); + additionalConfig.directConnect = true; + } merge_(this.config_, additionalConfig); }; diff --git a/lib/driverProviders/chrome.js b/lib/driverProviders/chrome.js deleted file mode 100644 index a5e7dcd34..000000000 --- a/lib/driverProviders/chrome.js +++ /dev/null @@ -1,76 +0,0 @@ -/* - * This is an implementation of the Chrome Driver Provider. - * It is responsible for setting up the account object, tearing - * it down, and setting up the driver correctly. - */ - -var util = require('util'), - webdriver = require('selenium-webdriver'), - chrome = require('selenium-webdriver/chrome'), - q = require('q'), - fs = require('fs'), - path = require('path'); - -var ChromeDriverProvider = function(config) { - this.config_ = config; - this.driver_ = null; -}; - -/** - * 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. - */ -ChromeDriverProvider.prototype.setupEnv = function() { - util.puts('Using ChromeDriver directly...'); - return q.fcall(function() {}); -}; - -/** - * Teardown and destroy the environment and do any associated cleanup. - * Shuts down the driver. - * - * @public - * @return {q.promise} A promise which will resolve when the environment - * is down. - */ -ChromeDriverProvider.prototype.teardownEnv = function() { - var deferred = q.defer(); - this.driver_.quit().then(function() { - deferred.resolve(); - }); - return deferred.promise; -}; - -/** - * Retrieve the webdriver for the runner. - * @public - * @return webdriver instance - */ -ChromeDriverProvider.prototype.getDriver = function() { - if (!this.driver_) { - var chromeDriverFile = this.config_.chromeDriver || - path.resolve(__dirname, '../../selenium/chromedriver'); - - // Check if file exists, if not try .exe or fail accordingly - if (!fs.existsSync(chromeDriverFile)) { - chromeDriverFile += '.exe'; - // Throw error if the client specified conf chromedriver and its not found - if (!fs.existsSync(chromeDriverFile)) { - throw new Error('Could not find chromedriver at ' + - chromeDriverFile); - } - } - - var service = new chrome.ServiceBuilder(chromeDriverFile).build(); - this.driver_ = chrome.createDriver( - new webdriver.Capabilities(this.config_.capabilities), service); - } - return this.driver_; -}; - -// new instance w/ each include -module.exports = function(config) { - return new ChromeDriverProvider(config); -}; diff --git a/lib/driverProviders/direct.js b/lib/driverProviders/direct.js new file mode 100644 index 000000000..e9a8a492b --- /dev/null +++ b/lib/driverProviders/direct.js @@ -0,0 +1,101 @@ +/* + * 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 util = require('util'), + webdriver = require('selenium-webdriver'), + chrome = require('selenium-webdriver/chrome'), + firefox = require('selenium-webdriver/firefox'), + q = require('q'), + fs = require('fs'), + path = require('path'); + +var DirectDriverProvider = function(config) { + this.config_ = config; + this.driver_ = null; +}; + +/** + * 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': + console.log('Using ChromeDriver directly...'); + break; + case 'firefox': + console.log('Using FirefoxDriver directly...'); + break; + default: + throw new Error('browserName (' + this.config_.capabilities.browserName + + ') is not supported with directConnect.'); + } + return q.fcall(function() {}); +}; + +/** + * Teardown and destroy the environment and do any associated cleanup. + * Shuts down the driver. + * + * @public + * @return {q.promise} A promise which will resolve when the environment + * is down. + */ +DirectDriverProvider.prototype.teardownEnv = function() { + var deferred = q.defer(); + this.driver_.quit().then(function() { + deferred.resolve(); + }); + return deferred.promise; +}; + +/** + * Retrieve the webdriver for the runner. + * @public + * @return webdriver instance + */ +DirectDriverProvider.prototype.getDriver = function() { + if (this.driver_) { + return this.driver_; + } + switch (this.config_.capabilities.browserName) { + case 'chrome': + var chromeDriverFile = this.config_.chromeDriver || + path.resolve(__dirname, '../../selenium/chromedriver'); + + // Check if file exists, if not try .exe or fail accordingly + if (!fs.existsSync(chromeDriverFile)) { + chromeDriverFile += '.exe'; + // Throw error if the client specified conf chromedriver and its not found + if (!fs.existsSync(chromeDriverFile)) { + throw new Error('Could not find chromedriver at ' + + chromeDriverFile); + } + } + + var service = new chrome.ServiceBuilder(chromeDriverFile).build(); + this.driver_ = chrome.createDriver( + new webdriver.Capabilities(this.config_.capabilities), service); + break; + case 'firefox': + if (this.config_.firefoxPath) { + this.config_.capabilities.firefox_binary = this.config_.firefoxPath; + } + this.driver_ = new firefox.Driver(this.config_.capabilities); + break; + default: + throw new Error('browserName ' + this.config_.capabilities.browserName + + 'is not supported with directConnect.'); + } + return this.driver_; +}; + +// new instance w/ each include +module.exports = function(config) { + return new DirectDriverProvider(config); +}; diff --git a/lib/runner.js b/lib/runner.js index f4d2f3c43..0f4ca4904 100644 --- a/lib/runner.js +++ b/lib/runner.js @@ -75,16 +75,16 @@ Runner.prototype.runTestPreparer = function() { * @private * * Priority - * 1) if chromeOnly, use that + * 1) if directConnect is true, use that * 2) if seleniumAddress is given, use that - * 3) if a sauceAccount 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_.chromeOnly) { - runnerPath = './driverProviders/chrome'; + if (this.config_.directConnect) { + runnerPath = './driverProviders/direct'; } else if (this.config_.seleniumAddress) { runnerPath = './driverProviders/hosted'; } else if (this.config_.sauceUser && this.config_.sauceKey) { diff --git a/lib/taskScheduler.js b/lib/taskScheduler.js index edd83f60b..205ab8273 100644 --- a/lib/taskScheduler.js +++ b/lib/taskScheduler.js @@ -31,27 +31,19 @@ var TaskScheduler = function(config) { return excludes.indexOf(path) < 0; }); - if (config.chromeOnly) { - console.log('Running using config.chromeOnly - ' + - 'ignoring capabilities, using Chrome instead.'); + if (config.capabilities) { + if (config.multiCapabilities.length) { + console.log('Running using config.multiCapabilities - ' + + 'config.capabilities will be ignored'); + } else { + // Use capabilities if multiCapabilities is empty. + config.multiCapabilities = [config.capabilities]; + } + } else if (!config.multiCapabilities.length) { + // Default to chrome if no capability given config.multiCapabilities = [{ - browserName: 'chrome' - }]; - } else { - if (config.capabilities) { - if (config.multiCapabilities.length) { - console.log('Running using config.multiCapabilities - ' + - 'config.capabilities will be ignored'); - } else { - // Use capabilities if multiCapabilities is empty. - config.multiCapabilities = [config.capabilities]; - } - } else if (!config.multiCapabilities.length) { - // Default to chrome if no capability given - config.multiCapabilities = [{ - browserName: 'chrome' - }]; - } + browserName: 'chrome' + }]; } var taskQueues = []; diff --git a/lib/util.js b/lib/util.js index 458ad2892..0cb352d6c 100644 --- a/lib/util.js +++ b/lib/util.js @@ -8,7 +8,7 @@ var q = require('q'), * @return {q.Promise} A promise that will resolve when filenameOrFn completes. */ exports.runFilenameOrFn_ = function(configDir, filenameOrFn, args) { - var returned; + var returned = null; if (filenameOrFn) { if (typeof filenameOrFn === 'function') { returned = filenameOrFn.apply(null, args); @@ -21,5 +21,5 @@ exports.runFilenameOrFn_ = function(configDir, filenameOrFn, args) { throw 'filenameOrFn must be a string or function'; } } - return q.when(returned); + return q(returned); }; diff --git a/spec/basicConf.js b/spec/basicConf.js index dd5c869c1..1a841574d 100644 --- a/spec/basicConf.js +++ b/spec/basicConf.js @@ -14,8 +14,6 @@ exports.config = { 'basic/exclude*.js' ], - chromeOnly: false, - capabilities: env.capabilities, baseUrl: env.baseUrl, diff --git a/spec/driverprovider_test.js b/spec/driverprovider_test.js index b5b6467e1..eec298120 100644 --- a/spec/driverprovider_test.js +++ b/spec/driverprovider_test.js @@ -44,11 +44,23 @@ var chromeConfig = { browserName: 'chrome' } }; -testDriverProvider(require('../lib/driverProviders/chrome')(chromeConfig)). +testDriverProvider(require('../lib/driverProviders/direct')(chromeConfig)). then(function() { - console.log('chrome.dp working!'); + console.log('direct.dp with chrome working!'); }, function(err) { - console.log('chrome.dp failed with ' + err); + console.log('direct.dp with chrome failed with ' + err.stack); + }); + +var firefoxConfig = { + capabilities: { + browserName: 'firefox' + } +}; +testDriverProvider(require('../lib/driverProviders/direct')(firefoxConfig)). + then(function() { + console.log('direct.dp with firefox working!'); + }, function(err) { + console.log('direct.dp with firefox failed with ' + err.stack); }); var hostedConfig = { diff --git a/spec/multiConf.js b/spec/multiConf.js index 268d7bc99..fc61bae1a 100644 --- a/spec/multiConf.js +++ b/spec/multiConf.js @@ -9,8 +9,6 @@ exports.config = { 'basic/lib_spec.js' ], - chromeOnly: false, - multiCapabilities: [{ 'browserName': 'chrome' }, { diff --git a/spec/shardingConf.js b/spec/shardingConf.js index 2f0e1319e..43bc2b68e 100644 --- a/spec/shardingConf.js +++ b/spec/shardingConf.js @@ -16,7 +16,6 @@ exports.config = { 'basic/exclude*.js' ], - chromeOnly: false, framework: 'debugprint', maxSessions: 3, multiCapabilities: [{ diff --git a/spec/suitesConf.js b/spec/suitesConf.js index 3e1dd0011..972d53b88 100644 --- a/spec/suitesConf.js +++ b/spec/suitesConf.js @@ -10,8 +10,6 @@ exports.config = { failingtest: 'suites/always_fail_spec.js' }, - chromeOnly: false, - capabilities: env.capabilities, baseUrl: env.baseUrl, diff --git a/website/protractor.conf.js b/website/protractor.conf.js index 66b7768ff..28e335207 100644 --- a/website/protractor.conf.js +++ b/website/protractor.conf.js @@ -1,5 +1,5 @@ exports.config = { - chromeOnly: true, + directConnect: true, // Spec patterns are relative to the location of this config. specs: [