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

Commit 28f3873

Browse files
committed
fix(ExpectedConditions): allow ExpectedConditions to handle missing elements
Expected conditions used `presenceOf` and `visibilityOf` to check that it's referencing elements which actually exist on the page, but there is a race condition with this strategy: an element could disappear after the `presenceOf`/`visibilityOf` check but before other checks, causing an error to be thrown. This PR handles this race condition in two ways: 1. `ElementFinder`'s `isEnabled`, `isDisplayed`, and `isSelected` functions now return false if no such element exists, rahter than throwing an error 2. `ExpectedConditions`'s `textToBePresent` and `textToBePresentInElementValue` now check for errors and also return false in those cases This is a general solution to the problem referenced in #3777 and #3958.
1 parent 6a4dc7a commit 28f3873

File tree

2 files changed

+22
-17
lines changed

2 files changed

+22
-17
lines changed

lib/element.ts

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ let WEB_ELEMENT_FUNCTIONS = [
1717
'getLocation', 'isEnabled', 'isSelected', 'submit', 'clear', 'isDisplayed', 'getId', 'serialize',
1818
'takeScreenshot'
1919
] as (keyof WebdriverWebElement)[];
20+
let FALSE_IF_MISSING_WEB_ELEMENT_FUNCTIONS = [
21+
'isEnabled', 'isSelected', 'isDisplayed'
22+
] as (keyof WebdriverWebElement)[];
2023

2124
/**
2225
* ElementArrayFinder is used for operations on an array of elements (as opposed
@@ -82,7 +85,8 @@ export class ElementArrayFinder extends WebdriverWebElement {
8285
constructor(
8386
public browser_: ProtractorBrowser,
8487
public getWebElements: () => wdpromise.Promise<WebElement[]> = null, public locator_?: any,
85-
public actionResults_: wdpromise.Promise<any> = null) {
88+
public actionResults_: wdpromise.Promise<any> = null,
89+
public falseIfMissing_: boolean) {
8690
super();
8791

8892
// TODO(juliemr): might it be easier to combine this with our docs and just
@@ -92,7 +96,8 @@ export class ElementArrayFinder extends WebdriverWebElement {
9296
let actionFn = (webElem: any) => {
9397
return webElem[fnName].apply(webElem, args);
9498
};
95-
return this.applyAction_(actionFn);
99+
return this.applyAction_(actionFn,
100+
FALSE_IF_MISSING_WEB_ELEMENT_FUNCTIONS.indexOf(fnName) !== -1);
96101
};
97102
});
98103
}
@@ -458,8 +463,8 @@ export class ElementArrayFinder extends WebdriverWebElement {
458463
* @private
459464
*/
460465
// map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[];
461-
private applyAction_(actionFn: (value: WebElement, index: number, array: WebElement[]) => any):
462-
ElementArrayFinder {
466+
private applyAction_(actionFn: (value: WebElement, index: number, array: WebElement[]) => any,
467+
falseIfMissing: boolean): ElementArrayFinder {
463468
let callerError = new Error();
464469
let actionResults = this.getWebElements()
465470
.then((arr: any) => wdpromise.all(arr.map(actionFn)))
@@ -474,7 +479,8 @@ export class ElementArrayFinder extends WebdriverWebElement {
474479
}
475480
throw noSuchErr;
476481
});
477-
return new ElementArrayFinder(this.browser_, this.getWebElements, this.locator_, actionResults);
482+
return new ElementArrayFinder(this.browser_, this.getWebElements, this.locator_, actionResults,
483+
falseIfMissing);
478484
}
479485

480486
/**
@@ -801,7 +807,14 @@ export class ElementFinder extends WebdriverWebElement {
801807
// Access the underlying actionResult of ElementFinder.
802808
this.then =
803809
(fn: (value: any) => any | wdpromise.IThenable<any>, errorFn?: (error: any) => any) => {
804-
return this.elementArrayFinder_.then((actionResults: any) => {
810+
return this.elementArrayFinder_.then(null, (error) => {
811+
if (elementArrayFinder_.falseIfMissing_ &&
812+
(error instanceof wderror.NoSuchElementError)) {
813+
return false;
814+
} else {
815+
throw error;
816+
}
817+
}).then((actionResults: any) => {
805818
if (!fn) {
806819
return actionResults[0];
807820
}

lib/expectedConditions.ts

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ export class ProtractorExpectedConditions {
210210
// MSEdge does not properly remove newlines, which causes false
211211
// negatives
212212
return actualText.replace(/\r?\n|\r/g, '').indexOf(text) > -1;
213-
});
213+
}, () => false);
214214
};
215215
return this.and(this.presenceOf(elementFinder), hasText);
216216
}
@@ -235,7 +235,7 @@ export class ProtractorExpectedConditions {
235235
let hasText = () => {
236236
return elementFinder.getAttribute('value').then((actualText: string): boolean => {
237237
return actualText.indexOf(text) > -1;
238-
});
238+
}, () => false);
239239
};
240240
return this.and(this.presenceOf(elementFinder), hasText);
241241
}
@@ -388,15 +388,7 @@ export class ProtractorExpectedConditions {
388388
* representing whether the element is visible.
389389
*/
390390
visibilityOf(elementFinder: ElementFinder): Function {
391-
return this.and(this.presenceOf(elementFinder), () => {
392-
return elementFinder.isDisplayed().then((displayed: boolean) => displayed, (err: any) => {
393-
if (err instanceof wderror.NoSuchElementError) {
394-
return false;
395-
} else {
396-
throw err;
397-
}
398-
});
399-
});
391+
return this.and(this.presenceOf(elementFinder), elementFinder.isDisplayed.bind(elementFinder));
400392
}
401393

402394
/**

0 commit comments

Comments
 (0)