Element APIs
Introduction
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.
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()
.
Usage
// using regular css selectors
const submitElem = browser.element.find('button[name=submit]');
// using Nightwatch selector objects
const addButtonElem = browser.element.find({
selector: '//button[@type="button"]',
locateStrategy: 'xpath',
index: 1
});
// using Selenium `by` locator
const addButtonElem2 = browser.element.find(
by.xpath('//button[@type="button"]')
);
// locating child elements
const childChildElem = browser.element
.find('.element')
.find('.child-element')
.find('.child-child-element');
// locating elements by text
const newsElem = browser.element.findByText('News');
// use await to retrieve Selenium WebElement instance
const addButtonWebElem = await addButtonElem;
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.
// `find()` and `findBy*()` commands return ScopedElement,
// a wrapper around the actual result, i.e., `WebElement`.
const inputElem = browser.element.find('input[name=q]');
// This wrapper provides a host of commands and assertions
// available directly on the result.
inputElem.click();
inputElem.sendKeys('Nightwatch.js');
// No need to await when performing actions or assertions
// on the element.
inputElem.assert.enabled();
inputElem.getText().assert.equals('Nightwatch.js');
// 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();
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 ofWebElement[]
. - count(): returns a count of all the elements present in the actual result, i.e.,
WebElement[]
.
// `findAll()` and `findAllBy*()` commands return ScopedElements,
// a wrapper around the actual result, i.e., `WebElement[]`.
const postElems = browser.element.findAll('.post');
// get count of all the posts
// use await to get the actual result
const postElemsCount = await postElems.cound();
// assert that the count is 15
postElems.count().assert.equals(15);
// assert that the 5th post contains "nightwatch" text
const post5Elem = postElems.nth(4); // 0-based indexing
post5Elem.getText().assert.contains("nightwatch");
// click on the 2nd post
postElems.nth(1).click();
// `findAll` can also be chained on `find()`
browser.element.find('body').findAll('.post').nth(1).findByText('Comments');
Why a new API?
The older element APIs had a few shortcomings, like:
- 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.
The newer API aimed at fixing these shortcomings, while also make the API more concise and easy-to-use.
Before
// no await required for normal actions
browser
.click('input[name=q]')
.sendKeys('input[name=q]', 'Nightwatch.js');
// await required to get the actual command result
const inputText = await browser.getText('input[name=q]');
browser.assert.equal(inputText, 'Nightwatch.js');
browser
.click('button[name=submit]')
.assert.not.visible('button[name=submit]');
Now
// no await required for normal actions
const inputElem = browser.element.find('input[name=q]');
inputElem.click(); // no repetition of selector
inputElem.sendKeys('Nightwatch.js');
// await required to get the actual command result
const inputText = await inputElem.getText();
browser.assert.equal(inputText, 'Nightwatch.js');
// 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');
const submitElem = browser.element.find('input[name=submit]');
submitElem.click();
submitElem.assert.not.visible();
// await the element to get the `WebElement` instance.
const submitWebElem = await submitElem;