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

Commit 324f69d

Browse files
committed
feat(locators): add by.exactRepeater
1 parent 4045386 commit 324f69d

File tree

3 files changed

+153
-81
lines changed

3 files changed

+153
-81
lines changed

lib/clientsidescripts.js

+58-18
Original file line numberDiff line numberDiff line change
@@ -93,13 +93,23 @@ functions.findBindings = function(binding, exactMatch, using, rootSelector) {
9393
* Returns an array of all the elements in one segment for ng-repeat-start.
9494
*
9595
* @param {string} repeater The text of the repeater, e.g. 'cat in cats'.
96+
* @param {boolean} exact Whether the repeater needs to be matched exactly
9697
* @param {number} index The row index.
9798
* @param {Element} using The scope of the search.
9899
*
99100
* @return {Array.<Element>} The row of the repeater, or an array of elements
100101
* in the first row in the case of ng-repeat-start.
101102
*/
102-
functions.findRepeaterRows = function(repeater, index, using) {
103+
functions.findRepeaterRows = function(repeater, exact, index, using) {
104+
function repeaterMatch(ngRepeat, repeater, exact) {
105+
if (exact) {
106+
return ngRepeat.split(' track by ')[0].split(' as ')[0].split('|')[0].
107+
trim() == repeater;
108+
} else {
109+
return ngRepeat.indexOf(repeater) != -1;
110+
}
111+
}
112+
103113
using = using || document;
104114

105115
var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:'];
@@ -109,7 +119,7 @@ functions.findBindings = function(binding, exactMatch, using, rootSelector) {
109119
var repeatElems = using.querySelectorAll('[' + attr + ']');
110120
attr = attr.replace(/\\/g, '');
111121
for (var i = 0; i < repeatElems.length; ++i) {
112-
if (repeatElems[i].getAttribute(attr).indexOf(repeater) != -1) {
122+
if (repeaterMatch(repeatElems[i].getAttribute(attr), repeater, exact)) {
113123
rows.push(repeatElems[i]);
114124
}
115125
}
@@ -122,11 +132,11 @@ functions.findBindings = function(binding, exactMatch, using, rootSelector) {
122132
var repeatElems = using.querySelectorAll('[' + attr + ']');
123133
attr = attr.replace(/\\/g, '');
124134
for (var i = 0; i < repeatElems.length; ++i) {
125-
if (repeatElems[i].getAttribute(attr).indexOf(repeater) != -1) {
135+
if (repeaterMatch(repeatElems[i].getAttribute(attr), repeater, exact)) {
126136
var elem = repeatElems[i];
127137
var row = [];
128138
while (elem.nodeType != 8 ||
129-
elem.nodeValue.indexOf(repeater) == -1) {
139+
!repeaterMatch(elem.nodeValue, repeater, exact)) {
130140
if (elem.nodeType == 1) {
131141
row.push(elem);
132142
}
@@ -144,11 +154,21 @@ functions.findBindings = function(binding, exactMatch, using, rootSelector) {
144154
* Find all rows of an ng-repeat.
145155
*
146156
* @param {string} repeater The text of the repeater, e.g. 'cat in cats'.
157+
* @param {boolean} exact Whether the repeater needs to be matched exactly
147158
* @param {Element} using The scope of the search.
148159
*
149160
* @return {Array.<Element>} All rows of the repeater.
150161
*/
151-
functions.findAllRepeaterRows = function(repeater, using) {
162+
functions.findAllRepeaterRows = function(repeater, exact, using) {
163+
function repeaterMatch(ngRepeat, repeater, exact) {
164+
if (exact) {
165+
return ngRepeat.split(' track by ')[0].split(' as ')[0].split('|')[0].
166+
trim() == repeater;
167+
} else {
168+
return ngRepeat.indexOf(repeater) != -1;
169+
}
170+
}
171+
152172
using = using || document;
153173

154174
var rows = [];
@@ -158,7 +178,7 @@ functions.findBindings = function(binding, exactMatch, using, rootSelector) {
158178
var repeatElems = using.querySelectorAll('[' + attr + ']');
159179
attr = attr.replace(/\\/g, '');
160180
for (var i = 0; i < repeatElems.length; ++i) {
161-
if (repeatElems[i].getAttribute(attr).indexOf(repeater) != -1) {
181+
if (repeaterMatch(repeatElems[i].getAttribute(attr), repeater, exact)) {
162182
rows.push(repeatElems[i]);
163183
}
164184
}
@@ -168,10 +188,10 @@ functions.findBindings = function(binding, exactMatch, using, rootSelector) {
168188
var repeatElems = using.querySelectorAll('[' + attr + ']');
169189
attr = attr.replace(/\\/g, '');
170190
for (var i = 0; i < repeatElems.length; ++i) {
171-
if (repeatElems[i].getAttribute(attr).indexOf(repeater) != -1) {
191+
if (repeaterMatch(repeatElems[i].getAttribute(attr), repeater, exact)) {
172192
var elem = repeatElems[i];
173193
while (elem.nodeType != 8 ||
174-
elem.nodeValue.indexOf(repeater) == -1) {
194+
!repeaterMatch(elem.nodeValue, repeater, exact)) {
175195
if (elem.nodeType == 1) {
176196
rows.push(elem);
177197
}
@@ -187,14 +207,24 @@ functions.findBindings = function(binding, exactMatch, using, rootSelector) {
187207
* Find an element within an ng-repeat by its row and column.
188208
*
189209
* @param {string} repeater The text of the repeater, e.g. 'cat in cats'.
210+
* @param {boolean} exact Whether the repeater needs to be matched exactly
190211
* @param {number} index The row index.
191212
* @param {string} binding The column binding, e.g. '{{cat.name}}'.
192213
* @param {Element} using The scope of the search.
193214
* @param {string} rootSelector The selector to use for the root app element.
194215
*
195216
* @return {Array.<Element>} The element in an array.
196217
*/
197-
functions.findRepeaterElement = function(repeater, index, binding, using, rootSelector) {
218+
functions.findRepeaterElement = function(repeater, exact, index, binding, using, rootSelector) {
219+
function repeaterMatch(ngRepeat, repeater, exact) {
220+
if (exact) {
221+
return ngRepeat.split(' track by ')[0].split(' as ')[0].split('|')[0].
222+
trim() == repeater;
223+
} else {
224+
return ngRepeat.indexOf(repeater) != -1;
225+
}
226+
}
227+
198228
var matches = [];
199229
var root = document.querySelector(rootSelector || 'body');
200230
using = using || document;
@@ -206,7 +236,7 @@ functions.findRepeaterElement = function(repeater, index, binding, using, rootSe
206236
var repeatElems = using.querySelectorAll('[' + attr + ']');
207237
attr = attr.replace(/\\/g, '');
208238
for (var i = 0; i < repeatElems.length; ++i) {
209-
if (repeatElems[i].getAttribute(attr).indexOf(repeater) != -1) {
239+
if (repeaterMatch(repeatElems[i].getAttribute(attr), repeater, exact)) {
210240
rows.push(repeatElems[i]);
211241
}
212242
}
@@ -219,11 +249,11 @@ functions.findRepeaterElement = function(repeater, index, binding, using, rootSe
219249
var repeatElems = using.querySelectorAll('[' + attr + ']');
220250
attr = attr.replace(/\\/g, '');
221251
for (var i = 0; i < repeatElems.length; ++i) {
222-
if (repeatElems[i].getAttribute(attr).indexOf(repeater) != -1) {
252+
if (repeaterMatch(repeatElems[i].getAttribute(attr), repeater, exact)) {
223253
var elem = repeatElems[i];
224254
var row = [];
225-
while (elem.nodeType != 8 ||
226-
(elem.nodeValue && elem.nodeValue.indexOf(repeater) == -1)) {
255+
while (elem.nodeType != 8 || (elem.nodeValue &&
256+
!repeaterMatch(elem.nodeValue, repeater, exact))) {
227257
if (elem.nodeType == 1) {
228258
row.push(elem);
229259
}
@@ -285,13 +315,23 @@ functions.findRepeaterElement = function(repeater, index, binding, using, rootSe
285315
* Find the elements in a column of an ng-repeat.
286316
*
287317
* @param {string} repeater The text of the repeater, e.g. 'cat in cats'.
318+
* @param {boolean} exact Whether the repeater needs to be matched exactly
288319
* @param {string} binding The column binding, e.g. '{{cat.name}}'.
289320
* @param {Element} using The scope of the search.
290321
* @param {string} rootSelector The selector to use for the root app element.
291322
*
292323
* @return {Array.<Element>} The elements in the column.
293324
*/
294-
functions.findRepeaterColumn = function(repeater, binding, using, rootSelector) {
325+
functions.findRepeaterColumn = function(repeater, exact, binding, using, rootSelector) {
326+
function repeaterMatch(ngRepeat, repeater, exact) {
327+
if (exact) {
328+
return ngRepeat.split(' track by ')[0].split(' as ')[0].split('|')[0].
329+
trim() == repeater;
330+
} else {
331+
return ngRepeat.indexOf(repeater) != -1;
332+
}
333+
}
334+
295335
var matches = [];
296336
var root = document.querySelector(rootSelector || 'body');
297337
using = using || document;
@@ -303,7 +343,7 @@ functions.findRepeaterColumn = function(repeater, binding, using, rootSelector)
303343
var repeatElems = using.querySelectorAll('[' + attr + ']');
304344
attr = attr.replace(/\\/g, '');
305345
for (var i = 0; i < repeatElems.length; ++i) {
306-
if (repeatElems[i].getAttribute(attr).indexOf(repeater) != -1) {
346+
if (repeaterMatch(repeatElems[i].getAttribute(attr), repeater, exact)) {
307347
rows.push(repeatElems[i]);
308348
}
309349
}
@@ -316,11 +356,11 @@ functions.findRepeaterColumn = function(repeater, binding, using, rootSelector)
316356
var repeatElems = using.querySelectorAll('[' + attr + ']');
317357
attr = attr.replace(/\\/g, '');
318358
for (var i = 0; i < repeatElems.length; ++i) {
319-
if (repeatElems[i].getAttribute(attr).indexOf(repeater) != -1) {
359+
if (repeaterMatch(repeatElems[i].getAttribute(attr), repeater, exact)) {
320360
var elem = repeatElems[i];
321361
var row = [];
322-
while (elem.nodeType != 8 ||
323-
(elem.nodeValue && elem.nodeValue.indexOf(repeater) == -1)) {
362+
while (elem.nodeType != 8 || (elem.nodeValue &&
363+
!repeaterMatch(elem.nodeValue, repeater, exact))) {
324364
if (elem.nodeType == 1) {
325365
row.push(elem);
326366
}

lib/locators.js

+90-63
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,74 @@ ProtractorBy.prototype.partialButtonText = function(searchText) {
228228
};
229229
};
230230

231+
// Generate either by.repeater or by.exactRepeater
232+
function byRepeaterInner(exact) {
233+
var name = 'by.' + (exact ? 'exactR' : 'r') + 'epeater';
234+
return function(repeatDescriptor) {
235+
return {
236+
findElementsOverride: function(driver, using, rootSelector) {
237+
return driver.findElements(
238+
webdriver.By.js(clientSideScripts.findAllRepeaterRows,
239+
repeatDescriptor, exact, using, rootSelector));
240+
},
241+
toString: function toString() {
242+
return name + '("' + repeatDescriptor + '")';
243+
},
244+
row: function(index) {
245+
return {
246+
findElementsOverride: function(driver, using, rootSelector) {
247+
return driver.findElements(
248+
webdriver.By.js(clientSideScripts.findRepeaterRows,
249+
repeatDescriptor, exact, index, using, rootSelector));
250+
},
251+
toString: function toString() {
252+
return name + '(' + repeatDescriptor + '").row("' + index + '")"';
253+
},
254+
column: function(binding) {
255+
return {
256+
findElementsOverride: function(driver, using, rootSelector) {
257+
return driver.findElements(
258+
webdriver.By.js(clientSideScripts.findRepeaterElement,
259+
repeatDescriptor, exact, index, binding, using,
260+
rootSelector));
261+
},
262+
toString: function toString() {
263+
return name + '("' + repeatDescriptor + '").row("' + index +
264+
'").column("' + binding + '")';
265+
}
266+
};
267+
}
268+
};
269+
},
270+
column: function(binding) {
271+
return {
272+
findElementsOverride: function(driver, using, rootSelector) {
273+
return driver.findElements(
274+
webdriver.By.js(clientSideScripts.findRepeaterColumn,
275+
repeatDescriptor, exact, binding, using, rootSelector));
276+
},
277+
toString: function toString() {
278+
return name + '("' + repeatDescriptor + '").column("' +
279+
binding + '")';
280+
},
281+
row: function(index) {
282+
return {
283+
findElementsOverride: function(driver, using, rootSelector) {
284+
return driver.findElements(
285+
webdriver.By.js(clientSideScripts.findRepeaterElement,
286+
repeatDescriptor, exact, index, binding, using, rootSelector));
287+
},
288+
toString: function toString() {
289+
return name + '("' + repeatDescriptor + '").column("' +
290+
binding + '").row("' + index + '")';
291+
}
292+
};
293+
}
294+
};
295+
}
296+
};
297+
};
298+
}
231299

232300
/**
233301
* Find elements inside an ng-repeat.
@@ -279,70 +347,29 @@ ProtractorBy.prototype.partialButtonText = function(searchText) {
279347
* // all top level elements repeated by the repeater. For 2 books divs
280348
* // resolves to an array of 4 elements.
281349
* var divs = element.all(by.repeater('book in library'));
350+
*
351+
* @param {string} repeatDescriptor
352+
* @return {{findElementsOverride: findElementsOverride, toString: Function|string}}
282353
*/
283-
ProtractorBy.prototype.repeater = function(repeatDescriptor) {
284-
return {
285-
findElementsOverride: function(driver, using, rootSelector) {
286-
return driver.findElements(
287-
webdriver.By.js(clientSideScripts.findAllRepeaterRows,
288-
repeatDescriptor, using, rootSelector));
289-
},
290-
toString: function toString() {
291-
return 'by.repeater("' + repeatDescriptor + '")';
292-
},
293-
row: function(index) {
294-
return {
295-
findElementsOverride: function(driver, using, rootSelector) {
296-
return driver.findElements(
297-
webdriver.By.js(clientSideScripts.findRepeaterRows,
298-
repeatDescriptor, index, using, rootSelector));
299-
},
300-
toString: function toString() {
301-
return 'by.repeater(' + repeatDescriptor + '").row("' + index + '")"';
302-
},
303-
column: function(binding) {
304-
return {
305-
findElementsOverride: function(driver, using, rootSelector) {
306-
return driver.findElements(
307-
webdriver.By.js(clientSideScripts.findRepeaterElement,
308-
repeatDescriptor, index, binding, using, rootSelector));
309-
},
310-
toString: function toString() {
311-
return 'by.repeater("' + repeatDescriptor + '").row("' + index +
312-
'").column("' + binding + '")';
313-
}
314-
};
315-
}
316-
};
317-
},
318-
column: function(binding) {
319-
return {
320-
findElementsOverride: function(driver, using, rootSelector) {
321-
return driver.findElements(
322-
webdriver.By.js(clientSideScripts.findRepeaterColumn,
323-
repeatDescriptor, binding, using, rootSelector));
324-
},
325-
toString: function toString() {
326-
return 'by.repeater("' + repeatDescriptor + '").column("' +
327-
binding + '")';
328-
},
329-
row: function(index) {
330-
return {
331-
findElementsOverride: function(driver, using, rootSelector) {
332-
return driver.findElements(
333-
webdriver.By.js(clientSideScripts.findRepeaterElement,
334-
repeatDescriptor, index, binding, using, rootSelector));
335-
},
336-
toString: function toString() {
337-
return 'by.repeater("' + repeatDescriptor + '").column("' +
338-
binding + '").row("' + index + '")';
339-
}
340-
};
341-
}
342-
};
343-
}
344-
};
345-
};
354+
ProtractorBy.prototype.repeater = byRepeaterInner(false);
355+
356+
/**
357+
* Find an element by exact repeater.
358+
*
359+
* @view
360+
* <li ng-repeat="person in peopleWithRedHair"></li>
361+
* <li ng-repeat="car in cars | orderBy:year"></li>
362+
*
363+
* @example
364+
* expect(element(by.exactRepeater('person in peopleWithRedHair')).isPresent()).toBe(true);
365+
* expect(element(by.exactRepeater('person in people')).isPresent()).toBe(false);
366+
* expect(element(by.exactRepeater('car in cars')).isPresent()).toBe(true);
367+
*
368+
* @param {string} repeatDescriptor
369+
* @return {{findElementsOverride: findElementsOverride, toString: Function|string}}
370+
*/
371+
ProtractorBy.prototype.exactRepeater = byRepeaterInner(true);
372+
346373

347374
/**
348375
* Find elements by CSS which contain a certain string.

spec/basic/locators_spec.js

+5
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,11 @@ describe('locators', function() {
274274
toBe(false);
275275
});
276276

277+
it('should have by.exactRepeater working', function() {
278+
expect(element(by.exactRepeater('day in d')).isPresent()).toBe(false);
279+
expect(element(by.exactRepeater('day in days')).isPresent()).toBe(true);
280+
});
281+
277282
describe('repeaters using ng-repeat-start and ng-repeat-end', function() {
278283
it('should return all elements when unmodified', function() {
279284
var all =

0 commit comments

Comments
 (0)