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

Commit

Permalink
feat(expectedConditions): add helper library for syncing with non-ang…
Browse files Browse the repository at this point in the history
…ular apps

usage:
```javascript
var EC = protractor.ExpectedConditions;
var button = $('#xyz');
var isClickable = EC.elementToBeClickable(button);

browser.get(URL);
browser.wait(isClickable, 5000); //wait for an element to become clickable
button.click();
```

You can also customize the conditions:

```javascript
var urlChanged = function() {
  return browser.getCurrentUrl().then(function(url) {
    return url != 'http://www.angularjs.org';
  });
};

// condition to wait for url to change, title to contain 'foo', and $('abc') element to contain text 'bar'
var condition = EC.and(urlChanged, EC.titleContains('foo'), EC.textToBePresentInElement($('abc'), 'bar'));
$('navButton').click();
browser.wait(condition, 5000); //wait for condition to be true.
// do other things
```
  • Loading branch information
hankduan committed Jan 20, 2015
1 parent eab47e7 commit 9bc1c53
Show file tree
Hide file tree
Showing 5 changed files with 474 additions and 4 deletions.
293 changes: 293 additions & 0 deletions lib/expectedConditions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,293 @@
var webdriver = require('selenium-webdriver');

/* globals browser */

/**
* Represents a library of canned expected conditions that are useful for
* protractor, especially when dealing with non-angular apps.
*
* Each condition returns a function that evaluates to a promise. You may mix
* multiple conditions using `and`, `or`, and/or `not`. You may also
* mix these conditions with any other conditions that you write.
*
* See https://selenium.googlecode.com/git/docs/api/java/org/openqa ...
* /selenium/support/ui/ExpectedConditions.html
*
*
* @example
* var EC = protractor.ExpectedConditions;
* var button = $('#xyz');
* var isClickable = EC.elementToBeClickable(button);
*
* browser.get(URL);
* browser.wait(isClickable, 5000); //wait for an element to become clickable
* button.click();
*
* // You can defined your own expected condition, which is a function that
* // takes no parameter and evaluates to a promise of a boolean.
* var urlChanged = function() {
* return browser.getCurrentUrl().then(function(url) {
* return url === 'http://www.angularjs.org';
* });
* };
*
* // You can customize the conditions with EC.and, EC.or, and EC.not.
* // Here's a condition to wait for url to change, $('abc') element to contain
* // text 'bar', and button becomes clickable.
* var condition = EC.and(urlChanged, EC.textToBePresentInElement($('abc'), 'bar'), isClickable);
* browser.get(URL);
* browser.wait(condition, 5000); //wait for condition to be true.
* button.click();
*
* @constructor
*/
var ExpectedConditions = function() {};

/**
* Negates the result of a promise.
*
* @param {!function} expectedCondition
*
* @return {!function} An expected condition that returns the negated value.
*/
ExpectedConditions.prototype.not = function(expectedCondition) {
return function() {
return expectedCondition.call().then(function(bool) {
return !bool;
});
};
};

/**
* Helper function that is equivalent to the logical_and if defaultRet==true,
* or logical_or if defaultRet==false
*
* @private
* @param {boolean} defaultRet
* @param {Array.<Function>} fns An array of expected conditions to chain.
*
* @return {!function} An expected condition that returns a promise which
* evaluates to the result of the logical chain.
*/
ExpectedConditions.prototype.logicalChain_ = function(defaultRet, fns) {
var self = this;
return function() {
if (fns.length === 0) {
return defaultRet;
}
var fn = fns[0];
return fn().then(function(bool) {
if (bool === defaultRet) {
return self.logicalChain_(defaultRet, fns.slice(1))();
} else {
return !defaultRet;
}
});
};
};

/**
* Chain a number of expected conditions using logical_and, short circuiting
* at the first expected condition that evaluates to false.
*
* @param {Array.<Function>} fns An array of expected conditions to 'and' together.
*
* @return {!function} An expected condition that returns a promise which
* evaluates to the result of the logical and.
*/
ExpectedConditions.prototype.and = function() {
var args = Array.prototype.slice.call(arguments);
return this.logicalChain_(true, args);
};

/**
* Chain a number of expected conditions using logical_or, short circuiting
* at the first expected condition that evaluates to true.
*
* @param {Array.<Function>} fns An array of expected conditions to 'or' together.
*
* @return {!function} An expected condition that returns a promise which
* evaluates to the result of the logical or.
*/
ExpectedConditions.prototype.or = function() {
var args = Array.prototype.slice.call(arguments);
return this.logicalChain_(false, args);
};

/**
* Expect an alert to be present.
*
* @return {!function} An expected condition that returns a promise
* representing whether an alert is present.
*/
ExpectedConditions.prototype.alertIsPresent = function() {
return function() {
return browser.switchTo().alert().then(function() {
return true;
}, function(err) {
if (err.code == webdriver.error.ErrorCode.NO_SUCH_ALERT) {
return false;
} else {
throw err;
}
});
};
};

/**
* An Expectation for checking an element is visible and enabled such that you
* can click it.
*
* @param {!ElementFinder} elementFinder The element to check
*
* @return {!function} An expected condition that returns a promise
* representing whether the element is clickable.
*/
ExpectedConditions.prototype.elementToBeClickable = function(elementFinder) {
return this.and(
this.visibilityOf(elementFinder),
elementFinder.isEnabled.bind(elementFinder));
};

/**
* An expectation for checking if the given text is present in the
* element. Returns false if the elementFinder does not find an element.
*
* @param {!ElementFinder} elementFinder The element to check
* @param {!string} text The text to verify against
*
* @return {!function} An expected condition that returns a promise
* representing whether the text is present in the element.
*/
ExpectedConditions.prototype.textToBePresentInElement = function(elementFinder, text) {
var hasText = function() {
return elementFinder.getText().then(function(actualText) {
return actualText.indexOf(text) > -1;
});
};
return this.and(this.presenceOf(elementFinder), hasText);
};

/**
* An expectation for checking if the given text is present in the element’s
* value. Returns false if the elementFinder does not find an element.
*
* @param {!ElementFinder} elementFinder The element to check
* @param {!string} text The text to verify against
*
* @return {!function} An expected condition that returns a promise
* representing whether the text is present in the element's value.
*/
ExpectedConditions.prototype.textToBePresentInElementValue = function(elementFinder, text) {
var hasText = function() {
return elementFinder.getAttribute('value').then(function(actualText) {
return actualText.indexOf(text) > -1;
});
};
return this.and(this.presenceOf(elementFinder), hasText);
};

/**
* An expectation for checking that the title contains a case-sensitive
* substring.
*
* @param {!string} title The fragment of title expected
*
* @return {!function} An expected condition that returns a promise
* representing whether the title contains the string.
*/
ExpectedConditions.prototype.titleContains = function(title) {
return function() {
return browser.getTitle().then(function(actualTitle) {
return actualTitle.indexOf(title) > -1;
});
};
};

/**
* An expectation for checking the title of a page.
*
* @param {!string} title The expected title, which must be an exact match.
*
* @return {!function} An expected condition that returns a promise
* representing whether the title equals the string.
*/
ExpectedConditions.prototype.titleIs = function(title) {
return function() {
return browser.getTitle().then(function(actualTitle) {
return actualTitle === title;
});
};
};

/**
* An expectation for checking that an element is present on the DOM
* of a page. This does not necessarily mean that the element is visible.
*
* @param {!ElementFinder} elementFinder The element to check
*
* @return {!function} An expected condition that returns a promise
* representing whether the element is present.
*/
ExpectedConditions.prototype.presenceOf = function(elementFinder) {
return elementFinder.isPresent.bind(elementFinder);
};

/**
* An expectation for checking that an element is not attached to the DOM
* of a page.
*
* @param {!ElementFinder} elementFinder The element to check
*
* @return {!function} An expected condition that returns a promise
* representing whether the element is stale.
*/
ExpectedConditions.prototype.stalenessOf = function(elementFinder) {
return this.not(this.presenceOf(elementFinder));
};

/**
* An expectation for checking that an element is present on the DOM of a
* page and visible. Visibility means that the element is not only displayed
* but also has a height and width that is greater than 0.
*
* @param {!ElementFinder} elementFinder The element to check
*
* @return {!function} An expected condition that returns a promise
* representing whether the element is visible.
*/
ExpectedConditions.prototype.visibilityOf = function(elementFinder) {
return this.and(
this.presenceOf(elementFinder),
elementFinder.isDisplayed.bind(elementFinder));
};

/**
* An expectation for checking that an element is either invisible or not
* present on the DOM.
*
* @param {!ElementFinder} elementFinder The element to check
*
* @return {!function} An expected condition that returns a promise
* representing whether the element is invisible.
*/
ExpectedConditions.prototype.invisibilityOf = function(elementFinder) {
return this.not(this.visibilityOf(elementFinder));
};

/**
* An expectation for checking the selection is selected.
*
* @param {!ElementFinder} elementFinder The element to check
*
* @return {!function} An expected condition that returns a promise
* representing whether the element is selected.
*/
ExpectedConditions.prototype.elementToBeSelected = function(elementFinder) {
return this.and(
this.presenceOf(elementFinder),
elementFinder.isSelected.bind(elementFinder));
};

module.exports = ExpectedConditions;

6 changes: 6 additions & 0 deletions lib/protractor.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ var ElementFinder = require('./element').ElementFinder;

var clientSideScripts = require('./clientsidescripts.js');
var ProtractorBy = require('./locators.js').ProtractorBy;
var ExpectedConditions = require('./expectedConditions.js');

/* global angular */

Expand Down Expand Up @@ -36,6 +37,11 @@ exports.ElementFinder = ElementFinder;
*/
exports.ElementArrayFinder = ElementArrayFinder;

/**
* @type {ExpectedConditions}
*/
exports.ExpectedConditions = new ExpectedConditions();

/**
* Mix a function from one object onto another. The function will still be
* called in the context of the original object.
Expand Down
8 changes: 4 additions & 4 deletions spec/basic/elements_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -221,16 +221,16 @@ describe('ElementArrayFinder', function() {
var checkboxesElms = $$('#checkboxes input');
browser.get('index.html');

expect(checkboxesElms.isSelected()).toEqual([true, false, false]);
expect(checkboxesElms.isSelected()).toEqual([true, false, false, false]);
checkboxesElms.click();
expect(checkboxesElms.isSelected()).toEqual([false, true, true]);
expect(checkboxesElms.isSelected()).toEqual([false, true, true, true]);
});

it('action should act on all elements selected by filter', function() {
browser.get('index.html');

var multiElement = $$('#checkboxes input').filter(function(elem, index) {
return index == 1 || index == 2;
return index == 2 || index == 3;
});
multiElement.click();
expect($('#letterlist').getText()).toEqual('wx');
Expand All @@ -240,7 +240,7 @@ describe('ElementArrayFinder', function() {
browser.get('index.html');

var elem = $$('#checkboxes input').filter(function(elem, index) {
return index == 1 || index == 2;
return index == 2 || index == 3;
}).last();
elem.click();
expect($('#letterlist').getText()).toEqual('x');
Expand Down
Loading

0 comments on commit 9bc1c53

Please sign in to comment.