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

Commit 9bc1c53

Browse files
committed
feat(expectedConditions): add helper library for syncing with non-angular 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 ```
1 parent eab47e7 commit 9bc1c53

File tree

5 files changed

+474
-4
lines changed

5 files changed

+474
-4
lines changed

lib/expectedConditions.js

+293
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
var webdriver = require('selenium-webdriver');
2+
3+
/* globals browser */
4+
5+
/**
6+
* Represents a library of canned expected conditions that are useful for
7+
* protractor, especially when dealing with non-angular apps.
8+
*
9+
* Each condition returns a function that evaluates to a promise. You may mix
10+
* multiple conditions using `and`, `or`, and/or `not`. You may also
11+
* mix these conditions with any other conditions that you write.
12+
*
13+
* See https://selenium.googlecode.com/git/docs/api/java/org/openqa ...
14+
* /selenium/support/ui/ExpectedConditions.html
15+
*
16+
*
17+
* @example
18+
* var EC = protractor.ExpectedConditions;
19+
* var button = $('#xyz');
20+
* var isClickable = EC.elementToBeClickable(button);
21+
*
22+
* browser.get(URL);
23+
* browser.wait(isClickable, 5000); //wait for an element to become clickable
24+
* button.click();
25+
*
26+
* // You can defined your own expected condition, which is a function that
27+
* // takes no parameter and evaluates to a promise of a boolean.
28+
* var urlChanged = function() {
29+
* return browser.getCurrentUrl().then(function(url) {
30+
* return url === 'http://www.angularjs.org';
31+
* });
32+
* };
33+
*
34+
* // You can customize the conditions with EC.and, EC.or, and EC.not.
35+
* // Here's a condition to wait for url to change, $('abc') element to contain
36+
* // text 'bar', and button becomes clickable.
37+
* var condition = EC.and(urlChanged, EC.textToBePresentInElement($('abc'), 'bar'), isClickable);
38+
* browser.get(URL);
39+
* browser.wait(condition, 5000); //wait for condition to be true.
40+
* button.click();
41+
*
42+
* @constructor
43+
*/
44+
var ExpectedConditions = function() {};
45+
46+
/**
47+
* Negates the result of a promise.
48+
*
49+
* @param {!function} expectedCondition
50+
*
51+
* @return {!function} An expected condition that returns the negated value.
52+
*/
53+
ExpectedConditions.prototype.not = function(expectedCondition) {
54+
return function() {
55+
return expectedCondition.call().then(function(bool) {
56+
return !bool;
57+
});
58+
};
59+
};
60+
61+
/**
62+
* Helper function that is equivalent to the logical_and if defaultRet==true,
63+
* or logical_or if defaultRet==false
64+
*
65+
* @private
66+
* @param {boolean} defaultRet
67+
* @param {Array.<Function>} fns An array of expected conditions to chain.
68+
*
69+
* @return {!function} An expected condition that returns a promise which
70+
* evaluates to the result of the logical chain.
71+
*/
72+
ExpectedConditions.prototype.logicalChain_ = function(defaultRet, fns) {
73+
var self = this;
74+
return function() {
75+
if (fns.length === 0) {
76+
return defaultRet;
77+
}
78+
var fn = fns[0];
79+
return fn().then(function(bool) {
80+
if (bool === defaultRet) {
81+
return self.logicalChain_(defaultRet, fns.slice(1))();
82+
} else {
83+
return !defaultRet;
84+
}
85+
});
86+
};
87+
};
88+
89+
/**
90+
* Chain a number of expected conditions using logical_and, short circuiting
91+
* at the first expected condition that evaluates to false.
92+
*
93+
* @param {Array.<Function>} fns An array of expected conditions to 'and' together.
94+
*
95+
* @return {!function} An expected condition that returns a promise which
96+
* evaluates to the result of the logical and.
97+
*/
98+
ExpectedConditions.prototype.and = function() {
99+
var args = Array.prototype.slice.call(arguments);
100+
return this.logicalChain_(true, args);
101+
};
102+
103+
/**
104+
* Chain a number of expected conditions using logical_or, short circuiting
105+
* at the first expected condition that evaluates to true.
106+
*
107+
* @param {Array.<Function>} fns An array of expected conditions to 'or' together.
108+
*
109+
* @return {!function} An expected condition that returns a promise which
110+
* evaluates to the result of the logical or.
111+
*/
112+
ExpectedConditions.prototype.or = function() {
113+
var args = Array.prototype.slice.call(arguments);
114+
return this.logicalChain_(false, args);
115+
};
116+
117+
/**
118+
* Expect an alert to be present.
119+
*
120+
* @return {!function} An expected condition that returns a promise
121+
* representing whether an alert is present.
122+
*/
123+
ExpectedConditions.prototype.alertIsPresent = function() {
124+
return function() {
125+
return browser.switchTo().alert().then(function() {
126+
return true;
127+
}, function(err) {
128+
if (err.code == webdriver.error.ErrorCode.NO_SUCH_ALERT) {
129+
return false;
130+
} else {
131+
throw err;
132+
}
133+
});
134+
};
135+
};
136+
137+
/**
138+
* An Expectation for checking an element is visible and enabled such that you
139+
* can click it.
140+
*
141+
* @param {!ElementFinder} elementFinder The element to check
142+
*
143+
* @return {!function} An expected condition that returns a promise
144+
* representing whether the element is clickable.
145+
*/
146+
ExpectedConditions.prototype.elementToBeClickable = function(elementFinder) {
147+
return this.and(
148+
this.visibilityOf(elementFinder),
149+
elementFinder.isEnabled.bind(elementFinder));
150+
};
151+
152+
/**
153+
* An expectation for checking if the given text is present in the
154+
* element. Returns false if the elementFinder does not find an element.
155+
*
156+
* @param {!ElementFinder} elementFinder The element to check
157+
* @param {!string} text The text to verify against
158+
*
159+
* @return {!function} An expected condition that returns a promise
160+
* representing whether the text is present in the element.
161+
*/
162+
ExpectedConditions.prototype.textToBePresentInElement = function(elementFinder, text) {
163+
var hasText = function() {
164+
return elementFinder.getText().then(function(actualText) {
165+
return actualText.indexOf(text) > -1;
166+
});
167+
};
168+
return this.and(this.presenceOf(elementFinder), hasText);
169+
};
170+
171+
/**
172+
* An expectation for checking if the given text is present in the element’s
173+
* value. Returns false if the elementFinder does not find an element.
174+
*
175+
* @param {!ElementFinder} elementFinder The element to check
176+
* @param {!string} text The text to verify against
177+
*
178+
* @return {!function} An expected condition that returns a promise
179+
* representing whether the text is present in the element's value.
180+
*/
181+
ExpectedConditions.prototype.textToBePresentInElementValue = function(elementFinder, text) {
182+
var hasText = function() {
183+
return elementFinder.getAttribute('value').then(function(actualText) {
184+
return actualText.indexOf(text) > -1;
185+
});
186+
};
187+
return this.and(this.presenceOf(elementFinder), hasText);
188+
};
189+
190+
/**
191+
* An expectation for checking that the title contains a case-sensitive
192+
* substring.
193+
*
194+
* @param {!string} title The fragment of title expected
195+
*
196+
* @return {!function} An expected condition that returns a promise
197+
* representing whether the title contains the string.
198+
*/
199+
ExpectedConditions.prototype.titleContains = function(title) {
200+
return function() {
201+
return browser.getTitle().then(function(actualTitle) {
202+
return actualTitle.indexOf(title) > -1;
203+
});
204+
};
205+
};
206+
207+
/**
208+
* An expectation for checking the title of a page.
209+
*
210+
* @param {!string} title The expected title, which must be an exact match.
211+
*
212+
* @return {!function} An expected condition that returns a promise
213+
* representing whether the title equals the string.
214+
*/
215+
ExpectedConditions.prototype.titleIs = function(title) {
216+
return function() {
217+
return browser.getTitle().then(function(actualTitle) {
218+
return actualTitle === title;
219+
});
220+
};
221+
};
222+
223+
/**
224+
* An expectation for checking that an element is present on the DOM
225+
* of a page. This does not necessarily mean that the element is visible.
226+
*
227+
* @param {!ElementFinder} elementFinder The element to check
228+
*
229+
* @return {!function} An expected condition that returns a promise
230+
* representing whether the element is present.
231+
*/
232+
ExpectedConditions.prototype.presenceOf = function(elementFinder) {
233+
return elementFinder.isPresent.bind(elementFinder);
234+
};
235+
236+
/**
237+
* An expectation for checking that an element is not attached to the DOM
238+
* of a page.
239+
*
240+
* @param {!ElementFinder} elementFinder The element to check
241+
*
242+
* @return {!function} An expected condition that returns a promise
243+
* representing whether the element is stale.
244+
*/
245+
ExpectedConditions.prototype.stalenessOf = function(elementFinder) {
246+
return this.not(this.presenceOf(elementFinder));
247+
};
248+
249+
/**
250+
* An expectation for checking that an element is present on the DOM of a
251+
* page and visible. Visibility means that the element is not only displayed
252+
* but also has a height and width that is greater than 0.
253+
*
254+
* @param {!ElementFinder} elementFinder The element to check
255+
*
256+
* @return {!function} An expected condition that returns a promise
257+
* representing whether the element is visible.
258+
*/
259+
ExpectedConditions.prototype.visibilityOf = function(elementFinder) {
260+
return this.and(
261+
this.presenceOf(elementFinder),
262+
elementFinder.isDisplayed.bind(elementFinder));
263+
};
264+
265+
/**
266+
* An expectation for checking that an element is either invisible or not
267+
* present on the DOM.
268+
*
269+
* @param {!ElementFinder} elementFinder The element to check
270+
*
271+
* @return {!function} An expected condition that returns a promise
272+
* representing whether the element is invisible.
273+
*/
274+
ExpectedConditions.prototype.invisibilityOf = function(elementFinder) {
275+
return this.not(this.visibilityOf(elementFinder));
276+
};
277+
278+
/**
279+
* An expectation for checking the selection is selected.
280+
*
281+
* @param {!ElementFinder} elementFinder The element to check
282+
*
283+
* @return {!function} An expected condition that returns a promise
284+
* representing whether the element is selected.
285+
*/
286+
ExpectedConditions.prototype.elementToBeSelected = function(elementFinder) {
287+
return this.and(
288+
this.presenceOf(elementFinder),
289+
elementFinder.isSelected.bind(elementFinder));
290+
};
291+
292+
module.exports = ExpectedConditions;
293+

lib/protractor.js

+6
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ var ElementFinder = require('./element').ElementFinder;
77

88
var clientSideScripts = require('./clientsidescripts.js');
99
var ProtractorBy = require('./locators.js').ProtractorBy;
10+
var ExpectedConditions = require('./expectedConditions.js');
1011

1112
/* global angular */
1213

@@ -36,6 +37,11 @@ exports.ElementFinder = ElementFinder;
3637
*/
3738
exports.ElementArrayFinder = ElementArrayFinder;
3839

40+
/**
41+
* @type {ExpectedConditions}
42+
*/
43+
exports.ExpectedConditions = new ExpectedConditions();
44+
3945
/**
4046
* Mix a function from one object onto another. The function will still be
4147
* called in the context of the original object.

spec/basic/elements_spec.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -221,16 +221,16 @@ describe('ElementArrayFinder', function() {
221221
var checkboxesElms = $$('#checkboxes input');
222222
browser.get('index.html');
223223

224-
expect(checkboxesElms.isSelected()).toEqual([true, false, false]);
224+
expect(checkboxesElms.isSelected()).toEqual([true, false, false, false]);
225225
checkboxesElms.click();
226-
expect(checkboxesElms.isSelected()).toEqual([false, true, true]);
226+
expect(checkboxesElms.isSelected()).toEqual([false, true, true, true]);
227227
});
228228

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

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

242242
var elem = $$('#checkboxes input').filter(function(elem, index) {
243-
return index == 1 || index == 2;
243+
return index == 2 || index == 3;
244244
}).last();
245245
elem.click();
246246
expect($('#letterlist').getText()).toEqual('x');

0 commit comments

Comments
 (0)