CUCUMBERJS Tutorial Course

Introduction to Cucumber.js

What is Cucumber.js?

Cucumber.js is a JavaScript framework for behavior-driven development (BDD) that allows developers to write tests in a human-readable format. It supports writing scenarios in Gherkin language, which describes the expected behavior of an application in plain English. This makes it easier for non-technical stakeholders to understand the tests and for developers to ensure that the application behaves as expected.

Installing Cucumber.js

1mkdir cucumber-js-project
2cd cucumber-js-project
3npm init -y
4npm install --save-dev @cucumber/cucumber
5npm install selenium-webdriver chai --save-dev

Getting Started with Cucumber.js

Setting Up Feature Files

1mkdir features
2mkdir features/step_definitions
3touch features/login.feature
Inside login.feature, add the following content:
1Feature: User Login
2
3  Scenario: Successful login with valid credentials
4    Given the user is on the login page
5    When the user enters a valid username and password
6    And the user clicks the login button
7    Then the user should be redirected to the dashboard

Writing Step Definitions

Inside the features/step_definitions directory, create a JavaScript file for your step definitions.
touch features/step_definitions/loginSteps.js
1const { Given, When, Then } = require('@cucumber/cucumber');
2const { expect } = require('chai');
3const { Builder, By } = require('selenium-webdriver');
4
5let driver;
6
7Given('the user is on the login page', async function() {
8  driver = new Builder().forBrowser('chrome').build();
9  await driver.get('https://example.com/login');
10});
11
12When('the user enters a valid username and password', async function() {
13  await driver.findElement(By.id('username')).sendKeys('validUser');
14  await driver.findElement(By.id('password')).sendKeys('validPassword');
15});
16
17When('the user clicks the login button', async function() {
18  await driver.findElement(By.id('loginButton')).click();
19});
20
21Then('the user should be redirected to the dashboard', async function() {
22  const url = await driver.getCurrentUrl();
23  expect(url).to.equal('https://example.com/dashboard');
24  await driver.quit();
25});
To run your Cucumber.js tests, add a script to the package.json file:
1"scripts": {
2  "test": "cucumber-js"
3}
Now, run your tests with:
npm test

Understanding Gherkin Syntax

Gherkin Keywords: Given, When, Then

The Given keyword is used to describe the initial context of the system. It sets up the preconditions or the initial state before the main actions occur. The When keyword describes an action or event. It represents the main event that triggers some behavior in the system.The Then keyword is used to describe the expected outcome or result. It checks whether the system behaves as expected after the action.
1Feature: User Login
2
3  Scenario: Successful login with valid credentials
4    Given the user is on the login page
5    When the user enters a valid username and password
6    And the user clicks the login button
7    Then the user should be redirected to the dashboard

Using Backgrounds in Gherkin

In Gherkin, the Background keyword is used to define a set of steps that are common to all scenarios in a feature file. This is especially useful when you have multiple scenarios that share the same setup steps. By using a Background, you can avoid repeating the same Given steps in each scenario, making your feature files more concise and easier to maintain.
1Feature: User Authentication
2
3  Background:
4    Given the user is on the login page
5    And the user has a valid username and password
6
7  Scenario: Successful login
8    When the user enters the username and password
9    And the user clicks the login button
10    Then the user should be redirected to the dashboard
11
12  Scenario: Failed login with incorrect password
13    When the user enters the username and an incorrect password
14    And the user clicks the login button
15    Then the user should see an error message
16
17  Scenario: Login with empty credentials
18    When the user leaves the username and password fields empty
19    And the user clicks the login button
20    Then the user should see a validation message

Scenario Outlines with Examples

In Gherkin, Scenario Outline is used to run the same scenario multiple times with different sets of data. This is particularly useful when you want to test the same behavior with various input values. The Scenario Outline works together with the Examples keyword, which provides the different sets of inputs and expected outcomes for each iteration of the scenario.
1Feature: User Login
2
3  Scenario Outline: Login with different types of credentials
4    Given the user is on the login page
5    When the user enters the username "<username>"
6    And the user enters the password "<password>"
7    And the user clicks the login button
8    Then the user should see the message "<message>"
9
10    Examples: Valid Credentials
11      | username    | password   | message              |
12      | validUser1  | validPass1 | Welcome, validUser1! |
13      | validUser2  | validPass2 | Welcome, validUser2! |
14
15    Examples: Invalid Credentials
16      | username    | password   | message                          |
17      | invalidUser | validPass  | Incorrect username or password.  |
18      | validUser   | invalidPass| Incorrect username or password.  |

Running Cucumber.js Tests

Cucumber.js CLI Options

1cucumber-js --require ./features/step_definitions/
2cucumber-js --tags "not @skip and @important"
3npx cucumber-js --tags "@login or @registration"
4npx cucumber-js --tags "@smoke and not @slow"
5npx cucumber-js --tags "@login and @smoke"
6
7cucumber-js --format json
8cucumber-js --format-options '{"output": "report.json"}'
9
10//Runs scenarios that match a given name or regex pattern.
11cucumber-js --name "Login"
12
13//Fails the test run if there are any undefined or pending steps.
14cucumber-js --strict
15			 
16//Passes parameters to the World constructor. This is useful for passing configuration settings to your tests.
17cucumber-js --world-parameters '{"baseUrl": "http://localhost"}'
18
19//Retries failed scenarios up to n times
20cucumber-js --retry 2
21
22//Stops the test execution immediately after the first failure.
23cucumber-js --fail-fast
24
25//parallel execution
26cucumber-js --parallel 4
27
28//Validates the configuration, and step definitions without actually running the steps.
29cucumber-js --dry-run
30
31//Specifies the syntax of the step definition snippets generated for undefined steps.
32cucumber-js --snippet-syntax async

Parallel Test Execution

Parallel test execution in Cucumber.js allows you to run multiple scenarios or feature files concurrently, significantly reducing the total execution time of your test suite. Cucumber.js supports parallel execution out of the box, which can be configured easily through the command-line interface (CLI).
cucumber-js --parallel 4
Ensure that your tests are independent of each other to avoid issues like race conditions or data conflicts.

Assertions

NodeJS Assert

1const assert = require('assert');
2
3// Check if two values are strictly equal
4const actualValue = 5;
5const expectedValue = 5;
6assert.strictEqual(actualValue, expectedValue, 'Values should be strictly equal');
7
8// Check if two values are loosely equal
9assert.equal(actualValue, expectedValue, 'Values should be loosely equal');
10
11
12// Check if two objects are deeply equal
13const actualObject = { name: 'Alice', age: 30 };
14const expectedObject = { name: 'Alice', age: 30 };
15assert.deepStrictEqual(actualObject, expectedObject, 'Objects should be deeply equal');
16
17
18// Check if a value is true
19const isTrue = true;
20assert.ok(isTrue, 'Value should be true');
21
22// Check if a value is false
23const isFalse = false;
24assert.fail(isFalse, 'Value should be false');
25
26
27// Function that should throw an error
28function throwsError() {
29  throw new Error('This is an error');
30}
31
32// Check if function throws an error
33assert.throws(() => {
34  throwsError();
35}, Error, 'Function should throw an error');
36
37
38// Function that should throw an error with a specific message
39function throwsSpecificError() {
40  throw new Error('Specific error message');
41}
42
43// Check if function throws an error with specific message
44assert.throws(() => {
45  throwsSpecificError();
46}, {
47  name: 'Error',
48  message: 'Specific error message'
49}, 'Function should throw an error with the specific message');
50
51
52// Function that should not throw an error
53function doesNotThrowError() {
54  return 'No error';
55}
56
57// Check if function does not throw an error
58assert.doesNotThrow(() => {
59  doesNotThrowError();
60}, 'Function should not throw an error');
61
62// Check if value is of a specific type
63const value = 'Hello';
64assert(typeof value === 'string', 'Value should be of type string');
65
66
67// Check if two values are equal with a custom error message
68const actual = 10;
69const expected = 20;
70assert.strictEqual(actual, expected, `Expected ${expected} but got ${actual}`);

Chai Integration

1//npm install chai
2
3const assert = require('chai').assert;
4
5// 1. Basic Equality Assertion
6const actualValue = 5;
7const expectedValue = 5;
8assert.equal(actualValue, expectedValue, 'Values should be equal');
9
10// 2. Deep Equality Assertion
11const actualObject = { name: 'Alice', age: 30 };
12const expectedObject = { name: 'Alice', age: 30 };
13assert.deepEqual(actualObject, expectedObject, 'Objects should be deeply equal');
14
15// 3. Assertion for True/False
16const isTrue = true;
17const isFalse = false;
18assert.isTrue(isTrue, 'Value should be true');
19assert.isFalse(isFalse, 'Value should be false');
20
21// 4. Assertion for Type of Value
22const value = 'Hello';
23assert.isString(value, 'Value should be of type string');
24
25// 5. Assertion for Array and Length
26const array = [1, 2, 3];
27assert.isArray(array, 'Value should be an array');
28assert.lengthOf(array, 3, 'Array should have a length of 3');
29
30// 6. Assertion for Errors
31function throwsError() {
32  throw new Error('This is an error');
33}
34
35assert.throws(() => {
36  throwsError();
37}, Error, 'Function should throw an error');
38
39// 7. Assertion for Specific Error Message
40function throwsSpecificError() {
41  throw new Error('Specific error message');
42}
43
44assert.throws(() => {
45  throwsSpecificError();
46}, Error, 'Specific error message');
47
48// 8. Assertion for Containing Value
49const string = 'Hello, world!';
50assert.include(string, 'world', 'String should contain "world"');
51
52// 9. Assertion for Matching Pattern
53const email = '[email protected]';
54assert.match(email, /^[^s@]+@[^s@]+.[^s@]+$/, 'Email should match the pattern');
55
56// 10. Custom Assertion Error Messages
57const actual = 10;
58const expected = 20;
59assert.equal(actual, expected, `Expected ${expected} but got ${actual}`);

Using Data Tables

Handling Data Tables in Step Definitions

1Feature: User Management
2
3  Scenario: Add multiple users
4    Given the following users exist:
5      | name    | email            | age |
6      | Alice   | [email protected] | 30  |
7      | Bob     | [email protected]   | 25  |
8      | Charlie | [email protected] | 35  |
9    When I retrieve the list of users
10    Then the list should contain the following users:
11      | name    | email            | age |
12      | Alice   | [email protected] | 30  |
13      | Bob     | [email protected]   | 25  |
14      | Charlie | [email protected] | 35  |
1const { Given, When, Then } = require('@cucumber/cucumber');
2
3// Step definition to handle the data table of existing users
4Given('the following users exist:', function (dataTable) {
5  // Convert the data table to an array of objects
6  const users = dataTable.hashes();
7  
8  // Process each user (e.g., add to a database or in-memory store)
9  users.forEach(user => {
10    console.log(`User: ${user.name}, Email: ${user.email}, Age: ${user.age}`);
11    // You can implement code to add these users to your system here
12  });
13});
14
15// Step definition to handle the data table for expected results
16Then('the list should contain the following users:', function (dataTable) {
17  // Convert the data table to an array of objects
18  const expectedUsers = dataTable.hashes();
19
20  // Fetch the actual list of users (e.g., from a database or API)
21  const actualUsers = [
22    // This should be replaced with the actual method to fetch users
23  ];
24
25  // Perform assertions to check if the actual users match the expected ones
26  assert.deepEqual(actualUsers, expectedUsers, 'The user lists do not match');
27});

Data Tables in Scenario Outlines

1Feature: User Registration
2
3  Scenario Outline: Register a user with different sets of data
4    Given I am on the registration page
5    When I fill in the registration form with the following details:
6      | name     | email              | password |
7      | <name>   | <email>            | <password> |
8    And I submit the registration form
9    Then I should see a confirmation message
10
11    Examples:
12      | name    | email             | password  |
13      | Alice   | [email protected] | password1 |
14      | Bob     | [email protected]   | password2 |
15      | Charlie | [email protected] | password3 |
1const { Given, When, Then } = require('@cucumber/cucumber');
2const assert = require('assert');
3
4// Mock for storing registration results
5let registrationResult = {};
6
7// Step definition for navigating to the registration page
8Given('I am on the registration page', function () {
9  // Code to navigate to the registration page
10});
11
12// Step definition for filling in the registration form with data from the table
13When('I fill in the registration form with the following details:', function (dataTable) {
14  const data = dataTable.rowsHash(); // Convert the data table to a hash map
15  const { name, email, password } = data;
16
17  // Mock registration process (replace this with actual code to interact with the form)
18  registrationResult = { name, email, password };
19});
20
21// Step definition for submitting the registration form
22When('I submit the registration form', function () {
23  // Code to submit the registration form
24  // Mock confirmation message (replace this with actual confirmation check)
25  registrationResult.confirmationMessage = `User ${registrationResult.name} registered successfully`;
26});
27
28// Step definition for verifying the confirmation message
29Then('I should see a confirmation message', function () {
30  // Mock expected confirmation message
31  const expectedMessage = `User ${registrationResult.name} registered successfully`;
32
33  // Assert that the actual confirmation message matches the expected message
34  assert.strictEqual(registrationResult.confirmationMessage, expectedMessage, 'Confirmation message does not match');
35});

Cucumber.js Hooks

Using Before and After Hooks

1Feature: Example Feature
2
3  Scenario: Simple scenario
4    Given I have a sample setup
5    When I perform an action
6    Then I should get the expected result
1const { Given, When, Then, Before, After } = require('@cucumber/cucumber');
2const assert = require('assert');
3
4// Define a global variable to be used in the hooks and steps
5let globalData = {};
6
7// Before hook - runs before each scenario
8Before(function () {
9  console.log('Running Before hook');
10  // Initialize global data or set up necessary conditions
11  globalData = { setup: true };
12});
13
14// After hook - runs after each scenario
15After(function () {
16  console.log('Running After hook');
17  // Clean up resources, close connections, etc.
18  globalData = {};
19});
20
21// Step definition for setting up
22Given('I have a sample setup', function () {
23  // Use the global data initialized in the Before hook
24  assert.strictEqual(globalData.setup, true, 'Setup was not initialized correctly');
25});
26
27// Step definition for performing an action
28When('I perform an action', function () {
29  // Perform some action; this example does not use globalData
30});
31
32// Step definition for checking the result
33Then('I should get the expected result', function () {
34  // Check the result of the action performed; this example does not use globalData
35  assert.strictEqual(true, true); // Example assertion
36});

Tagged Hooks

1@setup
2Feature: Example Feature with Setup
3
4  @important
5  Scenario: Important Scenario
6    Given I have a sample setup
7    When I perform an action
8    Then I should get the expected result
9
10  Scenario: Another Scenario
11    Given I have a sample setup
12    When I perform an action
13    Then I should get the expected result
1const { Before, After, BeforeAll, AfterAll } = require('@cucumber/cucumber');
2
3// Before hook that runs only for scenarios tagged with @important
4Before({ tags: '@important' }, function () {
5  console.log('Running Before hook for @important tagged scenarios');
6  // Setup code specific to important scenarios
7});
8
9// After hook that runs only for scenarios tagged with @important
10After({ tags: '@important' }, function () {
11  console.log('Running After hook for @important tagged scenarios');
12  // Teardown code specific to important scenarios
13});
14
15// Before hook that runs only for scenarios tagged with @setup
16Before({ tags: '@setup' }, function () {
17  console.log('Running Before hook for @setup tagged features');
18  // Setup code specific to features with @setup tag
19});
20
21// After hook that runs only for scenarios tagged with @setup
22After({ tags: '@setup' }, function () {
23  console.log('Running After hook for @setup tagged features');
24  // Teardown code specific to features with @setup tag
25});

Generating Test Reports

Generating JSON Reports

npx cucumber-js --format json:./reports/results.json

Generating HTML Reports

npm install cucumber-html-reporter --save-dev
Example generate-report.js:
1const reporter = require('cucumber-html-reporter');
2
3const options = {
4  theme: 'bootstrap',
5  jsonFile: './reports/results.json',
6  output: './reports/cucumber-report.html',
7  reportSuiteAsScenarios: true,
8  launchReport: true,
9  metadata: {
10    browser: {
11      name: 'chrome',
12      version: '91'
13    },
14    device: 'Local test machine',
15    platform: {
16      name: 'Windows',
17      version: '10'
18    }
19  }
20};
21
22reporter.generate(options);
Generate Report
node generate-report.js

Generating Allure Reports

JSON reports can be converted to HTML report using Allure command line.
1npm install allure-cucumberjs --save-dev
2
3npx allure-commandline generate ./reports/allure-results --clean
4npx allure-commandline open ./reports/allure-results

Integrating Cucumber.js with CI/CD

Setting Up Cucumber.js in CI/CD

Install dependencies
1npm install @cucumber/cucumber --save-dev
2npm install cucumber-html-reporter --save-dev
Create cucumber.js file with below options.
1{
2  "default": {
3    "require": ["./features/step_definitions/**/*.js"],
4    "format": ["json:./reports/results.json"],
5    "publishQuiet": true
6  }
7}
1"scripts": {
2  "test": "cucumber-js",
3  "report": "node generate-report.js"
4}

Running Cucumber.js Tests in CI/CD

Create a .github/workflows/ci.yml file in your repository.
1name: CI
2
3on:
4  push:
5    branches:
6      - main
7
8jobs:
9  build:
10    runs-on: ubuntu-latest
11
12    steps:
13      - name: Checkout code
14        uses: actions/checkout@v2
15
16      - name: Set up Node.js
17        uses: actions/setup-node@v2
18        with:
19          node-version: '14'
20
21      - name: Install dependencies
22        run: npm install
23
24      - name: Run tests
25        run: npm test
26
27      - name: Generate report
28        run: npm run report
29
30      - name: Upload test results
31        uses: actions/upload-artifact@v2
32        with:
33          name: cucumber-report
34          path: ./reports/cucumber-report.html