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

Commit

Permalink
fix(locators): locators should use the root element provided in config
Browse files Browse the repository at this point in the history
Previously, locators used 'document' as the root for their search. After this
change, they will use the root element provided in the config file -
`config.rootElement`. This will make sure behavior is correct if there are
multiple angular apps on one page, and also enables the getTestability
path, because that requires a root element under an ng-app.
  • Loading branch information
juliemr committed Aug 28, 2014
1 parent 1ef8c82 commit 9a8f45a
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 60 deletions.
84 changes: 54 additions & 30 deletions lib/clientsidescripts.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,13 @@ functions.waitForAngular = function(selector, callback) {
* @param {string} binding The binding, e.g. {{cat.name}}.
* @param {boolean} exactMatch Whether the binding needs to be matched exactly
* @param {Element} using The scope of the search.
* @param {string} rootSelector The selector to use for the root app element.
*
* @return {Array.<Element>} The elements containing the binding.
*/
functions.findBindings = function(binding, exactMatch, using) {
using = using || document;
functions.findBindings = function(binding, exactMatch, using, rootSelector) {
rootSelector = rootSelector || 'body';
using = using || document.querySelector(rootSelector);
if (angular.getTestability) {
return angular.getTestability(using).
findBindings(using, binding, exactMatch);
Expand Down Expand Up @@ -83,13 +85,15 @@ functions.findBindings = function(binding, exactMatch, using) {
*
* @param {string} repeater The text of the repeater, e.g. 'cat in cats'.
* @param {number} index The row index.
* @param {Element} using The scope of the search. Defaults to 'document'.
* @param {Element} using The scope of the search.
* @param {string} rootSelector The selector to use for the root app element.
*
* @return {Array.<Element>} The row of the repeater, or an array of elements
* in the first row in the case of ng-repeat-start.
*/
functions.findRepeaterRows = function(repeater, index, using) {
using = using || document;
functions.findRepeaterRows = function(repeater, index, using, rootSelector) {
rootSelector = rootSelector || 'body';
using = using || document.querySelector(rootSelector);

var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:'];
var rows = [];
Expand Down Expand Up @@ -132,12 +136,14 @@ functions.findBindings = function(binding, exactMatch, using) {
* Find all rows of an ng-repeat.
*
* @param {string} repeater The text of the repeater, e.g. 'cat in cats'.
* @param {Element} using The scope of the search. Defaults to 'document'.
* @param {Element} using The scope of the search.
* @param {string} rootSelector The selector to use for the root app element.
*
* @return {Array.<Element>} All rows of the repeater.
*/
functions.findAllRepeaterRows = function(repeater, using) {
using = using || document;
functions.findAllRepeaterRows = function(repeater, using, rootSelector) {
rootSelector = rootSelector || 'body';
using = using || document.querySelector(rootSelector);

var rows = [];
var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:'];
Expand Down Expand Up @@ -177,13 +183,15 @@ functions.findBindings = function(binding, exactMatch, using) {
* @param {string} repeater The text of the repeater, e.g. 'cat in cats'.
* @param {number} index The row index.
* @param {string} binding The column binding, e.g. '{{cat.name}}'.
* @param {Element} using The scope of the search. Defaults to 'document'.
* @param {Element} using The scope of the search.
* @param {string} rootSelector The selector to use for the root app element.
*
* @return {Array.<Element>} The element in an array.
*/
functions.findRepeaterElement = function(repeater, index, binding, using) {
functions.findRepeaterElement = function(repeater, index, binding, using, rootSelector) {
var matches = [];
using = using || document;
rootSelector = rootSelector || 'body';
using = using || document.querySelector(rootSelector);

var rows = [];
var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:'];
Expand Down Expand Up @@ -272,13 +280,15 @@ functions.findRepeaterElement = function(repeater, index, binding, using) {
*
* @param {string} repeater The text of the repeater, e.g. 'cat in cats'.
* @param {string} binding The column binding, e.g. '{{cat.name}}'.
* @param {Element} using The scope of the search. Defaults to 'document'.
* @param {Element} using The scope of the search.
* @param {string} rootSelector The selector to use for the root app element.
*
* @return {Array.<Element>} The elements in the column.
*/
functions.findRepeaterColumn = function(repeater, binding, using) {
functions.findRepeaterColumn = function(repeater, binding, using, rootSelector) {
var matches = [];
using = using || document;
rootSelector = rootSelector || 'body';
using = using || document.querySelector(rootSelector);

var rows = [];
var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:'];
Expand Down Expand Up @@ -364,12 +374,15 @@ functions.findRepeaterColumn = function(repeater, binding, using) {
* Find elements by model name.
*
* @param {string} model The model name.
* @param {Element} using The scope of the search. Defaults to 'document'.
* @param {Element} using The scope of the search.
* @param {string} rootSelector The selector to use for the root app element.
*
* @return {Array.<Element>} The matching elements.
*/
functions.findByModel = function(model, using) {
using = using || document;
functions.findByModel = function(model, using, rootSelector) {
rootSelector = rootSelector || 'body';
using = using || document.querySelector(rootSelector);

if (angular.getTestability) {
return angular.getTestability(using).
findModels(using, model);
Expand All @@ -389,12 +402,15 @@ functions.findByModel = function(model, using) {
*
* @param {string} optionsDescriptor The descriptor for the option
* (i.e. fruit for fruit in fruits).
* @param {Element} using The scope of the search. Defaults to 'document'.
* @param {Element} using The scope of the search.
* @param {string} rootSelector The selector to use for the root app element.
*
* @return {Array.<Element>} The matching elements.
*/
functions.findByOptions = function(optionsDescriptor, using) {
using = using || document;
functions.findByOptions = function(optionsDescriptor, using, rootSelector) {
rootSelector = rootSelector || 'body';
using = using || document.querySelector(rootSelector);

var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:'];
for (var p = 0; p < prefixes.length; ++p) {
var selector = '[' + prefixes[p] + 'options="' + optionsDescriptor + '"] option';
Expand All @@ -409,12 +425,15 @@ functions.findByOptions = function(optionsDescriptor, using) {
* Find buttons by textual content.
*
* @param {string} searchText The exact text to match.
* @param {Element} using The scope of the search. Defaults to 'document'.
* @param {Element} using The scope of the search.
* @param {string} rootSelector The selector to use for the root app element.
*
* @return {Array.<Element>} The matching elements.
*/
functions.findByButtonText = function(searchText, using) {
using = using || document;
functions.findByButtonText = function(searchText, using, rootSelector) {
rootSelector = rootSelector || 'body';
using = using || document.querySelector(rootSelector);

var elements = using.querySelectorAll('button, input[type="button"], input[type="submit"]');
var matches = [];
for (var i = 0; i < elements.length; ++i) {
Expand All @@ -437,12 +456,15 @@ functions.findByButtonText = function(searchText, using) {
* Find buttons by textual content.
*
* @param {string} searchText The exact text to match.
* @param {Element} using The scope of the search. Defaults to 'document'.
* @param {Element} using The scope of the search.
* @param {string} rootSelector The selector to use for the root app element.
*
* @return {Array.<Element>} The matching elements.
*/
functions.findByPartialButtonText = function(searchText, using) {
using = using || document;
functions.findByPartialButtonText = function(searchText, using, rootSelector) {
rootSelector = rootSelector || 'body';
using = using || document.querySelector(rootSelector);

var elements = using.querySelectorAll('button, input[type="button"], input[type="submit"]');
var matches = [];
for (var i = 0; i < elements.length; ++i) {
Expand All @@ -466,12 +488,15 @@ functions.findByPartialButtonText = function(searchText, using) {
*
* @param {string} cssSelector The css selector to match.
* @param {string} searchText The exact text to match.
* @param {Element} using The scope of the search. Defaults to 'document'.
* @param {Element} using The scope of the search.
* @param {string} rootSelector The selector to use for the root app element.
*
* @return {Array.<Element>} An array of matching elements.
*/
functions.findByCssContainingText = function(cssSelector, searchText, using) {
var using = using || document;
functions.findByCssContainingText = function(cssSelector, searchText, using, rootSelector) {
rootSelector = rootSelector || 'body';
using = using || document.querySelector(rootSelector);

var elements = using.querySelectorAll(cssSelector);
var matches = [];
for (var i = 0; i < elements.length; ++i) {
Expand Down Expand Up @@ -528,7 +553,6 @@ functions.testForAngular = function(attempts, asyncCallback) {
* @return {?Object} The result of the evaluation.
*/
functions.evaluate = function(element, expression) {

return angular.element(element).scope().$eval(expression);
};

Expand Down
61 changes: 33 additions & 28 deletions lib/locators.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,12 @@ util.inherits(ProtractorBy, WebdriverBy);
*
* @example
* // Add the custom locator.
* by.addLocator('buttonTextSimple', function(buttonText, opt_parentElement) {
* by.addLocator('buttonTextSimple',
* function(buttonText, opt_parentElement, opt_rootSelector) {
* // This function will be serialized as a string and will execute in the
* // browser. The first argument is the text for the button. The second
* // argument is the parent element, if any.
* var using = opt_parentElement || document,
* var using = opt_parentElement || document.querySelector(opt_rootSelector),
* buttons = using.querySelectorAll('button');
*
* // Return an array of buttons with the text.
Expand All @@ -50,18 +51,20 @@ util.inherits(ProtractorBy, WebdriverBy);
* @param {Function|string} script A script to be run in the context of
* the browser. This script will be passed an array of arguments
* that contains any args passed into the locator followed by the
* element scoping the search. It should return an array of elements.
* element scoping the search and the css selector for the root angular
* element. It should return an array of elements.
*/
ProtractorBy.prototype.addLocator = function(name, script) {
this[name] = function() {
var locatorArguments = arguments;
return {
findElementsOverride: function(driver, using) {
findElementsOverride: function(driver, using, rootSelector) {
var findElementArguments = [script];
for (var i = 0; i < locatorArguments.length; i++) {
findElementArguments.push(locatorArguments[i]);
}
findElementArguments.push(using);
findElementArguments.push(rootSelector);

return driver.findElements(
webdriver.By.js.apply(webdriver.By, findElementArguments));
Expand Down Expand Up @@ -93,10 +96,10 @@ ProtractorBy.prototype.addLocator = function(name, script) {
*/
ProtractorBy.prototype.binding = function(bindingDescriptor) {
return {
findElementsOverride: function(driver, using) {
findElementsOverride: function(driver, using, rootSelector) {
return driver.findElements(
webdriver.By.js(clientSideScripts.findBindings,
bindingDescriptor, false, using));
bindingDescriptor, false, using, rootSelector));
},
toString: function toString() {
return 'by.binding("' + bindingDescriptor + '")';
Expand Down Expand Up @@ -125,10 +128,10 @@ ProtractorBy.prototype.binding = function(bindingDescriptor) {
*/
ProtractorBy.prototype.exactBinding = function(bindingDescriptor) {
return {
findElementsOverride: function(driver, using) {
findElementsOverride: function(driver, using, rootSelector) {
return driver.findElements(
webdriver.By.js(clientSideScripts.findBindings,
bindingDescriptor, true, using));
bindingDescriptor, true, using, rootSelector));
},
toString: function toString() {
return 'by.exactBinding("' + bindingDescriptor + '")';
Expand All @@ -152,9 +155,10 @@ ProtractorBy.prototype.exactBinding = function(bindingDescriptor) {
*/
ProtractorBy.prototype.model = function(model) {
return {
findElementsOverride: function(driver, using) {
findElementsOverride: function(driver, using, rootSelector) {
return driver.findElements(
webdriver.By.js(clientSideScripts.findByModel, model, using));
webdriver.By.js(
clientSideScripts.findByModel, model, using, rootSelector));
},
toString: function toString() {
return 'by.model("' + model + '")';
Expand All @@ -176,10 +180,10 @@ ProtractorBy.prototype.model = function(model) {
*/
ProtractorBy.prototype.buttonText = function(searchText) {
return {
findElementsOverride: function(driver, using) {
findElementsOverride: function(driver, using, rootSelector) {
return driver.findElements(
webdriver.By.js(clientSideScripts.findByButtonText,
searchText, using));
searchText, using, rootSelector));
},
toString: function toString() {
return 'by.buttonText("' + searchText + '")';
Expand All @@ -201,10 +205,10 @@ ProtractorBy.prototype.buttonText = function(searchText) {
*/
ProtractorBy.prototype.partialButtonText = function(searchText) {
return {
findElementsOverride: function(driver, using) {
findElementsOverride: function(driver, using, rootSelector) {
return driver.findElements(
webdriver.By.js(clientSideScripts.findByPartialButtonText,
searchText, using));
searchText, using, rootSelector));
},
toString: function toString() {
return 'by.partialButtonText("' + searchText + '")';
Expand Down Expand Up @@ -266,30 +270,30 @@ ProtractorBy.prototype.partialButtonText = function(searchText) {
*/
ProtractorBy.prototype.repeater = function(repeatDescriptor) {
return {
findElementsOverride: function(driver, using) {
findElementsOverride: function(driver, using, rootSelector) {
return driver.findElements(
webdriver.By.js(clientSideScripts.findAllRepeaterRows,
repeatDescriptor, using));
repeatDescriptor, using, rootSelector));
},
toString: function toString() {
return 'by.repeater("' + repeatDescriptor + '")';
},
row: function(index) {
return {
findElementsOverride: function(driver, using) {
findElementsOverride: function(driver, using, rootSelector) {
return driver.findElements(
webdriver.By.js(clientSideScripts.findRepeaterRows,
repeatDescriptor, index, using));
repeatDescriptor, index, using, rootSelector));
},
toString: function toString() {
return 'by.repeater(' + repeatDescriptor + '").row("' + index + '")"';
},
column: function(binding) {
return {
findElementsOverride: function(driver, using) {
findElementsOverride: function(driver, using, rootSelector) {
return driver.findElements(
webdriver.By.js(clientSideScripts.findRepeaterElement,
repeatDescriptor, index, binding, using));
repeatDescriptor, index, binding, using, rootSelector));
},
toString: function toString() {
return 'by.repeater("' + repeatDescriptor + '").row("' + index +
Expand All @@ -301,21 +305,21 @@ ProtractorBy.prototype.repeater = function(repeatDescriptor) {
},
column: function(binding) {
return {
findElementsOverride: function(driver, using) {
findElementsOverride: function(driver, using, rootSelector) {
return driver.findElements(
webdriver.By.js(clientSideScripts.findRepeaterColumn,
repeatDescriptor, binding, using));
repeatDescriptor, binding, using, rootSelector));
},
toString: function toString() {
return 'by.repeater("' + repeatDescriptor + '").column("' +
binding + '")';
},
row: function(index) {
return {
findElementsOverride: function(driver, using) {
findElementsOverride: function(driver, using, rootSelector) {
return driver.findElements(
webdriver.By.js(clientSideScripts.findRepeaterElement,
repeatDescriptor, index, binding, using));
repeatDescriptor, index, binding, using, rootSelector));
},
toString: function toString() {
return 'by.repeater("' + repeatDescriptor + '").column("' +
Expand Down Expand Up @@ -343,10 +347,10 @@ ProtractorBy.prototype.repeater = function(repeatDescriptor) {
*/
ProtractorBy.prototype.cssContainingText = function(cssSelector, searchText) {
return {
findElementsOverride: function(driver, using) {
findElementsOverride: function(driver, using, rootSelector) {
return driver.findElements(
webdriver.By.js(clientSideScripts.findByCssContainingText,
cssSelector, searchText, using));
cssSelector, searchText, using, rootSelector));
},
toString: function toString() {
return 'by.cssContainingText("' + cssSelector + '", "' + searchText + '")';
Expand Down Expand Up @@ -374,9 +378,10 @@ ProtractorBy.prototype.cssContainingText = function(cssSelector, searchText) {
*/
ProtractorBy.prototype.options = function(optionsDescriptor) {
return {
findElementsOverride: function(driver, using) {
findElementsOverride: function(driver, using, rootSelector) {
return driver.findElements(
webdriver.By.js(clientSideScripts.findByOptions, optionsDescriptor, using));
webdriver.By.js(clientSideScripts.findByOptions, optionsDescriptor,
using, rootSelector));
},
toString: function toString() {
return 'by.option("' + optionsDescriptor + '")';
Expand Down
Loading

0 comments on commit 9a8f45a

Please sign in to comment.