JavaScript browser automation framework template using official selenium-webdriver v3 and cucumber-js v6. This repository is based on John Doherty's selenium-cucumber-js repository with upgrade to ES6 and new version of components. You can use it as a stand alone npm module or clone this repository as a template for your tests.
Table of Contents
npm install selenium-cucumber-es6 --save-dev
Or clone git repo:
git clone https://github.com/dcmarti/selenium-cucumber-es6.git
node ./node_modules/selenium-cucumber-es6/index.js -s ./step-definitions
Or in cloned repo:
npm test -- -t @serch
-h, --help output usage information
-V, --version output the version number
-e, --environment <name> name of environment. defaults to dev
-s, --steps <path> path to step definitions. defaults to ./step-definitions
-p, --pageObjects <path> path to page objects. defaults to ./page-objects
-o, --sharedObjects [paths] path to shared objects (repeatable). defaults to ./shared-objects
-b, --browser <path> name of browser to use. defaults to chrome
-k, --browser-teardown <optional> browser teardown strategy after every scenario (always, clear, none). defaults to "always"
-r, --reports <path> output path to save reports. defaults to ./reports
-d, --disableLaunchReport disable the auto opening the browser with test report
-j, --junit <path> output path to save junit-report.xml defaults to ./reports
-t, --tags <tagName> name of tag to run
-f, --featureFile <path> a specific feature file to run
-x, --timeOut <n> steps definition timeout in milliseconds. defaults to 10 seconds
-n, --noScreenshot disable auto capturing of screenshots when an error is encountered
By default tests are run using Google Chrome, to run tests using another browser supply the name of that browser along with the -b
switch. Available options are:
Browser | Example |
---|---|
Chrome | -b chrome |
Firefox | -b firefox |
Electron | -b electron |
Custom | -b customDriver.js |
To use your own driver, create a customDriver.js file in the root of your project and provide the filename with the -b
switch. Example for Selenium grid:
'use strict';
var { Builder } = require('selenium-webdriver');
/**
* Creates a Selenium WebDriver using Selenium grid with Chrome as the browser
* @returns {ThenableWebDriver} Selenium web driver
*/
module.exports = function() {
const gridUrl = `http://your-Selenium-grid-host:4444/wd/hub/`;
let driver = new Builder()
.forBrowser('chrome')
.usingServer(gridUrl)
.build();
return driver;
}
Configuration options can be set using a selenium-cucumber-es6.json
file at the root of your project. The JSON keys use the "long name" from the command line options. For example the following duplicates default configuration:
{
"steps": "./step-definitions",
"pageObjects": "./page-objects",
"sharedObjects": "./shared-objects",
"reports": "./reports",
"browser": "chrome",
"timeout": 10000
}
Whereas the following would set configuration to match the expected directory structure of IntelliJ's Cucumber plugin, and make default timeout one minute. Note that the default browser has not been overridden and will remain 'chrome'.
{
"steps": "./features/step_definitions",
"pageObjects": "./features/page_objects",
"sharedObjects": "./features/shared_objects",
"reports": "./features/reports",
"timeout": 60000
}
A feature file is a Business Readable, Domain Specific Language file that lets you describe software’s behavior without detailing how that behavior is implemented. Feature files are written using the Gherkin syntax and must live in a folder named features within the root of your project.
# ./features/google-search.feature
Feature: Searching for vote cards app
As an internet user
In order to find out more about the itunes vote cards app
I want to be able to search for information about the itunes vote cards app
Scenario: Google search for vote cards app
When I search Google for "itunes vote cards app"
Then I should see some results
The browser automatically closes after each scenario to ensure the next scenario uses a fresh browser environment. But you can change this behavior with the "-k" or the "--browser-teardown" parameter.
Value | Description |
---|---|
always |
the browser automatically closes (default) |
clear |
the browser automatically clears cookies, local and session storages |
none |
the browser does nothing |
Step definitions act as the glue between features files and the actual system under test.
To avoid confusion always use async notation.
// ./step-definitions/google-search-steps.js
Then('I should see some results', function () {
// wait for element to be present
await driver.wait(until.elementsLocated(by.css('div.g')), 10000);
let elements = await driver.findElements(by.css('div.g'));
expect(elements.length).to.not.equal(0);
});
The following variables are available within the Given()
, When()
and Then()
functions:
Variable | Description |
---|---|
driver |
an instance of selenium web driver (the browser) |
page |
collection of page objects loaded from disk and keyed by filename |
shared |
collection of shared objects loaded from disk and keyed by filename |
helpers |
a collection of [helper methods](runtime/await helpers.js) things selenium does not provide but really should! |
by |
the selenium By class used to locate elements on the page |
key |
the selenium key class used to wait for elements/events |
until |
the selenium until class used to wait for elements/events |
expect |
instance of chai expect to expect('something').to.equal('something') |
assert |
instance of chai assert to assert.isOk('everything', 'everything is ok') |
trace |
handy trace method to log console output with increased visibility |
Page objects are accessible via a global page
object and are automatically loaded from ./page-objects
(or the path specified using the -p
switch). Page objects are exposed via their filename, for example ./page-objects/googleSearch.js
becomes page.googleSearch
. You can also use subdirectories, for example ./page-objects/dir/googleSearch.js
becomes page.dir.googleSearch
.
Page objects also have access to the same runtime variables available to step definitions.
An example page object:
// ./page-objects/googleSearch.js
module.exports = {
url: 'http://www.google.co.uk',
elements: {
searchInput: by.name('q'),
searchResultLink: by.css('div.g > h3 > a')
},
/**
* enters a search term into Google's search box and presses enter
* @param {string} searchQuery
* @returns {Promise} a promise to enter the search values
*/
performSearch: function (searchQuery) {
var selector = page.googleSearch.elements.searchInput;
// return a promise so the calling function knows the task has completed
return driver.findElement(selector).sendKeys(searchQuery, selenium.Key.ENTER);
}
};
And its usage within a step definition:
// ./step-definitions/google-search-steps.js
When('I search Google for {string}', function (searchQuery) {
return await helpers.loadPage('http://www.google.com').then(function() {
// use a method on the page object which also returns a promise
return page.googleSearch.performSearch(searchQuery);
});
Shared objects allow you to share anything from test data to helper methods throughout your project via a global shared
object. Shared objects are automatically loaded from ./shared-objects
(or the path specified using the -o
switch) and made available via a filename, for example ./shared-objects/testData.js
becomes shared.testData
. You can also use subdirectories, for example ./shared-objects/dir/testData.js
becomes shared.dir.testData
.
Shared objects also have access to the same runtime variables available to step definitions.
An example shared object:
// ./shared-objects/testData.js
module.exports = {
username: "import-test-user",
password: "import-test-pa**word"
}
And its usage within a step definition:
module.exports = function () {
this.Given('I am logged in', function () {
driver.findElement(by.name('usn')).sendKeys(shared.testData.username);
driver.findElement(by.name('pass')).sendKeys(shared.testData.password);
});
};
selenium-cucumber-es6
contains a few helper methods to make working with selenium a bit easier, those methods are:
// Load a URL, returning only when the <body> tag is present
await helpers.loadPage('http://www.google.com');
// get the value of a HTML attribute
await helpers.getAttributeValue('body', 'class');
// get a list of elements matching a query selector who's inner text matches param.
await helpers.getElementsContainingText('nav[role="navigation"] ul li a', 'Safety Boots');
// get first elements matching a query selector who's inner text matches textToMatch param
await helpers.getFirstElementContainingText('nav[role="navigation"] ul li a', 'Safety Boots');
// click element(s) that are not visible (useful in situations where a menu needs a hover before a child link appears)
await helpers.clickHiddenElement('nav[role="navigation"] ul li a','Safety Boots');
// wait until a HTML attribute equals a particular value
await helpers.waitUntilAttributeEquals('html', 'data-busy', 'false', 5000);
// wait until a HTML attribute exists
await helpers.waitUntilAttributeExists('html', 'data-busy', 5000);
// wait until a HTML attribute no longer exists
await helpers.waitUntilAttributeDoesNotExists('html', 'data-busy', 5000);
// get the content value of a :before pseudo element
await helpers.getPseudoElementBeforeValue('body header');
// get the content value of a :after pseudo element
await helpers.getPseudoElementAfterValue('body header');
// clear the cookies
await helpers.clearCookies();
// clear both local and session storages
await helpers.clearStorages();
// clear both cookies and storages
await helpers.clearCookiesAndStorages('body header');
The selenium-cucumber-es6
framework uses Applitools Eyes to add visual checkpoints to your JavaScript Selenium tests. It takes care of getting screenshots of your application from the underlying WebDriver, sending them to the Applitools Eyes server for validation and failing the test when differences are detected. To perform visual comparisons within your tests, obtain an Applitools Eyes API Key and assign it to the eye_key
property of the selenium-cucumber-es6.json
config file in the root of your project.
For example the following configuration could be used with an increased timeout which allows enough time for visual checks:
{
"eye_key": "Your_Api_Key",
"timeout": 50000
}
And its usage within page Objects:
module.exports = {
url: 'https://applitools.com/helloworld',
elements: {
clickme: by.tagName('button'),
searchResultLink: by.css('div.g > h3 > a')
},
applitools_Eyes_Example: function () {
// Start the test and set the browser's viewport size to 800x600.
eyes.open(driver, 'Hello World!', 'My first Javascript test!',
{width: 800, height: 600});
// Navigate the browser to the "hello world!" web-site.
driver.get(page.HelloWorld.elements.url);
// Visual checkpoint #1.
eyes.checkWindow('Main Page');
// Click the "Click me!" button.
driver.findElement(page.HelloWorld.elements.clickme).click();
// Visual checkpoint #2.
eyes.checkWindow('Click!');
// End the test.
eyes.close();
}
};
You can register before and after handlers using Cucumber's built in features:
Event | Example |
---|---|
BeforeAll | BeforeAll(function(feature, callback) {}) |
AfterAll | AfterAll(function(feature, callback) {}); |
Before | Before(function(scenario, callback) {}); |
After | After(function(scenario, callback) {}); |
HTML and JSON reports are automatically generated and stored in the default ./reports
folder. This location can be changed by providing a new path using the -r
command line switch:
Most selenium methods return a JavaScript Promise that is resolved when the method completes. The easiest way is to use async
approach and place a debugger
statement between it, for example:
module.exports = function () {
this.When('I search Google for {string}', async function (searchQuery) {
trace(page); // <<- will display page object with increased visibility
let input = await driver.findElement(by.name('q'));
expect(input).to.exist;
debugger; // <<- your IDE should step in at this point, with the browser open
input.sendKeys(searchQuery);
input.sendKeys(selenium.Key.ENTER);
});
};
You can use the framework without any command line arguments if your application uses the following folder structure:
.
├── features
│ └── google-search.feature
├── step-definitions
│ └── google-search-steps.js
├── page-objects
│ └── google-search.js
└── shared-objects
│ ├── test-data.js
│ └── stuff.json
└── reports
├── cucumber-report.json
└── cucumber-report.html
This project includes an example to help you get started. You can run the example using the following command:
node ./node_modules/selenium-cucumber-es6/index.js
Licensed under ISC License © John Doherty & Marcin Szweda