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

Commit 9a8f45a

Browse files
committed
fix(locators): locators should use the root element provided in config
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.
1 parent 1ef8c82 commit 9a8f45a

File tree

3 files changed

+89
-60
lines changed

3 files changed

+89
-60
lines changed

lib/clientsidescripts.js

+54-30
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,13 @@ functions.waitForAngular = function(selector, callback) {
4545
* @param {string} binding The binding, e.g. {{cat.name}}.
4646
* @param {boolean} exactMatch Whether the binding needs to be matched exactly
4747
* @param {Element} using The scope of the search.
48+
* @param {string} rootSelector The selector to use for the root app element.
4849
*
4950
* @return {Array.<Element>} The elements containing the binding.
5051
*/
51-
functions.findBindings = function(binding, exactMatch, using) {
52-
using = using || document;
52+
functions.findBindings = function(binding, exactMatch, using, rootSelector) {
53+
rootSelector = rootSelector || 'body';
54+
using = using || document.querySelector(rootSelector);
5355
if (angular.getTestability) {
5456
return angular.getTestability(using).
5557
findBindings(using, binding, exactMatch);
@@ -83,13 +85,15 @@ functions.findBindings = function(binding, exactMatch, using) {
8385
*
8486
* @param {string} repeater The text of the repeater, e.g. 'cat in cats'.
8587
* @param {number} index The row index.
86-
* @param {Element} using The scope of the search. Defaults to 'document'.
88+
* @param {Element} using The scope of the search.
89+
* @param {string} rootSelector The selector to use for the root app element.
8790
*
8891
* @return {Array.<Element>} The row of the repeater, or an array of elements
8992
* in the first row in the case of ng-repeat-start.
9093
*/
91-
functions.findRepeaterRows = function(repeater, index, using) {
92-
using = using || document;
94+
functions.findRepeaterRows = function(repeater, index, using, rootSelector) {
95+
rootSelector = rootSelector || 'body';
96+
using = using || document.querySelector(rootSelector);
9397

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

142148
var rows = [];
143149
var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:'];
@@ -177,13 +183,15 @@ functions.findBindings = function(binding, exactMatch, using) {
177183
* @param {string} repeater The text of the repeater, e.g. 'cat in cats'.
178184
* @param {number} index The row index.
179185
* @param {string} binding The column binding, e.g. '{{cat.name}}'.
180-
* @param {Element} using The scope of the search. Defaults to 'document'.
186+
* @param {Element} using The scope of the search.
187+
* @param {string} rootSelector The selector to use for the root app element.
181188
*
182189
* @return {Array.<Element>} The element in an array.
183190
*/
184-
functions.findRepeaterElement = function(repeater, index, binding, using) {
191+
functions.findRepeaterElement = function(repeater, index, binding, using, rootSelector) {
185192
var matches = [];
186-
using = using || document;
193+
rootSelector = rootSelector || 'body';
194+
using = using || document.querySelector(rootSelector);
187195

188196
var rows = [];
189197
var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:'];
@@ -272,13 +280,15 @@ functions.findRepeaterElement = function(repeater, index, binding, using) {
272280
*
273281
* @param {string} repeater The text of the repeater, e.g. 'cat in cats'.
274282
* @param {string} binding The column binding, e.g. '{{cat.name}}'.
275-
* @param {Element} using The scope of the search. Defaults to 'document'.
283+
* @param {Element} using The scope of the search.
284+
* @param {string} rootSelector The selector to use for the root app element.
276285
*
277286
* @return {Array.<Element>} The elements in the column.
278287
*/
279-
functions.findRepeaterColumn = function(repeater, binding, using) {
288+
functions.findRepeaterColumn = function(repeater, binding, using, rootSelector) {
280289
var matches = [];
281-
using = using || document;
290+
rootSelector = rootSelector || 'body';
291+
using = using || document.querySelector(rootSelector);
282292

283293
var rows = [];
284294
var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:'];
@@ -364,12 +374,15 @@ functions.findRepeaterColumn = function(repeater, binding, using) {
364374
* Find elements by model name.
365375
*
366376
* @param {string} model The model name.
367-
* @param {Element} using The scope of the search. Defaults to 'document'.
377+
* @param {Element} using The scope of the search.
378+
* @param {string} rootSelector The selector to use for the root app element.
368379
*
369380
* @return {Array.<Element>} The matching elements.
370381
*/
371-
functions.findByModel = function(model, using) {
372-
using = using || document;
382+
functions.findByModel = function(model, using, rootSelector) {
383+
rootSelector = rootSelector || 'body';
384+
using = using || document.querySelector(rootSelector);
385+
373386
if (angular.getTestability) {
374387
return angular.getTestability(using).
375388
findModels(using, model);
@@ -389,12 +402,15 @@ functions.findByModel = function(model, using) {
389402
*
390403
* @param {string} optionsDescriptor The descriptor for the option
391404
* (i.e. fruit for fruit in fruits).
392-
* @param {Element} using The scope of the search. Defaults to 'document'.
405+
* @param {Element} using The scope of the search.
406+
* @param {string} rootSelector The selector to use for the root app element.
393407
*
394408
* @return {Array.<Element>} The matching elements.
395409
*/
396-
functions.findByOptions = function(optionsDescriptor, using) {
397-
using = using || document;
410+
functions.findByOptions = function(optionsDescriptor, using, rootSelector) {
411+
rootSelector = rootSelector || 'body';
412+
using = using || document.querySelector(rootSelector);
413+
398414
var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:'];
399415
for (var p = 0; p < prefixes.length; ++p) {
400416
var selector = '[' + prefixes[p] + 'options="' + optionsDescriptor + '"] option';
@@ -409,12 +425,15 @@ functions.findByOptions = function(optionsDescriptor, using) {
409425
* Find buttons by textual content.
410426
*
411427
* @param {string} searchText The exact text to match.
412-
* @param {Element} using The scope of the search. Defaults to 'document'.
428+
* @param {Element} using The scope of the search.
429+
* @param {string} rootSelector The selector to use for the root app element.
413430
*
414431
* @return {Array.<Element>} The matching elements.
415432
*/
416-
functions.findByButtonText = function(searchText, using) {
417-
using = using || document;
433+
functions.findByButtonText = function(searchText, using, rootSelector) {
434+
rootSelector = rootSelector || 'body';
435+
using = using || document.querySelector(rootSelector);
436+
418437
var elements = using.querySelectorAll('button, input[type="button"], input[type="submit"]');
419438
var matches = [];
420439
for (var i = 0; i < elements.length; ++i) {
@@ -437,12 +456,15 @@ functions.findByButtonText = function(searchText, using) {
437456
* Find buttons by textual content.
438457
*
439458
* @param {string} searchText The exact text to match.
440-
* @param {Element} using The scope of the search. Defaults to 'document'.
459+
* @param {Element} using The scope of the search.
460+
* @param {string} rootSelector The selector to use for the root app element.
441461
*
442462
* @return {Array.<Element>} The matching elements.
443463
*/
444-
functions.findByPartialButtonText = function(searchText, using) {
445-
using = using || document;
464+
functions.findByPartialButtonText = function(searchText, using, rootSelector) {
465+
rootSelector = rootSelector || 'body';
466+
using = using || document.querySelector(rootSelector);
467+
446468
var elements = using.querySelectorAll('button, input[type="button"], input[type="submit"]');
447469
var matches = [];
448470
for (var i = 0; i < elements.length; ++i) {
@@ -466,12 +488,15 @@ functions.findByPartialButtonText = function(searchText, using) {
466488
*
467489
* @param {string} cssSelector The css selector to match.
468490
* @param {string} searchText The exact text to match.
469-
* @param {Element} using The scope of the search. Defaults to 'document'.
491+
* @param {Element} using The scope of the search.
492+
* @param {string} rootSelector The selector to use for the root app element.
470493
*
471494
* @return {Array.<Element>} An array of matching elements.
472495
*/
473-
functions.findByCssContainingText = function(cssSelector, searchText, using) {
474-
var using = using || document;
496+
functions.findByCssContainingText = function(cssSelector, searchText, using, rootSelector) {
497+
rootSelector = rootSelector || 'body';
498+
using = using || document.querySelector(rootSelector);
499+
475500
var elements = using.querySelectorAll(cssSelector);
476501
var matches = [];
477502
for (var i = 0; i < elements.length; ++i) {
@@ -528,7 +553,6 @@ functions.testForAngular = function(attempts, asyncCallback) {
528553
* @return {?Object} The result of the evaluation.
529554
*/
530555
functions.evaluate = function(element, expression) {
531-
532556
return angular.element(element).scope().$eval(expression);
533557
};
534558

lib/locators.js

+33-28
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,12 @@ util.inherits(ProtractorBy, WebdriverBy);
2929
*
3030
* @example
3131
* // Add the custom locator.
32-
* by.addLocator('buttonTextSimple', function(buttonText, opt_parentElement) {
32+
* by.addLocator('buttonTextSimple',
33+
* function(buttonText, opt_parentElement, opt_rootSelector) {
3334
* // This function will be serialized as a string and will execute in the
3435
* // browser. The first argument is the text for the button. The second
3536
* // argument is the parent element, if any.
36-
* var using = opt_parentElement || document,
37+
* var using = opt_parentElement || document.querySelector(opt_rootSelector),
3738
* buttons = using.querySelectorAll('button');
3839
*
3940
* // Return an array of buttons with the text.
@@ -50,18 +51,20 @@ util.inherits(ProtractorBy, WebdriverBy);
5051
* @param {Function|string} script A script to be run in the context of
5152
* the browser. This script will be passed an array of arguments
5253
* that contains any args passed into the locator followed by the
53-
* element scoping the search. It should return an array of elements.
54+
* element scoping the search and the css selector for the root angular
55+
* element. It should return an array of elements.
5456
*/
5557
ProtractorBy.prototype.addLocator = function(name, script) {
5658
this[name] = function() {
5759
var locatorArguments = arguments;
5860
return {
59-
findElementsOverride: function(driver, using) {
61+
findElementsOverride: function(driver, using, rootSelector) {
6062
var findElementArguments = [script];
6163
for (var i = 0; i < locatorArguments.length; i++) {
6264
findElementArguments.push(locatorArguments[i]);
6365
}
6466
findElementArguments.push(using);
67+
findElementArguments.push(rootSelector);
6568

6669
return driver.findElements(
6770
webdriver.By.js.apply(webdriver.By, findElementArguments));
@@ -93,10 +96,10 @@ ProtractorBy.prototype.addLocator = function(name, script) {
9396
*/
9497
ProtractorBy.prototype.binding = function(bindingDescriptor) {
9598
return {
96-
findElementsOverride: function(driver, using) {
99+
findElementsOverride: function(driver, using, rootSelector) {
97100
return driver.findElements(
98101
webdriver.By.js(clientSideScripts.findBindings,
99-
bindingDescriptor, false, using));
102+
bindingDescriptor, false, using, rootSelector));
100103
},
101104
toString: function toString() {
102105
return 'by.binding("' + bindingDescriptor + '")';
@@ -125,10 +128,10 @@ ProtractorBy.prototype.binding = function(bindingDescriptor) {
125128
*/
126129
ProtractorBy.prototype.exactBinding = function(bindingDescriptor) {
127130
return {
128-
findElementsOverride: function(driver, using) {
131+
findElementsOverride: function(driver, using, rootSelector) {
129132
return driver.findElements(
130133
webdriver.By.js(clientSideScripts.findBindings,
131-
bindingDescriptor, true, using));
134+
bindingDescriptor, true, using, rootSelector));
132135
},
133136
toString: function toString() {
134137
return 'by.exactBinding("' + bindingDescriptor + '")';
@@ -152,9 +155,10 @@ ProtractorBy.prototype.exactBinding = function(bindingDescriptor) {
152155
*/
153156
ProtractorBy.prototype.model = function(model) {
154157
return {
155-
findElementsOverride: function(driver, using) {
158+
findElementsOverride: function(driver, using, rootSelector) {
156159
return driver.findElements(
157-
webdriver.By.js(clientSideScripts.findByModel, model, using));
160+
webdriver.By.js(
161+
clientSideScripts.findByModel, model, using, rootSelector));
158162
},
159163
toString: function toString() {
160164
return 'by.model("' + model + '")';
@@ -176,10 +180,10 @@ ProtractorBy.prototype.model = function(model) {
176180
*/
177181
ProtractorBy.prototype.buttonText = function(searchText) {
178182
return {
179-
findElementsOverride: function(driver, using) {
183+
findElementsOverride: function(driver, using, rootSelector) {
180184
return driver.findElements(
181185
webdriver.By.js(clientSideScripts.findByButtonText,
182-
searchText, using));
186+
searchText, using, rootSelector));
183187
},
184188
toString: function toString() {
185189
return 'by.buttonText("' + searchText + '")';
@@ -201,10 +205,10 @@ ProtractorBy.prototype.buttonText = function(searchText) {
201205
*/
202206
ProtractorBy.prototype.partialButtonText = function(searchText) {
203207
return {
204-
findElementsOverride: function(driver, using) {
208+
findElementsOverride: function(driver, using, rootSelector) {
205209
return driver.findElements(
206210
webdriver.By.js(clientSideScripts.findByPartialButtonText,
207-
searchText, using));
211+
searchText, using, rootSelector));
208212
},
209213
toString: function toString() {
210214
return 'by.partialButtonText("' + searchText + '")';
@@ -266,30 +270,30 @@ ProtractorBy.prototype.partialButtonText = function(searchText) {
266270
*/
267271
ProtractorBy.prototype.repeater = function(repeatDescriptor) {
268272
return {
269-
findElementsOverride: function(driver, using) {
273+
findElementsOverride: function(driver, using, rootSelector) {
270274
return driver.findElements(
271275
webdriver.By.js(clientSideScripts.findAllRepeaterRows,
272-
repeatDescriptor, using));
276+
repeatDescriptor, using, rootSelector));
273277
},
274278
toString: function toString() {
275279
return 'by.repeater("' + repeatDescriptor + '")';
276280
},
277281
row: function(index) {
278282
return {
279-
findElementsOverride: function(driver, using) {
283+
findElementsOverride: function(driver, using, rootSelector) {
280284
return driver.findElements(
281285
webdriver.By.js(clientSideScripts.findRepeaterRows,
282-
repeatDescriptor, index, using));
286+
repeatDescriptor, index, using, rootSelector));
283287
},
284288
toString: function toString() {
285289
return 'by.repeater(' + repeatDescriptor + '").row("' + index + '")"';
286290
},
287291
column: function(binding) {
288292
return {
289-
findElementsOverride: function(driver, using) {
293+
findElementsOverride: function(driver, using, rootSelector) {
290294
return driver.findElements(
291295
webdriver.By.js(clientSideScripts.findRepeaterElement,
292-
repeatDescriptor, index, binding, using));
296+
repeatDescriptor, index, binding, using, rootSelector));
293297
},
294298
toString: function toString() {
295299
return 'by.repeater("' + repeatDescriptor + '").row("' + index +
@@ -301,21 +305,21 @@ ProtractorBy.prototype.repeater = function(repeatDescriptor) {
301305
},
302306
column: function(binding) {
303307
return {
304-
findElementsOverride: function(driver, using) {
308+
findElementsOverride: function(driver, using, rootSelector) {
305309
return driver.findElements(
306310
webdriver.By.js(clientSideScripts.findRepeaterColumn,
307-
repeatDescriptor, binding, using));
311+
repeatDescriptor, binding, using, rootSelector));
308312
},
309313
toString: function toString() {
310314
return 'by.repeater("' + repeatDescriptor + '").column("' +
311315
binding + '")';
312316
},
313317
row: function(index) {
314318
return {
315-
findElementsOverride: function(driver, using) {
319+
findElementsOverride: function(driver, using, rootSelector) {
316320
return driver.findElements(
317321
webdriver.By.js(clientSideScripts.findRepeaterElement,
318-
repeatDescriptor, index, binding, using));
322+
repeatDescriptor, index, binding, using, rootSelector));
319323
},
320324
toString: function toString() {
321325
return 'by.repeater("' + repeatDescriptor + '").column("' +
@@ -343,10 +347,10 @@ ProtractorBy.prototype.repeater = function(repeatDescriptor) {
343347
*/
344348
ProtractorBy.prototype.cssContainingText = function(cssSelector, searchText) {
345349
return {
346-
findElementsOverride: function(driver, using) {
350+
findElementsOverride: function(driver, using, rootSelector) {
347351
return driver.findElements(
348352
webdriver.By.js(clientSideScripts.findByCssContainingText,
349-
cssSelector, searchText, using));
353+
cssSelector, searchText, using, rootSelector));
350354
},
351355
toString: function toString() {
352356
return 'by.cssContainingText("' + cssSelector + '", "' + searchText + '")';
@@ -374,9 +378,10 @@ ProtractorBy.prototype.cssContainingText = function(cssSelector, searchText) {
374378
*/
375379
ProtractorBy.prototype.options = function(optionsDescriptor) {
376380
return {
377-
findElementsOverride: function(driver, using) {
381+
findElementsOverride: function(driver, using, rootSelector) {
378382
return driver.findElements(
379-
webdriver.By.js(clientSideScripts.findByOptions, optionsDescriptor, using));
383+
webdriver.By.js(clientSideScripts.findByOptions, optionsDescriptor,
384+
using, rootSelector));
380385
},
381386
toString: function toString() {
382387
return 'by.option("' + optionsDescriptor + '")';

0 commit comments

Comments
 (0)