Skip to content

Commit

Permalink
Add Overview page for the new Element API.
Browse files Browse the repository at this point in the history
  • Loading branch information
garg3133 committed Oct 21, 2024
1 parent 2a8d8df commit 465bdf5
Showing 1 changed file with 120 additions and 89 deletions.
209 changes: 120 additions & 89 deletions docs/api/element/index.md
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>

0 comments on commit 465bdf5

Please sign in to comment.