From cc4f7b55e1fe46fcef1b8c3ca39d702a32ee6d82 Mon Sep 17 00:00:00 2001 From: Julie Date: Thu, 23 Jan 2014 01:39:27 -0800 Subject: [PATCH] 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. --- lib/protractor.js | 51 +++++++++++++++++++++------------ spec/basic/findelements_spec.js | 41 ++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 19 deletions(-) diff --git a/lib/protractor.js b/lib/protractor.js index cff083ac5..9efbba248 100644 --- a/lib/protractor.js +++ b/lib/protractor.js @@ -49,17 +49,29 @@ var mixin = function(to, from, fnName, setupFn) { * * @private * @param {Protractor} ptor + * @param {=Array.} 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; @@ -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; @@ -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); } @@ -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); }); @@ -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); diff --git a/spec/basic/findelements_spec.js b/spec/basic/findelements_spec.js index f8caac61a..79d5f935b 100644 --- a/spec/basic/findelements_spec.js +++ b/spec/basic/findelements_spec.js @@ -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) { @@ -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'))).