Page History
Versions Compared
Key
- This line was added.
- This line was removed.
- Formatting was changed.
Info | ||
---|---|---|
| ||
|
Note | ||||||
---|---|---|---|---|---|---|
| ||||||
|
Overview
WebdriverIO is the next-gen browser and mobile automation test framework for Node.js.Prerequisites
Expand | ||
---|---|---|
For this example we will use the WebdriverIO framework with the default assertion library provided by WebdriverIO and Junit as the reporter. In the official documentation we can find the following description:
If we want, we can use other runners or reporters. We will need:
|
To start using WebDriverIO please follow the Get Started documentation.
WebDriverIO provides a client that after being installed will guide you through bootstraping a Hello World test suite into your project. For this tutorial we will use the code generated by this tool for simplicity (with page objects).The test consists in validating the login feature (with valid and invalid credentials) of the demo site, for that we have created a base page object that will contain all methods and functionality that is shared across all page objects, a login page, that will extend the base page, that will have all the methods for interacting with the login page and a result page that will have the methods to interact in the page that is loaded after the login operation.
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
/** * main page object containing all methods, selectors and functionality * that is shared across all page objects */ module.exports = class Page { /** * Opens a sub page of the page * @param path path of the sub page (e.g. /path/to/page.html) */ open (path) { return browser.url(`https://the-internet.herokuapp.com/${path}`) } } |
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
const Page = require('./page'); /** * sub page containing specific selectors and methods for a specific page */ class LoginPage extends Page { /** * define selectors using getter methods */ get inputUsername () { return $('#username') } get inputPassword () { return $('#password') } get btnSubmit () { return $('button[type="submit"]') } /** * a method to encapsule automation code to interact with the page * e.g. to login using username and password */ async login (username, password) { await (await this.inputUsername).setValue(username); await (await this.inputPassword).setValue(password); await (await this.btnSubmit).click(); } /** * overwrite specifc options to adapt it to page object */ open () { return super.open('login'); } } module.exports = new LoginPage(); |
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
const Page = require('./page'); /** * sub page containing specific selectors and methods for a specific page */ class SecurePage extends Page { /** * define selectors using getter methods */ get flashAlert () { return $('#flash') } } module.exports = new SecurePage(); |
Define the test that will assert if the operation is successful or not
Code Block | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
| ||||||||||
const LoginPage = require('../pageobjects/login.page'); const SecurePage = require('../pageobjects/secure.page'); describe('My Login application', () => { it('should login with valid credentials', async () => { await LoginPage.open(); await LoginPage.login('tomsmith', 'SuperSecretPassword!'); await expect(SecurePage.flashAlert).toBeExisting(); await expect(SecurePage.flashAlert).toHaveTextContaining( 'You logged into a secure area!'); }); }); describe('My Login application', () => { it('should not login with invalid credentials', async () => { await LoginPage.open(); await LoginPage.login('tom', 'SuperPassword!'); await expect(SecurePage.flashAlert).toBeExisting(); await expect(SecurePage.flashAlert).toHaveTextContaining( 'Your username is invalid!'); }); }); |
All of the above were created by the tool provided by WebDriverIO, to create those we followed the documentation and executed first the command to install the WebDriverIO test runner:
Code Block | ||||
---|---|---|---|---|
| ||||
npm install @wdio/cli |
Then we answer a series of questions that will define the code to be generated using:
Code Block | ||||
---|---|---|---|---|
| ||||
npx wdio config |
The output of the questionnaire will look like this:
The last two steps to have everything configured is to define that we will use the Junit framework, for that we execute the following command:
Code Block | ||||
---|---|---|---|---|
| ||||
npm install @wdio/junit-reporter --save-dev |
In wdio.conf.js , we have added, in the reporters area, the following Junit definition:
Code Block | ||||
---|---|---|---|---|
| ||||
... reporters: ['spec', ['junit', { outputDir: './', outputFileFormat: function(options) { // optional return `results.xml` } }] ], ... |
Once the code is implemented (and we will make it fail on purpose on one test, to show the failure reports), it can be executed with the following command:
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
npx wdio run ./wdio.conf.js |
The results are immediately available in the terminal.
In this example, one test has failed and the other one has succeed, the output generated in the terminal is the above one and the correspondent Junit report is as below:
Code Block | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
<?xml version="1.0" encoding="UTF-8"?> <testsuites> <testsuite name="My Login application" timestamp="2021-06-12T08:42:21" time="2.37" tests="1" failures="0" errors="0" skipped="0"> <properties> <property name="specId" value="0"/> <property name="suiteName" value="My Login application"/> <property name="capabilities" value="chrome.91_0_4472_101.macosx"/> <property name="file" value="./test/specs/example.e2e.js"/> </properties> <testcase classname="chrome.91_0_4472_101.macosx.My Login application" name="should login with valid credentials" time="2.369"> <system-out><![CDATA[ COMMAND: POST /session/80357f832dc7f646258291140deaecbe/url - {"url":"https://the-internet.herokuapp.com/login"} RESULT: {"url":"https://the-internet.herokuapp.com/login"} COMMAND: POST /session/80357f832dc7f646258291140deaecbe/element - {"using":"css selector","value":"#username"} RESULT: {"using":"css selector","value":"#username"} COMMAND: POST /session/80357f832dc7f646258291140deaecbe/element/bdbe5639-b66b-4f3e-9551-d701b1040909/clear - {} RESULT: {} COMMAND: POST /session/80357f832dc7f646258291140deaecbe/element/bdbe5639-b66b-4f3e-9551-d701b1040909/value - {"text":"tomsmith"} RESULT: {"text":"tomsmith"} COMMAND: POST /session/80357f832dc7f646258291140deaecbe/element - {"using":"css selector","value":"#password"} RESULT: {"using":"css selector","value":"#password"} COMMAND: POST /session/80357f832dc7f646258291140deaecbe/element/f9b56cb7-f274-4370-889d-044db3b07ecb/clear - {} RESULT: {} COMMAND: POST /session/80357f832dc7f646258291140deaecbe/element/f9b56cb7-f274-4370-889d-044db3b07ecb/value - {"text":"SuperSecretPassword!"} RESULT: {"text":"SuperSecretPassword!"} COMMAND: POST /session/80357f832dc7f646258291140deaecbe/element - {"using":"css selector","value":"button[type=\"submit\"]"} RESULT: {"using":"css selector","value":"button[type=\"submit\"]"} COMMAND: POST /session/80357f832dc7f646258291140deaecbe/element/3f43e518-e698-4bff-b647-6255d6fbeab5/click - {} RESULT: {} COMMAND: POST /session/80357f832dc7f646258291140deaecbe/element - {"using":"css selector","value":"#flash"} RESULT: {"using":"css selector","value":"#flash"} COMMAND: POST /session/80357f832dc7f646258291140deaecbe/elements - {"using":"css selector","value":"#flash"} RESULT: {"using":"css selector","value":"#flash"} COMMAND: POST /session/80357f832dc7f646258291140deaecbe/element - {"using":"css selector","value":"#flash"} RESULT: {"using":"css selector","value":"#flash"} COMMAND: GET /session/80357f832dc7f646258291140deaecbe/element/7e87ed7b-be88-4023-bb59-b3439d06d53f/text - {} RESULT: {} ]]></system-out> </testcase> </testsuite> <testsuite name="My Login application" timestamp="2021-06-12T08:42:23" time="10.709" tests="1" failures="1" errors="1" skipped="0"> <properties> <property name="specId" value="0"/> <property name="suiteName" value="My Login application"/> <property name="capabilities" value="chrome.91_0_4472_101.macosx"/> <property name="file" value="./test/specs/example.e2e.js"/> </properties> <testcase classname="chrome.91_0_4472_101.macosx.My Login application" name="should not login with invalid credentials" time="10.707"> <failure/> <error message="Expect $(`#flash`) to have text containing [32m- Expected - 1[39m [31m+ Received + 2[39m [32m- Your username is invalid[7m.[27m[39m [31m+ Your username is invalid[7m![27m[39m [31m+ ×[39m"/> <system-out><![CDATA[ COMMAND: POST /session/80357f832dc7f646258291140deaecbe/url - {"url":"https://the-internet.herokuapp.com/login"} RESULT: {"url":"https://the-internet.herokuapp.com/login"} COMMAND: POST /session/80357f832dc7f646258291140deaecbe/element - {"using":"css selector","value":"#username"} RESULT: {"using":"css selector","value":"#username"} COMMAND: POST /session/80357f832dc7f646258291140deaecbe/element/af5062d3-a7f3-41f2-9fb1-9d7db3641681/clear - {} RESULT: {} COMMAND: POST /session/80357f832dc7f646258291140deaecbe/element/af5062d3-a7f3-41f2-9fb1-9d7db3641681/value - {"text":"tom"} RESULT: {"text":"tom"} COMMAND: POST /session/80357f832dc7f646258291140deaecbe/element - {"using":"css selector","value":"#password"} RESULT: {"using":"css selector","value":"#password"} COMMAND: POST /session/80357f832dc7f646258291140deaecbe/element/b4df7026-0164-4c38-93ee-419a9b963d67/clear - {} RESULT: {} COMMAND: POST /session/80357f832dc7f646258291140deaecbe/element/b4df7026-0164-4c38-93ee-419a9b963d67/value - {"text":"SuperPassword!"} RESULT: {"text":"SuperPassword!"} COMMAND: POST /session/80357f832dc7f646258291140deaecbe/element - {"using":"css selector","value":"button[type=\"submit\"]"} RESULT: {"using":"css selector","value":"button[type=\"submit\"]"} COMMAND: POST /session/80357f832dc7f646258291140deaecbe/element/457ae344-2625-495d-a17c-fa1189ceb0c8/click - {} RESULT: {} COMMAND: POST /session/80357f832dc7f646258291140deaecbe/element - {"using":"css selector","value":"#flash"} RESULT: {"using":"css selector","value":"#flash"} COMMAND: POST /session/80357f832dc7f646258291140deaecbe/elements - {"using":"css selector","value":"#flash"} RESULT: {"using":"css selector","value":"#flash"} COMMAND: POST /session/80357f832dc7f646258291140deaecbe/element - {"using":"css selector","value":"#flash"} RESULT: {"using":"css selector","value":"#flash"} COMMAND: GET /session/80357f832dc7f646258291140deaecbe/element/f73dfed1-9b8b-4dd0-86c2-ca9e2dea499f/text - {} RESULT: {} COMMAND: GET /session/80357f832dc7f646258291140deaecbe/element/f73dfed1-9b8b-4dd0-86c2-ca9e2dea499f/text - {} RESULT: {} COMMAND: GET /session/80357f832dc7f646258291140deaecbe/element/f73dfed1-9b8b-4dd0-86c2-ca9e2dea499f/text - {} RESULT: {} COMMAND: GET /session/80357f832dc7f646258291140deaecbe/element/f73dfed1-9b8b-4dd0-86c2-ca9e2dea499f/text - {} RESULT: {} COMMAND: GET /session/80357f832dc7f646258291140deaecbe/element/f73dfed1-9b8b-4dd0-86c2-ca9e2dea499f/text - {} RESULT: {} COMMAND: GET /session/80357f832dc7f646258291140deaecbe/element/f73dfed1-9b8b-4dd0-86c2-ca9e2dea499f/text - {} RESULT: {} COMMAND: GET /session/80357f832dc7f646258291140deaecbe/element/f73dfed1-9b8b-4dd0-86c2-ca9e2dea499f/text - {} RESULT: {} COMMAND: GET /session/80357f832dc7f646258291140deaecbe/element/f73dfed1-9b8b-4dd0-86c2-ca9e2dea499f/text - {} RESULT: {} COMMAND: GET /session/80357f832dc7f646258291140deaecbe/element/f73dfed1-9b8b-4dd0-86c2-ca9e2dea499f/text - {} RESULT: {} COMMAND: GET /session/80357f832dc7f646258291140deaecbe/element/f73dfed1-9b8b-4dd0-86c2-ca9e2dea499f/text - {} RESULT: {} COMMAND: GET /session/80357f832dc7f646258291140deaecbe/element/f73dfed1-9b8b-4dd0-86c2-ca9e2dea499f/text - {} RESULT: {} COMMAND: GET /session/80357f832dc7f646258291140deaecbe/element/f73dfed1-9b8b-4dd0-86c2-ca9e2dea499f/text - {} RESULT: {} COMMAND: GET /session/80357f832dc7f646258291140deaecbe/element/f73dfed1-9b8b-4dd0-86c2-ca9e2dea499f/text - {} RESULT: {} COMMAND: GET /session/80357f832dc7f646258291140deaecbe/element/f73dfed1-9b8b-4dd0-86c2-ca9e2dea499f/text - {} RESULT: {} COMMAND: GET /session/80357f832dc7f646258291140deaecbe/element/f73dfed1-9b8b-4dd0-86c2-ca9e2dea499f/text - {} RESULT: {} COMMAND: GET /session/80357f832dc7f646258291140deaecbe/element/f73dfed1-9b8b-4dd0-86c2-ca9e2dea499f/text - {} RESULT: {} COMMAND: GET /session/80357f832dc7f646258291140deaecbe/element/f73dfed1-9b8b-4dd0-86c2-ca9e2dea499f/text - {} RESULT: {} COMMAND: GET /session/80357f832dc7f646258291140deaecbe/element/f73dfed1-9b8b-4dd0-86c2-ca9e2dea499f/text - {} RESULT: {} COMMAND: GET /session/80357f832dc7f646258291140deaecbe/element/f73dfed1-9b8b-4dd0-86c2-ca9e2dea499f/text - {} RESULT: {} COMMAND: GET /session/80357f832dc7f646258291140deaecbe/element/f73dfed1-9b8b-4dd0-86c2-ca9e2dea499f/text - {} RESULT: {} COMMAND: GET /session/80357f832dc7f646258291140deaecbe/element/f73dfed1-9b8b-4dd0-86c2-ca9e2dea499f/text - {} COMMAND: DELETE /session/80357f832dc7f646258291140deaecbe - {} RESULT: {} RESULT: {} ]]></system-out> <system-err><![CDATA[ Error: Expect $(`#flash`) to have text containing [32m- Expected - 1[39m [31m+ Received + 2[39m [32m- Your username is invalid[7m.[27m[39m [31m+ Your username is invalid[7m![27m[39m [31m+ ×[39m at Context.<anonymous> (/Users/cristianocunha/Documents/Projects/webdriverio/test/specs/example.e2e.js:21:45) at processTicksAndRejections (internal/process/task_queues.js:93:5) at async Context.executeAsync (/Users/cristianocunha/Documents/Projects/webdriverio/node_modules/@wdio/utils/build/shim.js:136:16) at async Context.testFrameworkFnWrapper (/Users/cristianocunha/Documents/Projects/webdriverio/node_modules/@wdio/utils/build/test-framework/testFnWrapper.js:52:18) ]]></system-err> </testcase> </testsuite> </testsuites> |
Notes:
- There are a lot of other options on how to use WebDriverIO, please check their documentation for more information.
Integrating with Xray
As we saw in the example above where we produced Junit reports with the result of the tests, it is now a matter of importing those results to your Jira instance. This can be done by simply submitting automation results to Xray through the REST API, by using one of the available CI/CD plugins (e.g. for Jenkins) or using the Jira interface to do so.
UI Tabs | ||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
Tips
- after results are imported in Jira, Tests can be linked to existing requirements/user stories, so you can track the impact of their coverage.
- results from multiple builds can be linked to an existing Test Plan, to facilitate the analysis of test result trends across builds.
- results can be associated with a Test Environment, in case you want to analyze coverage and test results by that environment later on. A Test Environment can be a testing stage (e.g. dev, staging, preprod, prod) or an identifier of the device/application used to interact with the system (e.g. browser, mobile OS).
References
Table of Contents | ||
---|---|---|
|
CSS Stylesheet |
---|
.toc-btf { position: fixed; } |