42.5 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3const selenium_webdriver_1 = require("selenium-webdriver");
4const locators_1 = require("./locators");
5const logger_1 = require("./logger");
6const util_1 = require("./util");
7let clientSideScripts = require('./clientsidescripts');
8let logger = new logger_1.Logger('element');
9class WebdriverWebElement {
10}
11exports.WebdriverWebElement = WebdriverWebElement;
12let WEB_ELEMENT_FUNCTIONS = [
13 'click', 'sendKeys', 'getTagName', 'getCssValue', 'getAttribute', 'getText', 'getSize',
14 'getLocation', 'isEnabled', 'isSelected', 'submit', 'clear', 'isDisplayed', 'getId',
15 'takeScreenshot'
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 satisfy 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 * // Or using the shortcut $$() notation instead of element.all(by.css()):
60 *
61 * $$('.items li').then(function(items) {
62 * expect(items.length).toBe(3);
63 * expect(items[0].getText()).toBe('First');
64 * });
65 *
66 * @constructor
67 * @param {ProtractorBrowser} browser A browser instance.
68 * @param {function(): Array.<webdriver.WebElement>} getWebElements A function
69 * that returns a list of the underlying Web Elements.
70 * @param {webdriver.Locator} locator The most relevant locator. It is only
71 * used for error reporting and ElementArrayFinder.locator.
72 * @param {Array.<webdriver.promise.Promise>} opt_actionResults An array
73 * of promises which will be retrieved with then. Resolves to the latest
74 * action result, or null if no action has been called.
75 * @returns {ElementArrayFinder}
76 */
77class ElementArrayFinder extends WebdriverWebElement {
78 constructor(browser_, getWebElements = null, locator_, actionResults_ = null) {
79 super();
80 this.browser_ = browser_;
81 this.getWebElements = getWebElements;
82 this.locator_ = locator_;
83 this.actionResults_ = actionResults_;
84 // TODO(juliemr): might it be easier to combine this with our docs and just
85 // wrap each one explicity with its own documentation?
86 WEB_ELEMENT_FUNCTIONS.forEach((fnName) => {
87 this[fnName] = (...args) => {
88 let actionFn = (webElem) => {
89 return webElem[fnName].apply(webElem, args);
90 };
91 return this.applyAction_(actionFn);
92 };
93 });
94 }
95 /**
96 * Create a shallow copy of ElementArrayFinder.
97 *
98 * @returns {!ElementArrayFinder} A shallow copy of this.
99 */
100 clone() {
101 // A shallow copy is all we need since the underlying fields can never be
102 // modified. (Locator can be modified by the user, but that should
103 // rarely/never happen and it doesn't affect functionalities).
104 return new ElementArrayFinder(this.browser_, this.getWebElements, this.locator_, this.actionResults_);
105 }
106 /**
107 * Calls to ElementArrayFinder may be chained to find an array of elements
108 * using the current elements in this ElementArrayFinder as the starting
109 * point. This function returns a new ElementArrayFinder which would contain
110 * the children elements found (and could also be empty).
111 *
112 * @alias element.all(locator).all(locator)
113 * @view
114 * <div id='id1' class="parent">
115 * <ul>
116 * <li class="foo">1a</li>
117 * <li class="baz">1b</li>
118 * </ul>
119 * </div>
120 * <div id='id2' class="parent">
121 * <ul>
122 * <li class="foo">2a</li>
123 * <li class="bar">2b</li>
124 * </ul>
125 * </div>
126 *
127 * @example
128 * let foo = element.all(by.css('.parent')).all(by.css('.foo'));
129 * expect(foo.getText()).toEqual(['1a', '2a']);
130 * let baz = element.all(by.css('.parent')).all(by.css('.baz'));
131 * expect(baz.getText()).toEqual(['1b']);
132 * let nonexistent = element.all(by.css('.parent'))
133 * .all(by.css('.NONEXISTENT'));
134 * expect(nonexistent.getText()).toEqual(['']);
135 *
136 * // Or using the shortcut $$() notation instead of element.all(by.css()):
137 *
138 * let foo = $$('.parent').$$('.foo');
139 * expect(foo.getText()).toEqual(['1a', '2a']);
140 * let baz = $$('.parent').$$('.baz');
141 * expect(baz.getText()).toEqual(['1b']);
142 * let nonexistent = $$('.parent').$$('.NONEXISTENT');
143 * expect(nonexistent.getText()).toEqual(['']);
144 *
145 * @param {webdriver.Locator} subLocator
146 * @returns {ElementArrayFinder}
147 */
148 all(locator) {
149 let ptor = this.browser_;
150 let getWebElements = () => {
151 if (this.getWebElements === null) {
152 // This is the first time we are looking for an element
153 return ptor.waitForAngular('Locator: ' + locator)
154 .then(() => {
155 if (locators_1.isProtractorLocator(locator)) {
156 return locator.findElementsOverride(ptor.driver, null, ptor.rootEl);
157 }
158 else {
159 return ptor.driver.findElements(locator);
160 }
161 });
162 }
163 else {
164 return this.getWebElements().then((parentWebElements) => {
165 // For each parent web element, find their children and construct
166 // a list of Promise<List<child_web_element>>
167 let childrenPromiseList = parentWebElements.map((parentWebElement) => {
168 return locators_1.isProtractorLocator(locator) ?
169 locator.findElementsOverride(ptor.driver, parentWebElement, ptor.rootEl) :
170 parentWebElement.findElements(locator);
171 });
172 // Resolve the list of Promise<List<child_web_elements>> and merge
173 // into a single list
174 return selenium_webdriver_1.promise.all(childrenPromiseList)
175 .then((resolved) => {
176 return resolved.reduce((childrenList, resolvedE) => {
177 return childrenList.concat(resolvedE);
178 }, []);
179 });
180 });
181 }
182 };
183 return new ElementArrayFinder(this.browser_, getWebElements, locator);
184 }
185 /**
186 * Apply a filter function to each element within the ElementArrayFinder.
187 * Returns a new ElementArrayFinder with all elements that pass the filter
188 * function. The filter function receives the ElementFinder as the first
189 * argument and the index as a second arg. This does not actually retrieve
190 * the underlying list of elements, so it can be used in page objects.
191 *
192 * @alias element.all(locator).filter(filterFn)
193 * @view
194 * <ul class="items">
195 * <li class="one">First</li>
196 * <li class="two">Second</li>
197 * <li class="three">Third</li>
198 * </ul>
199 *
200 * @example
201 * element.all(by.css('.items li')).filter(function(elem, index) {
202 * return elem.getText().then(function(text) {
203 * return text === 'Third';
204 * });
205 * }).first().click();
206 *
207 * // Or using the shortcut $$() notation instead of element.all(by.css()):
208 *
209 * $$('.items li').filter(function(elem, index) {
210 * return elem.getText().then(function(text) {
211 * return text === 'Third';
212 * });
213 * }).first().click();
214 *
215 * @param {function(ElementFinder, number): webdriver.WebElement.Promise}
216 * filterFn
217 * Filter function that will test if an element should be returned.
218 * filterFn can either return a boolean or a promise that resolves to a
219 * boolean
220 * @returns {!ElementArrayFinder} A ElementArrayFinder that represents an
221 * array
222 * of element that satisfy the filter function.
223 */
224 filter(filterFn) {
225 let getWebElements = () => {
226 return this.getWebElements().then((parentWebElements) => {
227 let list = parentWebElements.map((parentWebElement, index) => {
228 let elementFinder = ElementFinder.fromWebElement_(this.browser_, parentWebElement, this.locator_);
229 return filterFn(elementFinder, index);
230 });
231 return selenium_webdriver_1.promise.all(list).then((resolvedList) => {
232 return parentWebElements.filter((parentWebElement, index) => {
233 return resolvedList[index];
234 });
235 });
236 });
237 };
238 return new ElementArrayFinder(this.browser_, getWebElements, this.locator_);
239 }
240 /**
241 * Get an element within the ElementArrayFinder by index. The index starts at 0.
242 * Negative indices are wrapped (i.e. -i means ith element from last)
243 * This does not actually retrieve the underlying element.
244 *
245 * @alias element.all(locator).get(index)
246 * @view
247 * <ul class="items">
248 * <li>First</li>
249 * <li>Second</li>
250 * <li>Third</li>
251 * </ul>
252 *
253 * @example
254 * let list = element.all(by.css('.items li'));
255 * expect(list.get(0).getText()).toBe('First');
256 * expect(list.get(1).getText()).toBe('Second');
257 *
258 * // Or using the shortcut $$() notation instead of element.all(by.css()):
259 *
260 * let list = $$('.items li');
261 * expect(list.get(0).getText()).toBe('First');
262 * expect(list.get(1).getText()).toBe('Second');
263 *
264 * @param {number|webdriver.promise.Promise} index Element index.
265 * @returns {ElementFinder} finder representing element at the given index.
266 */
267 get(index) {
268 let getWebElements = () => {
269 return selenium_webdriver_1.promise.all([index, this.getWebElements()]).then(([i, parentWebElements]) => {
270 if (i < 0) {
271 i += parentWebElements.length;
272 }
273 if (i < 0 || i >= parentWebElements.length) {
274 throw new selenium_webdriver_1.error.NoSuchElementError('Index out of bound. Trying to access element at index: ' + index +
275 ', but there are only ' + parentWebElements.length + ' elements that match ' +
276 'locator ' + this.locator_.toString());
277 }
278 return [parentWebElements[i]];
279 });
280 };
281 return new ElementArrayFinder(this.browser_, getWebElements, this.locator_).toElementFinder_();
282 }
283 /**
284 * Get the first matching element for the ElementArrayFinder. This does not
285 * actually retrieve the underlying element.
286 *
287 * @alias element.all(locator).first()
288 * @view
289 * <ul class="items">
290 * <li>First</li>
291 * <li>Second</li>
292 * <li>Third</li>
293 * </ul>
294 *
295 * @example
296 * let first = element.all(by.css('.items li')).first();
297 * expect(first.getText()).toBe('First');
298 *
299 * // Or using the shortcut $$() notation instead of element.all(by.css()):
300 *
301 * let first = $$('.items li').first();
302 * expect(first.getText()).toBe('First');
303 *
304 * @returns {ElementFinder} finder representing the first matching element
305 */
306 first() {
307 return this.get(0);
308 }
309 ;
310 /**
311 * Get the last matching element for the ElementArrayFinder. This does not
312 * actually retrieve the underlying element.
313 *
314 * @alias element.all(locator).last()
315 * @view
316 * <ul class="items">
317 * <li>First</li>
318 * <li>Second</li>
319 * <li>Third</li>
320 * </ul>
321 *
322 * @example
323 * let last = element.all(by.css('.items li')).last();
324 * expect(last.getText()).toBe('Third');
325 *
326 * // Or using the shortcut $$() notation instead of element.all(by.css()):
327 *
328 * let last = $$('.items li').last();
329 * expect(last.getText()).toBe('Third');
330 *
331 * @returns {ElementFinder} finder representing the last matching element
332 */
333 last() {
334 return this.get(-1);
335 }
336 /**
337 * Shorthand function for finding arrays of elements by css.
338 * `element.all(by.css('.abc'))` is equivalent to `$$('.abc')`
339 *
340 * @alias $$(cssSelector)
341 * @view
342 * <div class="count">
343 * <span class="one">First</span>
344 * <span class="two">Second</span>
345 * </div>
346 *
347 * @example
348 * // The following two blocks of code are equivalent.
349 * let list = element.all(by.css('.count span'));
350 * expect(list.count()).toBe(2);
351 * expect(list.get(0).getText()).toBe('First');
352 * expect(list.get(1).getText()).toBe('Second');
353 *
354 * // Or using the shortcut $$() notation instead of element.all(by.css()):
355 *
356 * let list = $$('.count span');
357 * expect(list.count()).toBe(2);
358 * expect(list.get(0).getText()).toBe('First');
359 * expect(list.get(1).getText()).toBe('Second');
360 *
361 * @param {string} selector a css selector
362 * @returns {ElementArrayFinder} which identifies the
363 * array of the located {@link webdriver.WebElement}s.
364 */
365 $$(selector) {
366 return this.all(selenium_webdriver_1.By.css(selector));
367 }
368 /**
369 * Returns an ElementFinder representation of ElementArrayFinder. It ensures
370 * that the ElementArrayFinder resolves to one and only one underlying
371 * element.
372 *
373 * @returns {ElementFinder} An ElementFinder representation
374 * @private
375 */
376 toElementFinder_() {
377 return new ElementFinder(this.browser_, this);
378 }
379 /**
380 * Count the number of elements represented by the ElementArrayFinder.
381 *
382 * @alias element.all(locator).count()
383 * @view
384 * <ul class="items">
385 * <li>First</li>
386 * <li>Second</li>
387 * <li>Third</li>
388 * </ul>
389 *
390 * @example
391 * let list = element.all(by.css('.items li'));
392 * expect(list.count()).toBe(3);
393 *
394 * // Or using the shortcut $$() notation instead of element.all(by.css()):
395 *
396 * let list = $$('.items li');
397 * expect(list.count()).toBe(3);
398 *
399 * @returns {!webdriver.promise.Promise} A promise which resolves to the
400 * number of elements matching the locator.
401 */
402 count() {
403 return this.getWebElements().then((arr) => {
404 return arr.length;
405 }, (err) => {
406 if (err instanceof selenium_webdriver_1.error.NoSuchElementError) {
407 return 0;
408 }
409 else {
410 throw err;
411 }
412 });
413 }
414 /**
415 * Returns true if there are any elements present that match the finder.
416 *
417 * @alias element.all(locator).isPresent()
418 *
419 * @example
420 * expect($('.item').isPresent()).toBeTruthy();
421 *
422 * @returns {Promise<boolean>}
423 */
424 isPresent() {
425 return this.count().then((count) => {
426 return count > 0;
427 });
428 }
429 /**
430 * Returns the most relevant locator.
431 *
432 * @example
433 * // returns by.css('#ID1')
434 * $('#ID1').locator();
435 *
436 * // returns by.css('#ID2')
437 * $('#ID1').$('#ID2').locator();
438 *
439 * // returns by.css('#ID1')
440 * $$('#ID1').filter(filterFn).get(0).click().locator();
441 *
442 * @returns {webdriver.Locator}
443 */
444 locator() {
445 return this.locator_;
446 }
447 /**
448 * Apply an action function to every element in the ElementArrayFinder,
449 * and return a new ElementArrayFinder that contains the results of the
450 * actions.
451 *
452 * @param {function(ElementFinder)} actionFn
453 *
454 * @returns {ElementArrayFinder}
455 * @private
456 */
457 // map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[];
458 applyAction_(actionFn) {
459 let callerError = new Error();
460 let actionResults = this.getWebElements()
461 .then((arr) => selenium_webdriver_1.promise.all(arr.map(actionFn)))
462 .then((value) => {
463 return { passed: true, value: value };
464 }, (error) => {
465 return { passed: false, value: error };
466 });
467 let getWebElements = () => actionResults.then(() => this.getWebElements());
468 actionResults = actionResults.then((result) => {
469 if (result.passed) {
470 return result.value;
471 }
472 else {
473 let noSuchErr;
474 if (result.value instanceof Error) {
475 noSuchErr = result.value;
476 noSuchErr.stack = noSuchErr.stack + callerError.stack;
477 }
478 else {
479 noSuchErr = new Error(result.value);
480 noSuchErr.stack = callerError.stack;
481 }
482 throw noSuchErr;
483 }
484 });
485 return new ElementArrayFinder(this.browser_, getWebElements, this.locator_, actionResults);
486 }
487 /**
488 * Represents the ElementArrayFinder as an array of ElementFinders.
489 *
490 * @returns {Array.<ElementFinder>} Return a promise, which resolves to a list
491 * of ElementFinders specified by the locator.
492 */
493 asElementFinders_() {
494 return this.getWebElements().then((arr) => {
495 return arr.map((webElem) => {
496 return ElementFinder.fromWebElement_(this.browser_, webElem, this.locator_);
497 });
498 });
499 }
500 /**
501 * Retrieve the elements represented by the ElementArrayFinder. The input
502 * function is passed to the resulting promise, which resolves to an
503 * array of ElementFinders.
504 *
505 * @alias element.all(locator).then(thenFunction)
506 * @view
507 * <ul class="items">
508 * <li>First</li>
509 * <li>Second</li>
510 * <li>Third</li>
511 * </ul>
512 *
513 * @example
514 * element.all(by.css('.items li')).then(function(arr) {
515 * expect(arr.length).toEqual(3);
516 * });
517 *
518 * // Or using the shortcut $$() notation instead of element.all(by.css()):
519 *
520 * $$('.items li').then(function(arr) {
521 * expect(arr.length).toEqual(3);
522 * });
523 *
524 * @param {function(Array.<ElementFinder>)} fn
525 * @param {function(Error)} errorFn
526 *
527 * @returns {!webdriver.promise.Promise} A promise which will resolve to
528 * an array of ElementFinders represented by the ElementArrayFinder.
529 */
530 then(fn, errorFn) {
531 if (this.actionResults_) {
532 return this.actionResults_.then(fn, errorFn);
533 }
534 else {
535 return this.asElementFinders_().then(fn, errorFn);
536 }
537 }
538 /**
539 * Calls the input function on each ElementFinder represented by the
540 * ElementArrayFinder.
541 *
542 * @alias element.all(locator).each(eachFunction)
543 * @view
544 * <ul class="items">
545 * <li>First</li>
546 * <li>Second</li>
547 * <li>Third</li>
548 * </ul>
549 *
550 * @example
551 * element.all(by.css('.items li')).each(function(element, index) {
552 * // Will print 0 First, 1 Second, 2 Third.
553 * element.getText().then(function (text) {
554 * console.log(index, text);
555 * });
556 * });
557 *
558 * // Or using the shortcut $$() notation instead of element.all(by.css()):
559 *
560 * $$('.items li').each(function(element, index) {
561 * // Will print 0 First, 1 Second, 2 Third.
562 * element.getText().then(function (text) {
563 * console.log(index, text);
564 * });
565 * });
566 *
567 * @param {function(ElementFinder)} fn Input function
568 *
569 * @returns {!webdriver.promise.Promise} A promise that will resolve when the
570 * function has been called on all the ElementFinders. The promise will
571 * resolve to null.
572 */
573 each(fn) {
574 return this.map(fn).then(() => {
575 return null;
576 });
577 }
578 /**
579 * Apply a map function to each element within the ElementArrayFinder. The
580 * callback receives the ElementFinder as the first argument and the index as
581 * a second arg.
582 *
583 * @alias element.all(locator).map(mapFunction)
584 * @view
585 * <ul class="items">
586 * <li class="one">First</li>
587 * <li class="two">Second</li>
588 * <li class="three">Third</li>
589 * </ul>
590 *
591 * @example
592 * let items = element.all(by.css('.items li')).map(function(elm, index) {
593 * return {
594 * index: index,
595 * text: elm.getText(),
596 * class: elm.getAttribute('class')
597 * };
598 * });
599 * expect(items).toEqual([
600 * {index: 0, text: 'First', class: 'one'},
601 * {index: 1, text: 'Second', class: 'two'},
602 * {index: 2, text: 'Third', class: 'three'}
603 * ]);
604 *
605 * // Or using the shortcut $$() notation instead of element.all(by.css()):
606 *
607 * let items = $$('.items li').map(function(elm, index) {
608 * return {
609 * index: index,
610 * text: elm.getText(),
611 * class: elm.getAttribute('class')
612 * };
613 * });
614 * expect(items).toEqual([
615 * {index: 0, text: 'First', class: 'one'},
616 * {index: 1, text: 'Second', class: 'two'},
617 * {index: 2, text: 'Third', class: 'three'}
618 * ]);
619 *
620 * @param {function(ElementFinder, number)} mapFn Map function that
621 * will be applied to each element.
622 * @returns {!webdriver.promise.Promise} A promise that resolves to an array
623 * of values returned by the map function.
624 */
625 map(mapFn) {
626 return this.asElementFinders_().then((arr) => {
627 let list = arr.map((elementFinder, index) => {
628 let mapResult = mapFn(elementFinder, index);
629 // All nested arrays and objects will also be fully resolved.
630 return selenium_webdriver_1.promise.fullyResolved(mapResult);
631 });
632 return selenium_webdriver_1.promise.all(list);
633 });
634 }
635 ;
636 /**
637 * Apply a reduce function against an accumulator and every element found
638 * using the locator (from left-to-right). The reduce function has to reduce
639 * every element into a single value (the accumulator). Returns promise of
640 * the accumulator. The reduce function receives the accumulator, current
641 * ElementFinder, the index, and the entire array of ElementFinders,
642 * respectively.
643 *
644 * @alias element.all(locator).reduce(reduceFn)
645 * @view
646 * <ul class="items">
647 * <li class="one">First</li>
648 * <li class="two">Second</li>
649 * <li class="three">Third</li>
650 * </ul>
651 *
652 * @example
653 * let value = element.all(by.css('.items li')).reduce(function(acc, elem) {
654 * return elem.getText().then(function(text) {
655 * return acc + text + ' ';
656 * });
657 * }, '');
658 *
659 * expect(value).toEqual('First Second Third ');
660 *
661 * // Or using the shortcut $$() notation instead of element.all(by.css()):
662 *
663 * let value = $$('.items li').reduce(function(acc, elem) {
664 * return elem.getText().then(function(text) {
665 * return acc + text + ' ';
666 * });
667 * }, '');
668 *
669 * expect(value).toEqual('First Second Third ');
670 *
671 * @param {function(number, ElementFinder, number, Array.<ElementFinder>)}
672 * reduceFn Reduce function that reduces every element into a single
673 * value.
674 * @param {*} initialValue Initial value of the accumulator.
675 * @returns {!webdriver.promise.Promise} A promise that resolves to the final
676 * value of the accumulator.
677 */
678 reduce(reduceFn, initialValue) {
679 let valuePromise = selenium_webdriver_1.promise.when(initialValue);
680 return this.asElementFinders_().then((arr) => {
681 return arr.reduce((valuePromise, elementFinder, index) => {
682 return valuePromise.then((value) => {
683 return reduceFn(value, elementFinder, index, arr);
684 });
685 }, valuePromise);
686 });
687 }
688 /**
689 * Evaluates the input as if it were on the scope of the current underlying
690 * elements.
691 *
692 * @view
693 * <span class="foo">{{letiableInScope}}</span>
694 *
695 * @example
696 * let value = element.all(by.css('.foo')).evaluate('letiableInScope');
697 *
698 * // Or using the shortcut $$() notation instead of element.all(by.css()):
699 *
700 * let value = $$('.foo').evaluate('letiableInScope');
701 *
702 * @param {string} expression
703 *
704 * @returns {ElementArrayFinder} which resolves to the
705 * evaluated expression for each underlying element.
706 * The result will be resolved as in
707 * {@link webdriver.WebDriver.executeScript}. In summary - primitives will
708 * be resolved as is, functions will be converted to string, and elements
709 * will be returned as a WebElement.
710 */
711 evaluate(expression) {
712 let evaluationFn = (webElem) => {
713 return webElem.getDriver().executeScript(clientSideScripts.evaluate, webElem, expression);
714 };
715 return this.applyAction_(evaluationFn);
716 }
717 /**
718 * Determine if animation is allowed on the current underlying elements.
719 * @param {string} value
720 *
721 * @example
722 * // Turns off ng-animate animations for all elements in the <body>
723 * element(by.css('body')).allowAnimations(false);
724 *
725 * // Or using the shortcut $() notation instead of element(by.css()):
726 *
727 * $('body').allowAnimations(false);
728 *
729 * @returns {ElementArrayFinder} which resolves to whether animation is
730 * allowed.
731 */
732 allowAnimations(value) {
733 let allowAnimationsTestFn = (webElem) => {
734 return webElem.getDriver().executeScript(clientSideScripts.allowAnimations, webElem, value);
735 };
736 return this.applyAction_(allowAnimationsTestFn);
737 }
738}
739exports.ElementArrayFinder = ElementArrayFinder;
740/**
741 * The ElementFinder simply represents a single element of an
742 * ElementArrayFinder (and is more like a convenience object). As a result,
743 * anything that can be done with an ElementFinder, can also be done using
744 * an ElementArrayFinder.
745 *
746 * The ElementFinder can be treated as a WebElement for most purposes, in
747 * particular, you may perform actions (i.e. click, getText) on them as you
748 * would a WebElement. Once an action is performed on an ElementFinder, the
749 * latest result from the chain can be accessed using the then method.
750 * Unlike a WebElement, an ElementFinder will wait for angular to settle before
751 * performing finds or actions.
752 *
753 * ElementFinder can be used to build a chain of locators that is used to find
754 * an element. An ElementFinder does not actually attempt to find the element
755 * until an action is called, which means they can be set up in helper files
756 * before the page is available.
757 *
758 * @alias element(locator)
759 * @view
760 * <span>{{person.name}}</span>
761 * <span ng-bind="person.email"></span>
762 * <input type="text" ng-model="person.name"/>
763 *
764 * @example
765 * // Find element with {{scopelet}} syntax.
766 * element(by.binding('person.name')).getText().then(function(name) {
767 * expect(name).toBe('Foo');
768 * });
769 *
770 * // Find element with ng-bind="scopelet" syntax.
771 * expect(element(by.binding('person.email')).getText()).toBe('foo@bar.com');
772 *
773 * // Find by model.
774 * let input = element(by.model('person.name'));
775 * input.sendKeys('123');
776 * expect(input.getAttribute('value')).toBe('Foo123');
777 *
778 * @constructor
779 * @extends {webdriver.WebElement}
780 * @param {ProtractorBrowser} browser_ A browser instance.
781 * @param {ElementArrayFinder} elementArrayFinder The ElementArrayFinder
782 * that this is branched from.
783 * @returns {ElementFinder}
784 */
785class ElementFinder extends WebdriverWebElement {
786 constructor(browser_, elementArrayFinder) {
787 super();
788 this.browser_ = browser_;
789 this.then = null;
790 if (!elementArrayFinder) {
791 throw new Error('BUG: elementArrayFinder cannot be empty');
792 }
793 this.parentElementArrayFinder = elementArrayFinder;
794 // Only have a `then` method if the parent element array finder
795 // has action results.
796 if (this.parentElementArrayFinder.actionResults_) {
797 // Access the underlying actionResult of ElementFinder.
798 this.then =
799 (fn, errorFn) => {
800 return this.elementArrayFinder_.then((actionResults) => {
801 if (!fn) {
802 return actionResults[0];
803 }
804 return fn(actionResults[0]);
805 }, errorFn);
806 };
807 }
808 // This filter verifies that there is only 1 element returned by the
809 // elementArrayFinder. It will warn if there are more than 1 element and
810 // throw an error if there are no elements.
811 let getWebElements = () => {
812 return elementArrayFinder.getWebElements().then((webElements) => {
813 if (webElements.length === 0) {
814 throw new selenium_webdriver_1.error.NoSuchElementError('No element found using locator: ' + elementArrayFinder.locator().toString());
815 }
816 else {
817 if (webElements.length > 1) {
818 logger.warn('more than one element found for locator ' +
819 elementArrayFinder.locator().toString() + ' - the first result will be used');
820 }
821 return [webElements[0]];
822 }
823 });
824 };
825 // Store a copy of the underlying elementArrayFinder, but with the more
826 // restrictive getWebElements (which checks that there is only 1 element).
827 this.elementArrayFinder_ = new ElementArrayFinder(this.browser_, getWebElements, elementArrayFinder.locator(), elementArrayFinder.actionResults_);
828 WEB_ELEMENT_FUNCTIONS.forEach((fnName) => {
829 (this)[fnName] = (...args) => {
830 return (this.elementArrayFinder_)[fnName]
831 .apply(this.elementArrayFinder_, args)
832 .toElementFinder_();
833 };
834 });
835 }
836 static fromWebElement_(browser, webElem, locator) {
837 let getWebElements = () => {
838 return selenium_webdriver_1.promise.when([webElem]);
839 };
840 return new ElementArrayFinder(browser, getWebElements, locator).toElementFinder_();
841 }
842 /**
843 * Create a shallow copy of ElementFinder.
844 *
845 * @returns {!ElementFinder} A shallow copy of this.
846 */
847 clone() {
848 // A shallow copy is all we need since the underlying fields can never be
849 // modified
850 return new ElementFinder(this.browser_, this.parentElementArrayFinder);
851 }
852 /**
853 * @see ElementArrayFinder.prototype.locator
854 *
855 * @returns {webdriver.Locator}
856 */
857 locator() {
858 return this.elementArrayFinder_.locator();
859 }
860 /**
861 * Returns the WebElement represented by this ElementFinder.
862 * Throws the WebDriver error if the element doesn't exist.
863 *
864 * @alias element(locator).getWebElement()
865 * @view
866 * <div class="parent">
867 * some text
868 * </div>
869 *
870 * @example
871 * // The following four expressions are equivalent.
872 * $('.parent').getWebElement();
873 * element(by.css('.parent')).getWebElement();
874 * browser.driver.findElement(by.css('.parent'));
875 * browser.findElement(by.css('.parent'));
876 *
877 * @returns {webdriver.WebElementPromise}
878 */
879 getWebElement() {
880 let id = this.elementArrayFinder_.getWebElements().then((parentWebElements) => {
881 return parentWebElements[0];
882 });
883 return new selenium_webdriver_1.WebElementPromise(this.browser_.driver, id);
884 }
885 /**
886 * Calls to {@code all} may be chained to find an array of elements within a
887 * parent.
888 *
889 * @alias element(locator).all(locator)
890 * @view
891 * <div class="parent">
892 * <ul>
893 * <li class="one">First</li>
894 * <li class="two">Second</li>
895 * <li class="three">Third</li>
896 * </ul>
897 * </div>
898 *
899 * @example
900 * let items = element(by.css('.parent')).all(by.tagName('li'));
901 *
902 * // Or using the shortcut $() notation instead of element(by.css()):
903 *
904 * let items = $('.parent').all(by.tagName('li'));
905 *
906 * @param {webdriver.Locator} subLocator
907 * @returns {ElementArrayFinder}
908 */
909 all(subLocator) {
910 return this.elementArrayFinder_.all(subLocator);
911 }
912 /**
913 * Calls to {@code element} may be chained to find elements within a parent.
914 *
915 * @alias element(locator).element(locator)
916 * @view
917 * <div class="parent">
918 * <div class="child">
919 * Child text
920 * <div>{{person.phone}}</div>
921 * </div>
922 * </div>
923 *
924 * @example
925 * // Chain 2 element calls.
926 * let child = element(by.css('.parent')).
927 * element(by.css('.child'));
928 * expect(child.getText()).toBe('Child text\n555-123-4567');
929 *
930 * // Chain 3 element calls.
931 * let triple = element(by.css('.parent')).
932 * element(by.css('.child')).
933 * element(by.binding('person.phone'));
934 * expect(triple.getText()).toBe('555-123-4567');
935 *
936 * // Or using the shortcut $() notation instead of element(by.css()):
937 *
938 * // Chain 2 element calls.
939 * let child = $('.parent').$('.child');
940 * expect(child.getText()).toBe('Child text\n555-123-4567');
941 *
942 * // Chain 3 element calls.
943 * let triple = $('.parent').$('.child').
944 * element(by.binding('person.phone'));
945 * expect(triple.getText()).toBe('555-123-4567');
946 *
947 * @param {webdriver.Locator} subLocator
948 * @returns {ElementFinder}
949 */
950 element(subLocator) {
951 return this.all(subLocator).toElementFinder_();
952 }
953 /**
954 * Calls to {@code $$} may be chained to find an array of elements within a
955 * parent.
956 *
957 * @alias element(locator).all(selector)
958 * @view
959 * <div class="parent">
960 * <ul>
961 * <li class="one">First</li>
962 * <li class="two">Second</li>
963 * <li class="three">Third</li>
964 * </ul>
965 * </div>
966 *
967 * @example
968 * let items = element(by.css('.parent')).$$('li');
969 *
970 * // Or using the shortcut $() notation instead of element(by.css()):
971 *
972 * let items = $('.parent').$$('li');
973 *
974 * @param {string} selector a css selector
975 * @returns {ElementArrayFinder}
976 */
977 $$(selector) {
978 return this.all(selenium_webdriver_1.By.css(selector));
979 }
980 /**
981 * Calls to {@code $} may be chained to find elements within a parent.
982 *
983 * @alias element(locator).$(selector)
984 * @view
985 * <div class="parent">
986 * <div class="child">
987 * Child text
988 * <div>{{person.phone}}</div>
989 * </div>
990 * </div>
991 *
992 * @example
993 * // Chain 2 element calls.
994 * let child = element(by.css('.parent')).
995 * $('.child');
996 * expect(child.getText()).toBe('Child text\n555-123-4567');
997 *
998 * // Chain 3 element calls.
999 * let triple = element(by.css('.parent')).
1000 * $('.child').
1001 * element(by.binding('person.phone'));
1002 * expect(triple.getText()).toBe('555-123-4567');
1003 *
1004 * // Or using the shortcut $() notation instead of element(by.css()):
1005 *
1006 * // Chain 2 element calls.
1007 * let child = $('.parent').$('.child');
1008 * expect(child.getText()).toBe('Child text\n555-123-4567');
1009 *
1010 * // Chain 3 element calls.
1011 * let triple = $('.parent').$('.child').
1012 * element(by.binding('person.phone'));
1013 * expect(triple.getText()).toBe('555-123-4567');
1014 *
1015 * @param {string} selector A css selector
1016 * @returns {ElementFinder}
1017 */
1018 $(selector) {
1019 return this.element(selenium_webdriver_1.By.css(selector));
1020 }
1021 /**
1022 * Determine whether the element is present on the page.
1023 *
1024 * @view
1025 * <span>{{person.name}}</span>
1026 *
1027 * @example
1028 * // Element exists.
1029 * expect(element(by.binding('person.name')).isPresent()).toBe(true);
1030 *
1031 * // Element not present.
1032 * expect(element(by.binding('notPresent')).isPresent()).toBe(false);
1033 *
1034 * @returns {webdriver.promise.Promise<boolean>} which resolves to whether
1035 * the element is present on the page.
1036 */
1037 isPresent() {
1038 return this.parentElementArrayFinder.getWebElements().then((arr) => {
1039 if (arr.length === 0) {
1040 return false;
1041 }
1042 return arr[0].isEnabled().then(() => {
1043 return true; // is present, whether it is enabled or not
1044 }, util_1.falseIfMissing);
1045 }, util_1.falseIfMissing);
1046 }
1047 /**
1048 * Same as ElementFinder.isPresent(), except this checks whether the element
1049 * identified by the subLocator is present, rather than the current element
1050 * finder, i.e.: `element(by.css('#abc')).element(by.css('#def')).isPresent()`
1051 * is identical to `element(by.css('#abc')).isElementPresent(by.css('#def'))`.
1052 *
1053 * // Or using the shortcut $() notation instead of element(by.css()):
1054 *
1055 * `$('#abc').$('#def').isPresent()` is identical to
1056 * `$('#abc').isElementPresent($('#def'))`.
1057 *
1058 * @see ElementFinder.isPresent
1059 *
1060 * @param {webdriver.Locator} subLocator Locator for element to look for.
1061 * @returns {webdriver.promise.Promise<boolean>} which resolves to whether
1062 * the subelement is present on the page.
1063 */
1064 isElementPresent(subLocator) {
1065 if (!subLocator) {
1066 throw new Error('SubLocator is not supplied as a parameter to ' +
1067 '`isElementPresent(subLocator)`. You are probably looking for the ' +
1068 'function `isPresent()`.');
1069 }
1070 return this.element(subLocator).isPresent();
1071 }
1072 /**
1073 * Evaluates the input as if it were on the scope of the current element.
1074 * @see ElementArrayFinder.prototype.evaluate
1075 *
1076 * @view
1077 * <span id="foo">{{letiableInScope}}</span>
1078 *
1079 * @example
1080 * let value = element(by.id('foo')).evaluate('letiableInScope');
1081 *
1082 * @param {string} expression
1083 *
1084 * @returns {ElementFinder} which resolves to the evaluated expression.
1085 */
1086 evaluate(expression) {
1087 return this.elementArrayFinder_.evaluate(expression).toElementFinder_();
1088 }
1089 /**
1090 * @see ElementArrayFinder.prototype.allowAnimations.
1091 * @param {string} value
1092 *
1093 * @returns {ElementFinder} which resolves to whether animation is allowed.
1094 */
1095 allowAnimations(value) {
1096 return this.elementArrayFinder_.allowAnimations(value).toElementFinder_();
1097 }
1098 /**
1099 * Compares an element to this one for equality.
1100 *
1101 * @param {!ElementFinder|!webdriver.WebElement} The element to compare to.
1102 *
1103 * @returns {!webdriver.promise.Promise.<boolean>} A promise that will be
1104 * resolved to whether the two WebElements are equal.
1105 */
1106 equals(element) {
1107 return selenium_webdriver_1.WebElement.equals(this.getWebElement(), element.getWebElement ? element.getWebElement() :
1108 element);
1109 }
1110}
1111exports.ElementFinder = ElementFinder;
1112/**
1113 * Shortcut for querying the document directly with css.
1114 * `element(by.css('.abc'))` is equivalent to `$('.abc')`
1115 *
1116 * @alias $(cssSelector)
1117 * @view
1118 * <div class="count">
1119 * <span class="one">First</span>
1120 * <span class="two">Second</span>
1121 * </div>
1122 *
1123 * @example
1124 * let item = $('.count .two');
1125 * expect(item.getText()).toBe('Second');
1126 *
1127 * @param {string} selector A css selector
1128 * @returns {ElementFinder} which identifies the located
1129 * {@link webdriver.WebElement}
1130 */
1131exports.build$ = (element, by) => {
1132 return (selector) => {
1133 return element(by.css(selector));
1134 };
1135};
1136/**
1137 * Shortcut for querying the document directly with css.
1138 * `element.all(by.css('.abc'))` is equivalent to `$$('.abc')`
1139 *
1140 * @alias $$(cssSelector)
1141 * @view
1142 * <div class="count">
1143 * <span class="one">First</span>
1144 * <span class="two">Second</span>
1145 * </div>
1146 *
1147 * @example
1148 * // The following protractor expressions are equivalent.
1149 * let list = element.all(by.css('.count span'));
1150 * expect(list.count()).toBe(2);
1151 *
1152 * list = $$('.count span');
1153 * expect(list.count()).toBe(2);
1154 * expect(list.get(0).getText()).toBe('First');
1155 * expect(list.get(1).getText()).toBe('Second');
1156 *
1157 * @param {string} selector a css selector
1158 * @returns {ElementArrayFinder} which identifies the
1159 * array of the located {@link webdriver.WebElement}s.
1160 */
1161exports.build$$ = (element, by) => {
1162 return (selector) => {
1163 return element.all(by.css(selector));
1164 };
1165};
1166//# sourceMappingURL=element.js.map
\No newline at end of file