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

Commit

Permalink
feat(element): allow chaining of element finders with element().eleme…
Browse files Browse the repository at this point in the history
…nt()...

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.
  • Loading branch information
juliemr committed Jan 23, 2014
1 parent 0550e37 commit cc4f7b5
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 19 deletions.
51 changes: 32 additions & 19 deletions lib/protractor.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,29 @@ var mixin = function(to, from, fnName, setupFn) {
*
* @private
* @param {Protractor} ptor
* @param {=Array.<webdriver.Locator>} opt_usingChain
* @return {function(webdriver.Locator): ElementFinder}
*/
var buildElementHelper = function(ptor) {
var buildElementHelper = function(ptor, opt_usingChain) {
var usingChain = opt_usingChain || [];
var using = function() {
var base = ptor;
for (var i = 0; i < usingChain.length; ++i) {
base = base.findElement(usingChain[i]);
}
return base;
}

var element = function(locator) {
var elementFinder = {};

var webElementFns = WEB_ELEMENT_FUNCTIONS.concat(
['findElements', 'isElementPresent', 'evaluate', '$$']);
webElementFns.forEach(function(fnName) {
elementFinder[fnName] = function() {
var args = arguments;
return ptor.findElement(locator).then(function(element) {

return using().findElement(locator).then(function(element) {
return element[fnName].apply(element, args);
}, function(error) {
throw error;
Expand All @@ -70,22 +82,23 @@ var buildElementHelper = function(ptor) {
// This is a special case since it doesn't return a promise, instead it
// returns a WebElement.
elementFinder.findElement = function(subLocator) {
return ptor.findElement(locator).findElement(subLocator);
};

// This is a special case since it doesn't return a promise, instead it
// returns a WebElement.
elementFinder.$ = function(cssSelector) {
return ptor.findElement(locator).
findElement(webdriver.By.css(cssSelector));
return using().findElement(locator).findElement(subLocator);
};

elementFinder.find = function() {
return ptor.findElement(locator);
return using().findElement(locator);
};

elementFinder.isPresent = function() {
return ptor.isElementPresent(locator);
return using().isElementPresent(locator);
};

elementFinder.element =
buildElementHelper(ptor, usingChain.concat(locator));

elementFinder.$ = function(cssSelector) {
return buildElementHelper(ptor, usingChain.concat(locator))(
webdriver.By.css(cssSelector));
};

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

elementArrayFinder.count = function() {
return ptor.findElements(locator).then(function(arr) {
return using().findElements(locator).then(function(arr) {
return arr.length;
});
};

elementArrayFinder.get = function(index) {
var id = ptor.findElements(locator).then(function(arr) {
var id = using().findElements(locator).then(function(arr) {
return arr[index];
});
return ptor.wrapWebElement(new webdriver.WebElement(ptor.driver, id));
};

elementArrayFinder.first = function() {
var id = ptor.findElements(locator).then(function(arr) {
var id = using().findElements(locator).then(function(arr) {
if (!arr.length) {
throw new Error('No element found using locator: ' + locator.message);
}
Expand All @@ -121,18 +134,18 @@ var buildElementHelper = function(ptor) {
};

elementArrayFinder.last = function() {
var id = ptor.findElements(locator).then(function(arr) {
var id = using().findElements(locator).then(function(arr) {
return arr[arr.length - 1];
});
return ptor.wrapWebElement(new webdriver.WebElement(ptor.driver, id));
};

elementArrayFinder.then = function(fn) {
return ptor.findElements(locator).then(fn);
return using().findElements(locator).then(fn);
};

elementArrayFinder.each = function(fn) {
ptor.findElements(locator).then(function(arr) {
using().findElements(locator).then(function(arr) {
arr.forEach(function(webElem) {
fn(webElem);
});
Expand Down Expand Up @@ -168,7 +181,7 @@ var buildElementHelper = function(ptor) {
* of values returned by the map function.
*/
elementArrayFinder.map = function(mapFn) {
return ptor.findElements(locator).then(function(arr) {
return using().findElements(locator).then(function(arr) {
var list = [];
arr.forEach(function(webElem, index) {
var mapResult = mapFn(webElem, index);
Expand Down
41 changes: 41 additions & 0 deletions spec/basic/findelements_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,32 @@ describe('chaining find elements', function() {
toEqual('Inner: inner');
});

it('should wait to grab the chained WebElement until a method is called',
function() {
browser.driver.get('about:blank');

// These should throw no error before a page is loaded.
var reused = element(by.id('baz')).
element(by.binding('item.reusedBinding'));

browser.get('index.html#/conflict');

expect(reused.getText()).toEqual('Inner: inner');
expect(reused.isPresent()).toBe(true);
});

it('should chain deeper than 2', function() {
browser.driver.get('about:blank');

// These should throw no error before a page is loaded.
var reused = element(by.css('body')).element(by.id('baz')).
element(by.binding('item.reusedBinding'));

browser.get('index.html#/conflict');

expect(reused.getText()).toEqual('Inner: inner');
})

it('should find multiple elements scoped properly with chaining',
function() {
element.all(by.binding('item')).then(function(elems) {
Expand All @@ -337,6 +363,21 @@ describe('chaining find elements', function() {
});
});

it('should wait to grab multiple chained elements',
function() {
browser.driver.get('about:blank');

// These should throw no error before a page is loaded.
var reused = element(by.id('baz')).
element.all(by.binding('item'));

browser.get('index.html#/conflict');

expect(reused.count()).toEqual(2);
expect(reused.get(0).getText()).toEqual('Inner: inner');
expect(reused.last().getText()).toEqual('Inner other: innerbarbaz');
});

it('should determine element presence properly with chaining', function() {
expect(element(by.id('baz')).
isElementPresent(by.binding('item.reusedBinding'))).
Expand Down

0 comments on commit cc4f7b5

Please sign in to comment.