Advanced Test Automation: WDIO with BDD Cucumber

digitaljignect 13 views 44 slides Aug 28, 2025
Slide 1
Slide 1 of 44
Slide 1
1
Slide 2
2
Slide 3
3
Slide 4
4
Slide 5
5
Slide 6
6
Slide 7
7
Slide 8
8
Slide 9
9
Slide 10
10
Slide 11
11
Slide 12
12
Slide 13
13
Slide 14
14
Slide 15
15
Slide 16
16
Slide 17
17
Slide 18
18
Slide 19
19
Slide 20
20
Slide 21
21
Slide 22
22
Slide 23
23
Slide 24
24
Slide 25
25
Slide 26
26
Slide 27
27
Slide 28
28
Slide 29
29
Slide 30
30
Slide 31
31
Slide 32
32
Slide 33
33
Slide 34
34
Slide 35
35
Slide 36
36
Slide 37
37
Slide 38
38
Slide 39
39
Slide 40
40
Slide 41
41
Slide 42
42
Slide 43
43
Slide 44
44

About This Presentation

In today’s fast development environment, effective communication among developers, testers, and stakeholders is the need of the hour. Cucumber BDD in JavaScript, by bridging the technical and non-technical team members, provides a powerful solution for the same. Writing tests in a natural, human-r...


Slide Content

In today’s fast development environment, effective communication
among developers, testers, and stakeholders is the need of the hour.
Cucumber BDD in JavaScript, by bridging the technical and non-
technical team members, provides a powerful solution for the same.
Writing tests in a natural, human-readable language helps everyone
understand the behavior of the application from a business
perspective.
We’ll talk in this blog about implementingCucumber BDD in
JavaScript and help you write clean, understandable test cases that
reflect real-life user behavior. This would help set up your
environment from scratch, creating feature fqles and executing them
to incorporate BDD into your workflow ontest automation using
JavaScript. Get ready to learn how Cucumber BDD in JavaScript can
Mastering Advanced BDD Automation with
WebdriverIO, JavaScript, and Cucumber

improve collaboration, increase test coverage, and make your testing
process more effqcient!
Table of Content
Behavior-Driven Development (BDD) and Cucumber with
JavaScript
How JavaScript Integrates with Cucumber?
Why Use Cucumber with JavaScript?
Writing Your First Cucumber Feature File
Example of a Simple Feature File
Understanding Feature File Structure
Connecting Cucumber with JavaScript
Creating Step Defqnitions in JavaScript
Step Defqnitions Structure:
Example: Step Defqnitions File (steps.js)
Mapping Gherkin Steps to JavaScript Functions
Regular Expressions and Cucumber Steps
Using async/await for Asynchronous Actions in Step
Defqnitions
Example of Asynchronous Step Defqnitions
Step Defqnitions Syntax
Writing Test Steps with WebDriverIO + Cucumber
Interacting with Web Elements (Click, Set Value, Assert
Text)
Navigating to Different Pages
Waiting for Elements (timeouts, waits)
Handling Dynamic Content (Waiting for Changes)
Using Cucumber Tags and Hooks
What are Cucumber Tags?
Applying Tags to Features and Scenarios
Running Tests Based on Tags
Cucumber Hooks

Executing Code Before/After Tests or Scenarios
Benefqts of Using Hooks and Tags
Data-Driven Testing with Cucumber
Using Examples in Gherkin
How It Works:
Passing Data from the Feature File to Step Defqnitions
Step Defqnition for the Scenario
Parameterized Tests and Looping Through Examples
Working with Tables in Gherkin
Assertions and Validations in Cucumber
Using WebDriverIO Assertions
Comon WebDriverIO Assertions
Custom Assertions in Step Defqnitions
Handling Validation Errors
Using Assertions to Verify UI and Functionality
Best Practices for Assertions and Validations in
Cucumber
Organizing Tests with Cucumber
Creating Reusable Step Defqnitions
Modularizing Feature Files
Using Page Object Model (POM) with Cucumber
Grouping Scenarios and Features
Best Practices for Maintaining Tests
Debugging Cucumber Tests in JavaScript
Comon Issues and Errors in Cucumber
Using console.log() for Debugging Step Defqnitions
Running Tests in Verbose Mode
How to Enable Verbose Mode for WebDriverIO:
Debugging WebDriverIO Tests
Advanced Cucumber Features 
Backgrounds in Gherkin
Scenario Outline with Examples
Reusable Step Libraries
Parallel Test Execution
Custom Cucumber Formatters for Reporting

Continuous Integration using Cucumber
Setting Up CI Pipelines
Running Tests in CI/CD Environments
Integrating Cucumber Test Results with CI Tools
Generating and Interpreting Cucumber Reports
 Cucumber Reporting and Test Results
Using Built-in Reporters in Cucumber.js
Generating JSON, HTML, and Other Reports
Integration with Third-party Reporting Tools (Allure,
Cucumber HTML Reporter)
Visualizing Test Results
Conclusion
Behavior-Driven Development (BDD)
and Cucumber with JavaScript
For Overview of Behavior-Driven Development (BDD) ,Cucumber
Basics and Setting Up the Environment You can refer our blog
Cucumber BDD in JavaScript
How JavaScript Integrates with
Cucumber?
JavaScript is another language that can be used in writing Cucumber
tests, giving JavaScript developers a chance to utilize the capability
of BDD offered by Cucumber. Cucumber-JS is one implementation of
Cucumber in the JavaScript environment where one may write, run,
and manage BDD tests.
Basic Steps to Integrate JavaScript with Cucumber
Writing Features in Gherkin: In the fqrst step, the feature fqles
are written with Gherkin syntax. The behavior of the system is

described in plain English (or another natural language).
Step Defqnitions: Once a feature is written, JavaScript step
defqnitions are developed to map Gherkin steps to actual
JavaScript code implementing the actions. For example, given
that a feature had the following 
step: Given I am on the homepage, this would have
corresponding JavaScript in the step defqnition fqle that
implements action to get to the home page.
Running the Tests: Once Gherkin features and JavaScript step
defqnitions are created, Cucumber-JS executes the tests by
running a scenario in the feature fqle and then matching those up
with the corresponding step defqnition in JavaScript.
Assertions: Finally, the step defqnitions validate their expected
behavior using JavaScript assertions – usually with libraries
such as Chai or Jest.
Here’s an example of how Cucumber integrates with JavaScript.
Feature File (login.feature):
Feature: User login functionality
Scenario: User logs in successfully
Given I navigate to the login page
When I enter valid credentials
Then I should see the dashboard page
Step Defqnitions (loginSteps.js):
const { Given, When, Then } = require('@cucumber/cucumber');
const { expect } = require('chai');
const { navigateToLoginPage, enterCredentials, seeDashboard } =
require('./helpers');
Given('I navigate to the login page', async function() {
await navigateToLoginPage();
});

When('I enter valid credentials', async function() {
await enterCredentials('[email protected]', 'password123');
});
Then('I should see the dashboard page', async function() {
const dashboardVisible = await seeDashboard();
expect(dashboardVisible).to.be.true;
});
In this example:
Gherkin defqnes the behavior in a human-readable format.
JavaScript provides the actual test steps in loginSteps.js,
where the step defqnitions map to functions that interact with
the application.
Chai assertions are used to verify that the test behaves as
expected.
Why Use Cucumber with JavaScript?
Seamless Integration with Web and Mobile Testing
Frameworks: JavaScript is the language of choice for web
applications testing with Selenium, WebDriverIO, or Cypress. For
mobile applications testing, Appium supports the language as
well. So, the combination of Cucumber with JavaScript lets
teams leverage the strength of BDD and the flexibility of the
chosen testing frameworks to easily write, maintain, and execute
acceptance tests.
By writing tests in Gherkin: you are actually creating readable
and maintainable specifqcations for your system’s behavior. This
will make sure that anyone can understand the business
requirements irrespective of their technical expertise and then
verify that the application really meets them.
Unifqed Testing Approach: Cucumber-JS helps in unifying your
testing approach by allowing frontend JavaScript-based tests

and backend Node.js testing in one language, thus eliminating
the context-switching between different languages and tools.
Collaboration among Stakeholders: Another strength of
Cucumber is that it engages business analysts, testers, and
developers in testing. Gherkin language makes writing tests
quite easy, and therefore involving stakeholders in the
development process ensures that the software does exactly
what is expected of it by business stakeholders.
Cross-Platform Testing: Since Cucumber is a platform-
agnostic tool, it can be used in conjunction with various testing
frameworks for web applications (Selenium, Cypress) and mobile
applications (Appium).
Writing Your First Cucumber Feature File
This tool supports BDD and writes tests in plain language for its users
to read and be clearly understood both by technical as well as non-
technical stakeholders. Cucumber test code is in Gherkin, the
language which will describe test cases in specifqc syntax in
the.feature fqle format.
This guide walks through the process of creating your fqrst Cucumber
feature fqle. You will see how to start with a basic example with Given,
When, and Then steps as well as how to write tests in Gherkin.
Creating.feature Files
A feature fqle is a very simple text fqle with a `.feature` extension
containing your test written in Gherkin syntax. The feature fqle
describes a feature – typically a high-level description of the
functionality – and all the scenarios that would point out how the
feature should behave.
Here’s how to structure the .feature fqle:

U. Feature: Describes the feature being tested.
2. Scenario: Describes a specifqc situation or behavior within that
feature.
3. Given: A precondition or setup for the test.
4. When: The action that takes place.
5. Then: The expected outcome or result.
V. And/But: Used to extend Given, When, and Then steps with
more conditions.
Example of a Simple Feature File
Feature: User Login
Scenario: User logs in with correct credentials
Given the user is on the login page
When the user enters a valid username and password
Then the user should be redirected to the homepage
This feature fqle is written in Gherkin syntax and describes the
behavior of a user logging into a website with valid credentials.
Scenario with Given, When, Then Steps
Let’s break down the structure of a Gherkin scenario:
Given: This step sets up the initial state of the system.
It describes the conditions before the user performs any action.
Example: “Given the user is on the login page”
When: This step describes the action the user performs.
It defqnes what happens in response to the user’s actions.
Example: “When the user enters a valid username and password”

Then: This step describes the expected outcome or result of the
action.
It specifqes what the system should do after the action is
performed.
Example: “Then the user should be redirected to the homepage”
You can also use And and But to group Bltiple conditions together
within a single step:
Scenario: User logs in with incorrect credentials
Given the user is on the login page
When the user enters an invalid username
And the user enters an invalid password
Then the user should see an error message
In this example, And is used to add more steps in the “When” and
“Then” sections.
Understanding Feature File Structure
A typical feature fqle structure consists of several key components:
Feature: The name of the feature you’re testing.
This can be followed by a short description of the feature.
Example: Feature: User Login and then a brief description of
what this feature entails.
Scenario: Each scenario represents a specifqc test case or use case.
This is the “test” part of the BDD scenario.
It provides specifqc examples of how the feature should work.

Steps: Steps are defqned using the keywords Given, When, Then, And,
and But, and explain the behavior of what is happening, or should
happen.
Here’s an extended example with multiple scenarios and steps:
Feature: User Login
# Scenario 1: Successful login
Scenario: User logs in with correct credentials
Given the user is on the login page
When the user enters a valid username and password
Then the user should be redirected to the homepage
# Scenario 2: Unsuccessful login with incorrect password
Scenario: User logs in with incorrect password
Given the user is on the login page
When the user enters a valid username and an incorrect
password
Then the user should see an error message
# Scenario 3: Login with empty fields
Scenario: User submits the form with empty fields
Given the user is on the login page
When the user leaves the username and password fields empty
Then the user should see a validation message
Connecting Cucumber with JavaScript
You can write your automated acceptance tests in an almost human-
readable format when using Gherkin for expressing Cucumber. In this
regard, you would typically pair these tests with JavaScript’s step
defqnitions to run those tests in JavaScript.. In this section, we will

walk through how to connect Cucumberwith JavaScript, create step
defqnitions, map Gherkin steps to JavaScript functions, and handle
asynchronous actions using async/await.
Creating Step Definitions in JavaScript
Step defqnitions are JavaScript functions that are linked to the steps
in your Gherkin scenarios. When a scenario step is executed,
Cucumber will call the corresponding JavaScript function in your step
defqnition fqles.
Step Definitions Structure:
Given: Sets up the initial state.
When: Describes the action or behavior.
Then: Specifqes the expected outcome.
Example: Step Definitions File (steps.js)
steps.js File:
const { Given, When, Then } = require('@cucumber/cucumber');
const { browser } = require('webdriverio'); // Assuming you're
using WebDriverIO for automation
// Mapping the "Given" step in the Gherkin file to this JavaScript
function
Given('the user is on the login page', async function () {
// Navigate to the login page (replace with your actual login
page URL)
await browser.url('https://example.com/login');
console.log('Navigating to the login page...');
});
// Mapping the "When" step in the Gherkin file to this JavaScript
function
When('the user enters a valid username and password', async

function () {
// Replace these with your actual locators for the username and
password fields
const usernameField = await $('#username'); // CSS selector for
username input field
const passwordField = await $('#password'); // CSS selector for
password input field
// Replace with valid credentials
const validUsername = 'testuser';
const validPassword = 'Test1234';
// Input the username and password
await usernameField.setValue(validUsername);
await passwordField.setValue(validPassword);
console.log('Entering valid credentials...');
});
// Mapping the "Then" step in the Gherkin file to this JavaScript
function
Then('the user should be redirected to the homepage', async
function () {
// Wait for the homepage element to be visible
const homepageElement = await $('#homepage'); // CSS selector
for an element that confirms the homepage has loaded
// Assert that the homepage element is displayed
const isDisplayed = await homepageElement.isDisplayed();
if (isDisplayed) {
console.log('User successfully redirected to the homepage.');
} else {
console.log('Redirection to homepage failed.');
}
});
In this example, each of the Gherkin steps (Given, When, Then) is
mapped to a JavaScript function. These functions contain the logic
for interacting with the application, such as navigating to a page,
entering credentials, checking the redirection.
Mapping Gherkin Steps to JavaScript

Functions
Gherkin steps (e.g., Given, When, Then) are mapped to JavaScript
functions through regular expressions or step defqnitions. Cucumber
uses regular expressions to match the Gherkin steps and map them
to the corresponding JavaScript functions.
Here’s a closer look at how mapping works:
Given:
Describes the state of the system before the action happens.
Example: “Given the user is on the login page”
When:
Describes an action taken by the user.
Example: “When the user enters a valid username and password”
Then:
Describes the expected outcome after the action.
Example: “Then the user should be redirected to the homepage”
These steps in the feature fqle are mapped to step defqnition functions
in the JavaScript fqle. For example:
Gherkin: Given the user is on the login page
JavaScript: Given(‘the user is on the login page’, function() { /*
code */ });
Regular Expressions and Cucumber
Steps
Cucumber allows you to use regular expressions or literal strings to

defqne step defqnitions. The expression in the step defqnition should
match the Gherkin step text.
Example:
Given("I am on the {string} page", function (pageName) {
// Perform action based on the page name
console.log(`Navigating to ${pageName} page`);
});
In this case, {string} is a parameter that Cucumber passes into the
function as pageName.
Using async/await for Asynchronous
Actions in Step Definitions
JavaScript, especially in modern applications, often involves
asynchronous actions (e.g., interacting with a database, waiting for
API calls, or automating browser actions with a tool like WebDriverIO
or Selenium). Since Cucumber runs in an asynchronous environment,
it is important to handle asynchronous steps using async/await.
Here’s how you can use async/await in Cucumber step defqnitions:
Example of Asynchronous Step Definitions
const { Given, When, Then } = require('@cucumber/cucumber');
Given('the user is on the login page', async function () {
await navigateToLoginPage();
});
When('the user enters a valid username and password', async
function () {

await enterCredentials('validUser', 'validPassword');
});
Then('the user should be redirected to the homepage', async
function () {
const isRedirected = await checkRedirection();
if (isRedirected) {
console.log('User redirected to homepage');
} else {
console.log('User not redirected');
}
});
async function navigateToLoginPage() {
console.log('Navigating to the login page...');
await new Promise(resolve => setTimeout(resolve, 1000));
}
async function enterCredentials(username, password) {
console.log(`Entering username: ${username} and password:
${password}`);
await new Promise(resolve => setTimeout(resolve, 1000));
}
async function checkRedirection() {
console.log('Checking if the user is redirected to the
homepage...');
return new Promise(resolve => {
setTimeout(() => {
resolve(true);
}, 2000);
});
}
In this example:
Each of the steps (Given, When, Then) is asynchronous and
uses async/await to handle tasks such as navigating to pages,
entering data, or checking results.
The checkRedirection() function returns a promise, which
siBlates an asynchronous check for a redirect.

Step Definitions Syntax
Cucumber step defqnitions are written using a syntax that matches
the Gherkin steps. You use the Given, When, Then, And, and But
keywords to defqne steps in the feature fqle, and then map these steps
to JavaScript functions.
Basic Syntax:
const { Given, When, Then } = require('@cucumber/cucumber');
Given('the user is on the login page', async function () {
await browser.url('https://example.com/login');
});
When('the user enters a valid username and password', async
function () {
const usernameInput = await $('#username');
const passwordInput = await $('#password');
const loginButton = await $('#login-button');
await usernameInput.setValue('validUser');
await passwordInput.setValue('validPassword');
await loginButton.click();
});
Then('the user should be redirected to the homepage', async
function () {
const currentUrl = await browser.getUrl();
if (currentUrl !== 'https://example.com/home') {
throw new Error(`Expected to be redirected to the homepage,
but was redirected to ${currentUrl}`);
}
});
Using Parameters in Steps:
You can use parameters in the Gherkin steps and pass them into the

step defqnitions. These parameters can be anything, such as a page
name, a username, or an expected outcome.
Example with parameters:
Given('the user is on the {string} page', function (pageName) {
// Code to navigate to a dynamic page
console.log(`Navigating to the ${pageName} page.`);
});
In this example:
{string} is a placeholder that will be replaced with a value (e.g.,
“login” or “dashboard”) when the feature is run.
The parameter pageName is passed into the function, where you
can use it to navigate to the appropriate page.
Writing Test Steps with WebDriverIO +
Cucumber
Interacting with Web Elements (Click,
Set Value, Assert Text)
const { Given, When, Then } = require('@cucumber/cucumber');
const assert = require('assert');
Given(/^I am on the "([^"]*)" page$/, async (pageName) => {
if (pageName === 'homepage') {
await browser.url('https://example.com');
} else if (pageName === 'dashboard') {
await browser.url('https://example.com/dashboard');
} else {
throw new Error(`Page "${pageName}" not recognized.`);

}
});
When(/^I click the "([^"]*)" button$/, async (buttonText) => {
const button = await $(`button=${buttonText}`);
await button.click();
});
Then(/^I should see the text "([^"]*)"$/, async (expectedText) =>
{
const pageText = await $('body').getText();
try {
assert(pageText.includes(expectedText), `Expected text to
include "${expectedText}", but found "${pageText}".`);
} catch (error) {
console.log('Assertion failed:', error);
throw error;
}
});
When(/^I set the value of the "([^"]*)" field to "([^"]*)"$/,
async (fieldName, value) => {
const inputField = await $(`input[name="${fieldName}"]`);
if (inputField) {
await inputField.setValue(value);
} else {
throw new Error(`Field "${fieldName}" not found.`);
}
});
Navigating to Different Pages
To test navigation, use the browser’s url function and verify the result
after the action.
Given(/^I am on the homepage$/, async () => {
await browser.url('https://example.com');
});

When(/^I navigate to the "([^"]*)" page$/, async (pageName) => {
if (pageName === 'about') {
await browser.url('https://example.com/about');
}
// You can add more conditions to handle other page
navigations
});
Then(/^I should be on the "([^"]*)" page$/, async (expectedPage)
=> {
if (expectedPage === 'about') {
const currentUrl = await browser.getUrl();
assert(currentUrl.includes('about'), 'Expected to be on
the About page');
}
});
Waiting for Elements (timeouts, waits)
WebDriverIO has several ways to wait for elements, such as
waitForExist(), waitForDisplayed(), or custom waits. Here’s an
example using waitForDisplayed():
const { Given, When, Then } = require('@cucumber/cucumber');
const assert = require('assert');
When(/^I click the "([^"]*)" button$/, async (buttonText) => {
const button = await $(`button=${buttonText}`);
try {
await button.waitForDisplayed({ timeout: 5000 });
await button.click();
console.log(`Clicked the button with text:
${buttonText}`);
} catch (error) {
throw new Error(`Failed to click the button with text
"${buttonText}". Button might not be visible or clickable.`);
}
});
Then(/^I should see the "([^"]*)" button$/, async (buttonText) =>
{

const button = await $(`button=${buttonText}`);
try {
await button.waitForDisplayed({ timeout: 5000 });
const isDisplayed = await button.isDisplayed();
assert(isDisplayed, `Button with text "${buttonText}"
should be displayed`);
console.log(`Button with text "${buttonText}" is
visible.`);
} catch (error) {
throw new Error(`Button with text "${buttonText}" not
found or not displayed after waiting.`);
}
});
Explanation: waitForDisplayed() waits for an element to be visible
before proceeding with the next action. You can adjust the timeout
value as needed.
Handling Dynamic Content (Waiting for
Changes)
For handling dynamic content like loading spinners, you can wait until
certain elements are either displayed or hidden.
const { Given, When, Then } = require('@cucumber/cucumber');
const assert = require('assert');
When(/^I click the "([^"]*)" button$/, async (buttonText) => {
const button = await $(`button=${buttonText}`);
try {
await button.waitForDisplayed({ timeout: 5000 });
await button.click();
console.log(`Clicked the button with text:
${buttonText}`);
} catch (error) {
throw new Error(`Failed to click the button with text
"${buttonText}". It might not be visible or clickable after
waiting for 5 seconds.`);

}
});
Then(/^I should see the "([^"]*)" button$/, async (buttonText) =>
{
const button = await $(`button=${buttonText}`);
try {
await button.waitForDisplayed({ timeout: 5000 });
const isDisplayed = await button.isDisplayed();
assert(isDisplayed, `Expected the button with text
"${buttonText}" to be displayed, but it was not.`);
console.log(`Button with text "${buttonText}" is
visible.`);
} catch (error) {
throw new Error(`Button with text "${buttonText}" was not
found or not displayed after waiting for 5 seconds.`);
}
});
Explanation: This waits for the loading spinner to appear and
disappear after submitting a form. We use waitForDisplayed() with the
reverse: true option to check if the element disappears.
Using Cucumber Tags and Hooks
Tags in Cucumber are used to group and fqlter tests. You can tag
features or scenarios with custom labels to organize and selectively
run them.
What are Cucumber Tags?
Tags are special annotations in Cucumber that help categorize
tests.
You can add tags to features or scenarios in your .feature fqles.
Tags are prefqxed with @ (e.g., @smoke, @regression).
Multiple tags can be used, separated by spaces (e.g., @smoke
@highpriority).

Tags allow for easy fqltering, running specifqc subsets of tests, or
organizing your test scenarios by functionality or priority.
Applying Tags to Features and
Scenarios
Tags can be applied to both Feature and Scenario levels.
Example: Feature Level
@regression @login
Feature: Login functionality
Scenario: User logs in with valid credentials
Given the user is on the login page
When the user enters valid credentials
Then the user should be logged in successfully
Example: Scenario Level
Feature: Checkout functionality
@smoke
Scenario: User checks out successfully
Given the user has items in the cart
When the user proceeds to checkout
Then the user should be taken to the payment page
@regression
Scenario: User fails checkout due to empty cart
Given the cart is empty
When the user attempts to checkout
Then the user should see an error message

Running Tests Based on Tags
You can run specifqc tests based on tags using Cucumber’s
comand-line interface.
Example Command:
cucumber-js –tags @smoke
This will run all scenarios with the @smoke tag. You can also combine
tags to run specifqc subsets of tests.
Example of Combining Tags:
cucumber-js –tags “@smoke and @regression”
This will run scenarios that are tagged with both @smoke and
@regression.
Example of Excluding Tags:
cucumber-js –tags “not @slow”
This will run all tests except those that are tagged with @slow.
Cucumber Hooks
Hooks are special methods in Cucumber that allow you to run code
before or after a test, scenario, or feature.
Before Hook
The Before hook runs before each scenario or feature. You can use it
to set up test data, initialize test objects, or perform any necessary

steps before the test executes.
Example: Before Hook
const { Before } = require('@cucumber/cucumber');
Before(async function () {
console.log('Setting up before scenario');
await browser.url('https://example.com/login');
const usernameField = await $('#username');
const passwordField = await $('#password');
await usernameField.setValue('testuser');
await passwordField.setValue('password123');
const loginButton = await $('button[type="submit"]');
await loginButton.click();
await $('#dashboard').waitForDisplayed({ timeout: 5000 });
});
After Hook
The After hook runs after each scenario or feature. It’s useful for
cleanup tasks, such as deleting test data, logging out, or closing
browser windows.
Example: After Hook
const { After } = require('@cucumber/cucumber');
After(async function () {
console.log('Cleaning up after scenario');
// Example: Log out the user if logged in
const logoutButton = await $('#logout');
if (await logoutButton.isDisplayed()) {
await logoutButton.click();
}
// Example: Clear any test data if necessary (e.g., clear the
cart)
const cartItems = await $$('.cart-item');

if (cartItems.length > 0) {
for (const item of cartItems) {
await item.click(); // Example: remove item from cart
}
}
});
BeforeAll Hook
The BeforeAll hook runs once before all scenarios in a feature fqle. It’s
useful for any setup that is required only once, such as database
connections or opening a browser session.
Example: BeforeAll Hook
const { BeforeAll } = require('@cucumber/cucumber');
BeforeAll(function () {
console.log('Setting up before all scenarios');
// Setup code that only needs to run once
});
AfterAll Hook
The AfterAll hook runs once after all scenarios in a feature fqle. This is
useful for cleanup actions that need to happen once after all tests,
such as closing a browser or disconnecting from a database.
Example: AfterAll Hook
const { AfterAll } = require('@cucumber/cucumber');
AfterAll(function () {
  console.log('Cleaning up after all scenarios');

  // Cleanup code that only needs to run once
});
Executing Code Before/After Tests or
Scenarios
You can use the Before and After hooks to execute code before or
after each individual test scenario. The BeforeAll and AfterAll hooks
are executed once for the entire test suite.
Example: Combining Hooks in JavaScript
const { BeforeAll, Before, After, AfterAll, Given, When, Then } =
require('@cucumber/cucumber');
const assert = require('assert');
BeforeAll(async function () {
console.log('Running BeforeAll: Initializing browser session');
await browser.url('https://example.com'); // Open the website
before any tests
});
Before(async function () {
console.log('Running Before: Setting up test data');
// Set up pre-condition data for each scenario like checking if
user is logged in
const loginButton = await $('#loginButton');
if (await loginButton.isDisplayed()) {
await loginButton.click();
}
});
Given('I am on the login page', async function () {
console.log('Scenario starts: Navigating to login page');
const loginPageUrl = 'https://example.com/login';
await browser.url(loginPageUrl); // Navigate to the login page

});
When('I log in with valid credentials', async function () {
console.log('User logs in');
const usernameField = await $('#username'); // Locator for
username field
const passwordField = await $('#password'); // Locator for
password field
const submitButton = await $('#submit'); // Locator for submit
button
// Interact with login form
await usernameField.setValue('testuser'); // Input username
await passwordField.setValue('password123'); // Input password
await submitButton.click(); // Click submit button
});
Then('I should be logged in successfully', async function () {
console.log('Verifying successful login');
const dashboardTitle = await $('#dashboardTitle'); // Locator
for an element visible after login

// Wait for the dashboard title to be displayed, indicating
login success
await dashboardTitle.waitForDisplayed({ timeout: 5000 });
assert(await dashboardTitle.isDisplayed(), 'Dashboard title
should be displayed after login');
});
After(async function () {
console.log('Running After: Cleaning up after scenario');
const logoutButton = await $('#logoutButton'); // Locator for
logout button
if (await logoutButton.isDisplayed()) {
await logoutButton.click(); // Log out if the logout button is
displayed
}
});
AfterAll(async function () {
console.log('Running AfterAll: Closing browser session');
await browser.close(); // Close the browser session after all
tests are done
});

Benefits of Using Hooks and Tags
Separation of Concerns: Hooks allow you to isolate setup and
teardown logic from the actual test scenarios, keeping the tests
clean.
Reusability: Hooks can be reused across Bltiple scenarios,
reducing redundancy.
Filtering Tests: Tags enable you to run subsets of tests, which is
particularly useful for large test suites. You can tag tests as
@smoke, @regression, @sanity, and run them selectively to
ensure fast feedback on critical functionality.
Test Environment Setup: With hooks, you can prepare and
clean up the test environment before and after running tests.
Data-Driven Testing with Cucumber
Data-driven testing allows testing the application with numerous sets
of input data without repeated test scenarios for each dataset, which
makes it a powerful approach. In Cucumber, the same can be
achieved with the help of examples in Gherkin syntax where data will
be passed down to the step defqnitions while working with tables.
Let’s dive into each aspect of data-driven testing in Cucumber.
Using Examples in Gherkin
Examples in Gherkin allow you to run the same scenario multiple
times with different sets of input data. This is often referred to as
parameterized testing.
You can defqne examples directly below a scenario using the
Examples keyword, followed by a table containing the test data.

Example Scenario with Examples Table
Feature: Login functionality
Scenario Outline: User logs in with different credentials
Given the user is on the login page
When the user enters "<username>" and "<password>"
Then the user should be logged in successfully
Examples:
| username | password |
| user1 | password1 |
| user2 | password2 |
| user3 | password3 |
In this example, the same scenario is run with three different sets of
data:
user1 with password1
user2 with password2
user3 with password3
How It Works:
Scenario Outline: A template for the scenario where
placeholders (e.g., <username> and <password>) are used for
dynamic data.
Examples Table: The actual data that will be substituted into the
placeholders. Each row represents a test case.
Passing Data from the Feature File to

Step Definitions
Once the data is provided in the
Examples table, Cucumber will automatically substitute the
placeholders in the step defqnition with the actual values from each
row.
Step Definition for the Scenario
In the step defqnition, we can reference the placeholders defqned in
the Gherkin scenario outline. These are passed as parameters to the
step defqnition methods.
const { Given, When, Then } = require('@cucumber/cucumber');
Given('the user is on the login page', function () {
console.log('User is on the login page');
});
When('the user enters "{string}" and "{string}"', function
(username, password) {
console.log(`User enters username: ${username} and password:
${password}`);
// Code to simulate login with the provided username and
password
});
Then('the user should be logged in successfully', function () {
console.log('User successfully logged in');
// Code to verify that the user is logged in
});
Explanation:
The placeholders like {string} in the When step are matched with
the data from the examples table.

The values from the table (user1, password1, etc.) are passed to
the step defqnition function as arguments (username, password).
Parameterized Tests and Looping
Through Examples
Cucumber automatically loops through each row in the
Examples table and executes the scenario for each set of
parameters. This eliminates the need for manually writing separate
tests for each dataset.
Example: Parameterized Test
Cucumber automatically loops through each row in the
Examples table and executes the scenario for each set of
parameters. This eliminates the need for manually writing separate
tests for each dataset.
Scenario Outline: User registers with different data
Given the user navigates to the registration page
When the user enters "<email>" and "<password>"
Then the registration should be successful
Examples:
| email | password |
| [email protected] | pass123 |
| [email protected] | pass456 |
| [email protected] | pass789 |

In this case, the same scenario is tested with three different sets of
email and password data.
Working with Tables in Gherkin
Tables in Gherkin are used to pass multiple sets of data to a scenario.
This is often done using the Examples keyword, but tables can also be
used in Given, When, and Then steps for more complex data
structures, such as lists or more detailed inputs.
Example with a Table in the Scenario Steps
Let’s say we want to test a scenario where a user adds multiple items
to their shopping cart.
Feature: Shopping Cart
Scenario: User adds items to the cart
Given the user is on the shopping page
When the user adds the following items to the cart:
| item | quantity | price |
| Laptop | 1 | 1000 |
| Smartphone | 2 | 500 |
| Headphones | 1 | 200 |
Then the total price should be 2200
Step Defqnition for Table Data
In the step defqnition, you can pass the table as an argument and
iterate through it.
const { Given, When, Then } = require('@cucumber/cucumber');
Given('the user is on the shopping page', function () {

console.log('User is on the shopping page');
});
When('the user adds the following items to the cart:', function
(dataTable) {
let totalPrice = 0;
dataTable.rows().forEach(row => {
const item = row[0]; // Item name
const quantity = parseInt(row[1]); // Quantity
const price = parseInt(row[2]); // Price per item
console.log(`Added ${quantity} of ${item} with price ${price}
each.`);
totalPrice += quantity * price;
});
this.totalPrice = totalPrice; // Save the total price for
validation later
});
Then('the total price should be {int}', function (expectedTotal) {
console.log(`Expected total price: ${expectedTotal}, Actual
total price: ${this.totalPrice}`);
if (this.totalPrice !== expectedTotal) {
throw new Error(`Total price mismatch! Expected:
${expectedTotal}, but got: ${this.totalPrice}`);
}
});
Explanation:
dataTable.rows() provides an array of rows from the table.
Each row in the table is an array of values (in this case: item,
quantity, and price).
The code loops through the rows, calculates the total price, and
stores it in the this.totalPrice property to validate it later.
Assertions and Validations in Cucumber
Assertions are very essential in test automation because it verifqes
that the system acts as expected. In Cucumber, assertions help

check both the functionality and the UI of an application. In
JavaScript, WebDriverIO is a popular automation framework for
interacting with web elements, and it provides a rich set of assertion
methods.
Let’s explore how to use assertions in Cucumber with WebDriverIO,
handle validation errors, and verify the UI and functionality of an
application.
Using WebDriverIO Assertions
WebDriverIO provides several built-in assertion methods that help you
verify various conditions during test execution. Some of the most
comon WebDriverIO assertions are:
.toBe()
.toHaveText()
.toExist()
.toHaveValue()
.toBeDisplayed()
These assertions are used to validate web elements and ensure that
the application behaves correctly. They are generally used in the step
defqnitions to validate different parts of the application (e.g., page
elements, text content, etc.).
Common WebDriverIO Assertions
toBe(): Verifqes that a value is exactly equal to the expected
value.
const { Given, When, Then } = require('@cucumber/cucumber');
Given('I am on the login page', async function () {
await browser.url('https://example.com/login');

});
When('I enter valid credentials', async function () {
await $('#username').setValue('user1');
await $('#password').setValue('password1');
await $('#loginButton').click();
});
Then('I should be logged in', async function () {
const url = await browser.getUrl();
expect(url).toBe('https://example.com/dashboard');
});
toHaveText(): Verifqes that an element has a specifqc text.
Then('the welcome message should be displayed', async function ()
{
const message = await $('#welcomeMessage').getText();
expect(message).toHaveText('Welcome, user1!');
});
toExist(): Verifqes that an element exists in the DOM.
Then('the login button should be present', async function () {
const button = await $('#loginButton');
expect(button).toExist();
});
toHaveValue(): Verifqes that an input fqeld has the correct value.
Then('the username field should have the correct value', async
function () {
const usernameField = await $('#username');
expect(usernameField).toHaveValue('user1');
});

toBeDisplayed(): Verifqes that an element is visible on the page.
Then('the login button should be displayed', async function () {
const loginButton = await $('#loginButton');
expect(loginButton).toBeDisplayed();
});
Custom Assertions in Step Definitions
In addition to WebDriverIO’s built-in assertions, you may need to
create custom assertions for more complex validation logic,
especially if your tests need to check specifqc conditions or complex
business rules.
Example: Custom Assertion for Validating a Range
Let’s say you want to verify that a price is within a valid range:
const { Then } = require('@cucumber/cucumber');
Then('the product price should be between {int} and {int}', async
function (minPrice, maxPrice) {
const priceElement = await $('#productPrice');
const price = parseFloat(await
priceElement.getText().replace('$', '').trim());
if (price < minPrice || price > maxPrice) {
throw new Error(`Price ${price} is not within the expected
range of ${minPrice} to ${maxPrice}`);
}
console.log(`Price ${price} is within the expected range.`);
});
This custom assertion checks if the price of a product is within the
specifqed range and throws an error if it’s not.

Handling Validation Errors
When validation fails in Cucumber with WebDriverIO, you want to
make sure that errors are handled gracefully and that test results
provide meaningful feedback. This can be achieved by using try-
catch blocks, handling exceptions, and reporting meaningful error
messages.
Example: Handling Validation Errors
const { Then } = require('@cucumber/cucumber');
Then('I should see an error message', async function () {
try {
const errorMessageElement = await $('#errorMessage');
const errorMessage = await errorMessageElement.getText();
expect(errorMessage).toBe('Invalid credentials');
} catch (error) {
console.error('Error while validating the error message:',
error);
throw new Error('Error while validating the error message');
}
});
In this example, if an error occurs while fqnding or validating the error
message, it’s caught and reported. This makes it easier to diagnose
issues in your tests.
Using Assertions to Verify UI and
Functionality
Assertions are not limited to verifying backend functionality. They are
also used to validate the UI elements, ensuring that the application
behaves as expected and providing feedback to users.

Example 1: Verifying UI Elements
You can verify if elements like buttons, inputs, and links are present
and functioning as expected:
Then('the login button should be enabled', async function () {
const loginButton = await $('#loginButton');
const isEnabled = await loginButton.isEnabled();
expect(isEnabled).toBe(true);
});
Then('the submit button should be visible', async function () {
const submitButton = await $('#submitButton');
expect(submitButton).toBeDisplayed();
});
Example 2: Verifying Page Title
Assertions can also be used to verify the title of the page:
Then('the page title should be {string}', async function
(expectedTitle) {
const pageTitle = await browser.getTitle();
expect(pageTitle).toBe(expectedTitle);
});
Example 3: Verifying Form Submission
You might want to verify that a form was successfully submitted:
When('I submit the registration form', async function () {
await $('#username').setValue('newuser');
await $('#password').setValue('newpassword');

await $('#submitButton').click();
});
Then('I should see the confirmation message', async function () {
const confirmationMessage = await $('#confirmationMessage');
expect(confirmationMessage).toHaveText('Registration
successful!');
});
This test submits a form and verifqes that the confqrmation message
appears after submission.
Best Practices for Assertions and
Validations in Cucumber
Use WebDriverIO assertions: WebDriverIO comes with built-in
assertions that cover a wide range of checks, including visibility,
existence, text matching, and more.
Keep assertions in the steps: Place assertions directly in the
step defqnitions to make tests more readable and to ensure that
test execution flows naturally.
Clear error messages: When handling validation errors, make
sure error messages are clear and provide context for the failure.
Custom assertions: For more complex conditions, create
custom assertions to handle specifqc validation logic or business
rules.
UI validation: Use assertions not only to validate functional
aspects of your application but also to verify UI elements and
behavior (e.g., visibility, enabled/disabled state, text content).
Handle asynchronous behavior: Cucumber with WebDriverIO
operates asynchronously, so always handle promises and use
async/await when interacting with web elements.
Organizing Tests with Cucumber

Creating Reusable Step Definitions
Reusable step defqnitions are one of the core principles of making
your tests scalable and maintainable. You defqne reusable steps to
handle comon operations across multiple feature fqles.
const { Given, When, Then } = require('@cucumber/cucumber');
// Reusable step to navigate to a URL
Given('I navigate to the {string} page', async function (url) {
await this.driver.get(url); // Assuming this.driver is properly
initialized
});
// Reusable step for clicking a button
When('I click the {string} button', async function (buttonText) {
const button = await this.driver.findElement({ xpath:
`//button[contains(text(), '${buttonText}')]` });
await button.click();
});
// Reusable step for verifying the page title
Then('I should see the page title {string}', async function
(expectedTitle) {
const title = await this.driver.getTitle();
if (title !== expectedTitle) {
throw new Error(`Expected title to be ${expectedTitle} but got
${title}`);
}
});
Here, the steps like I navigate to the {string} page, I click the {string}
button, and I should see the page title {string} are reusable. You can
call them from any feature fqle.
Example Feature File:

Feature: User Login
Scenario: User navigates to login page
Given I navigate to the "login" page
When I click the "Login" button
Then I should see the page title "Login Page"
Modularizing Feature Files
Modularization is the breaking down of your feature fqles into smaller,
more manageable pieces. Instead of one large feature fqle, you can
create Bltiple smaller feature fqles based on different functionality.
For instance:
login.feature: Contains scenarios for user login.
registration.feature: Contains scenarios for user registration.
product.feature: Contains scenarios for product-related
functionality.
This approach makes your tests more maintainable and ensures that
your feature fqles are focused on specifqc areas of the application.
Using Page Object Model (POM) with
Cucumber
The Page Object Model is a design pattern that helps keep your test
code clean and maintainable.
With POM, you create a page object for each page in your application
that contains methods for interacting with the page elements.
Instead of having step defqnitions with direct interactions with the
DOM, you call methods from the corresponding page object.
Example of Page Object Model Implementation:

Page Object (LoginPage.js):
class LoginPage {
constructor() {
// Define the element selectors directly as strings
this.usernameInput = '#username';
this.passwordInput = '#password';
this.loginButton = '#login-button';
}
// Method to enter the username
async enterUsername(username) {
const usernameField = await $(this.usernameInput); // Using
WebDriverIO's $() for element selection
await usernameField.setValue(username); // setValue is used
in WebDriverIO to enter text
}
// Method to enter the password
async enterPassword(password) {
const passwordField = await $(this.passwordInput); // Locate
the password field
await passwordField.setValue(password); // Enter password
using setValue
}
// Method to click the login button
async clickLoginButton() {
const loginButton = await $(this.loginButton); // Locate the
login button
await loginButton.click(); // Click on the login button
}
}
module.exports = LoginPage;
Step Defqnition (login_steps.js):

const { Given, When, Then } = require('cucumber');
const LoginPage = require('../pages/LoginPage');
const { driver } = require('../support/driver');
let loginPage;
Given('I am on the login page', async function () {
loginPage = new LoginPage(driver);
await driver.get('http://example.com/login');
});
When('I enter {string} in the username field', async function
(username) {
await loginPage.enterUsername(username);
});
When('I enter {string} in the password field', async function
(password) {
await loginPage.enterPassword(password);
});
When('I click the login button', async function () {
await loginPage.clickLoginButton();
});
Then('I should be redirected to the dashboard', async function ()
{
const currentUrl = await driver.getCurrentUrl();
if (!currentUrl.includes('dashboard')) {
throw new Error(`Expected dashboard URL, but got
${currentUrl}`);
}
By using Page Objects, your step defqnitions become cleaner and
more reusable. The logic for interacting with the page is encapsulated
inside the Page Object, and your steps simply call the Page Object
methods.
Grouping Scenarios and Features
As your test suite grows, you may want to organize scenarios by

functionality, user role, or feature. You can group scenarios within a
feature fqle using @tags or organize them in different feature fqles.
Using Tags to Group Scenarios:
@smoke
Feature: User login
@valid
Scenario: Valid login with correct credentials
Given I navigate to the "login" page
When I enter "user" in the username field
And I enter "password" in the password field
Then I should be redirected to the dashboard
@invalid
Scenario: Invalid login with incorrect credentials
Given I navigate to the "login" page
When I enter "invalidUser" in the username field
And I enter "invalidPass" in the password field
Then I should see an error message
You can run scenarios with specifqc tags to focus on certain tests
during execution:
npx cucumber-js –tags @smoke
Best Practices for Maintaining Tests
To maintain a healthy and scalable Cucumber-based testing suite,
follow these best practices:
U. Keep Feature Files Small and Focused: Organize feature fqles
by functionality, avoiding large fqles.
2. Use Reusable Step Defqnitions: Defqne comon steps that can
ff fq
be reused across dierent scenarios and feature les.
Read The Full Blog Here:- Jignect Technolgies