WebDriverJS: Writing Cucumber tests


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

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:

  1. Global
  2. 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.

Cucumber

Selenium



Please support this site and join our Discord!