|
| 1 | +Using Locators |
| 2 | +============== |
| 3 | + |
| 4 | +The heart of end to end tests for webpages is finding DOM elements, interacting with them, and getting information about the current state of your application. This doc is an overview of how to locate and perform actions on DOM elements using Protractor. |
| 5 | + |
| 6 | +Overview |
| 7 | +-------- |
| 8 | + |
| 9 | +Protractor exports a global function `element`, which takes a *Locator* and will return an *ElementFinder*. This function finds a single element - if you need to manipulate multiple elements, use the `element.all` function. |
| 10 | + |
| 11 | +The *ElementFinder* has a set of *action methods*, such as `click()`, `getText()`, and `sendKeys`. These are the core way to interact with an element and get information back from it. |
| 12 | + |
| 13 | +When you find elements in Protractor all actions are asynchronous. Behind the scenes, all actions are sent to the browser being controlled using the JSON Webdriver Wire Protocol. The browser then performs the action as a user natively would. |
| 14 | + |
| 15 | +Locators |
| 16 | +-------- |
| 17 | + |
| 18 | +A locator tells Protractor how to find a certain DOM element. Protractor exports locator factories on the global `by` object. The most common locators are: |
| 19 | + |
| 20 | +``` |
| 21 | +// find an element using a css selector |
| 22 | +by.css('.myclass') |
| 23 | +
|
| 24 | +// find an element with the given id |
| 25 | +by.id('myid') |
| 26 | +
|
| 27 | +// find an element with a certain ng-model |
| 28 | +by.model('name') |
| 29 | +
|
| 30 | +// find an element bound to the given variable |
| 31 | +by.binding('bindingname') |
| 32 | +``` |
| 33 | + |
| 34 | +See a [list of Protractor specific locators](/docs/api.md#api-protractorby). |
| 35 | + |
| 36 | +The locators are passed to the `element` function, as below: |
| 37 | + |
| 38 | +```js |
| 39 | +element(by.css('some-css')); |
| 40 | +element(by.model('item.name')); |
| 41 | +element(by.binding('item.name')); |
| 42 | +``` |
| 43 | + |
| 44 | +When using CSS Selectors as a locator, you can use the shortcut $() notation: |
| 45 | + |
| 46 | +```js |
| 47 | +$('my-css'); |
| 48 | + |
| 49 | +// Is the same as |
| 50 | + |
| 51 | +element(by.css('my-css')); |
| 52 | +``` |
| 53 | + |
| 54 | +Actions |
| 55 | +------- |
| 56 | + |
| 57 | +`element()` returns an ElementFinder object. The ElementFinder knows how to locate the DOM element using the locator you passed in as a parameter, but it has not actually done so yet. It will not contact the browser until an *action* method has been called. |
| 58 | + |
| 59 | +The most common action methods are: |
| 60 | + |
| 61 | +```js |
| 62 | +var el = element(locator); |
| 63 | + |
| 64 | +// Click on the element |
| 65 | +el.click(); |
| 66 | + |
| 67 | +// Send keys to the element (usually an input) |
| 68 | +el.sendKeys('my text'); |
| 69 | + |
| 70 | +// Clear the text in an element (usually an input) |
| 71 | +el.clear(); |
| 72 | + |
| 73 | +// Get the value of an attribute, for example, get the value of an input |
| 74 | +el.getAttribute('value'); |
| 75 | +``` |
| 76 | + |
| 77 | +Since all actions are asynchronous, all action methods return a [promise](https://code.google.com/p/selenium/wiki/WebDriverJs#Promises). So, to log the text of an element, you would do something like: |
| 78 | +```js |
| 79 | +var el = element(locator); |
| 80 | +el.getText().then(function(text) { |
| 81 | + console.log(text); |
| 82 | +}); |
| 83 | +``` |
| 84 | + |
| 85 | +Any action available in WebDriverJS on a WebElement is available on an ElementFinder. [See a full list](https://github.com/angular/protractor/blob/master/docs/api.md#api-webdriver-webelement-prototype-getdriver). |
| 86 | + |
| 87 | + |
| 88 | +Finding Multiple Elements |
| 89 | +------------------------- |
| 90 | + |
| 91 | +To deal with multiple DOM elements, use the `element.all` function. This also takes a locator as its only parameter. |
| 92 | + |
| 93 | +```js |
| 94 | +element.all(by.css('.selector')).then(function(elements) { |
| 95 | + // elements is an array of ElementFinders. |
| 96 | +}); |
| 97 | +``` |
| 98 | + |
| 99 | +`element.all()` has several helper functions: |
| 100 | + |
| 101 | +```js |
| 102 | +// Number of elements. |
| 103 | +element.all(locator).count(); |
| 104 | + |
| 105 | +// Get my index (starting at 0). |
| 106 | +element.all(locator).get(index); |
| 107 | + |
| 108 | +// First and last. |
| 109 | +element.all(locator).first(); |
| 110 | +element.all(locator).last(); |
| 111 | +``` |
| 112 | + |
| 113 | + |
| 114 | +Finding Sub-Elements |
| 115 | +-------------------- |
| 116 | + |
| 117 | +Simply chain element and element.all together to find sub-elements. For example: |
| 118 | + |
| 119 | +Using a single locator to find an element: |
| 120 | + |
| 121 | +```js |
| 122 | +element(by.css('some-css')); |
| 123 | +``` |
| 124 | + |
| 125 | +Using a single locator to find a list of elements: |
| 126 | +```js |
| 127 | +element.all(by.css('some-css')); |
| 128 | +``` |
| 129 | + |
| 130 | +to find a sub-element: |
| 131 | +```js |
| 132 | +element(by.css('some-css')).element(by.tag('tag-within-css')); |
| 133 | +``` |
| 134 | + |
| 135 | +to find a list of sub-elements: |
| 136 | +```js |
| 137 | +element(by.css('some-css')).all(by.tag('tag-within-css')); |
| 138 | +``` |
| 139 | + |
| 140 | +You can chain with get/first/last as well like so: |
| 141 | + |
| 142 | +```js |
| 143 | +element.all(by.css('some-css')).first().element(by.tag('tag-within-css')); |
| 144 | +element.all(by.css('some-css')).get(index).element(by.tag('tag-within-css')); |
| 145 | +element.all(by.css('some-css')).first().all(by.tag('tag-within-css')); |
| 146 | +``` |
| 147 | + |
| 148 | +Behind the scenes: ElementFinders versus WebElements |
| 149 | +---------------------------------------------------- |
| 150 | + |
| 151 | +If you're familiar with WebDriver and WebElements, or you're just curious about the WebElements mentioned above, you may be wondering how they relate to ElementFinders. |
| 152 | + |
| 153 | +When you call `driver.findElement(locator)`, WebDriver immediately sends a command over to the browser asking it to locate the element. This isn't great for creating page objects, because we want to be able to do things in setup (before a page may have been loaded) like |
| 154 | + |
| 155 | +```js |
| 156 | +var myButton = ??; |
| 157 | +``` |
| 158 | +
|
| 159 | +and re-use the varialbe `myButton` throughout your test. ElementFinders get around this by simply storing the locator information until an action is called. |
| 160 | +
|
| 161 | +```js |
| 162 | +var myButton = element(locator); |
| 163 | +// No command has been sent to the browser yet. |
| 164 | +``` |
| 165 | +
|
| 166 | +Not until you call an action will the browser recieve any commands. |
| 167 | +
|
| 168 | +```js |
| 169 | +myButton.click(); |
| 170 | +// Now two commands are sent to the browser - find the element, and then click it |
| 171 | +``` |
| 172 | +
|
| 173 | +ElementFinders also enable chaining to find subelements, such as `element(locator1).element(locator2)`. |
| 174 | +
|
| 175 | +All WebElement actions are wrapped in this way and available on the ElementFinder, in addition to a couple helper methods like `isPresent`. |
| 176 | +
|
| 177 | +You may always access the underlying WebElement using `element(locator).getWebElement()`, but you should not need to. |
| 178 | +
|
0 commit comments