Cucumber.js Tutorial With Examples For Selenium JavaScript
This Cucumber.js tutorial will help you automate browser testing with Selenium and JavaScript across a cloud-based Selenium Grid of 2000+ browsers for mobile and desktop.
Join the DZone community and get the full member experience.
Join For FreeThe 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 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 is $1,000 and the card is valid, and the machine contains enough money. When the account holder requests $200, 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 with the tests and makes them so legible that there is hardly a need for documentation for the test cases, and can even be digested by 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 the node package manager from the nodejs.org official website or using the package installer for different operating systems downloaded from the website 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 set up/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.
$ npm install -g chromedriver
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 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
file that has the scenarios and each of these scenarios with a given-when-then structure defined. - Next, we write down step definition files which typically define the functions that match 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 configurations 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 the 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 what 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.
steps_def.js
Now moving on to define the steps. Here we define the functions that match the steps in our scenarios and the actions that they 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 we 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
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
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 to choosing a cloud-based Selenium Grid over a 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 3000+ 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
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:
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
.
Bottomline
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!
Published at DZone with permission of Rahul Rana. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments