This post goes over how to write E2E tests using Selenium, Cucumber, Node.js, and Firefox.
Background
From the Cucumber.js README:
Cucumber is a tool for running automated tests written in plain language.
In other words, Cucumber helps accomplish Behavior-Driven Development (BDD) using Gherkin syntax.
Prerequisites
- Node.js and npm
- Firefox and geckodriver
If you’re on macOS, you can install the prerequisites with Homebrew.
Install Node.js:
brew install node
Install Firefox browser:
brew cask install firefox
Install geckodriver (WebDriver for Firefox):
brew install geckodriver
Install
Install cucumber and selenium-webdriver:
npm install cucumber@6 selenium-webdriver
The versions we’re using are:
npm ls --depth=0
├── [email protected]
└── [email protected]
Setup
Create a file named cucumber.js
:
touch cucumber.js
Add the content:
// cucumber.js
module.exports = {
default: `--format-options '{"snippetInterface": "synchronous"}'`,
};
Run cucumber-js
to see that no scenarios/steps are found.
npx cucumber-js
0 scenarios
0 steps
0m00.000s
Feature
Create a directory named features
:
mkdir features
The spelling and capitalization of the directory name must be exact here.
Create a .feature
file inside the directory. We’ll name it google-search.feature
:
touch features/google-search.feature
Write the scenario:
# features/google-search.feature
Feature: Google search
Scenario: Googling remarkablemark.
Given I am on the Google homepage
When I search for "remarkablemark"
Then the page title is "remarkablemark - Google Search"
Run npx cucumber-js
to see the warnings:
npx cucumber-js
UUU
Warnings:
1) Scenario: Googling remarkablemark. # features/google-search.feature:4
? Given I am on the Google homepage
Undefined. Implement with the following snippet:
Given('I am on the Google homepage', function () {
// Write code here that turns the phrase above into concrete actions
return 'pending';
});
? When I search for "remarkablemark"
Undefined. Implement with the following snippet:
When('I search for {string}', function (string) {
// Write code here that turns the phrase above into concrete actions
return 'pending';
});
? Then the page title is "remarkablemark - Google Search"
Undefined. Implement with the following snippet:
Then('the page title is {string}', function (string) {
// Write code here that turns the phrase above into concrete actions
return 'pending';
});
1 scenario (1 undefined)
3 steps (3 undefined)
0m00.000s
This is expected because the step definitions are undefined.
Step Definitions
Create a .js
file inside the features
directory for your steps. We’ll name it google-search-steps.js
:
touch features/google-search-steps.js
Import Given
, When
, and Then
from cucumber
and paste the steps from earlier:
// features/google-search-steps.js
const { Given, When, Then } = require('cucumber');
Given('I am on the Google homepage', function () {
// Write code here that turns the phrase above into concrete actions
return 'pending';
});
When('I search for {string}', function (string) {
// Write code here that turns the phrase above into concrete actions
return 'pending';
});
Then('the page title is {string}', function (string) {
// Write code here that turns the phrase above into concrete actions
return 'pending';
});
Run npx cucumber-js
to see the warnings:
npx cucumber-js
P--
Warnings:
1) Scenario: Googling remarkablemark. # features/google-search.feature:4
? Given I am on the Google homepage # features/google-search-steps.js:4
Pending
- When I search for "remarkablemark" # features/google-search-steps.js:9
- Then the page title is "remarkablemark - Google Search" # features/google-search-steps.js:14
1 scenario (1 pending)
3 steps (1 pending, 2 skipped)
0m00.002s
Selenium WebDriver
Fill out the step definitions with WebDriverJS actions in features/google-search-steps.js
.
BeforeAll
Build the driver before the Given
step:
const { Builder } = require('selenium-webdriver');
const driver = new Builder().forBrowser('firefox').build();
AfterAll
Quit the driver in the AfterAll
hook:
const { AfterAll } = require('cucumber');
AfterAll('end', async function () {
await driver.quit();
});
Given
Fill out the Given
step:
Given('I am on the Google homepage', async function () {
await driver.get('https://www.google.com/');
});
When
Fill out the When
step:
When('I search for {string}', async function (string) {
const element = await driver.findElement(By.name('q'));
element.sendKeys(string, Key.RETURN);
await driver.sleep(1000);
});
It’s recommended to use wait instead of sleep so the test runs fast and doesn’t become flaky.
Then
Fill out the Then
step:
Then('the page title is {string}', async function (string) {
assert.equal(await driver.getTitle(), string);
});
Run npx cucumber-js
to see the failure:
npx cucumber-js
F--
Failures:
1) Scenario: Googling remarkablemark. # features/google-search.feature:4
✖ Given I am on the Google homepage # features/google-search-steps.js:8
Error: function timed out, ensure the promise resolves within 5000 milliseconds
at Timeout._onTimeout (./node_modules/cucumber/lib/user_code_runner.js:76:18)
at listOnTimeout (internal/timers.js:531:17)
at processTimers (internal/timers.js:475:7)
- When I search for "remarkablemark" # features/google-search-steps.js:12
- Then the page title is "remarkablemark - Google Search" # features/google-search-steps.js:18
1 scenario (1 failed)
3 steps (1 failed, 2 skipped)
0m05.045s
Timeout
Timeouts can be specified via 2 ways:
- Global
- Step (or hook)
Global Timeout
To specify a global timeout:
const { setDefaultTimeout } = require('cucumber');
const FIVE_SECONDS = 5 * 1000; // default
setDefaultTimeout(FIVE_SECONDS);
Step Timeout
To specify a step timeout:
Given('I am on the Google homepage', { timeout: 10000 }, async function () {
await driver.get('https://www.google.com/');
});
Notice how an object containing the timeout is specified as the 2nd argument of the step function.
Run npx cucumber-js
to verify that the scenario passes:
npx cucumber-js
...
1 scenario (1 passed)
3 steps (3 passed)
0m06.881s
Success!
Final Steps
The final features/google-search-steps.js
looks like this:
// features/google-search-steps.js
const assert = require('assert');
const { Given, When, Then, AfterAll } = require('cucumber');
const { Builder, By, Key } = require('selenium-webdriver');
const driver = new Builder().forBrowser('firefox').build();
Given('I am on the Google homepage', { timeout: 10000 }, async function () {
await driver.get('https://www.google.com/');
});
When('I search for {string}', async function (string) {
const element = await driver.findElement(By.name('q'));
element.sendKeys(string, Key.RETURN);
await driver.sleep(1000);
});
Then('the page title is {string}', async function (string) {
assert.equal(await driver.getTitle(), string);
});
AfterAll('end', async function () {
await driver.quit();
});
Resources
Check out WebDriverJS recipes for more examples.