DEV Community

Cover image for Cucumber.js Tutorial with Examples For Selenium JavaScript
Nate-Grey
Nate-Grey

Posted on • Originally published at lambdatest.com

Cucumber.js Tutorial with Examples For Selenium JavaScript

The relevance of using a BDD framework such as Cucumber.js is often questioned by our fellow automation testers. Many feel that it is simply adding more work to their table. However, using a BDD framework has its own advantages, ones that can help you take your Selenium test automation a long way. Not to sideline, these BDD frameworks help all of your stakeholders to easily interpret the logic behind your Selenium test automation script. Leveraging Cucumber.js for your Selenium JavaScript testing can help you specify an acceptance criterion that would be easy for any non-programmer to understand. It could also help you quickly evaluate the logic implied in your Selenium test automation suite without going through huge chunks of code.

With a given-when-then structure, Behaviour Driven Development frameworks like Cucumber.js have made tests much simpler to understand. To put this into context, let’s take a small scenario, you have to test an ATM if it is functioning well. We’ll write the conditions that, Given the account balance, are $1000 And the card is valid And the machine contains enough money When the Account Holder requests $200 Then the Cashpoint should dispense $200 And the account balance should be $800 And the card should be returned.

In this Cucumber.js tutorial, we’ll take a deep dive into a clear understanding of the setup, installation, and execution of our first automation test with Cucumber.js for Selenium JavaScript testing.

What is Cucumber.js and what makes it so popular?

Let’s start our Cucumber.js tutorial with a small brief about the framework. Cucumber.js is a very robust and effective Selenium JavaScript testing framework that works on the Behavior Driver Development process. This test library provides easy integration with Selenium and provides us the capability to define our tests in simple language that is even understood by a layman. Cucumber.js Selenium JavaScript testing library follows a given-when-then structure that helps in representing the tests in plain language, which also makes our tests to be a point of communication and collaboration. This feature improves the readability of the test and hence helps understand each use case in a better way. It is used for unit testing by the developers but majorly used for integration and end to end tests. Moreover, it collaborates the tests and makes it so legible that there is hardly a need for documentation for the test cases and can be even digested by the business users.

Setting up Cucumber.js for Selenium Javascript Testing

So before we carry on with our Cucumber.js Tutorial, to begin writing and executing our automated test scripts using Cucumber, we need to set up our system with the Cucumber.js framework and install all the necessary libraries and packages to begin Selenium JavaScript testing.

Node JS and Node Package Manager (npm): This is the foundation package and most important for any Selenium Javascript testing framework. It can be downloaded via the npm manager i.e. by installing node package manager from the nodejs.org official website: http://www.nodejs.org/en/download/package-manager or using the package installer for different operating systems downloaded from the web site here for Mac OS, Windows or Linux. We can execute the npm command in the command line and check if it is correctly installed on the system.

Cucumber.js Library Module: The next prerequisite required for our test execution is the Cucumber.js library. We would need the Cucumber.js package as a development dependency. After the successful installation and validation of the Node JS on the system, we would use the node package manager i.e. npm that it provides to install the Cucumber.js library package in the npm repository.

So, in order to install the latest version of the Cucumber.js module, we will use the npm command as shown below

$ npm install -g cucumber 
and
npm install  --save-dev cucumber

Here the parameter ‘g’ indicates the global installation of the module, which means that it does not limit the use of the module to the current project and it also can be accessed with command-line tools. The command executed using the parameter ‘–save-dev ’ will place the Cucumber executable in the base directory i.e. ./node_modules/.bin directory and execute the commands in our command-line tool by using the cucumber keyword.

Java – SDK: Since, all the Selenium test framework internally uses Java, next we would move ahead and install the Java Development Kit on our systems. It is advised to use JDK having version 6.0 and above and setup /configure the system environment variables for JAVA.

Selenium Web Driver:. To automate the system browser, we would need to install Selenium Web Driver Library using the below npm command. In most cases, it automatically gets installed in our npm node_modules base directory as a dependency when installing other libraries.

$ npm install selenium-webdriver

Browser Driver: Finally, it is required to install the browser driver. It can be any browser where we would want to execute the test scenarios and hence the corresponding driver needs to be installed. This executable needs to be added to our PATH environment variable and also placed inside the same bin folder. Here we are installing the chrome driver.

Here is the link to the documentation where we can find and download the version that matches the version of our browser.

automation-trial-now

Working on the Cucumber.js test framework?

Now that we’ve set up our system for our Cucumber.js tutorial, we will move ahead with creating our project structure and create a directory named cucumber_test. Then we will create two subfolders i.e feature and step_definition which will contain respective scripts written for our features and step definition.

$ mkdir features

$ mkdir step_definitions

Finally, the folder will have a generated package.json file in the base directory of the package and save all of the dev dependencies for these modules. Another important thing to do with the package.json file is to add the test attribute in the scripts parameter.

{
  "scripts": {
    "test": "./node_modules/.bin/cucumber-js"
  }
}

By adding this snippet to our package.json file we can run all your cucumber tests from the command line by just typing in “npm test” on the command line. Our final project folder structure stands as below.

cucumber_test
        | - - feature
                    | - - feature_test.feature
                  | - - step_definition
                                     | - - steps_def.js
      | - - support
            | - - support.js
        | - - package.json

Below is the working procedure of a Cucumber.js project:

  • We begin with writing a .feature files which have the scenarios and each of these scenarios with a given-when-then structure defined.
  • Next, we write down step definition files which typically defines the functions that match with the steps in our scenarios.
  • Further, we implement these functions as per our requirement or automate the tests in the browser with the Selenium driver.
  • Finally, we run the tests by executing the Cucumber.js executable file present in the node_modules/.bin folder.

Running Our First Cucumber.js Test Script

The next step in this Cucumber.js tutorial is to execute a sample application. We will start by creating a project directory named cucumber_test and then a subfolder name script with a test script name single_test.js inside it.

Then we’ll add a scenario with the help of a .feature file. Which will be served to our application as we instruct Cucumber.js to run the feature file. Finally, the Cucumber.js framework will parse the file and call the code that matches the information in the feature file.

Here for our first test scenario, we will start with a very easy browser-based application that on visiting the Selenium official homepage allows us to make a search by clicking the search button.

Please make a note of the package.json file that we will be using in our upcoming demonstrations.

package.json

The package.json file contains all the configuration related to the project and certain dependencies that are essential for the project setup. It is important to note that the definitions from this file are used for executing the script and hence this acts as our project descriptor.

{
  "name": "Cucumber.js Javascript test with Selenium",
  "version": "1.0.0",
  "description": "CucumberJS Tutorial for Selenium JavaScript Testing",
  "main": "index.js",
  "scripts": {
    "test": "./node_modules/.bin/cucumber-js"
  },
  "repository": {
    "type": "git",
    "url": ""
  },
  "author": "",
  "license": "ISC",
  "description": {
    "url": ""
  },
  "homepage": "",
  "dependencies": {
    "assert": "^1.4.1",
    "chromedriver": "^2.24.1",
    "cucumber": "^1.3.0",
    "geckodriver": "^1.1.3"
  },
  "devDependencies": {
    "selenium-webdriver": "^3.6.0"
  }
}

Now, the first step of the project is to define our feature that we are going to implement i.e within this file, we will describe the behavior that we would want from our application, which is visiting the website in our case. This feature lets the browser check for the elements. Hence , we will update our feature file with the code. Below is how our feature file looks like which contains the given when and then scenarios.

feature_test.feature

Now we will have our first basic scenarios for visiting the website defined in the feature file followed by other scenarios. These scenarios will follow a given-when-then template.

  • Given: It sets the initial context or preconditions.
  • When: This resembles the event that is supposed to occur in the scenario.
  • Then: This is the expected outcome of the test scenario.

Feature: A feature to validate certain actions on visiting the Selenium Dev website

Scenario: On visiting the homepage of selenium.dev Given I have visited the Selenium official web page on www.selenium.dev When There is a tile on the page as SeleniumHQ Browser Automation Then I should be able to click Search in the sidebar.

steps_def.js

Now moving on to define the steps. Here we define the functions that match with the steps in our scenarios and the actions that it should perform whenever a scenario is triggered.

/* This Cucumber.js tutorial file contains the step definition or the description of each of the behavior that is expected from the application */

'use strict';

const { Given, When, Then } = require('cucumber');

const assert = require('assert')

const webdriver = require('selenium-webdriver');

// // The step definitions are defined for each of the scenarios // //

// // The “given” condition for our test scenario // //
Given(/^I have visited the Selenium official web page on "([^"]*)"$/, function (url, next) {

this.driver.get('https://www.selenium.dev').then(next);

  });

// // The “when” condition for our test scenario // //
  When(/^There is a title on the page as "SeleniumHQ Browser Automation" "([^"]*)"$/, function (titleMatch, next) {
    this.driver.getTitle()
      .then(function(title) {
        assert.equal(title, titleMatch, next, 'Expected title to be ' + titleMatch);

// // The “then” condition for our test scenario // //
Then(/^I should be able to click Search in the sidebar $/, function (text, next) {
     this.driver.findElement({ id: 'searchText' }).click();
     this.driver.findElement({ id: 'searchText' }).sendKeys(text).then(next);
  });

An important thing to note here is that, if we execute the test with only written the .feature file and nothing else, the cucumber framework will throw us an error and prompt us to define the steps. It states that although we have defined the feature, the step definitions are missing, and it will further suggest us to write the code snippets that turn the phrases defined above into concrete actions.

support.js

The support and hooks file is used with the step definition to initialize the variables and perform certain validations.

// // This Cucumber.js tutorial support file to perform validations and initialization for our app // //


const { setWorldConstructor } = require('cucumber')

const { seleniumWebdriver } = require('selenium-webdriver');

var firefox = require('selenium-webdriver/firefox');

var chrome = require('selenium-webdriver/chrome');

class CustomWorld {
  constructor() {
    this.variable = 0
  }

function CustomWorld() {

  this.driver = new seleniumWebdriver.Builder()
                  .forBrowser('chrome')
                  .build();
}

setWorldConstructor(CustomWorld)

module.exports = function() {

  this.World = CustomWorld;

  this.setDefaultTimeout(30 * 1000);
};

hooks.js

It releases the driver when the test execution is complete.

module.exports = function() {
  this.After(function() {
    return this.driver.quit();
  });
};

Finally, when we execute the test, we can see in the command line that our test got executed successfully.

$ npm test

test-senario

Now let’s have a look at another example that will perform a search query on google and verify the title of the website to assert whether the correct website is launched in the browser.

feature_test2.feature

Feature: A feature to check on visiting the Google Search website

Scenario: Visiting the homepage of Google.com Given I have visited the Google homepage. Then I should be able to see Google in the title bar

steps_def2.js

/* This Cucumber.js Tutorial file contains the step definition or the description of each of the behavior that is expected from the application which in our case is the webpage that we are visiting for selenium javascript testing .*/

var assert = require('assert');

// // This scenario has only “given” and “then” condition defined // //

module.exports = function () {
  this.Given(/^I have visited the Google homepage$/, function() {
    return this.driver.get('http://www.google.com');
  });

  this.Then(/^I should be able to see Google in title bar$/, function() {
    this.driver.getTitle().then(function (title) {
      assert.equal(title, "Google");
      return title;
    });
  });
};

support2.js

// This Cucumber.js tutorial support file is used to perform validations and initialization for our application // 

var seleniumWebdriver = require('selenium-webdriver');

var firefox = require('selenium-webdriver/firefox');

var chrome = require('selenium-webdriver/chrome');

function CustomWorld() {

  this.driver = new seleniumWebdriver.Builder()
                  .forBrowser('chrome')
                  .build();
}

module.exports = function() {

  this.World = CustomWorld;

  this.setDefaultTimeout(30 * 1000);
};

hooks2.js

module.exports = function() {
  this.After(function() {
    return this.driver.quit();
  });
};

Again, when we execute the test, we can see in the command line that our test got executed successfully.

$ npm test

test-senario1

Kudos! You have successfully executed your first Cucumber.js script for Selenium test automation. However, this Cucumber.js tutorial doesn’t end there! Now that you are familiar with Selenium and Cucumber.js, I want you to think about the scalability issues here.

So far you have successfully executed the Cucumber.js script over your operating system. However, if you are to perform automated browser testing, how would you go about testing your web-application over hundreds of different browsers + OS combinations?

You can go ahead and build a Selenium Grid to leverage parallel testing. However, as your test requirements will grow, you will need to expand your Selenium Grid which would mean spending a considerable amount of money over the hardware. Also, every month a new browser or device will be launched in the market. To test your website over them, you would have to build your own device lab.

All of this could cost you money and time in maintaining an in-house Selenium infrastructure. So what can you do?

You can leverage a Selenium Grid on-cloud. There are various advantages over choosing cloud-based Selenium Grid over local setup. The most pivotal advantage is that it frees you from the hassle of maintaining your in-house Selenium infrastructure. It’ll save you the effort to install and manage unnecessary virtual machines and browsers. That way, all you need to focus on is running your Selenium test automation scripts. Let us try and execute our Cucumber.js script over an online Selenium Grid on-cloud.

Running Cucumber.js Script Over An Online Selenium Grid

It is time that we get to experience a cloud Selenium Grid by getting ourselves trained on executing the test script on LambdaTest, a cross browser testing cloud.

LambdaTest allows you to test your website on 2000+ combinations of browsers & operating systems, hosted on the cloud. Not only enhancing your test coverage but also saving your time around the overall test execution.

To run the same script on LambdaTest Selenium Grid, you only need to tweak your Selenium JavaScript testing script a little. As you would now want to specify the hub URL for the Remote WebDriver which would execute your script on our Selenium Grid. Add the username and access key token. For this, we must add the access key token and also the username details in the configuration files i.e. cred.conf.js file present within the conf directory. The username and access key token can be exported in two ways as mentioned below.

cred.conf.js

exports.cred = {
    username: process.env.LT_USERNAME || 'rahulr',
    access_key: process.env.LT_ACCESS_KEY || 'AbcdefgSTAYSAFEhijklmnop'
}

Alternatively, the username and access key token can be easily exported using the command as shown below.

export LT_USERNAME=irohitgoyal

export LT_ACCESS_KEY= AbcdefgSTAYSAFEhijklmnop

Next, we will look at the feature file. We will be executing our test on the Google Chrome browser. In our test case, we will open the LambdaTest website to perform certain operations on it such as launching the search engine, validating the content , etc. So, our directory structure will be pretty simple as below:

feature_test.feature

Feature: Google Search the website and check for certain actions Scenario: Perform certain Actions on the homepage When I visit the website of Google on “https://google.com” When the homepage has the field with “Google Search” is present When the homepage has the field with “I’m Feeling Lucky” is present When I move the cursor and select the textbox to make a search on Google Then click the “Google Search” on the text box
Then I must see title “Google” on the homepage

Now, we need to think of our desired capabilities. We can leverage LambdaTest Selenium Desired Capabilities Generator feature to select the environment specification details and allows us to pick from various combinations that it offers, we can use this to select the combination we want to perform our Selenium javascript testing for this Cucumber.js tutorial.

lambdatest-capability-generator

So, in our test scenario the desired capabilities class will look something similar as below:

const desiredCapabilities = {
  'build': 'Cucumber-JS-Selenium-Webdriver-Test', // the build name that is to be display in the test logs
  'browserName': 'chrome', // the browser that we would use to perform test
  'version':'74.0', // the browser version that we would use.
  'platform': 'WIN10', // The type of the Operating System that we would use
  'video': true, // flag to check whether to capture the video selenium javascript testing 
.
  'network': true, // flag to check whether to capture the network logs
  'console': true, // flag to check whether to capture the console logs
  'visual': true // flag to check whether to the capture visual for selenium javascript testing 
};

With that set, we now look at the step definitions and the cucumber runner.js.

step_def.js

/*
This Cucumber.js tutorial file contains the step definition or the description of each of the behavior that is expected from the application which in our case is the webpage that we are visiting.
It is aligned with the feature file and reads all the instructions from it and finds the matching case to execute it for selenium javascript testing 

.
*/


'use strict';

const assert = require('cucumber-assert');
const webdriver = require('selenium-webdriver');

module.exports = function() {

  this.When(/^I visit website of Google on "([^"]*)"$/, function (url, next) {
    this.driver.get('https://google.com ').then(next);
  });

  this.When(/^the homepage has the field with "Google Search" is present
$/, function (next) {
      this.driver.findElement({ name: 'li1' })
      .click().then(next);
  });

  this.When(/^the homepage has the field with "I’m Feeling Lucky" is present $/, function (next) {
      this.driver.findElement({ name: 'li3' })
      .click().then(next);
  });

  this.When(/^I move the cursor and select the textbox to make a search on Google $/, function (text, next) {
      this.driver.findElement({ id: 'buttonText' }).click();
      this.driver.findElement({ id: 'buttonText' }).sendKeys(text).then(next);
  });

  this.Then(/^click the "Google Search" on the text box "([^"]*)"$/, function (button, next) {
    this.driver.findElement({ id: button }).click().then(next);
  });

  this.Then(/^I must see title "Google" on the homepage "([^"]*)"$/, function (titleMatch, next) {
    this.driver.getTitle()
      .then(function(title) {
        assert.equal(title, titleMatch, next, 'Expected title to be ' + titleMatch);
      });
  });
};

cucumber-runner.js

#!/usr/bin/env/node

//
It resembles our runner file for parallel tests. This file is responsible to create multiple child processes, and  it is equal to the total number of test environments passed for selenium javascript testing 

.
//

let childProcess = require ('child_process') ;
let configFile  = '../conf/' + ( process.env.CONFIG_FILE  || 'single' ) + '.conf.js';
let config = require (configFile ).config;

process.argv[0] = 'node';
process.argv[1] = './node_modules/.bin/cucumber-js';

const getValidJson = function(jkInput) {
    let json = jkInput;
    json = json.replace(/\\n/g, "");
    json = json.replace('\\/g', '');
    return json;
};

let lt_browsers = null;
if(process.env.LT_BROWSERS) {
    let input = getValidJson(process.env.LT_BROWSERS);
    lt_browsers = JSON.parse(input);
}

for( let i in (lt_browsers || config.capabilities) ){
  let env = Object.create( process.env );
  env.TASK_ID = i.toString();
  let p = childProcess.spawn('/usr/bin/env', process.argv, { env: env } ); 
  p.stdout.pipe(process.stdout);
}

Now since our test scripts are ready to be executed in the cloud grid , the final thing that we are required to do is to run the tests from the base project directory using the below command:

$ npm test

This command will validate the test cases and execute our test suite across all the test groups that we have defined. And, if we open the LambdaTest Selenium Grid and navigate to the automation dashboard, we can check that the user interface shows that the test ran successfully and passed with positive results.

Below is the sample screenshot:

sample-screenshot

Don’t Forget To Leverage Parallel Testing

Parallel testing with Selenium can help you significantly trim down your test cycles. Imagine if we have at least 50 test cases to execute and each of them runs for an average run time of 1 minute. Ideally, it would take around 50 minutes to execute the test suite. But if we execute 2 test cases in 2 parallel concurrent sessions, the total test time drops down to 25 minutes. Hence, we can see a drastic decrease in test time. To execute the parallel testing with Selenium for this Cucumber.js tutorial, execute the below command $ npm run parallel.

Bottom line

Cucumber.js provides us the capability to write tests in a way that is easily read by everyone. Making the framework very flexible and enabling us to create human-readable descriptions of user requirements as the basis for web application tests. WIth Cucumber.js we can interact with our webpage on the browser and make various assertions to verify that the changes we performed are actually reflected in our web application on every Browser-OS combination by utilizing Selenium Grid. Still, there is a lot more that can be done with Cucumber.js. Since this test framework is developed over the Selenium interface, it empowers us with limitless capabilities in terms of Selenium JavaScript testing. Let us know if you liked this Cucumber.js tutorial and if there’s any topic you want us to write on. Happy Testing and Stay Safe!

Top comments (1)

Collapse
 
ads-bne profile image
ADS-BNE

Great tutorial!

I have one issue that's been bugging me for ages. That is, running the same test on multiple pages.

For example, if I want to check that 2 different pages have a banner element on them:

On home-page.feature

Feature: Home Page

Scenario: Check the home page
    Given I am on the home page
    Then there should be a banner
    // other test
Enter fullscreen mode Exit fullscreen mode

and on about-page.feature

Feature: About Page

Scenario: Check the about page
    Given I am on the about page
    Then there should be a banner
    // other test
Enter fullscreen mode Exit fullscreen mode

and then on each matching steps.js I have:

Then('there should be a banner', async function() {....
Enter fullscreen mode Exit fullscreen mode

Which fails with the error: Multiple step definitions match

Do you know of a way I can run the same test on multiple pages?

Many thanks!