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:
- Serenity – 3.6.12
- Serenity Cucumber – 7.11.0
- JUnit Jupiter – 5.9.2
- Java 11
- Maven – 3.8.1
- Maven Compiler Plugin – 3.8.1
- Maven Surefire Plugin – 3.0.0-M5
- Maven FailSafe Plugin – 3.0.0-M5
Implementation Steps
- Download and Install Java on system
- Download and setup Eclipse IDE on system
- Setup Maven and create a new Maven Project
- Update Properties section in Maven pom.xml
- Add Serenity, Serenity Cucumber and JUnit5 dependencies to POM.xml
- Update Build Section of pom.xml
- Create a feature file under src/test/resources
- Create the Step page classes
- Create the Step Definition class or Glue Code
- Create a Serenity-Cucumber Runner class
- Create a cucumber.properties file in src/test/resources (optional)
- Create junit-platform.properties in src/test/resources
- Create serenity.conf file in src/test/resources
- Create serenity.properties file in the root of the project
- Run the tests from Command Line
- Run the tests from CucumberRunner
- 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.