capybara-ng 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,896 @@
1
+ module Angular
2
+ # Build the helper 'element' function for a given instance of Protractor.
3
+ #
4
+ # @private
5
+ # @param {Protractor} ptor
6
+ # @return {function(webdriver.Locator): ElementFinder}
7
+ class ElementHelper
8
+ def by_binding
9
+ end
10
+ end
11
+ end
12
+
13
+ <<-JS
14
+
15
+ var buildElementHelper = function(ptor) {
16
+
17
+ /**
18
+ * ElementArrayFinder is used for operations on an array of elements (as opposed
19
+ * to a single element).
20
+ *
21
+ * The ElementArrayFinder is used to set up a chain of conditions that identify
22
+ * an array of elements. In particular, you can call all(locator) and
23
+ * filter(filterFn) to return a new ElementArrayFinder modified by the
24
+ * conditions, and you can call get(index) to return a single ElementFinder at
25
+ * position 'index'.
26
+ *
27
+ * Similar to jquery, ElementArrayFinder will search all branches of the DOM
28
+ * to find the elements that satify the conditions (i.e. all, filter, get).
29
+ * However, an ElementArrayFinder will not actually retrieve the elements until
30
+ * an action is called, which means it can be set up in helper files (i.e.
31
+ * page objects) before the page is available, and reused as the page changes.
32
+ *
33
+ * You can treat an ElementArrayFinder as an array of WebElements for most
34
+ * purposes, in particular, you may perform actions (i.e. click, getText) on
35
+ * them as you would an array of WebElements. The action will apply to
36
+ * every element identified by the ElementArrayFinder. ElementArrayFinder
37
+ * extends Promise, and once an action is performed on an ElementArrayFinder,
38
+ * the latest result can be accessed using then, and will be returned as an
39
+ * array of the results; the array has length equal to the length of the
40
+ * elements found by the ElementArrayFinder and each result represents the
41
+ * result of performing the action on the element. Unlike a WebElement, an
42
+ * ElementArrayFinder will wait for the angular app to settle before
43
+ * performing finds or actions.
44
+ *
45
+ * @alias element.all(locator)
46
+ * @view
47
+ * <ul class="items">
48
+ * <li>First</li>
49
+ * <li>Second</li>
50
+ * <li>Third</li>
51
+ * </ul>
52
+ *
53
+ * @example
54
+ * element.all(by.css('.items li')).then(function(items) {
55
+ * expect(items.length).toBe(3);
56
+ * expect(items[0].getText()).toBe('First');
57
+ * });
58
+ *
59
+ * @constructor
60
+ * @param {function(): Array.<webdriver.WebElement>} getWebElements A function
61
+ * that returns a list of the underlying Web Elements.
62
+ * @param {webdriver.Locator} locator The most relevant locator. It is only
63
+ * used for error reporting and ElementArrayFinder.locator.
64
+ * @param {Array.<webdriver.promise.Promise>} opt_actionResults An array
65
+ * of promises which will be retrieved with then. Resolves to the latest
66
+ * action result, or null if no action has been called.
67
+ * @return {ElementArrayFinder}
68
+ */
69
+ var ElementArrayFinder = function(getWebElements, locator, opt_actionResults) {
70
+ this.getWebElements = getWebElements || null;
71
+ this.actionResults_ = opt_actionResults;
72
+ this.locator_ = locator;
73
+
74
+ var self = this;
75
+ WEB_ELEMENT_FUNCTIONS.forEach(function(fnName) {
76
+ self[fnName] = function() {
77
+ var callerError = new Error();
78
+ var args = arguments;
79
+ var actionFn = function(webElem) {
80
+ return webElem[fnName].apply(webElem, args).then(null, function(e) {
81
+ e.stack = e.stack + '\n' + callerError.stack;
82
+ throw e;
83
+ });
84
+ };
85
+ return self.applyAction_(actionFn);
86
+ };
87
+ });
88
+ };
89
+ util.inherits(ElementArrayFinder, webdriver.promise.Promise);
90
+
91
+ /**
92
+ * Calls to ElementArrayFinder may be chained to find an array of elements
93
+ * using the current elements in this ElementArrayFinder as the starting point.
94
+ * This function returns a new ElementArrayFinder which would contain the
95
+ * children elements found (and could also be empty).
96
+ *
97
+ * @alias element.all(locator).all(locator)
98
+ * @view
99
+ * <div id='id1' class="parent">
100
+ * <ul>
101
+ * <li class="foo">1a</li>
102
+ * <li class="baz">1b</li>
103
+ * </ul>
104
+ * </div>
105
+ * <div id='id2' class="parent">
106
+ * <ul>
107
+ * <li class="foo">2a</li>
108
+ * <li class="bar">2b</li>
109
+ * </ul>
110
+ * </div>
111
+ *
112
+ * @example
113
+ * var foo = element.all(by.css('.parent')).all(by.css('.foo'))
114
+ * expect(foo.getText()).toEqual(['1a', '2a'])
115
+ * var baz = element.all(by.css('.parent')).all(by.css('.baz'))
116
+ * expect(baz.getText()).toEqual(['1b'])
117
+ * var nonexistent = element.all(by.css('.parent')).all(by.css('.NONEXISTENT'))
118
+ * expect(nonexistent.getText()).toEqual([''])
119
+ *
120
+ * @param {webdriver.Locator} subLocator
121
+ * @return {ElementArrayFinder}
122
+ */
123
+ ElementArrayFinder.prototype.all = function(locator) {
124
+ var self = this;
125
+ var getWebElements = function() {
126
+ if (self.getWebElements === null) {
127
+ // This is the first time we are looking for an element
128
+ return ptor.waitForAngular().then(function() {
129
+ if (locator.findElementsOverride) {
130
+ return locator.findElementsOverride(ptor.driver);
131
+ } else {
132
+ return ptor.driver.findElements(locator);
133
+ }
134
+ });
135
+ } else {
136
+ return self.getWebElements().then(function(parentWebElements) {
137
+ var childrenPromiseList = [];
138
+ // For each parent web element, find their children and construct a
139
+ // list of Promise<List<child_web_element>>
140
+ parentWebElements.forEach(function(parentWebElement) {
141
+ var childrenPromise = locator.findElementsOverride ?
142
+ locator.findElementsOverride(ptor.driver, parentWebElement) :
143
+ parentWebElement.findElements(locator);
144
+ childrenPromiseList.push(childrenPromise);
145
+ });
146
+
147
+
148
+ // Resolve the list of Promise<List<child_web_elements>> and merge into
149
+ // a single list
150
+ return webdriver.promise.all(childrenPromiseList).then(
151
+ function(resolved) {
152
+ var childrenList = [];
153
+ resolved.forEach(function(resolvedE) {
154
+ childrenList = childrenList.concat(resolvedE);
155
+ });
156
+ return childrenList;
157
+ });
158
+ });
159
+ }
160
+ };
161
+ return new ElementArrayFinder(getWebElements, locator);
162
+ };
163
+
164
+ /**
165
+ * Apply a filter function to each element within the ElementArrayFinder. Returns
166
+ * a new ElementArrayFinder with all elements that pass the filter function. The
167
+ * filter function receives the ElementFinder as the first argument
168
+ * and the index as a second arg.
169
+ * This does not actually retrieve the underlying list of elements, so it can
170
+ * be used in page objects.
171
+ *
172
+ * @alias element.all(locator).filter(filterFn)
173
+ * @view
174
+ * <ul class="items">
175
+ * <li class="one">First</li>
176
+ * <li class="two">Second</li>
177
+ * <li class="three">Third</li>
178
+ * </ul>
179
+ *
180
+ * @example
181
+ * element.all(by.css('.items li')).filter(function(elem, index) {
182
+ * return elem.getText().then(function(text) {
183
+ * return text === 'Third';
184
+ * });
185
+ * }).then(function(filteredElements) {
186
+ * filteredElements[0].click();
187
+ * });
188
+ *
189
+ * @param {function(ElementFinder, number): webdriver.WebElement.Promise} filterFn
190
+ * Filter function that will test if an element should be returned.
191
+ * filterFn can either return a boolean or a promise that resolves to a boolean.
192
+ * @return {!ElementArrayFinder} A ElementArrayFinder that represents an array
193
+ * of element that satisfy the filter function.
194
+ */
195
+ ElementArrayFinder.prototype.filter = function(filterFn) {
196
+ var self = this;
197
+ var getWebElements = function() {
198
+ return self.getWebElements().then(function(parentWebElements) {
199
+ var list = [];
200
+ parentWebElements.forEach(function(parentWebElement, index) {
201
+ var elementFinder = self.get(index); // Wrap in ElementFinder
202
+ var filterResults = filterFn(elementFinder, index);
203
+ if (filterResults instanceof webdriver.promise.Promise) {
204
+ filterResults.then(function(satisfies) {
205
+ if (satisfies) {
206
+ list.push(parentWebElements[index]);
207
+ }
208
+ });
209
+ } else if (filterResults) {
210
+ list.push(parentWebElements[index]);
211
+ }
212
+ });
213
+ return list;
214
+ });
215
+ };
216
+ return new ElementArrayFinder(getWebElements, this.locator_);
217
+ };
218
+
219
+ /**
220
+ * Get an element within the ElementArrayFinder by index. The index starts at 0.
221
+ * This does not actually retrieve the underlying element.
222
+ *
223
+ * @alias element.all(locator).get(index)
224
+ * @view
225
+ * <ul class="items">
226
+ * <li>First</li>
227
+ * <li>Second</li>
228
+ * <li>Third</li>
229
+ * </ul>
230
+ *
231
+ * @example
232
+ * var list = element.all(by.css('.items li'));
233
+ * expect(list.get(0).getText()).toBe('First');
234
+ * expect(list.get(1).getText()).toBe('Second');
235
+ *
236
+ * @param {number} index Element index.
237
+ * @return {ElementFinder} finder representing element at the given index.
238
+ */
239
+ ElementArrayFinder.prototype.get = function(index) {
240
+ var self = this;
241
+ var getWebElements = function() {
242
+ return self.getWebElements().then(function(parentWebElements) {
243
+ if (index === -1) {
244
+ // -1 is special and means last
245
+ index = parentWebElements.length - 1;
246
+ }
247
+ if (index >= parentWebElements.length) {
248
+ throw new Error('Index out of bound. Trying to access element at ' +
249
+ 'index:' + index + ', but there are only ' +
250
+ parentWebElements.length + ' elements');
251
+ }
252
+ return [parentWebElements[index]];
253
+ });
254
+ };
255
+ return new ElementArrayFinder(getWebElements, this.locator_).toElementFinder_();
256
+ };
257
+
258
+ /**
259
+ * Get the first matching element for the ElementArrayFinder. This does not
260
+ * actually retrieve the underlying element.
261
+ *
262
+ * @alias element.all(locator).first()
263
+ * @view
264
+ * <ul class="items">
265
+ * <li>First</li>
266
+ * <li>Second</li>
267
+ * <li>Third</li>
268
+ * </ul>
269
+ *
270
+ * @example
271
+ * var first = element.all(by.css('.items li')).first();
272
+ * expect(first.getText()).toBe('First');
273
+ *
274
+ * @return {ElementFinder} finder representing the first matching element
275
+ */
276
+ ElementArrayFinder.prototype.first = function() {
277
+ return this.get(0);
278
+ };
279
+
280
+ /**
281
+ * Get the last matching element for the ElementArrayFinder. This does not
282
+ * actually retrieve the underlying element.
283
+ *
284
+ * @alias element.all(locator).last()
285
+ * @view
286
+ * <ul class="items">
287
+ * <li>First</li>
288
+ * <li>Second</li>
289
+ * <li>Third</li>
290
+ * </ul>
291
+ *
292
+ * @example
293
+ * var last = element.all(by.css('.items li')).last();
294
+ * expect(last.getText()).toBe('Third');
295
+ *
296
+ * @return {ElementFinder} finder representing the last matching element
297
+ */
298
+ ElementArrayFinder.prototype.last = function() {
299
+ return this.get(-1);
300
+ };
301
+
302
+ /**
303
+ * Shorthand function for finding arrays of elements by css.
304
+ *
305
+ * @type {function(string): ElementArrayFinder}
306
+ */
307
+ ElementArrayFinder.prototype.$$ = function(selector) {
308
+ return this.all(webdriver.By.css(selector));
309
+ };
310
+
311
+ /**
312
+ * Returns an ElementFinder representation of ElementArrayFinder. It ensures
313
+ * that the ElementArrayFinder resolves to one and only one underlying element.
314
+ *
315
+ * @return {ElementFinder} An ElementFinder representation
316
+ * @private
317
+ */
318
+ ElementArrayFinder.prototype.toElementFinder_ = function() {
319
+ return new ElementFinder(this);
320
+ };
321
+
322
+ /**
323
+ * Count the number of elements represented by the ElementArrayFinder.
324
+ *
325
+ * @alias element.all(locator).count()
326
+ * @view
327
+ * <ul class="items">
328
+ * <li>First</li>
329
+ * <li>Second</li>
330
+ * <li>Third</li>
331
+ * </ul>
332
+ *
333
+ * @example
334
+ * var list = element.all(by.css('.items li'));
335
+ * expect(list.count()).toBe(3);
336
+ *
337
+ * @return {!webdriver.promise.Promise} A promise which resolves to the
338
+ * number of elements matching the locator.
339
+ */
340
+ ElementArrayFinder.prototype.count = function() {
341
+ return this.getWebElements().then(function(arr) {
342
+ return arr.length;
343
+ }, function(err) {
344
+ if (err.code == webdriver.error.ErrorCode.NO_SUCH_ELEMENT) {
345
+ return 0;
346
+ } else {
347
+ throw err;
348
+ }
349
+ });
350
+ };
351
+
352
+ /**
353
+ * Returns the most relevant locator.
354
+ * i.e.
355
+ * $('#ID1').locator() returns by.css('#ID1')
356
+ * $('#ID1').$('#ID2').locator() returns by.css('#ID2')
357
+ * $$('#ID1').filter(filterFn).get(0).click().locator() returns by.css('#ID1')
358
+ * @return {webdriver.Locator}
359
+ */
360
+ ElementArrayFinder.prototype.locator = function() {
361
+ return this.locator_;
362
+ };
363
+
364
+ /**
365
+ * Apply an action function to every element in the ElementArrayFinder,
366
+ * and return a new ElementArrayFinder that contains the results of the actions.
367
+ *
368
+ * @param {function(ElementFinder)} actionFn
369
+ *
370
+ * @return {ElementArrayFinder}
371
+ * @private
372
+ */
373
+ ElementArrayFinder.prototype.applyAction_ = function(actionFn) {
374
+ var actionResults = this.getWebElements().then(function(arr) {
375
+ var list = [];
376
+ arr.forEach(function(webElem) {
377
+ list.push(actionFn(webElem));
378
+ });
379
+ return webdriver.promise.all(list);
380
+ });
381
+ return new ElementArrayFinder(this.getWebElements, this.locator_, actionResults);
382
+ };
383
+
384
+ /**
385
+ * Represents the ElementArrayFinder as an array of ElementFinders.
386
+ *
387
+ * @return {Array.<ElementFinder>} Return a promise, which resolves to a list
388
+ * of ElementFinders specified by the locator.
389
+ */
390
+ ElementArrayFinder.prototype.asElementFinders_ = function() {
391
+ var self = this;
392
+ return this.getWebElements().then(function(arr) {
393
+ var list = [];
394
+ arr.forEach(function(webElem, index) {
395
+ list.push(self.get(index));
396
+ });
397
+ return list;
398
+ });
399
+ };
400
+
401
+ /**
402
+ * Retrieve the elements represented by the ElementArrayFinder. The input
403
+ * function is passed to the resulting promise, which resolves to an
404
+ * array of ElementFinders.
405
+ *
406
+ * @alias element.all(locator).then(thenFunction)
407
+ * @view
408
+ * <ul class="items">
409
+ * <li>First</li>
410
+ * <li>Second</li>
411
+ * <li>Third</li>
412
+ * </ul>
413
+ *
414
+ * @example
415
+ * element.all(by.css('.items li')).then(function(arr) {
416
+ * expect(arr.length).toEqual(3);
417
+ * });
418
+ *
419
+ * @param {function(Array.<ElementFinder>)} fn
420
+ * @param {function(Error)} errorFn
421
+ *
422
+ * @type {webdriver.promise.Promise} a promise which will resolve to
423
+ * an array of ElementFinders represented by the ElementArrayFinder.
424
+ */
425
+ ElementArrayFinder.prototype.then = function(fn, errorFn) {
426
+ if (this.actionResults_) {
427
+ return this.actionResults_.then(fn, errorFn);
428
+ } else {
429
+ return this.asElementFinders_().then(fn, errorFn);
430
+ }
431
+ };
432
+
433
+ /**
434
+ * Calls the input function on each ElementFinder represented by the ElementArrayFinder.
435
+ *
436
+ * @alias element.all(locator).each(eachFunction)
437
+ * @view
438
+ * <ul class="items">
439
+ * <li>First</li>
440
+ * <li>Second</li>
441
+ * <li>Third</li>
442
+ * </ul>
443
+ *
444
+ * @example
445
+ * element.all(by.css('.items li')).each(function(element) {
446
+ * // Will print First, Second, Third.
447
+ * element.getText().then(console.log);
448
+ * });
449
+ *
450
+ * @param {function(ElementFinder)} fn Input function
451
+ */
452
+ ElementArrayFinder.prototype.each = function(fn) {
453
+ return this.asElementFinders_().then(function(arr) {
454
+ arr.forEach(function(elementFinder, index) {
455
+ fn(elementFinder, index);
456
+ });
457
+ });
458
+ };
459
+
460
+ /**
461
+ * Apply a map function to each element within the ElementArrayFinder. The
462
+ * callback receives the ElementFinder as the first argument and the index as
463
+ * a second arg.
464
+ *
465
+ * @alias element.all(locator).map(mapFunction)
466
+ * @view
467
+ * <ul class="items">
468
+ * <li class="one">First</li>
469
+ * <li class="two">Second</li>
470
+ * <li class="three">Third</li>
471
+ * </ul>
472
+ *
473
+ * @example
474
+ * var items = element.all(by.css('.items li')).map(function(elm, index) {
475
+ * return {
476
+ * index: index,
477
+ * text: elm.getText(),
478
+ * class: elm.getAttribute('class')
479
+ * };
480
+ * });
481
+ * expect(items).toEqual([
482
+ * {index: 0, text: 'First', class: 'one'},
483
+ * {index: 1, text: 'Second', class: 'two'},
484
+ * {index: 2, text: 'Third', class: 'three'}
485
+ * ]);
486
+ *
487
+ * @param {function(ElementFinder, number)} mapFn Map function that
488
+ * will be applied to each element.
489
+ * @return {!webdriver.promise.Promise} A promise that resolves to an array
490
+ * of values returned by the map function.
491
+ */
492
+ ElementArrayFinder.prototype.map = function(mapFn) {
493
+ return this.asElementFinders_().then(function(arr) {
494
+ var list = [];
495
+ arr.forEach(function(elementFinder, index) {
496
+ var mapResult = mapFn(elementFinder, index);
497
+ // All nested arrays and objects will also be fully resolved.
498
+ webdriver.promise.fullyResolved(mapResult).then(function(resolved) {
499
+ list.push(resolved);
500
+ });
501
+ });
502
+ return list;
503
+ });
504
+ };
505
+
506
+ /**
507
+ * Apply a reduce function against an accumulator and every element found
508
+ * using the locator (from left-to-right). The reduce function has to reduce
509
+ * every element into a single value (the accumulator). Returns promise of
510
+ * the accumulator. The reduce function receives the accumulator, current
511
+ * ElementFinder, the index, and the entire array of ElementFinders,
512
+ * respectively.
513
+ *
514
+ * @alias element.all(locator).reduce(reduceFn)
515
+ * @view
516
+ * <ul class="items">
517
+ * <li class="one">First</li>
518
+ * <li class="two">Second</li>
519
+ * <li class="three">Third</li>
520
+ * </ul>
521
+ *
522
+ * @example
523
+ * var value = element.all(by.css('.items li')).reduce(function(acc, elem) {
524
+ * return elem.getText().then(function(text) {
525
+ * return acc + text + ' ';
526
+ * });
527
+ * });
528
+ *
529
+ * expect(value).toEqual('First Second Third ');
530
+ *
531
+ * @param {function(number, ElementFinder, number, Array.<ElementFinder>)}
532
+ * reduceFn Reduce function that reduces every element into a single value.
533
+ * @param {*} initialValue Initial value of the accumulator.
534
+ * @return {!webdriver.promise.Promise} A promise that resolves to the final
535
+ * value of the accumulator.
536
+ */
537
+ ElementArrayFinder.prototype.reduce = function(reduceFn, initialValue) {
538
+ var valuePromise = webdriver.promise.fulfilled(initialValue);
539
+ return this.asElementFinders_().then(function(arr) {
540
+ arr.forEach(function(elementFinder, index) {
541
+ valuePromise = valuePromise.then(function(value) {
542
+ return reduceFn(value, elementFinder, index, arr);
543
+ });
544
+ });
545
+ return valuePromise;
546
+ });
547
+ };
548
+
549
+ /**
550
+ * Evaluates the input as if it were on the scope of the current underlying
551
+ * elements.
552
+ * @param {string} expression
553
+ *
554
+ * @return {ElementArrayFinder} which resolves to the
555
+ * evaluated expression for each underlying element.
556
+ * The result will be resolved as in
557
+ * {@link webdriver.WebDriver.executeScript}. In summary - primitives will
558
+ * be resolved as is, functions will be converted to string, and elements
559
+ * will be returned as a WebElement.
560
+ */
561
+ ElementArrayFinder.prototype.evaluate = function(expression) {
562
+ var evaluationFn = function(webElem) {
563
+ return webElem.getDriver().executeScript(
564
+ clientSideScripts.evaluate, webElem, expression);
565
+ };
566
+ return this.applyAction_(evaluationFn);
567
+ };
568
+
569
+ /**
570
+ * Determine if animation is allowed on the current underlying elements.
571
+ * @param {string} value
572
+ *
573
+ * @return {ElementArrayFinder} which resolves to whether animation is allowed.
574
+ */
575
+ ElementArrayFinder.prototype.allowAnimations = function(value) {
576
+ var allowAnimationsTestFn = function(webElem) {
577
+ return webElem.getDriver().executeScript(
578
+ clientSideScripts.allowAnimations, webElem, value);
579
+ };
580
+ return this.applyAction_(allowAnimationsTestFn);
581
+ };
582
+
583
+ /**
584
+ * The ElementFinder simply represents a single element of an
585
+ * ElementArrayFinder (and is more like a convenience object). As a result,
586
+ * anything that can be done with an ElementFinder, can also be done using
587
+ * an ElementArrayFinder.
588
+ *
589
+ * The ElementFinder can be treated as a WebElement for most purposes, in
590
+ * particular, you may perform actions (i.e. click, getText) on them as you
591
+ * would a WebElement. ElementFinders extend Promise, and once an action
592
+ * is performed on an ElementFinder, the latest result from the chain can be
593
+ * accessed using then. Unlike a WebElement, an ElementFinder will wait for
594
+ * angular to settle before performing finds or actions.
595
+ *
596
+ * ElementFinder can be used to build a chain of locators that is used to find
597
+ * an element. An ElementFinder does not actually attempt to find the element
598
+ * until an action is called, which means they can be set up in helper files
599
+ * before the page is available.
600
+ *
601
+ * @alias element(locator)
602
+ * @view
603
+ * <span>{{person.name}}</span>
604
+ * <span ng-bind="person.email"></span>
605
+ * <input type="text" ng-model="person.name"/>
606
+ *
607
+ * @example
608
+ * // Find element with {{scopeVar}} syntax.
609
+ * element(by.binding('person.name')).getText().then(function(name) {
610
+ * expect(name).toBe('Foo');
611
+ * });
612
+ *
613
+ * // Find element with ng-bind="scopeVar" syntax.
614
+ * expect(element(by.binding('person.email')).getText()).toBe('foo@bar.com');
615
+ *
616
+ * // Find by model.
617
+ * var input = element(by.model('person.name'));
618
+ * input.sendKeys('123');
619
+ * expect(input.getAttribute('value')).toBe('Foo123');
620
+ *
621
+ * @constructor
622
+ * @param {ElementArrayFinder} elementArrayFinder The ElementArrayFinder
623
+ * that this is branched from.
624
+ * @return {ElementFinder}
625
+ */
626
+ var ElementFinder = function(elementArrayFinder) {
627
+ if (!elementArrayFinder) {
628
+ throw new Error('BUG: elementArrayFinder cannot be empty');
629
+ }
630
+ this.parentElementArrayFinder = elementArrayFinder;
631
+
632
+ // This filter verifies that there is only 1 element returned by the
633
+ // elementArrayFinder. It will warn if there are more than 1 element and
634
+ // throw an error if there are no elements.
635
+ var getWebElements = function() {
636
+ return elementArrayFinder.getWebElements().then(function(webElements) {
637
+ if (webElements.length === 0) {
638
+ throw new webdriver.error.Error(
639
+ webdriver.error.ErrorCode.NO_SUCH_ELEMENT,
640
+ 'No element found using locator: ' +
641
+ elementArrayFinder.locator_.toString());
642
+ } else {
643
+ if (webElements.length > 1) {
644
+ console.log('warning: more than one element found for locator ' +
645
+ elementArrayFinder.locator_.toString() +
646
+ ' - you may need to be more specific');
647
+ }
648
+ return [webElements[0]];
649
+ }
650
+ });
651
+ };
652
+
653
+ // Store a copy of the underlying elementArrayFinder, but with the more
654
+ // restrictive getWebElements (which checks that there is only 1 element).
655
+ this.elementArrayFinder_ = new ElementArrayFinder(
656
+ getWebElements, elementArrayFinder.locator_,
657
+ elementArrayFinder.actionResults_);
658
+
659
+ // Decorate ElementFinder with webdriver functions. Simply calls the
660
+ // underlying elementArrayFinder to perform these functions.
661
+ var self = this;
662
+ WEB_ELEMENT_FUNCTIONS.forEach(function(fnName) {
663
+ self[fnName] = function() {
664
+ return self.elementArrayFinder_[fnName].
665
+ apply(self.elementArrayFinder_, arguments).toElementFinder_();
666
+ };
667
+ });
668
+ };
669
+ util.inherits(ElementFinder, webdriver.promise.Promise);
670
+
671
+ /**
672
+ * See ElementArrayFinder.prototype.locator
673
+ * @return {webdriver.Locator}
674
+ */
675
+ ElementFinder.prototype.locator = function() {
676
+ return this.elementArrayFinder_.locator();
677
+ };
678
+
679
+ /**
680
+ * Returns the WebElement represented by this ElementFinder.
681
+ * Throws the WebDriver error if the element doesn't exist.
682
+ *
683
+ * @example
684
+ * The following three expressions are equivalent.
685
+ * - element(by.css('.parent')).getWebElement();
686
+ * - browser.waitForAngular(); browser.driver.findElement(by.css('.parent'));
687
+ * - browser.findElement(by.css('.parent'))
688
+ *
689
+ * @alias element(locator).getWebElement()
690
+ * @return {webdriver.WebElement}
691
+ */
692
+ ElementFinder.prototype.getWebElement = function() {
693
+ var id = this.elementArrayFinder_.getWebElements().then(
694
+ function(parentWebElements) {
695
+ return parentWebElements[0];
696
+ });
697
+ return new webdriver.WebElementPromise(ptor.driver, id);
698
+ };
699
+
700
+ /**
701
+ * Access the underlying actionResult of ElementFinder. Implementation allows
702
+ * ElementFinder to be used as a webdriver.promise.Promise
703
+ * @param {function(webdriver.promise.Promise)} fn Function which takes
704
+ * the value of the underlying actionResult.
705
+ *
706
+ * @return {webdriver.promise.Promise} Promise which contains the results of
707
+ * evaluating fn.
708
+ */
709
+ ElementFinder.prototype.then = function(fn, errorFn) {
710
+ return this.elementArrayFinder_.then(function(actionResults) {
711
+ return fn(actionResults[0]);
712
+ }, errorFn);
713
+ };
714
+
715
+ /**
716
+ * Calls to element may be chained to find an array of elements within a parent.
717
+ *
718
+ * @alias element(locator).all(locator)
719
+ * @view
720
+ * <div class="parent">
721
+ * <ul>
722
+ * <li class="one">First</li>
723
+ * <li class="two">Second</li>
724
+ * <li class="three">Third</li>
725
+ * </ul>
726
+ * </div>
727
+ *
728
+ * @example
729
+ * var items = element(by.css('.parent')).all(by.tagName('li'))
730
+ *
731
+ * @param {webdriver.Locator} subLocator
732
+ * @return {ElementArrayFinder}
733
+ */
734
+ ElementFinder.prototype.all = function(subLocator) {
735
+ return this.elementArrayFinder_.all(subLocator);
736
+ };
737
+
738
+ /**
739
+ * Calls to element may be chained to find elements within a parent.
740
+ *
741
+ * @alias element(locator).element(locator)
742
+ * @view
743
+ * <div class="parent">
744
+ * <div class="child">
745
+ * Child text
746
+ * <div>{{person.phone}}</div>
747
+ * </div>
748
+ * </div>
749
+ *
750
+ * @example
751
+ * // Chain 2 element calls.
752
+ * var child = element(by.css('.parent')).
753
+ * element(by.css('.child'));
754
+ * expect(child.getText()).toBe('Child text\n555-123-4567');
755
+ *
756
+ * // Chain 3 element calls.
757
+ * var triple = element(by.css('.parent')).
758
+ * element(by.css('.child')).
759
+ * element(by.binding('person.phone'));
760
+ * expect(triple.getText()).toBe('555-123-4567');
761
+ *
762
+ * @param {webdriver.Locator} subLocator
763
+ * @return {ElementFinder}
764
+ */
765
+ ElementFinder.prototype.element = function(subLocator) {
766
+ return this.all(subLocator).toElementFinder_();
767
+ };
768
+
769
+ /**
770
+ * Shortcut for querying the document directly with css.
771
+ *
772
+ * @alias $$(cssSelector)
773
+ * @view
774
+ * <div class="count">
775
+ * <span class="one">First</span>
776
+ * <span class="two">Second</span>
777
+ * </div>
778
+ *
779
+ * @example
780
+ * // The following protractor expressions are equivalent.
781
+ * var list = element.all(by.css('.count span'));
782
+ * expect(list.count()).toBe(2);
783
+ *
784
+ * list = $$('.count span');
785
+ * expect(list.count()).toBe(2);
786
+ * expect(list.get(0).getText()).toBe('First');
787
+ * expect(list.get(1).getText()).toBe('Second');
788
+ *
789
+ * @param {string} selector a css selector
790
+ * @return {ElementArrayFinder} which identifies the
791
+ * array of the located {@link webdriver.WebElement}s.
792
+ */
793
+ ElementFinder.prototype.$$ = function(selector) {
794
+ return this.all(webdriver.By.css(selector));
795
+ };
796
+
797
+ /**
798
+ * Shortcut for querying the document directly with css.
799
+ *
800
+ * @alias $(cssSelector)
801
+ * @view
802
+ * <div class="count">
803
+ * <span class="one">First</span>
804
+ * <span class="two">Second</span>
805
+ * </div>
806
+ *
807
+ * @example
808
+ * var item = $('.count .two');
809
+ * expect(item.getText()).toBe('Second');
810
+ *
811
+ * @param {string} selector A css selector
812
+ * @return {ElementFinder} which identifies the located
813
+ * {@link webdriver.WebElement}
814
+ */
815
+ ElementFinder.prototype.$ = function(selector) {
816
+ return this.element(webdriver.By.css(selector));
817
+ };
818
+
819
+ /**
820
+ * Determine whether the element is present on the page.
821
+ *
822
+ * @view
823
+ * <span>{{person.name}}</span>
824
+ *
825
+ * @example
826
+ * // Element exists.
827
+ * expect(element(by.binding('person.name')).isPresent()).toBe(true);
828
+ *
829
+ * // Element not present.
830
+ * expect(element(by.binding('notPresent')).isPresent()).toBe(false);
831
+ *
832
+ * @return {ElementFinder} which resolves to whether
833
+ * the element is present on the page.
834
+ */
835
+ ElementFinder.prototype.isPresent = function() {
836
+ return this.parentElementArrayFinder.count().then(function(count) {
837
+ return !!count;
838
+ });
839
+ };
840
+
841
+ /**
842
+ * Override for WebElement.prototype.isElementPresent so that protractor waits
843
+ * for Angular to settle before making the check.
844
+ *
845
+ * @see ElementFinder.isPresent
846
+ *
847
+ * @param {webdriver.Locator} subLocator Locator for element to look for.
848
+ * @return {ElementFinder} which resolves to whether
849
+ * the element is present on the page.
850
+ */
851
+ ElementFinder.prototype.isElementPresent = function(subLocator) {
852
+ return this.element(subLocator).isPresent();
853
+ };
854
+
855
+ /**
856
+ * Evaluates the input as if it were on the scope of the current element.
857
+ * @param {string} expression
858
+ *
859
+ * @return {ElementFinder} which resolves to the evaluated expression.
860
+ */
861
+ ElementFinder.prototype.evaluate = function(expression) {
862
+ return this.elementArrayFinder_.evaluate(expression).toElementFinder_();
863
+ };
864
+
865
+ /**
866
+ * See ElementArrayFinder.prototype.allowAnimations.
867
+ * @param {string} value
868
+ *
869
+ * @return {ElementFinder} which resolves to whether animation is allowed.
870
+ */
871
+ ElementFinder.prototype.allowAnimations = function(value) {
872
+ return this.elementArrayFinder_.allowAnimations(value).toElementFinder_();
873
+ };
874
+
875
+ /**
876
+ * Webdriver relies on this function to be present on Promises, so adding
877
+ * this dummy function as we inherited from webdriver.promise.Promise, but
878
+ * this function is irrelevant to our usage
879
+ *
880
+ * @return {boolean} Always false as ElementFinder is never in pending state.
881
+ */
882
+ ElementFinder.prototype.isPending = function() {
883
+ return false;
884
+ };
885
+
886
+ var element = function(locator) {
887
+ return new ElementArrayFinder().all(locator).toElementFinder_();
888
+ };
889
+
890
+ element.all = function(locator) {
891
+ return new ElementArrayFinder().all(locator);
892
+ };
893
+
894
+ return element;
895
+ };
896
+ JS