Parallel Execution of Cucumber with Serenity and JUnit5

HOME

In the previous tutorial, I explained the Serenity BDD with Cucumber for Web Application using Junit4. In this tutorial, I will explain the parallel execution of Cucumber Scenarios with Serenity and JUnit5. This tutorial gives a clear picture of the initial setup of a BDD Framework.

Starting with version 3.6.0, is possible to run the Cucumber scenarios in parallel.

We need to mention these in the junit-platform.properties to run the Cucumber scenarios parallelly.

cucumber.execution.parallel.enabled=true
cucumber.execution.parallel.config.strategy=fixed
cucumber.execution.parallel.config.fixed.parallelism=2
cucumber.plugin=io.cucumber.core.plugin.SerenityReporterParallel

This framework consists of:

  1. Serenity – 3.6.12
  2. Serenity Cucumber – 7.11.0
  3. JUnit Jupiter – 5.9.2
  4. Java 11
  5. Maven – 3.8.1
  6. Maven Compiler Plugin – 3.8.1
  7. Maven Surefire Plugin – 3.0.0-M5
  8. Maven FailSafe Plugin – 3.0.0-M5

Implementation Steps

  1. Download and Install Java on system
  2. Download and setup Eclipse IDE on system
  3. Setup Maven and create a new Maven Project
  4. Update Properties section in Maven pom.xml
  5. Add Serenity, Serenity Cucumber and JUnit5 dependencies to POM.xml
  6. Update Build Section of pom.xml
  7. Create a feature file under src/test/resources
  8. Create the Step page classes
  9. Create the Step Definition class or Glue Code
  10. Create a Serenity-Cucumber Runner class
  11. Create a cucumber.properties file in src/test/resources (optional)
  12. Create junit-platform.properties in src/test/resources
  13. Create serenity.conf file in src/test/resources
  14. Create serenity.properties file in the root of the project
  15. Run the tests from Command Line
  16. Run the tests from CucumberRunner
  17. Serenity Report Generation – Index.html and Serenity-Summary.html

Project Structure

Step 1- Download and Install Java

Click here to know How to install Java.

Step 2 – Download and setup Eclipse IDE on system

The Eclipse IDE (integrated development environment) provides strong support for Java developer which is needed to write Java code. Click here to know How to install Eclipse.

Step 3 – Setup Maven and create a new Maven Project

Click here to know How to install Maven.

Click here to know How to create a Maven project

Below is the Maven project structure. Here,

Group Id – org.example
Artifact Id – ParallelTests_Serenity_Cucumber_Junit5_Demo
Version – 0.0.1-SNAPSHOT
Package – org.example. ParallelTests_Serenity_Cucumber_Junit5_Demo

Step 4 – Update Properties section in Maven pom.xml

     <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <serenity.version>3.6.12</serenity.version>
        <serenity.cucumber.version>3.6.12</serenity.cucumber.version>
        <junit.platform.version>1.9.2</junit.platform.version>
        <cucumber.version>7.11.0</cucumber.version>
        <maven.compiler.plugin.version>3.10.1</maven.compiler.plugin.version>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <maven.surefire.plugin.version>3.0.0-M7</maven.surefire.plugin.version>
        <maven.failsafe.plugin.version>3.0.0-M7</maven.failsafe.plugin.version>
    </properties>

Step 5 – Add Serenity, Serenity-Cucumber, and JUnit5 dependencies to POM.xml

<dependencies>

        <dependency>
            <groupId>net.serenity-bdd</groupId>
            <artifactId>serenity-core</artifactId>
            <version>${serenity.version}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>net.serenity-bdd</groupId>
            <artifactId>serenity-junit5</artifactId>
            <version>${serenity.version}</version>
        </dependency>

        <dependency>
            <groupId>net.serenity-bdd</groupId>
            <artifactId>serenity-screenplay</artifactId>
            <version>${serenity.version}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>net.serenity-bdd</groupId>
            <artifactId>serenity-cucumber</artifactId>
            <version>${serenity.version}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-junit-platform-engine</artifactId>
            <version>${cucumber.version}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.junit.platform</groupId>
            <artifactId>junit-platform-suite</artifactId>
            <version>${junit.platform.version}</version>
            <scope>test</scope>
        </dependency>

    </dependencies>

Step 6 – Update the Build Section of pom.xml

 <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>${maven.surefire.plugin.version}</version>
                <configuration>
                    <skip>true</skip>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-failsafe-plugin</artifactId>
                <version>${maven.failsafe.plugin.version}</version>
                <configuration>
                    <includes>
                        <include>**/*.java</include>
                    </includes>
                    <parallel>methods</parallel>
                    <useUnlimitedThreads>true</useUnlimitedThreads>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>integration-test</goal>
                            <goal>verify</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>${maven.compiler.plugin.version}</version>
                <configuration>
                    <source>${maven.compiler.source}</source>
                    <target>${maven.compiler.target}</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>net.serenity-bdd.maven.plugins</groupId>
                <artifactId>serenity-maven-plugin</artifactId>
                <version>${serenity.version}</version>
                <dependencies>
                    <dependency>
                        <groupId>net.serenity-bdd</groupId>
                        <artifactId>serenity-single-page-report</artifactId>
                        <version>${serenity.version}</version>
                    </dependency>
                </dependencies>
                <configuration>
                    <reports>single-page-html</reports>
                </configuration>
                <executions>
                    <execution>
                        <id>serenity-reports</id>
                        <phase>post-integration-test</phase>
                        <goals>
                            <goal>aggregate</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

The complete POM.xml looks like as shown below:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>ParallelTests_Serenity_Cucumber_JUnit5_Demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <serenity.version>3.6.12</serenity.version>
        <serenity.cucumber.version>3.6.12</serenity.cucumber.version>
        <junit.platform.version>1.9.2</junit.platform.version>
        <cucumber.version>7.11.0</cucumber.version>
        <maven.compiler.plugin.version>3.10.1</maven.compiler.plugin.version>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <maven.surefire.plugin.version>3.0.0-M7</maven.surefire.plugin.version>
        <maven.failsafe.plugin.version>3.0.0-M7</maven.failsafe.plugin.version>
    </properties>

    <dependencies>

        <dependency>
            <groupId>net.serenity-bdd</groupId>
            <artifactId>serenity-core</artifactId>
            <version>${serenity.version}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>net.serenity-bdd</groupId>
            <artifactId>serenity-junit5</artifactId>
            <version>${serenity.version}</version>
        </dependency>

        <dependency>
            <groupId>net.serenity-bdd</groupId>
            <artifactId>serenity-screenplay</artifactId>
            <version>${serenity.version}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>net.serenity-bdd</groupId>
            <artifactId>serenity-cucumber</artifactId>
            <version>${serenity.version}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-junit-platform-engine</artifactId>
            <version>${cucumber.version}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.junit.platform</groupId>
            <artifactId>junit-platform-suite</artifactId>
            <version>${junit.platform.version}</version>
            <scope>test</scope>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>${maven.surefire.plugin.version}</version>
                <configuration>
                    <skip>true</skip>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-failsafe-plugin</artifactId>
                <version>${maven.failsafe.plugin.version}</version>
                <configuration>
                    <includes>
                        <include>**/*.java</include>
                    </includes>
                    <parallel>methods</parallel>
                    <useUnlimitedThreads>true</useUnlimitedThreads>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>integration-test</goal>
                            <goal>verify</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>${maven.compiler.plugin.version}</version>
                <configuration>
                    <source>${maven.compiler.source}</source>
                    <target>${maven.compiler.target}</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>net.serenity-bdd.maven.plugins</groupId>
                <artifactId>serenity-maven-plugin</artifactId>
                <version>${serenity.version}</version>
                <dependencies>
                    <dependency>
                        <groupId>net.serenity-bdd</groupId>
                        <artifactId>serenity-single-page-report</artifactId>
                        <version>${serenity.version}</version>
                    </dependency>
                </dependencies>
                <configuration>
                    <reports>single-page-html</reports>
                </configuration>
                <executions>
                    <execution>
                        <id>serenity-reports</id>
                        <phase>post-integration-test</phase>
                        <goals>
                            <goal>aggregate</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

Step 7 – Create a feature file in src/test/resources

The purpose of the Feature keyword is to provide a high-level description of a software feature and to group related scenarios. To know more about the Feature files, please refer this tutorial.

Feature: Login to HRM

  @ValidCredentials
  Scenario: Login with valid credentials

    Given User is on Home page
    When User enters username as "Admin"
    And User enters password as "admin123"
    Then User should be able to login successfully

  @InValidCredentials
  Scenario: Login with invalid credentials

    Given User is on Home page
    When User enters username as "Admin1"
    And User enters password as "Admin123"
    Then User should be able to see error message "Invalid credentials"

  @BlankUsername
  Scenario: Login with blank username

    Given User is on Home page
    When User enters username as ""
    And User enters password as "Admin123"
    Then User should be able to see error message "Required" below username

Step 8 – Create the Step pages for StepDefinition class

In Serenity, tests are broken down into reusable steps. An important principle behind Serenity is the idea that it is easier to maintain a test that uses several layers of abstraction to hide the complexity behind different parts of a test. So, in Step class, we will declare the locators of the web elements and the actions performed on these web elements.

There are multiple ways to identify a web element on the web page – one of the ways is to use @FindBy or $(By.).

I prefer to use @FindBy as I do need not to find the same element multiple times. Using @FindBy, I have identified a web element and defined a WebElementFacacde for the same which is reusable.

StepLoginPage

import net.serenitybdd.core.pages.PageObject;
import net.serenitybdd.core.pages.WebElementFacade;
import net.thucydides.core.annotations.Step;
import org.openqa.selenium.support.FindBy;

public class StepLoginPage extends PageObject {

    @FindBy(name = "username")
    WebElementFacade username;

    @FindBy(name = "password")
    WebElementFacade password;

    @FindBy(xpath = "//*[@id='app']/div[1]/div/div[1]/div/div[2]/div[2]/form/div[3]/button")
    WebElementFacade submitButton;

    @FindBy(xpath = "//*[@id='app']/div[1]/div/div[1]/div/div[2]/div[2]/div/div[1]/div[1]/p")
    WebElementFacade errorMessage;

    @FindBy(xpath = "//*[@id='app']/div[1]/div/div[1]/div/div[2]/div[2]/form/div[1]/div/span")
    WebElementFacade missingUsername;

    @Step("Enter Username")
    public void inputUserName(String userName) {
        username.sendKeys((userName));
    }

    @Step("Enter Password")
    public void inputPassword(String passWord) {
        password.sendKeys((passWord));
    }

    @Step("Click Submit Button")
    public void clickLogin() {
        submitButton.click();
    }

    @Step("Error Message on unsuccessful login")
    public String errorMessage() {
        String actualErrorMessage = errorMessage.getText();
        return actualErrorMessage;
    }

    @Step("Error Message for missing username")
    public String missingUsernameErrorMessage() {
        String actualErrorMessage = missingUsername.getText();
        return actualErrorMessage;
    }

}

StepHomePage

import net.serenitybdd.core.pages.PageObject;
import net.serenitybdd.core.pages.WebElementFacade;
import net.thucydides.core.annotations.Step;
import org.openqa.selenium.support.FindBy;

public class StepHomePage extends PageObject {

    @FindBy(xpath = "//*[@id='app']/div[1]/div[1]/header/div[1]/div[1]/span/h6")
    WebElementFacade dashboardText;

    @Step("Successful login")
    public String getHomPageTitle() {
        String dashboardTitle = dashboardText.getText();
       return dashboardTitle;


    }
}

Step 9 – Create the Step Definition class or Glue Code

A Step Definition is a Java method with an expression that links it to one or more Gherkin steps. When Cucumber executes a Gherkin step in a scenario, it will look for a matching step definition to execute. You can have all of your step definitions in one file, or in multiple files.

LoginPageDefinitions

import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
import net.thucydides.core.annotations.Steps;
import org.example.steps.StepHomePage;
import org.example.steps.StepLoginPage;
import static org.junit.jupiter.api.Assertions.*;

public class LoginPageDefinitions {

    @Steps
    StepLoginPage loginPage;

    @Steps
    StepHomePage homePage;


    @Given("User is on Home page")
    public void openApplication() {
        loginPage.open();
    }

    @When("User enters username as {string}")
    public void enterUsername(String userName) {
        loginPage.inputUserName(userName);
    }

    @When("User enters password as {string}")
    public void enterPassword(String passWord) {
        loginPage.inputPassword(passWord);

        loginPage.clickLogin();
    }

    @Then("User should be able to login successfully")
    public void clickOnLoginButton() {

        assertTrue(homePage.getHomPageTitle().contains("Dashboard"));
    }

    @Then("User should be able to see error message {string}")
    public void unsuccessfulLogin(String expectedErrorMessage) {

        String actualErrorMessage = loginPage.errorMessage();
        assertEquals(expectedErrorMessage, actualErrorMessage);
    }

    @Then("User should be able to see error message {string} below username")
    public void missingUsername (String expectedErrorMessage) {

        String actualErrorMessage = loginPage.missingUsernameErrorMessage();
        assertEquals(expectedErrorMessage, actualErrorMessage);
    }

}

Assertions in JUnit-Jupiter are imported from the below package:-

import static org.junit.jupiter.api.Assertions.*;

Step 10 – Create a Serenity-Cucumber Runner class

Cucumber runs the feature files via JUnit and needs a dedicated test runner class to actually run the feature files.

import static io.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME;
import org.junit.platform.suite.api.ConfigurationParameter;
import org.junit.platform.suite.api.IncludeEngines;
import org.junit.platform.suite.api.SelectClasspathResource;
import org.junit.platform.suite.api.Suite;

@Suite
@IncludeEngines("cucumber")
@SelectClasspathResource("org/example")
@SelectClasspathResource("/features")
@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "org.example")
public class CucumberTestSuite {

}

Step 11 – Create cucumber.properties file under src/test/resources (optional)

This is an optional step. Cucumber of version 6.7 and above provides the functionality to generate a beautiful cucumber report. For this, it is needed to add a file cucumber.properties under src/test/resources.

cucumber.publish.enabled = true

Step 12 – Create junit-platform.properties in src/test/resources

cucumber.execution.parallel.enabled=true
cucumber.execution.parallel.config.strategy=fixed
cucumber.execution.parallel.config.fixed.parallelism=3
cucumber.plugin=io.cucumber.core.plugin.SerenityReporterParallel

Step 13 – Create serenity.conf file under src/test/resources

The serenity configuration file is used to configure the drivers so the test cases can run successfully. This file contains an operating system-specific binary. The binary file sits between your test and the browser. It acts as an intermediary, an interface between your tests and the browser you are using.

You can also configure the webdriver.base.url property for different environments in the serenity.conf configuration file.

webdriver {
    driver = chrome
}


#
# Define drivers for different platforms. Serenity will automatically pick the correct driver for the current platform
#

environments {
  default {
    webdriver.base.url = "https://opensource-demo.orangehrmlive.com/"
  }
  dev {
    webdriver.base.url = "https://opensource-demo.orangehrmlive.com/dev"
  }
  staging {
    webdriver.base.url = "https://opensource-demo.orangehrmlive.com/staging"
  }
  prod {
    webdriver.base.url = "https://opensource-demo.orangehrmlive.com/prod"
  }
}

Step 14 – Create serenity.properties file in the root of the project

serenity.project.name = Parallel Execution of Cucumber Scenarios with Serenity

Step 15 – Run the tests from Command Line

Open the command line and go to the location where the pom.xml of the project is present and type the below command.

mvn clean verify

Below is the test result of the test execution.

Step 16 – Run the tests from CucumberRunner

Right-click on the Ruuner class (CucumberTestSuite) and select Run ‘CucumberTestSuite’. (This is an image of IntelliJ Runner class).

The below image shows that 3 browsers open simultaneously.

Below is the test result of the test execution.

Step 17 – Serenity Report Generation

The best part about Serenity is the report generation by it. The Reports contain all possible types of information, you can think of with minimal extra effort. There is multiple types of reports are generated. We are interested in index.html and serenity-summary.html. To know more about Serenity Reports, please refer to tutorials for Index.html and Serenity-Summary.html. Below is the new Serenity Report.

Index.html

serenity-summary.html

If you want to control the number of browsers open in the test, then add the below-mentioned parameters in the junit-platform.properties:

cucumber.execution.parallel.config.fixed.parallelism=2
cucumber.execution.parallel.config.fixed.max-pool-size=2

Here, count=3 is the number of browsers that will open.

Please also remove <useUnlimitedThreads>true</useUnlimitedThreads> from pom.xml.

Note: While .fixed.max-pool-size effectively limits the maximum number of concurrent threads, Cucumber does not guarantee that the number of concurrently executing scenarios will not exceed this. This is from JUnit-Platform documentation.

We are done! Congratulations on making it through this tutorial and hope you found it useful! Happy Learning!!

You can see this framework in GitHub.

Advertisement

2 thoughts on “Parallel Execution of Cucumber with Serenity and JUnit5

  1. I have tried this same POM, And i figured unlimited Chrome Browser is opening while executing in parallel. However, execution could not be completed as Chrome Browser was keep invoking. How to limit the Chrome Browser invoke?

    Like

    1. We need to add the below in the junit-platform.properties file.

      cucumber.execution.parallel.config.fixed.parallelism=2
      cucumber.execution.parallel.config.fixed.max-pool-size=2

      This is also updated in the tutorial.

      Along with this, please also delete useUnlimitedThreads>true</useUnlimitedThreads from pom.xml

      Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s