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

Commit cc4f7b5

Browse files
committed
feat(element): allow chaining of element finders with element().element()...
Chaining calls to element will now build a scoped element finder. No webdriver functions will be called until a method (such as getText) is called on the final element. Example: var elem = element(by.id('outer')).element(by.css('inner')); browser.get('myPage'); elem.click(); Closes #340.
1 parent 0550e37 commit cc4f7b5

File tree

2 files changed

+73
-19
lines changed

2 files changed

+73
-19
lines changed

lib/protractor.js

+32-19
Original file line numberDiff line numberDiff line change
@@ -49,17 +49,29 @@ var mixin = function(to, from, fnName, setupFn) {
4949
*
5050
* @private
5151
* @param {Protractor} ptor
52+
* @param {=Array.<webdriver.Locator>} opt_usingChain
5253
* @return {function(webdriver.Locator): ElementFinder}
5354
*/
54-
var buildElementHelper = function(ptor) {
55+
var buildElementHelper = function(ptor, opt_usingChain) {
56+
var usingChain = opt_usingChain || [];
57+
var using = function() {
58+
var base = ptor;
59+
for (var i = 0; i < usingChain.length; ++i) {
60+
base = base.findElement(usingChain[i]);
61+
}
62+
return base;
63+
}
64+
5565
var element = function(locator) {
5666
var elementFinder = {};
67+
5768
var webElementFns = WEB_ELEMENT_FUNCTIONS.concat(
5869
['findElements', 'isElementPresent', 'evaluate', '$$']);
5970
webElementFns.forEach(function(fnName) {
6071
elementFinder[fnName] = function() {
6172
var args = arguments;
62-
return ptor.findElement(locator).then(function(element) {
73+
74+
return using().findElement(locator).then(function(element) {
6375
return element[fnName].apply(element, args);
6476
}, function(error) {
6577
throw error;
@@ -70,22 +82,23 @@ var buildElementHelper = function(ptor) {
7082
// This is a special case since it doesn't return a promise, instead it
7183
// returns a WebElement.
7284
elementFinder.findElement = function(subLocator) {
73-
return ptor.findElement(locator).findElement(subLocator);
74-
};
75-
76-
// This is a special case since it doesn't return a promise, instead it
77-
// returns a WebElement.
78-
elementFinder.$ = function(cssSelector) {
79-
return ptor.findElement(locator).
80-
findElement(webdriver.By.css(cssSelector));
85+
return using().findElement(locator).findElement(subLocator);
8186
};
8287

8388
elementFinder.find = function() {
84-
return ptor.findElement(locator);
89+
return using().findElement(locator);
8590
};
8691

8792
elementFinder.isPresent = function() {
88-
return ptor.isElementPresent(locator);
93+
return using().isElementPresent(locator);
94+
};
95+
96+
elementFinder.element =
97+
buildElementHelper(ptor, usingChain.concat(locator));
98+
99+
elementFinder.$ = function(cssSelector) {
100+
return buildElementHelper(ptor, usingChain.concat(locator))(
101+
webdriver.By.css(cssSelector));
89102
};
90103

91104
return elementFinder;
@@ -98,20 +111,20 @@ var buildElementHelper = function(ptor) {
98111
var elementArrayFinder = {};
99112

100113
elementArrayFinder.count = function() {
101-
return ptor.findElements(locator).then(function(arr) {
114+
return using().findElements(locator).then(function(arr) {
102115
return arr.length;
103116
});
104117
};
105118

106119
elementArrayFinder.get = function(index) {
107-
var id = ptor.findElements(locator).then(function(arr) {
120+
var id = using().findElements(locator).then(function(arr) {
108121
return arr[index];
109122
});
110123
return ptor.wrapWebElement(new webdriver.WebElement(ptor.driver, id));
111124
};
112125

113126
elementArrayFinder.first = function() {
114-
var id = ptor.findElements(locator).then(function(arr) {
127+
var id = using().findElements(locator).then(function(arr) {
115128
if (!arr.length) {
116129
throw new Error('No element found using locator: ' + locator.message);
117130
}
@@ -121,18 +134,18 @@ var buildElementHelper = function(ptor) {
121134
};
122135

123136
elementArrayFinder.last = function() {
124-
var id = ptor.findElements(locator).then(function(arr) {
137+
var id = using().findElements(locator).then(function(arr) {
125138
return arr[arr.length - 1];
126139
});
127140
return ptor.wrapWebElement(new webdriver.WebElement(ptor.driver, id));
128141
};
129142

130143
elementArrayFinder.then = function(fn) {
131-
return ptor.findElements(locator).then(fn);
144+
return using().findElements(locator).then(fn);
132145
};
133146

134147
elementArrayFinder.each = function(fn) {
135-
ptor.findElements(locator).then(function(arr) {
148+
using().findElements(locator).then(function(arr) {
136149
arr.forEach(function(webElem) {
137150
fn(webElem);
138151
});
@@ -168,7 +181,7 @@ var buildElementHelper = function(ptor) {
168181
* of values returned by the map function.
169182
*/
170183
elementArrayFinder.map = function(mapFn) {
171-
return ptor.findElements(locator).then(function(arr) {
184+
return using().findElements(locator).then(function(arr) {
172185
var list = [];
173186
arr.forEach(function(webElem, index) {
174187
var mapResult = mapFn(webElem, index);

spec/basic/findelements_spec.js

+41
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,32 @@ describe('chaining find elements', function() {
324324
toEqual('Inner: inner');
325325
});
326326

327+
it('should wait to grab the chained WebElement until a method is called',
328+
function() {
329+
browser.driver.get('about:blank');
330+
331+
// These should throw no error before a page is loaded.
332+
var reused = element(by.id('baz')).
333+
element(by.binding('item.reusedBinding'));
334+
335+
browser.get('index.html#/conflict');
336+
337+
expect(reused.getText()).toEqual('Inner: inner');
338+
expect(reused.isPresent()).toBe(true);
339+
});
340+
341+
it('should chain deeper than 2', function() {
342+
browser.driver.get('about:blank');
343+
344+
// These should throw no error before a page is loaded.
345+
var reused = element(by.css('body')).element(by.id('baz')).
346+
element(by.binding('item.reusedBinding'));
347+
348+
browser.get('index.html#/conflict');
349+
350+
expect(reused.getText()).toEqual('Inner: inner');
351+
})
352+
327353
it('should find multiple elements scoped properly with chaining',
328354
function() {
329355
element.all(by.binding('item')).then(function(elems) {
@@ -337,6 +363,21 @@ describe('chaining find elements', function() {
337363
});
338364
});
339365

366+
it('should wait to grab multiple chained elements',
367+
function() {
368+
browser.driver.get('about:blank');
369+
370+
// These should throw no error before a page is loaded.
371+
var reused = element(by.id('baz')).
372+
element.all(by.binding('item'));
373+
374+
browser.get('index.html#/conflict');
375+
376+
expect(reused.count()).toEqual(2);
377+
expect(reused.get(0).getText()).toEqual('Inner: inner');
378+
expect(reused.last().getText()).toEqual('Inner other: innerbarbaz');
379+
});
380+
340381
it('should determine element presence properly with chaining', function() {
341382
expect(element(by.id('baz')).
342383
isElementPresent(by.binding('item.reusedBinding'))).

0 commit comments

Comments
 (0)