Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
33 changes: 13 additions & 20 deletions lib/element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export class ElementArrayFinder extends WebdriverWebElement {
public browser_: ProtractorBrowser,
public getWebElements: () => wdpromise.Promise<WebElement[]> = null, public locator_?: any,
public actionResults_: wdpromise.Promise<any> = null,
public falseIfMissing_: boolean = false) {
public isFalseIfMissing: boolean = false) {
super();

// TODO(juliemr): might it be easier to combine this with our docs and just
Expand Down Expand Up @@ -465,7 +465,7 @@ export class ElementArrayFinder extends WebdriverWebElement {
// map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[];
private applyAction_(
actionFn: (value: WebElement, index: number, array: WebElement[]) => any,
falseIfMissing?: boolean): ElementArrayFinder {
isFalseIfMissing?: boolean): ElementArrayFinder {
let callerError = new Error();
let actionResults = this.getWebElements()
.then((arr: any) => wdpromise.all(arr.map(actionFn)))
Expand All @@ -480,8 +480,11 @@ export class ElementArrayFinder extends WebdriverWebElement {
}
throw noSuchErr;
});
if (isFalseIfMissing) {
actionResults = actionResults.then(null, falseIfMissing);
}
return new ElementArrayFinder(
this.browser_, this.getWebElements, this.locator_, actionResults, falseIfMissing);
this.browser_, this.getWebElements, this.locator_, actionResults, isFalseIfMissing);
}

/**
Expand Down Expand Up @@ -808,22 +811,12 @@ export class ElementFinder extends WebdriverWebElement {
// Access the underlying actionResult of ElementFinder.
this.then =
(fn: (value: any) => any | wdpromise.IThenable<any>, errorFn?: (error: any) => any) => {
return this.elementArrayFinder_
.then(
null,
(error) => {
if (this.elementArrayFinder_.falseIfMissing_) {
return falseIfMissing(error);
} else {
throw error;
}
})
.then((actionResults: any) => {
if (!fn) {
return actionResults[0];
}
return fn(actionResults[0]);
}, errorFn);
return this.elementArrayFinder_.then((actionResults: any) => {
if (!fn) {
return actionResults[0];
}
return fn(actionResults[0]);
}, errorFn);
};
}

Expand Down Expand Up @@ -1078,7 +1071,7 @@ export class ElementFinder extends WebdriverWebElement {
}
return arr[0].isEnabled().then(() => {
return true; // is present, whether it is enabled or not
});
}, falseIfMissing);
}, falseIfMissing);
}

Expand Down
23 changes: 23 additions & 0 deletions spec/basic/elements_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,29 @@ describe('ElementFinder', function() {
expect(usernameInput.equals(usernameInput)).toEqual(true);
expect(usernameInput.equals(name)).toEqual(false);
});

describe('missing element handling', function () {
it('should return false for isDisplayed()', function() {
browser.get('index.html#/form');

var missingElement = $('#MISSING');
expect(missingElement.isDisplayed()).toBe(false);
});

it('should return false for isSelected()', function() {
browser.get('index.html#/form');

var missingElement = $('#MISSING');
expect(missingElement.isSelected()).toBe(false);
});

it('should return false for isSelected()', function() {
browser.get('index.html#/form');

var missingElement = $('#MISSING');
expect(missingElement.isEnabled()).toBe(false);
});
});
});

describe('ElementArrayFinder', function() {
Expand Down
77 changes: 57 additions & 20 deletions spec/basic/expected_conditions_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,26 +45,6 @@ describe('expected conditions', function() {
expect(visibilityOfHideable.call()).toBe(false);
});

it('should have visibilityOf (handling race conditions)', function() {
var disabledButton = $('#disabledButton[disabled="disabled"]');

// toggle presence (of .ng-hide) between visibility evaluation to simulate race condition
var originalIsDisplayedFn = disabledButton.isDisplayed;
disabledButton.isDisplayed = function () {
element(by.model('disabled')).click();
return originalIsDisplayedFn.call(this);
};

var visibilityOfDisabledButtonWithInterceptor = EC.visibilityOf(disabledButton);

element(by.model('disabled')).click();

expect(originalIsDisplayedFn.call(disabledButton)).toBe(true);
expect(disabledButton.isPresent()).toBe(true);

expect(visibilityOfDisabledButtonWithInterceptor.call()).toBe(false);
});

it('should have invisibilityOf', function() {
var invisibilityOfInvalid = EC.invisibilityOf($('#INVALID'));
var invisibilityOfHideable = EC.invisibilityOf($('#shower'));
Expand Down Expand Up @@ -215,4 +195,61 @@ describe('expected conditions', function() {
browser2.switchTo().alert().accept();
});
});

describe('race condition handling', function () {

var disabledButton;

beforeEach(function () {
disabledButton = $('#disabledButton[disabled="disabled"]');
});

function enableButtonBeforeCallToUnmatchSelector(testElement, fnName) {
var originalFn = testElement[fnName];

testElement[fnName] = function () {
element(by.model('disabled')).click();
return originalFn.apply(this, arguments);
};

// save original fn with _ prefix
testElement['_' + fnName] = originalFn;
}

it('can deal with missing elements in visibilityOf', function() {
enableButtonBeforeCallToUnmatchSelector(disabledButton, 'isDisplayed');

element(by.model('disabled')).click();

expect(disabledButton._isDisplayed()).toBe(true);
expect(EC.visibilityOf(disabledButton).call()).toBe(false);
});

it('can deal with missing elements in textToBePresentInElement', function() {
enableButtonBeforeCallToUnmatchSelector(disabledButton, 'getText');

element(by.model('disabled')).click();

expect(disabledButton._getText()).toBe('Dummy');
expect(EC.textToBePresentInElement(disabledButton, 'Dummy').call()).toBe(false);
});

it('can deal with missing elements in textToBePresentInValue', function() {
enableButtonBeforeCallToUnmatchSelector(disabledButton, 'getAttribute');

element(by.model('disabled')).click();

expect(disabledButton._getAttribute('value')).toBe('');
expect(EC.textToBePresentInElementValue(disabledButton, '').call()).toBe(false);
});

it('can deal with missing elements in elementToBeClickable', function() {
enableButtonBeforeCallToUnmatchSelector(disabledButton, 'isEnabled');

element(by.model('disabled')).click();

expect(disabledButton._isEnabled()).toBe(false);
expect(EC.elementToBeClickable(disabledButton).call()).toBe(false);
});
});
});