-
Notifications
You must be signed in to change notification settings - Fork 199
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Overview page for the new Element API.
- Loading branch information
Showing
1 changed file
with
120 additions
and
89 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,107 +1,138 @@ | ||
## Element APIs | ||
|
||
### Overview | ||
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. | ||
### Introduction | ||
|
||
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()`. | ||
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. | ||
|
||
### Usage | ||
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()`. | ||
|
||
##### Using regular CSS (or Xpath) selectors | ||
### Usage | ||
|
||
<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"]'); | ||
<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 | ||
const submitElem = browser.element.find('button[name=submit]'); | ||
<br> | ||
// using Nightwatch selector objects | ||
const addButtonElem = browser.element.find({ | ||
selector: '//button[@type="button"]', | ||
locateStrategy: 'xpath', | ||
index: 1 | ||
}); | ||
<br> | ||
// using Selenium `by` locator | ||
const addButtonElem2 = browser.element.find( | ||
by.xpath('//button[@type="button"]') | ||
); | ||
<br> | ||
// locating child elements | ||
const childChildElem = browser.element | ||
.find('.element') | ||
.find('.child-element') | ||
.find('.child-child-element'); | ||
<br> | ||
// locating elements by text | ||
const newsElem = browser.element.findByText('News'); | ||
<br> | ||
// use await to retrieve Selenium WebElement instance | ||
const addButtonWebElem = await addButtonElem; | ||
</code></pre></div> | ||
|
||
##### Using Nightwatch selector objects | ||
### How it works? | ||
|
||
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. | ||
|
||
<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, | ||
// a wrapper around the actual result, i.e., `WebElement`. | ||
const inputElem = browser.element.find('input[name=q]'); | ||
<br> | ||
// This wrapper provides a host of commands and assertions | ||
// available directly on the result. | ||
inputElem.click(); | ||
inputElem.sendKeys('Nightwatch.js'); | ||
<br> | ||
// No need to await when performing actions or assertions | ||
// on the element. | ||
inputElem.assert.enabled(); | ||
inputElem.getText().assert.equals('Nightwatch.js'); | ||
<br> | ||
// Use await to retrieve the actual result from the command. | ||
const inputWebElem = await inputElem; // returns a `WebElement` instance | ||
const inputText = await inputElem.getText(); | ||
const inputSize = await inputElem.getSize(); | ||
</code></pre></div> | ||
|
||
<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({ | ||
selector: 'button[type="button"]', | ||
index: 0 | ||
}); | ||
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: | ||
|
||
* **nth()**: returns a wrapper (`ScopedElement`) around the nth element of `WebElement[]`. | ||
* **count()**: returns a count of all the elements present in the actual result, i.e., `WebElement[]`. | ||
|
||
<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, | ||
// a wrapper around the actual result, i.e., `WebElement[]`. | ||
const postElems = browser.element.findAll('.post'); | ||
<br> | ||
// get count of all the posts | ||
// use await to get the actual result | ||
const postElemsCount = await postElems.cound(); | ||
<br> | ||
// assert that the count is 15 | ||
postElems.count().assert.equals(15); | ||
<br> | ||
// assert that the 5th post contains "nightwatch" text | ||
const post5Elem = postElems.nth(4); // 0-based indexing | ||
post5Elem.getText().assert.contains("nightwatch"); | ||
<br> | ||
// click on the 2nd post | ||
postElems.nth(1).click(); | ||
<br> | ||
// `findAll` can also be chained on `find()` | ||
browser.element.find('body').findAll('.post').nth(1).findByText('Comments'); | ||
</code></pre></div> | ||
|
||
##### Selenium locators | ||
### Why a new API? | ||
|
||
<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"]'); | ||
const addButtonEl = element(locator); | ||
</code></pre></div> | ||
The older element APIs had a few shortcomings, like: | ||
|
||
##### Selenium WebElements as arguments | ||
* The selector had to be passed repeatedly for every interaction or assertion with element. | ||
* No easy way to find child element apart from using complex CSS selector. | ||
* No easy way to find elements by text, role, label, etc. | ||
* It did a lookup for element for every command and assertion. | ||
|
||
<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 | ||
const addButtonEl = element(webElement); | ||
</code></pre></div> | ||
The newer API aimed at fixing these shortcomings, while also make the API more concise and easy-to-use. | ||
|
||
##### Retrieve the Selenium WebElement instance | ||
#### Before | ||
|
||
<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"]'); | ||
const instance = await addButtonEl.findElement(); | ||
<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 | ||
browser | ||
.click('input[name=q]') | ||
.sendKeys('input[name=q]', 'Nightwatch.js'); | ||
<br> | ||
// await required to get the actual command result | ||
const inputText = await browser.getText('input[name=q]'); | ||
browser.assert.equal(inputText, 'Nightwatch.js'); | ||
<br> | ||
browser | ||
.click('button[name=submit]') | ||
.assert.not.visible('button[name=submit]'); | ||
</code></pre></div> | ||
|
||
|
||
### API Commands | ||
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. | ||
|
||
**Available element commands:** | ||
- [.clear()](https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebElement.html#clear) | ||
- [.click()](https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebElement.html#click) | ||
- [.findElement()](https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebElement.html#findElement) | ||
- [.findElements()](https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebElement.html#findElements) | ||
- [.getAttribute()](https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebElement.html#getAttribute) | ||
- [.getCssValue()](https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebElement.html#getCssValue) | ||
- [.getDriver()](https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebElement.html#getDriver) | ||
- [.getId()](https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebElement.html#getId) | ||
- [.getRect()](https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebElement.html#getRect) | ||
- [.getTagName()](https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebElement.html#getTagName) | ||
- [.getText()](https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebElement.html#getText) | ||
- [.isDisplayed()](https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebElement.html#isDisplayed) | ||
- [.isEnabled()](https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebElement.html#isEnabled) | ||
- [.isSelected()](https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebElement.html#isSelected) | ||
- [.sendKeys()](https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebElement.html#sendKeys) | ||
- [.submit()](https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebElement.html#submit) | ||
- [.takeScreenshot()](https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebElement.html#takeScreenshot) | ||
|
||
### Working Example | ||
|
||
The example below navigates to the AngularJS homepage and adds a new to-do item in the sample ToDo App available there. | ||
|
||
<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() { | ||
<br> | ||
// using the new element() global utility in Nightwatch 2 to init elements | ||
// before tests and use them later | ||
const todoElement = element('[ng-model="todoList.todoText"]'); | ||
const addButtonEl = element('[value="add"]'); | ||
<br> | ||
it('should add a todo using global element()', function() { | ||
// adding a new task to the list | ||
browser | ||
.navigateTo('https://angularjs.org') | ||
.sendKeys(todoElement, 'what is nightwatch?') | ||
.click(addButtonEl); | ||
<br> | ||
// verifying if there are 3 tasks in the list | ||
expect.elements('[ng-repeat="todo in todoList.todos"]').count.to.equal(3); | ||
<br> | ||
// verifying if the third task if the one we have just added | ||
const lastElementTask = element({ | ||
selector: '[ng-repeat="todo in todoList.todos"]', | ||
index: 2 | ||
}); | ||
<br> | ||
expect(lastElementTask).text.to.equal('what is nightwatch?'); | ||
<br> | ||
// find our task in the list and mark it as done | ||
lastElementTask.findElement('input', function(inputResult) { | ||
if (inputResult.error) { | ||
throw inputResult.error; | ||
} | ||
<br> | ||
const inputElement = element(inputResult.value); | ||
browser.click(inputElement); | ||
}); | ||
<br> | ||
// verify if there are 2 tasks which are marked as done in the list | ||
expect.elements('*[module=todoApp] li .done-true').count.to.equal(2); | ||
}); | ||
}); | ||
</code></pre></div> | ||
#### Now | ||
|
||
<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 | ||
const inputElem = browser.element.find('input[name=q]'); | ||
inputElem.click(); // no repetition of selector | ||
inputElem.sendKeys('Nightwatch.js'); | ||
<br> | ||
// await required to get the actual command result | ||
const inputText = await inputElem.getText(); | ||
browser.assert.equal(inputText, 'Nightwatch.js'); | ||
<br> | ||
// assertions can also be done directly | ||
// no await required as we are not storing the actual command result anywhere | ||
inputElem.getText().assert.equals('Nightwatch.js'); | ||
<br> | ||
const submitElem = browser.element.find('input[name=submit]'); | ||
submitElem.click(); | ||
submitElem.assert.not.visible(); | ||
<br> | ||
// await the element to get the `WebElement` instance. | ||
const submitWebElem = await submitElem; | ||
</code></pre></div> |