From 390e236d23c1c1c5dd10631a98cb84102fd1d408 Mon Sep 17 00:00:00 2001 From: Andy Jack Date: Thu, 29 Mar 2018 18:41:46 -0400 Subject: [PATCH] feat(driverProvider): Add useExistingWebDriver driver provider --- docs/server-setup.md | 34 +++++++++++ lib/config.ts | 8 +++ lib/driverProviders/index.ts | 8 +++ lib/driverProviders/useExistingWebDriver.ts | 57 +++++++++++++++++++ scripts/test.js | 2 + spec/driverProviderUseExistingWebDriver.js | 22 +++++++ .../useExistingDriver_spec.js | 16 ++++++ 7 files changed, 147 insertions(+) create mode 100644 lib/driverProviders/useExistingWebDriver.ts create mode 100644 spec/driverProviderUseExistingWebDriver.js create mode 100644 spec/driverProviders/useExistingWebDriver/useExistingDriver_spec.js diff --git a/docs/server-setup.md b/docs/server-setup.md index 296722d2f..aa15e0e26 100644 --- a/docs/server-setup.md +++ b/docs/server-setup.md @@ -108,3 +108,37 @@ Protractor can test directly against Chrome and Firefox without using a Selenium - `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 directly connecting to browser drivers is that your test scripts may start up and run faster. + +Re-using an Existing WebDriver +------------------------------ + +The use case for re-using an existing WebDriver is when you have existing +`selenium-webdriver` code and are already in control of how the WebDriver is +created, but would also like Protractor to use the same browser, so you can +use protractor's element locators and the rest of its API. This could be +done with the `attachSession` driver provider, but the `attachSession` API is +being removed in `selenium-webdriver` 4.0.0. + +Instead of a protractor config file, you create a config object in your test +setup code, and add your already-created WebDriver object and base URL. + +```javascript +const ProtractorConfigParser = require('protractor/built/configParser').ConfigParser; +const ProtractorRunner = require('protractor/built/runner').Runner; + +const ptorConfig = new ProtractorConfigParser().config_; +ptorConfig.baseUrl = myExistingBaseUrl; +ptorConfig.seleniumWebDriver = myExistingWebDriver; +ptorConfig.noGlobals = true; // local preference + +// looks similar to protractor/built/runner.js run() +const ptorRunner = new ProtractorRunner(ptorConfig); +ptorRunner.driverProvider_.setupEnv(); +const browser = ptorRunner.createBrowser(); +ptorRunner.setupGlobals_(browser); // now you can access protractor.$, etc. +``` + +Note that this driver provider leaves you in control of quitting the driver, +but that also means Protractor API calls that expect the driver to properly +quit and/or restart the browser, e.g. `restart`, `restartSync`, and +`forkNewDriverInstance`, will not behave as documented. diff --git a/lib/config.ts b/lib/config.ts index 043ed9202..f16e769f1 100644 --- a/lib/config.ts +++ b/lib/config.ts @@ -1,3 +1,5 @@ +import {WebDriver} from 'selenium-webdriver'; + import {PluginConfig} from './plugins'; export interface Config { @@ -221,6 +223,12 @@ export interface Config { */ firefoxPath?: string; + // ---- 8. To re-use an existing WebDriver object --------------------------- + + // This would not appear in a configuration file. Instead a configuration + // object would be created that includes an existing webdriver. + seleniumWebDriver?: WebDriver; + // --------------------------------------------------------------------------- // ----- What tests to run --------------------------------------------------- // --------------------------------------------------------------------------- diff --git a/lib/driverProviders/index.ts b/lib/driverProviders/index.ts index 25fcabcf6..87bc431a1 100644 --- a/lib/driverProviders/index.ts +++ b/lib/driverProviders/index.ts @@ -8,6 +8,7 @@ export * from './mock'; export * from './sauce'; export * from './testObject'; export * from './kobiton'; +export * from './useExistingWebDriver'; import {AttachSession} from './attachSession'; @@ -20,6 +21,7 @@ import {Mock} from './mock'; import {Sauce} from './sauce'; import {TestObject} from './testObject'; import {Kobiton} from './kobiton'; +import {UseExistingWebDriver} from './useExistingWebDriver'; import {Config} from '../config'; import {Logger} from '../logger'; @@ -32,6 +34,9 @@ export let buildDriverProvider = (config: Config): DriverProvider => { if (config.directConnect) { driverProvider = new Direct(config); logWarnings('directConnect', config); + } else if (config.seleniumWebDriver) { + driverProvider = new UseExistingWebDriver(config); + logWarnings('useExistingWebDriver', config); } else if (config.seleniumAddress) { if (config.seleniumSessionId) { driverProvider = new AttachSession(config); @@ -109,6 +114,9 @@ export let logWarnings = (providerType: string, config: Config): void => { if ('mock' !== providerType && config.mockSelenium) { warnList.push('mockSelenium'); } + if ('useExistingWebDriver' !== providerType && config.seleniumWebDriver) { + warnList.push('seleniumWebDriver'); + } if (warnList.length !== 0) { logger.warn(warnInto + warnList.join(', ')); } diff --git a/lib/driverProviders/useExistingWebDriver.ts b/lib/driverProviders/useExistingWebDriver.ts new file mode 100644 index 000000000..36b279455 --- /dev/null +++ b/lib/driverProviders/useExistingWebDriver.ts @@ -0,0 +1,57 @@ +/* + * This is an implementation of the Use Existing WebDriver 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 {promise as wdpromise, WebDriver} from 'selenium-webdriver'; + +import {Config} from '../config'; +import {Logger} from '../logger'; + +import {DriverProvider} from './driverProvider'; + +const http = require('selenium-webdriver/http'); + +let logger = new Logger('useExistingWebDriver'); + +export class UseExistingWebDriver extends DriverProvider { + constructor(config: Config) { + super(config); + } + + /** + * Configure and launch (if applicable) the object's environment. + * @return {q.promise} A promise which will resolve when the environment is + * ready to test. + */ + protected setupDriverEnv(): q.Promise { + const defer = q.defer(); + this.config_.seleniumWebDriver.getSession().then((session) => { + logger.info('Using session id - ' + session.getId()); + return defer.resolve(); + }); + return q(undefined); + } + + /** + * Getting a new driver by attaching an existing session. + * + * @public + * @return {WebDriver} webdriver instance + */ + getNewDriver(): WebDriver { + const newDriver = this.config_.seleniumWebDriver; + this.drivers_.push(newDriver); + return newDriver; + } + + /** + * Maintains the existing session and does not quit the driver. + * + * @public + */ + quitDriver(): wdpromise.Promise { + return wdpromise.when(undefined); + } +} diff --git a/scripts/test.js b/scripts/test.js index 7e15e45b8..99684f16f 100755 --- a/scripts/test.js +++ b/scripts/test.js @@ -43,6 +43,8 @@ var passingTests = [ 'node built/cli.js spec/built/noCFBasicConf.js --useBlockingProxy', 'node built/cli.js spec/built/noCFPluginConf.js', //'node scripts/driverProviderAttachSession.js', + 'node built/cli.js spec/driverProviderUseExistingWebDriver.js', + 'node built/cli.js spec/driverProviderUseExistingWebDriver.js --useBlockingProxy', 'node scripts/errorTest.js', // Interactive Element Explorer tasks 'node scripts/interactive_tests/interactive_test.js', diff --git a/spec/driverProviderUseExistingWebDriver.js b/spec/driverProviderUseExistingWebDriver.js new file mode 100644 index 000000000..6bf045579 --- /dev/null +++ b/spec/driverProviderUseExistingWebDriver.js @@ -0,0 +1,22 @@ +var env = require('./environment'); +var webdriver = require('selenium-webdriver'); + +var existingDriver = new webdriver.Builder() + .usingServer(env.seleniumAddress) + .withCapabilities(env.capabilities) + .build(); + +exports.config = { + + framework: 'jasmine', + + specs: [ + 'driverProviders/useExistingWebDriver/*_spec.js' + ], + + capabilities: env.capabilities, + + baseUrl: env.baseUrl, + + seleniumWebDriver: existingDriver, +}; diff --git a/spec/driverProviders/useExistingWebDriver/useExistingDriver_spec.js b/spec/driverProviders/useExistingWebDriver/useExistingDriver_spec.js new file mode 100644 index 000000000..a69bf939b --- /dev/null +++ b/spec/driverProviders/useExistingWebDriver/useExistingDriver_spec.js @@ -0,0 +1,16 @@ +describe('uses existing webdriver', function() { + var URL = '/ng2/#/async'; + + beforeEach(function() { + browser.get(URL); + }); + it('should be able to use an existing session', function() { + var increment = $('#increment'); + expect(increment).toBeDefined(); + }); + // the driverProvider is set up to ignore the quitDriver() call; + // so we call quit() ourselves to tidy up when testing is done. + afterEach(function() { + browser.quit(); + }); +});