|
1 | 1 | ## Element APIs
|
2 | 2 |
|
3 |
| -### Overview |
4 |
| -The newly added `element()` global object adds to Nightwatch 3 the functionality present in the [Selenium WebElement](https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebElement.html) class. |
| 3 | +### Introduction |
5 | 4 |
|
6 |
| -It supports all the usual ways of locating elements in Nightwatch, as well as the Selenium locators built using `By()`, which is also available in Nightwatch as a global named `by()`. |
| 5 | +The new Element API introduced in Nightwatch v3 is a major upgrade from the older element APIs, making it more intuitive and convenient to find and interact with elements. It does so by offering a fluent and chainable syntax, minimizing the selector repetition and making the tests more concise and easy to read. |
7 | 6 |
|
8 |
| -### Usage |
| 7 | +The new Element API is available on the `browser.element` namespace and supports all the usual ways of locating elements in Nightwatch, as well as the Selenium locators built using `By()`, which is also available in Nightwatch as a global named `by()`. |
9 | 8 |
|
10 |
| -##### Using regular CSS (or Xpath) selectors |
| 9 | +### Usage |
11 | 10 |
|
12 |
| -<div class="sample-test" style="max-width:800px"><pre data-language="javascript" style="padding-top: 10px" class="language-javascript"><code class="language-javascript">const addButtonEl = element('button[type="submit"]'); |
| 11 | +<div class="sample-test" style="max-width:800px"><pre data-language="javascript" style="padding-top: 10px" class="language-javascript"><code class="language-javascript">// using regular css selectors |
| 12 | +const submitElem = browser.element.find('button[name=submit]'); |
| 13 | +<br> |
| 14 | +// using Nightwatch selector objects |
| 15 | +const addButtonElem = browser.element.find({ |
| 16 | + selector: '//button[@type="button"]', |
| 17 | + locateStrategy: 'xpath', |
| 18 | + index: 1 |
| 19 | +}); |
| 20 | +<br> |
| 21 | +// using Selenium `by` locator |
| 22 | +const addButtonElem2 = browser.element.find( |
| 23 | + by.xpath('//button[@type="button"]') |
| 24 | +); |
| 25 | +<br> |
| 26 | +// locating child elements |
| 27 | +const childChildElem = browser.element |
| 28 | + .find('.element') |
| 29 | + .find('.child-element') |
| 30 | + .find('.child-child-element'); |
| 31 | +<br> |
| 32 | +// locating elements by text |
| 33 | +const newsElem = browser.element.findByText('News'); |
| 34 | +<br> |
| 35 | +// use await to retrieve Selenium WebElement instance |
| 36 | +const addButtonWebElem = await addButtonElem; |
13 | 37 | </code></pre></div>
|
14 | 38 |
|
15 |
| -##### Using Nightwatch selector objects |
| 39 | +### How it works? |
| 40 | + |
| 41 | +All the `find()` and `findBy*()` commands in the new Element API returns `ScopedElement`, which is nothing but a wrapper around the actual result returned by these commands (`WebElement` instance in this case). This wrapper provides access to a host of commands and assertions that can be performed directly on the element and the actual result from these commands can be obtained by using `await` on them. |
| 42 | + |
| 43 | +<div class="sample-test" style="max-width:800px"><pre data-language="javascript" style="padding-top: 10px" class="language-javascript"><code class="language-javascript">// `find()` and `findBy*()` commands return ScopedElement, |
| 44 | +// a wrapper around the actual result, i.e., `WebElement`. |
| 45 | +const inputElem = browser.element.find('input[name=q]'); |
| 46 | +<br> |
| 47 | +// This wrapper provides a host of commands and assertions |
| 48 | +// available directly on the result. |
| 49 | +inputElem.click(); |
| 50 | +inputElem.sendKeys('Nightwatch.js'); |
| 51 | +<br> |
| 52 | +// No need to await when performing actions or assertions |
| 53 | +// on the element. |
| 54 | +inputElem.assert.enabled(); |
| 55 | +inputElem.getText().assert.equals('Nightwatch.js'); |
| 56 | +<br> |
| 57 | +// Use await to retrieve the actual result from the command. |
| 58 | +const inputWebElem = await inputElem; // returns a `WebElement` instance |
| 59 | +const inputText = await inputElem.getText(); |
| 60 | +const inputSize = await inputElem.getSize(); |
| 61 | +</code></pre></div> |
16 | 62 |
|
17 |
| -<div class="sample-test" style="max-width:800px"><pre data-language="javascript" style="padding-top: 10px" class="language-javascript"><code class="language-javascript">const addButtonEl = element({ |
18 |
| - selector: 'button[type="button"]', |
19 |
| - index: 0 |
20 |
| -}); |
| 63 | +Similarly, the `findAll()` and `findAllBy*()` commands return `ScopedElements`, a wrapper around the actual result from these commands, i.e., `WebElement[]` (an array of `WebElement`s). But unlike `ScopedElement`, `ScopedElements` provide two methods: |
| 64 | + |
| 65 | +* **nth()**: returns a wrapper (`ScopedElement`) around the nth element of `WebElement[]`. |
| 66 | +* **count()**: returns a count of all the elements present in the actual result, i.e., `WebElement[]`. |
| 67 | + |
| 68 | +<div class="sample-test" style="max-width:800px"><pre data-language="javascript" style="padding-top: 10px" class="language-javascript"><code class="language-javascript">// `findAll()` and `findAllBy*()` commands return ScopedElements, |
| 69 | +// a wrapper around the actual result, i.e., `WebElement[]`. |
| 70 | +const postElems = browser.element.findAll('.post'); |
| 71 | +<br> |
| 72 | +// get count of all the posts |
| 73 | +// use await to get the actual result |
| 74 | +const postElemsCount = await postElems.cound(); |
| 75 | +<br> |
| 76 | +// assert that the count is 15 |
| 77 | +postElems.count().assert.equals(15); |
| 78 | +<br> |
| 79 | +// assert that the 5th post contains "nightwatch" text |
| 80 | +const post5Elem = postElems.nth(4); // 0-based indexing |
| 81 | +post5Elem.getText().assert.contains("nightwatch"); |
| 82 | +<br> |
| 83 | +// click on the 2nd post |
| 84 | +postElems.nth(1).click(); |
| 85 | +<br> |
| 86 | +// `findAll` can also be chained on `find()` |
| 87 | +browser.element.find('body').findAll('.post').nth(1).findByText('Comments'); |
21 | 88 | </code></pre></div>
|
22 | 89 |
|
23 |
| -##### Selenium locators |
| 90 | +### Why a new API? |
24 | 91 |
|
25 |
| -<div class="sample-test" style="max-width:800px"><pre data-language="javascript" style="padding-top: 10px" class="language-javascript"><code class="language-javascript">const locator = by.css('button[type="button"]'); |
26 |
| -const addButtonEl = element(locator); |
27 |
| -</code></pre></div> |
| 92 | +The older element APIs had a few shortcomings, like: |
28 | 93 |
|
29 |
| -##### Selenium WebElements as arguments |
| 94 | +* The selector had to be passed repeatedly for every interaction or assertion with element. |
| 95 | +* No easy way to find child element apart from using complex CSS selector. |
| 96 | +* No easy way to find elements by text, role, label, etc. |
| 97 | +* It did a lookup for element for every command and assertion. |
30 | 98 |
|
31 |
| -<div class="sample-test" style="max-width:800px"><pre data-language="javascript" style="padding-top: 10px" class="language-javascript"><code class="language-javascript">// webElement is an instance of WebElement class from Selenium |
32 |
| -const addButtonEl = element(webElement); |
33 |
| -</code></pre></div> |
| 99 | +The newer API aimed at fixing these shortcomings, while also make the API more concise and easy-to-use. |
34 | 100 |
|
35 |
| -##### Retrieve the Selenium WebElement instance |
| 101 | +#### Before |
36 | 102 |
|
37 |
| -<div class="sample-test" style="max-width:800px"><pre data-language="javascript" style="padding-top: 10px" class="language-javascript"><code class="language-javascript">const addButtonEl = element('button[type="submit"]'); |
38 |
| -const instance = await addButtonEl.findElement(); |
| 103 | +<div class="sample-test" style="max-width:800px"><pre data-language="javascript" style="padding-top: 10px" class="language-javascript"><code class="language-javascript">// no await required for normal actions |
| 104 | +browser |
| 105 | + .click('input[name=q]') |
| 106 | + .sendKeys('input[name=q]', 'Nightwatch.js'); |
| 107 | +<br> |
| 108 | +// await required to get the actual command result |
| 109 | +const inputText = await browser.getText('input[name=q]'); |
| 110 | +browser.assert.equal(inputText, 'Nightwatch.js'); |
| 111 | +<br> |
| 112 | +browser |
| 113 | + .click('button[name=submit]') |
| 114 | + .assert.not.visible('button[name=submit]'); |
39 | 115 | </code></pre></div>
|
40 | 116 |
|
41 |
| - |
42 |
| -### API Commands |
43 |
| -All the existing methods from a regular [WebElement](https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebElement.html) instance are available. If a method is called, it will be added to the Nightwatch queue accordingly. |
44 |
| - |
45 |
| -**Available element commands:** |
46 |
| -- [.clear()](https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebElement.html#clear) |
47 |
| -- [.click()](https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebElement.html#click) |
48 |
| -- [.findElement()](https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebElement.html#findElement) |
49 |
| -- [.findElements()](https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebElement.html#findElements) |
50 |
| -- [.getAttribute()](https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebElement.html#getAttribute) |
51 |
| -- [.getCssValue()](https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebElement.html#getCssValue) |
52 |
| -- [.getDriver()](https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebElement.html#getDriver) |
53 |
| -- [.getId()](https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebElement.html#getId) |
54 |
| -- [.getRect()](https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebElement.html#getRect) |
55 |
| -- [.getTagName()](https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebElement.html#getTagName) |
56 |
| -- [.getText()](https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebElement.html#getText) |
57 |
| -- [.isDisplayed()](https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebElement.html#isDisplayed) |
58 |
| -- [.isEnabled()](https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebElement.html#isEnabled) |
59 |
| -- [.isSelected()](https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebElement.html#isSelected) |
60 |
| -- [.sendKeys()](https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebElement.html#sendKeys) |
61 |
| -- [.submit()](https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebElement.html#submit) |
62 |
| -- [.takeScreenshot()](https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebElement.html#takeScreenshot) |
63 |
| - |
64 |
| -### Working Example |
65 |
| - |
66 |
| -The example below navigates to the AngularJS homepage and adds a new to-do item in the sample ToDo App available there. |
67 |
| - |
68 |
| -<div class="sample-test" style="max-width:800px"><pre data-language="javascript" style="padding-top: 10px" class="language-javascript"><code class="language-javascript">describe('angularjs homepage todo list', function() { |
69 |
| - <br> |
70 |
| - // using the new element() global utility in Nightwatch 2 to init elements |
71 |
| - // before tests and use them later |
72 |
| - const todoElement = element('[ng-model="todoList.todoText"]'); |
73 |
| - const addButtonEl = element('[value="add"]'); |
74 |
| - <br> |
75 |
| - it('should add a todo using global element()', function() { |
76 |
| - // adding a new task to the list |
77 |
| - browser |
78 |
| - .navigateTo('https://angularjs.org') |
79 |
| - .sendKeys(todoElement, 'what is nightwatch?') |
80 |
| - .click(addButtonEl); |
81 |
| - <br> |
82 |
| - // verifying if there are 3 tasks in the list |
83 |
| - expect.elements('[ng-repeat="todo in todoList.todos"]').count.to.equal(3); |
84 |
| - <br> |
85 |
| - // verifying if the third task if the one we have just added |
86 |
| - const lastElementTask = element({ |
87 |
| - selector: '[ng-repeat="todo in todoList.todos"]', |
88 |
| - index: 2 |
89 |
| - }); |
90 |
| - <br> |
91 |
| - expect(lastElementTask).text.to.equal('what is nightwatch?'); |
92 |
| - <br> |
93 |
| - // find our task in the list and mark it as done |
94 |
| - lastElementTask.findElement('input', function(inputResult) { |
95 |
| - if (inputResult.error) { |
96 |
| - throw inputResult.error; |
97 |
| - } |
98 |
| - <br> |
99 |
| - const inputElement = element(inputResult.value); |
100 |
| - browser.click(inputElement); |
101 |
| - }); |
102 |
| - <br> |
103 |
| - // verify if there are 2 tasks which are marked as done in the list |
104 |
| - expect.elements('*[module=todoApp] li .done-true').count.to.equal(2); |
105 |
| - }); |
106 |
| -}); |
107 |
| -</code></pre></div> |
| 117 | +#### Now |
| 118 | + |
| 119 | +<div class="sample-test" style="max-width:800px"><pre data-language="javascript" style="padding-top: 10px" class="language-javascript"><code class="language-javascript">// no await required for normal actions |
| 120 | +const inputElem = browser.element.find('input[name=q]'); |
| 121 | +inputElem.click(); // no repetition of selector |
| 122 | +inputElem.sendKeys('Nightwatch.js'); |
| 123 | +<br> |
| 124 | +// await required to get the actual command result |
| 125 | +const inputText = await inputElem.getText(); |
| 126 | +browser.assert.equal(inputText, 'Nightwatch.js'); |
| 127 | +<br> |
| 128 | +// assertions can also be done directly |
| 129 | +// no await required as we are not storing the actual command result anywhere |
| 130 | +inputElem.getText().assert.equals('Nightwatch.js'); |
| 131 | +<br> |
| 132 | +const submitElem = browser.element.find('input[name=submit]'); |
| 133 | +submitElem.click(); |
| 134 | +submitElem.assert.not.visible(); |
| 135 | +<br> |
| 136 | +// await the element to get the `WebElement` instance. |
| 137 | +const submitWebElem = await submitElem; |
| 138 | +</code></pre></div> |
0 commit comments