Skip to content
This repository was archived by the owner on Jul 29, 2024. It is now read-only.

chore(driverProviders): clean up driver provider q usage #5034

Merged
merged 3 commits into from
Nov 15, 2018
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 0 additions & 34 deletions docs/server-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,37 +108,3 @@ 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.
106 changes: 39 additions & 67 deletions lib/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,24 +193,18 @@ export class ProtractorBrowser extends AbstractExtendedWebDriver {
* this method is called use the new app root. Pass nothing to get a promise that
* resolves to the value of the selector.
*
* @param {string|webdriver.promise.Promise<string>} value The new selector.
* @param {string|webdriver.promise.Promise<string>} valuePromise The new selector.
* @returns A promise that resolves with the value of the selector.
*/
angularAppRoot(value: string|wdpromise.Promise<string> = null): wdpromise.Promise<string> {
return this.driver.controlFlow().execute(() => {
if (value != null) {
return wdpromise.when(value).then((value: string) => {
this.internalRootEl = value;
if (this.bpClient) {
const bpCommandPromise = this.bpClient.setWaitParams(value);
// Convert to webdriver promise as best as possible
return wdpromise.when(bpCommandPromise as any).then(() => this.internalRootEl);
}
return this.internalRootEl;
});
async angularAppRoot(valuePromise: string|wdpromise.Promise<string> = null): Promise<string> {
if (valuePromise != null) {
const value = await Promise.resolve(valuePromise);
this.internalRootEl = value;
if (this.bpClient) {
await this.bpClient.setWaitParams(value);
}
return wdpromise.when(this.internalRootEl);
}, `Set angular root selector to ${value}`);
}
return this.internalRootEl;
}

/**
Expand Down Expand Up @@ -417,23 +411,17 @@ export class ProtractorBrowser extends AbstractExtendedWebDriver {
* Call waitForAngularEnabled() without passing a value to read the current
* state without changing it.
*/
waitForAngularEnabled(enabled: boolean|wdpromise.Promise<boolean> = null):
wdpromise.Promise<boolean> {
if (enabled != null) {
const ret = this.driver.controlFlow().execute(() => {
return wdpromise.when(enabled).then((enabled: boolean) => {
if (this.bpClient) {
logger.debug('Setting waitForAngular' + !enabled);
const bpCommandPromise = this.bpClient.setWaitEnabled(enabled);
// Convert to webdriver promise as best as possible
return wdpromise.when(bpCommandPromise as any).then(() => enabled);
}
});
}, `Set proxy synchronization enabled to ${enabled}`);
async waitForAngularEnabled(enabledPromise: boolean|wdpromise.Promise<boolean> = null):
Promise<boolean> {
if (enabledPromise != null) {
const enabled = await Promise.resolve(enabledPromise);
if (this.bpClient) {
logger.debug('Setting waitForAngular' + !enabled);
await this.bpClient.setWaitEnabled(enabled);
}
this.internalIgnoreSynchronization = !enabled;
return ret;
}
return wdpromise.when(!this.ignoreSynchronization);
return !this.ignoreSynchronization;
}

/**
Expand Down Expand Up @@ -602,15 +590,15 @@ export class ProtractorBrowser extends AbstractExtendedWebDriver {
* @template T
*/
private executeAsyncScript_(script: string|Function, description: string, ...scriptArgs: any[]):
wdpromise.Promise<any> {
Promise<any> {
if (typeof script === 'function') {
script = 'return (' + script + ').apply(null, arguments);';
}
return this.driver.schedule(
new Command(CommandName.EXECUTE_ASYNC_SCRIPT)
.setParameter('script', script)
.setParameter('args', scriptArgs),
description);
new Command(CommandName.EXECUTE_ASYNC_SCRIPT)
.setParameter('script', script)
.setParameter('args', scriptArgs),
description) as Promise<any>;
}

/**
Expand All @@ -624,26 +612,20 @@ export class ProtractorBrowser extends AbstractExtendedWebDriver {
* @returns {!webdriver.promise.Promise} A promise that will resolve to the
* scripts return value.
*/
waitForAngular(opt_description?: string): wdpromise.Promise<any> {
async waitForAngular(opt_description?: string): Promise<any> {
let description = opt_description ? ' - ' + opt_description : '';
if (this.ignoreSynchronization) {
return this.driver.controlFlow().execute(() => {
return true;
}, 'Ignore Synchronization Protractor.waitForAngular()');
return Promise.resolve(true);
}

let runWaitForAngularScript: () => wdpromise.Promise<any> = () => {
let runWaitForAngularScript = async(): Promise<any> => {
if (this.plugins_.skipAngularStability() || this.bpClient) {
return this.driver.controlFlow().execute(() => {
return wdpromise.when(null);
}, 'bpClient or plugin stability override');
return Promise.resolve(null);
} else {
// Need to wrap this so that we read rootEl in the control flow, not synchronously.
return this.angularAppRoot().then((rootEl: string) => {
return this.executeAsyncScript_(
clientSideScripts.waitForAngular, 'Protractor.waitForAngular()' + description,
rootEl);
});
let rootEl = await this.angularAppRoot();
return this.executeAsyncScript_(
clientSideScripts.waitForAngular, 'Protractor.waitForAngular()' + description, rootEl);
}
};

Expand All @@ -656,20 +638,12 @@ export class ProtractorBrowser extends AbstractExtendedWebDriver {
}
})
.then(
() => {
return this.driver.controlFlow()
.execute(
() => {
return this.plugins_.waitForPromise(this);
},
'Plugins.waitForPromise()')
.then(() => {
return this.driver.wait(() => {
return this.plugins_.waitForCondition(this).then((results: boolean[]) => {
return results.reduce((x, y) => x && y, true);
});
}, this.allScriptsTimeout, 'Plugins.waitForCondition()');
});
async () => {
await this.plugins_.waitForPromise(this);
return this.driver.wait(async () => {
let results = await this.plugins_.waitForCondition(this);
return results.reduce((x, y) => x && y, true);
}, this.allScriptsTimeout, 'Plugins.waitForCondition()');
},
(err: Error) => {
let timeout: RegExpExecArray;
Expand Down Expand Up @@ -978,16 +952,14 @@ export class ProtractorBrowser extends AbstractExtendedWebDriver {
.then(() => {
// Reset bpClient sync
if (this.bpClient) {
return this.driver.controlFlow().execute(() => {
return this.bpClient.setWaitEnabled(!this.internalIgnoreSynchronization);
});
return this.bpClient.setWaitEnabled(!this.internalIgnoreSynchronization);
}
})
.then(() => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This promise chain should eventually become async/await, but let's save that for another Pr.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, this promise chain might be better contained in a separate PR.

// Run Plugins
return this.driver.controlFlow().execute(() => {
if (!this.ignoreSynchronization) {
return this.plugins_.onPageStable(this);
});
}
})
.then(() => null);
}
Expand Down
8 changes: 0 additions & 8 deletions lib/config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import {WebDriver} from 'selenium-webdriver';

import {PluginConfig} from './plugins';

export interface Config {
Expand Down Expand Up @@ -230,12 +228,6 @@ 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 ---------------------------------------------------
// ---------------------------------------------------------------------------
Expand Down
11 changes: 5 additions & 6 deletions lib/driverProviders/attachSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
* 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 {WebDriver} from 'selenium-webdriver';

import {Config} from '../config';
import {Logger} from '../logger';
Expand All @@ -25,10 +24,10 @@ export class AttachSession extends DriverProvider {
* @return {q.promise} A promise which will resolve when the environment is
* ready to test.
*/
protected setupDriverEnv(): q.Promise<any> {
protected async setupDriverEnv(): Promise<any> {
logger.info('Using the selenium server at ' + this.config_.seleniumAddress);
logger.info('Using session id - ' + this.config_.seleniumSessionId);
return q(undefined);
return Promise.resolve();
}

/**
Expand All @@ -50,7 +49,7 @@ export class AttachSession extends DriverProvider {
*
* @public
*/
quitDriver(): wdpromise.Promise<void> {
return wdpromise.when(undefined);
quitDriver(): Promise<void> {
return Promise.resolve();
}
}
78 changes: 35 additions & 43 deletions lib/driverProviders/browserStack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
* It is responsible for setting up the account object, tearing
* it down, and setting up the driver correctly.
*/
import * as https from 'https';
import * as q from 'q';
import {Session, WebDriver} from 'selenium-webdriver';
import * as util from 'util';

Expand All @@ -29,59 +27,54 @@ export class BrowserStack extends 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.
* @return {Promise} A promise that will resolve when the update is complete.
*/
updateJob(update: any): q.Promise<any> {
let deferredArray = this.drivers_.map((driver: WebDriver) => {
let deferred = q.defer();
async updateJob(update: any): Promise<any> {
let mappedDrivers = this.drivers_.map(async (driver: WebDriver) => {
let session = await driver.getSession();

driver.getSession().then((session: Session) => {

// Fetching BrowserStack session details.
this.browserstackClient.getSession(
session.getId(), function(error: Error, automate_session: any) {
if (error) {
// Fetching BrowserStack session details.
this.browserstackClient.getSession(
session.getId(), function(error: Error, automate_session: any) {
if (error) {
logger.info(
'BrowserStack results available at ' +
'https://www.browserstack.com/automate');
} else {
if (automate_session && automate_session.browser_url) {
logger.info('BrowserStack results available at ' + automate_session.browser_url);
} else {
logger.info(
'BrowserStack results available at ' +
'https://www.browserstack.com/automate');
} else {
if (automate_session && automate_session.browser_url) {
logger.info('BrowserStack results available at ' + automate_session.browser_url);
} else {
logger.info(
'BrowserStack results available at ' +
'https://www.browserstack.com/automate');
}
}
});
}
});

let jobStatus = update.passed ? 'completed' : 'error';
let statusObj = {status: jobStatus};
let jobStatus = update.passed ? 'completed' : 'error';
let statusObj = {status: jobStatus};

// Updating status of BrowserStack session.
this.browserstackClient.updateSession(
session.getId(), statusObj, function(error: Error, automate_session: any) {
if (error) {
throw new BrowserError(
logger, 'Error updating BrowserStack pass/fail status: ' + util.inspect(error));
} else {
logger.info(automate_session);
deferred.resolve();
}
});
});
return deferred.promise;
// Updating status of BrowserStack session.
this.browserstackClient.updateSession(
session.getId(), statusObj, function(error: Error, automate_session: any) {
if (error) {
throw new BrowserError(
logger, 'Error updating BrowserStack pass/fail status: ' + util.inspect(error));
} else {
logger.info(automate_session);
}
});
});
return q.all(deferredArray);

return Promise.all(mappedDrivers);
}

/**
* Configure and launch (if applicable) the object's environment.
* @return {q.promise} A promise which will resolve when the environment is
* @return {promise} A promise which will resolve when the environment is
* ready to test.
*/
protected setupDriverEnv(): q.Promise<any> {
let deferred = q.defer();
protected async setupDriverEnv(): Promise<any> {
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';
Expand All @@ -99,8 +92,7 @@ export class BrowserStack extends DriverProvider {
(':' + this.config_.specs.toString().replace(/^.*[\\\/]/, ''));
}

logger.info('Using BrowserStack selenium server at ' + this.config_.seleniumAddress);
deferred.resolve();
return deferred.promise;
logger.info(`Using BrowserStack selenium server at ${this.config_.seleniumAddress}`);
return Promise.resolve();
}
}
5 changes: 2 additions & 3 deletions lib/driverProviders/direct.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
*/
import * as fs from 'fs';
import * as path from 'path';
import * as q from 'q';
import {Capabilities, WebDriver} from 'selenium-webdriver';
import {Driver as ChromeDriver, ServiceBuilder as ChromeServiceBuilder} from 'selenium-webdriver/chrome';
import {Driver as FirefoxDriver} from 'selenium-webdriver/firefox';
Expand All @@ -29,7 +28,7 @@ export class Direct extends DriverProvider {
* @return {q.promise} A promise which will resolve when the environment is
* ready to test.
*/
protected setupDriverEnv(): q.Promise<any> {
protected async setupDriverEnv(): Promise<any> {
switch (this.config_.capabilities.browserName) {
case 'chrome':
logger.info('Using ChromeDriver directly...');
Expand All @@ -43,7 +42,7 @@ export class Direct extends DriverProvider {
'browserName ' + this.config_.capabilities.browserName +
' is not supported with directConnect.');
}
return q.fcall(function() {});
return Promise.resolve(function() {});
}

/**
Expand Down
Loading