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

Commit e22065c

Browse files
committed
chore(promises): clean up driver providers and browser control flow (#5034)
Driver providers and tests: - Use native promises over q promises in driver providers - Remove driverProviderUseExistingWebDriver since the generation of the selenium server is already accomplished when providing a selenium address in driverProvider.ts. Also clean up docs and tests. - Enabled the driverProviderLocal tests - Clean up JSDocs for q.promise Basic lib spec: - Remove auto unwrap test for a WebElement. Reference PR #3471 Browser: - Remove control flow from waitForAngularEnabled, waitForAngular, and angularAppRoot in the Browser class.
1 parent 69791ad commit e22065c

22 files changed

+186
-466
lines changed

docs/server-setup.md

-34
Original file line numberDiff line numberDiff line change
@@ -108,37 +108,3 @@ Protractor can test directly against Chrome and Firefox without using a Selenium
108108
- `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.
109109

110110
The advantage of directly connecting to browser drivers is that your test scripts may start up and run faster.
111-
112-
Re-using an Existing WebDriver
113-
------------------------------
114-
115-
The use case for re-using an existing WebDriver is when you have existing
116-
`selenium-webdriver` code and are already in control of how the WebDriver is
117-
created, but would also like Protractor to use the same browser, so you can
118-
use protractor's element locators and the rest of its API. This could be
119-
done with the `attachSession` driver provider, but the `attachSession` API is
120-
being removed in `selenium-webdriver` 4.0.0.
121-
122-
Instead of a protractor config file, you create a config object in your test
123-
setup code, and add your already-created WebDriver object and base URL.
124-
125-
```javascript
126-
const ProtractorConfigParser = require('protractor/built/configParser').ConfigParser;
127-
const ProtractorRunner = require('protractor/built/runner').Runner;
128-
129-
const ptorConfig = new ProtractorConfigParser().config_;
130-
ptorConfig.baseUrl = myExistingBaseUrl;
131-
ptorConfig.seleniumWebDriver = myExistingWebDriver;
132-
ptorConfig.noGlobals = true; // local preference
133-
134-
// looks similar to protractor/built/runner.js run()
135-
const ptorRunner = new ProtractorRunner(ptorConfig);
136-
ptorRunner.driverProvider_.setupEnv();
137-
const browser = ptorRunner.createBrowser();
138-
ptorRunner.setupGlobals_(browser); // now you can access protractor.$, etc.
139-
```
140-
141-
Note that this driver provider leaves you in control of quitting the driver,
142-
but that also means Protractor API calls that expect the driver to properly
143-
quit and/or restart the browser, e.g. `restart`, `restartSync`, and
144-
`forkNewDriverInstance`, will not behave as documented.

lib/browser.ts

+100-140
Original file line numberDiff line numberDiff line change
@@ -193,24 +193,18 @@ export class ProtractorBrowser extends AbstractExtendedWebDriver {
193193
* this method is called use the new app root. Pass nothing to get a promise that
194194
* resolves to the value of the selector.
195195
*
196-
* @param {string|webdriver.promise.Promise<string>} value The new selector.
196+
* @param {string|webdriver.promise.Promise<string>} valuePromise The new selector.
197197
* @returns A promise that resolves with the value of the selector.
198198
*/
199-
angularAppRoot(value: string|wdpromise.Promise<string> = null): wdpromise.Promise<string> {
200-
return this.driver.controlFlow().execute(() => {
201-
if (value != null) {
202-
return wdpromise.when(value).then((value: string) => {
203-
this.internalRootEl = value;
204-
if (this.bpClient) {
205-
const bpCommandPromise = this.bpClient.setWaitParams(value);
206-
// Convert to webdriver promise as best as possible
207-
return wdpromise.when(bpCommandPromise as any).then(() => this.internalRootEl);
208-
}
209-
return this.internalRootEl;
210-
});
199+
async angularAppRoot(valuePromise: string|wdpromise.Promise<string> = null): Promise<string> {
200+
if (valuePromise != null) {
201+
const value = await valuePromise;
202+
this.internalRootEl = value;
203+
if (this.bpClient) {
204+
await this.bpClient.setWaitParams(value);
211205
}
212-
return wdpromise.when(this.internalRootEl);
213-
}, `Set angular root selector to ${value}`);
206+
}
207+
return this.internalRootEl;
214208
}
215209

216210
/**
@@ -417,23 +411,17 @@ export class ProtractorBrowser extends AbstractExtendedWebDriver {
417411
* Call waitForAngularEnabled() without passing a value to read the current
418412
* state without changing it.
419413
*/
420-
waitForAngularEnabled(enabled: boolean|wdpromise.Promise<boolean> = null):
421-
wdpromise.Promise<boolean> {
422-
if (enabled != null) {
423-
const ret = this.driver.controlFlow().execute(() => {
424-
return wdpromise.when(enabled).then((enabled: boolean) => {
425-
if (this.bpClient) {
426-
logger.debug('Setting waitForAngular' + !enabled);
427-
const bpCommandPromise = this.bpClient.setWaitEnabled(enabled);
428-
// Convert to webdriver promise as best as possible
429-
return wdpromise.when(bpCommandPromise as any).then(() => enabled);
430-
}
431-
});
432-
}, `Set proxy synchronization enabled to ${enabled}`);
414+
async waitForAngularEnabled(enabledPromise: boolean|wdpromise.Promise<boolean> = null):
415+
Promise<boolean> {
416+
if (enabledPromise != null) {
417+
const enabled = await enabledPromise;
418+
if (this.bpClient) {
419+
logger.debug('Setting waitForAngular' + !enabled);
420+
await this.bpClient.setWaitEnabled(enabled);
421+
}
433422
this.internalIgnoreSynchronization = !enabled;
434-
return ret;
435423
}
436-
return wdpromise.when(!this.ignoreSynchronization);
424+
return !this.ignoreSynchronization;
437425
}
438426

439427
/**
@@ -602,15 +590,15 @@ export class ProtractorBrowser extends AbstractExtendedWebDriver {
602590
* @template T
603591
*/
604592
private executeAsyncScript_(script: string|Function, description: string, ...scriptArgs: any[]):
605-
wdpromise.Promise<any> {
593+
Promise<any> {
606594
if (typeof script === 'function') {
607595
script = 'return (' + script + ').apply(null, arguments);';
608596
}
609597
return this.driver.schedule(
610-
new Command(CommandName.EXECUTE_ASYNC_SCRIPT)
611-
.setParameter('script', script)
612-
.setParameter('args', scriptArgs),
613-
description);
598+
new Command(CommandName.EXECUTE_ASYNC_SCRIPT)
599+
.setParameter('script', script)
600+
.setParameter('args', scriptArgs),
601+
description) as Promise<any>;
614602
}
615603

616604
/**
@@ -624,116 +612,90 @@ export class ProtractorBrowser extends AbstractExtendedWebDriver {
624612
* @returns {!webdriver.promise.Promise} A promise that will resolve to the
625613
* scripts return value.
626614
*/
627-
waitForAngular(opt_description?: string): wdpromise.Promise<any> {
615+
async waitForAngular(opt_description?: string): Promise<any> {
628616
let description = opt_description ? ' - ' + opt_description : '';
629617
if (this.ignoreSynchronization) {
630-
return this.driver.controlFlow().execute(() => {
631-
return true;
632-
}, 'Ignore Synchronization Protractor.waitForAngular()');
618+
return true;
633619
}
634620

635-
let runWaitForAngularScript: () => wdpromise.Promise<any> = () => {
621+
let runWaitForAngularScript = async(): Promise<any> => {
636622
if (this.plugins_.skipAngularStability() || this.bpClient) {
637-
return this.driver.controlFlow().execute(() => {
638-
return wdpromise.when(null);
639-
}, 'bpClient or plugin stability override');
623+
return null;
640624
} else {
641-
// Need to wrap this so that we read rootEl in the control flow, not synchronously.
642-
return this.angularAppRoot().then((rootEl: string) => {
643-
return this.executeAsyncScript_(
644-
clientSideScripts.waitForAngular, 'Protractor.waitForAngular()' + description,
645-
rootEl);
646-
});
625+
let rootEl = await this.angularAppRoot();
626+
return this.executeAsyncScript_(
627+
clientSideScripts.waitForAngular, `Protractor.waitForAngular() ${description}`, rootEl);
647628
}
648629
};
649630

650-
return runWaitForAngularScript()
651-
.then((browserErr: Function) => {
652-
if (browserErr) {
653-
throw new Error(
654-
'Error while waiting for Protractor to ' +
655-
'sync with the page: ' + JSON.stringify(browserErr));
631+
try {
632+
let browserErr = await runWaitForAngularScript();
633+
if (browserErr) {
634+
throw new Error(
635+
'Error while waiting for Protractor to ' +
636+
'sync with the page: ' + JSON.stringify(browserErr));
637+
}
638+
await this.plugins_.waitForPromise(this);
639+
640+
await this.driver.wait(async () => {
641+
let results = await this.plugins_.waitForCondition(this);
642+
return results.reduce((x, y) => x && y, true);
643+
}, this.allScriptsTimeout, 'Plugins.waitForCondition()');
644+
} catch (err) {
645+
let timeout: RegExpExecArray;
646+
if (/asynchronous script timeout/.test(err.message)) {
647+
// Timeout on Chrome
648+
timeout = /-?[\d\.]*\ seconds/.exec(err.message);
649+
} else if (/Timed out waiting for async script/.test(err.message)) {
650+
// Timeout on Firefox
651+
timeout = /-?[\d\.]*ms/.exec(err.message);
652+
} else if (/Timed out waiting for an asynchronous script/.test(err.message)) {
653+
// Timeout on Safari
654+
timeout = /-?[\d\.]*\ ms/.exec(err.message);
655+
}
656+
if (timeout) {
657+
let errMsg = `Timed out waiting for asynchronous Angular tasks to finish after ` +
658+
`${timeout}. This may be because the current page is not an Angular ` +
659+
`application. Please see the FAQ for more details: ` +
660+
`https://github.com/angular/protractor/blob/master/docs/timeouts.md#waiting-for-angular`;
661+
if (description.indexOf(' - Locator: ') == 0) {
662+
errMsg += '\nWhile waiting for element with locator' + description;
663+
}
664+
let pendingTimeoutsPromise: wdpromise.Promise<any>;
665+
if (this.trackOutstandingTimeouts_) {
666+
pendingTimeoutsPromise = this.executeScriptWithDescription(
667+
'return window.NG_PENDING_TIMEOUTS',
668+
'Protractor.waitForAngular() - getting pending timeouts' + description);
669+
} else {
670+
pendingTimeoutsPromise = wdpromise.when({});
671+
}
672+
let pendingHttpsPromise = this.executeScriptWithDescription(
673+
clientSideScripts.getPendingHttpRequests,
674+
'Protractor.waitForAngular() - getting pending https' + description,
675+
this.internalRootEl);
676+
677+
let arr = await Promise.all([pendingTimeoutsPromise, pendingHttpsPromise]);
678+
679+
let pendingTimeouts = arr[0] || [];
680+
let pendingHttps = arr[1] || [];
681+
682+
let key: string, pendingTasks: string[] = [];
683+
for (key in pendingTimeouts) {
684+
if (pendingTimeouts.hasOwnProperty(key)) {
685+
pendingTasks.push(' - $timeout: ' + pendingTimeouts[key]);
656686
}
657-
})
658-
.then(
659-
() => {
660-
return this.driver.controlFlow()
661-
.execute(
662-
() => {
663-
return this.plugins_.waitForPromise(this);
664-
},
665-
'Plugins.waitForPromise()')
666-
.then(() => {
667-
return this.driver.wait(() => {
668-
return this.plugins_.waitForCondition(this).then((results: boolean[]) => {
669-
return results.reduce((x, y) => x && y, true);
670-
});
671-
}, this.allScriptsTimeout, 'Plugins.waitForCondition()');
672-
});
673-
},
674-
(err: Error) => {
675-
let timeout: RegExpExecArray;
676-
if (/asynchronous script timeout/.test(err.message)) {
677-
// Timeout on Chrome
678-
timeout = /-?[\d\.]*\ seconds/.exec(err.message);
679-
} else if (/Timed out waiting for async script/.test(err.message)) {
680-
// Timeout on Firefox
681-
timeout = /-?[\d\.]*ms/.exec(err.message);
682-
} else if (/Timed out waiting for an asynchronous script/.test(err.message)) {
683-
// Timeout on Safari
684-
timeout = /-?[\d\.]*\ ms/.exec(err.message);
685-
}
686-
if (timeout) {
687-
let errMsg = `Timed out waiting for asynchronous Angular tasks to finish after ` +
688-
`${timeout}. This may be because the current page is not an Angular ` +
689-
`application. Please see the FAQ for more details: ` +
690-
`https://github.com/angular/protractor/blob/master/docs/timeouts.md#waiting-for-angular`;
691-
if (description.indexOf(' - Locator: ') == 0) {
692-
errMsg += '\nWhile waiting for element with locator' + description;
693-
}
694-
let pendingTimeoutsPromise: wdpromise.Promise<any>;
695-
if (this.trackOutstandingTimeouts_) {
696-
pendingTimeoutsPromise = this.executeScriptWithDescription(
697-
'return window.NG_PENDING_TIMEOUTS',
698-
'Protractor.waitForAngular() - getting pending timeouts' + description);
699-
} else {
700-
pendingTimeoutsPromise = wdpromise.when({});
701-
}
702-
let pendingHttpsPromise = this.executeScriptWithDescription(
703-
clientSideScripts.getPendingHttpRequests,
704-
'Protractor.waitForAngular() - getting pending https' + description,
705-
this.internalRootEl);
706-
707-
return wdpromise.all([pendingTimeoutsPromise, pendingHttpsPromise])
708-
.then(
709-
(arr: any[]) => {
710-
let pendingTimeouts = arr[0] || [];
711-
let pendingHttps = arr[1] || [];
712-
713-
let key: string, pendingTasks: string[] = [];
714-
for (key in pendingTimeouts) {
715-
if (pendingTimeouts.hasOwnProperty(key)) {
716-
pendingTasks.push(' - $timeout: ' + pendingTimeouts[key]);
717-
}
718-
}
719-
for (key in pendingHttps) {
720-
pendingTasks.push(' - $http: ' + pendingHttps[key].url);
721-
}
722-
if (pendingTasks.length) {
723-
errMsg += '. \nThe following tasks were pending:\n';
724-
errMsg += pendingTasks.join('\n');
725-
}
726-
err.message = errMsg;
727-
throw err;
728-
},
729-
() => {
730-
err.message = errMsg;
731-
throw err;
732-
});
733-
} else {
734-
throw err;
735-
}
736-
});
687+
}
688+
for (key in pendingHttps) {
689+
pendingTasks.push(' - $http: ' + pendingHttps[key].url);
690+
}
691+
if (pendingTasks.length) {
692+
errMsg += '. \nThe following tasks were pending:\n';
693+
errMsg += pendingTasks.join('\n');
694+
}
695+
err.message = errMsg;
696+
}
697+
throw err;
698+
}
737699
}
738700

739701
/**
@@ -978,16 +940,14 @@ export class ProtractorBrowser extends AbstractExtendedWebDriver {
978940
.then(() => {
979941
// Reset bpClient sync
980942
if (this.bpClient) {
981-
return this.driver.controlFlow().execute(() => {
982-
return this.bpClient.setWaitEnabled(!this.internalIgnoreSynchronization);
983-
});
943+
return this.bpClient.setWaitEnabled(!this.internalIgnoreSynchronization);
984944
}
985945
})
986946
.then(() => {
987947
// Run Plugins
988-
return this.driver.controlFlow().execute(() => {
948+
if (!this.ignoreSynchronization) {
989949
return this.plugins_.onPageStable(this);
990-
});
950+
}
991951
})
992952
.then(() => null);
993953
}

lib/config.ts

-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import {WebDriver} from 'selenium-webdriver';
2-
31
import {PluginConfig} from './plugins';
42

53
export interface Config {
@@ -238,12 +236,6 @@ export interface Config {
238236
*/
239237
firefoxPath?: string;
240238

241-
// ---- 8. To re-use an existing WebDriver object ---------------------------
242-
243-
// This would not appear in a configuration file. Instead a configuration
244-
// object would be created that includes an existing webdriver.
245-
seleniumWebDriver?: WebDriver;
246-
247239
// ---------------------------------------------------------------------------
248240
// ----- What tests to run ---------------------------------------------------
249241
// ---------------------------------------------------------------------------

lib/driverProviders/attachSession.ts

+4-8
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
* It is responsible for setting up the account object, tearing
44
* it down, and setting up the driver correctly.
55
*/
6-
import * as q from 'q';
7-
import {promise as wdpromise, WebDriver} from 'selenium-webdriver';
6+
import {WebDriver} from 'selenium-webdriver';
87

98
import {Config} from '../config';
109
import {Logger} from '../logger';
@@ -22,13 +21,12 @@ export class AttachSession extends DriverProvider {
2221

2322
/**
2423
* Configure and launch (if applicable) the object's environment.
25-
* @return {q.promise} A promise which will resolve when the environment is
24+
* @return {Promise} A promise which will resolve when the environment is
2625
* ready to test.
2726
*/
28-
protected setupDriverEnv(): q.Promise<any> {
27+
protected async setupDriverEnv(): Promise<any> {
2928
logger.info('Using the selenium server at ' + this.config_.seleniumAddress);
3029
logger.info('Using session id - ' + this.config_.seleniumSessionId);
31-
return q(undefined);
3230
}
3331

3432
/**
@@ -50,7 +48,5 @@ export class AttachSession extends DriverProvider {
5048
*
5149
* @public
5250
*/
53-
quitDriver(): wdpromise.Promise<void> {
54-
return wdpromise.when(undefined);
55-
}
51+
async quitDriver(): Promise<void> {}
5652
}

0 commit comments

Comments
 (0)