Parallel Testing in Cucumber with JUnit

HOME

In this tutorial, I will explain Parallel Testing using Cucumber with JUnit.

Cucumber-JVM allows parallel execution across multiple threads since version 4.0.0. There are several options to incorporate this built-in feature in a Cucumber project. You can do so by using JUnit, TestNG, or CLI.

Cucumber can be executed in parallel using JUnit and Maven test execution plugins.

In JUnit, the feature files are run in parallel rather than scenarios, which means all the scenarios in a feature file will be executed by the same thread. You can use either Maven Surefire or Failsafe plugin to execute the runner. In this tutorial, I’m using the Maven Surefire plugin.

This framework consists of:-

  1. Cucumber Java- 6.8.1
  2. Cucumber JUnit – 6.8.1
  3. Java 11
  4. JUnit– 4.13.2
  5. Maven – 3.8.1
  6. Selenium – 3.141.59
  7. Maven Surefire Plugin – 3.0.0-M5

Steps to create a project for parallel Testing in Cucumber

  1. Create a Maven project.
  2. Add Cucumber and JUnit dependencies to the project.
  3. Add Surefire plugin configuration to the build section to the POM.
  4. Create a feature file under src/test/resources.
  5. Create the Step Definition class or Glue Code.
  6. Create a Cucumber Runner class.
  7. Execute the test from Command-Line.
  8. Generate Cucumber Report.

Detailed Step Description

Step 1 – Create a Maven project

Create a Maven project in your favorite IDE using the cucumber-archetype or by adding Cucumber dependencies to the POM as detailed here and Junit dependencies here. To know more about this, click here.

Below is the structure of the project.

Step 2 – Update the Properties section in Maven pom.xml

   <properties>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      <selenium.version>3.141.59</selenium.version>
      <cucumber.version>6.8.1</cucumber.version>
      <junit.version>4.13.2</junit.version>
      <webdrivermanager.version>5.1.0</webdrivermanager.version>
      <maven.compiler.plugin.version>3.7.0</maven.compiler.plugin.version>
      <maven.surefire.plugin.version>3.0.0-M5</maven.surefire.plugin.version>
   </properties>

Step 2 – Add Cucumber and JUnit dependencies to the project

Add below mentioned Cucumber-Java and Cucumber-JUnit dependencies to the project.

<!-- Cucumber Dependencies -->
      <dependency>
         <groupId>io.cucumber</groupId>
         <artifactId>cucumber-java</artifactId>
         <version>${cucumber.version}</version>
      </dependency>
      
      <dependency>
         <groupId>io.cucumber</groupId>
         <artifactId>cucumber-junit</artifactId>
         <version>${cucumber.version}</version>
         <scope>test</scope>
      </dependency>
      
      <!-- JUnit Dependency -->
      <dependency>
         <groupId>junit</groupId>
         <artifactId>junit</artifactId>
         <version>${junit.version}</version>
         <scope>test</scope>
      </dependency>
      
      <dependency>
         <groupId>org.seleniumhq.selenium</groupId>
         <artifactId>selenium-java</artifactId>
         <version>${selenium.version}</version>
      </dependency>
      
      <dependency>
         <groupId>io.github.bonigarcia</groupId>
         <artifactId>webdrivermanager</artifactId>
         <version>${webdrivermanager.version}</version>
      </dependency>

   </dependencies>
  

Step 3 – Add Surefire plugin configuration to the build section of the POM.

 <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>${maven.surefire.plugin.version}</version>
   <configuration>
        <parallel>methods</parallel>
        <useUnlimitedThreads>true</useUnlimitedThreads>
        <testFailureIgnore>true</testFailureIgnore>
	</configuration>
</plugin> 

To set the thread count to a specific number instead of useUnlimitedThreads use the below setting.

<configuration>
    <parallel>methods</parallel>
    <threadCount>4</threadCount>
</configuration>

The thread count in the above setting is 4 threads per core.

Step 4 – Create a feature folder under src/test/resources

Add 4 feature files – FlightBooking.feature, TrainBooking.feature, HomePage.feature and Login.feature

FlightBooking.feature

Feature: Book flight ticket 
 
@BookOneWayFlight
Scenario: Book Flight for one way trip
 
Given I want to book one way flight ticket from Dublin to London on 22nd July for 2 adults and 2 kids
Then TripAdvisor should provide me options to book flight ticket

@BookRoundTripFlight
Scenario: Book Flight for round trip
 
Given  I want to book roundtrip flight ticket from Dublin to India on 30th June and return 10th July for 2 adults and 1 kid
Then TripAdvisor should provide me options to book flight ticket 

TrainBooking.feature

Feature: Book Train Ticket
 
@BookOneWayTrain
Scenario: Book train ticket for one way
 
Given I want to book one way train ticket from Dublin to Cork on 10th June for 2 adults and 2 kids
Then IrishRail should provide me options to book train ticket for the specified date

HomePage.feature

Feature: Home page validation
  
Background:
   Given User Navigates to HRM login page
   And User login with valid credentials
 
   @ValidQuickLaunch
   Scenario Outline: Login with valid credentials to check QuickLanuch options  - Feature 2, Scenario -1
     
   When User is in Dashboard page
     Then there are valid QuickLaunch options '<options>'
         
    Examples: 
        |options                  |
        |Assign Leave             |
        |Leave List               |
 
     
    @ValidLegendOptions    
    Scenario Outline: Login with valid credentials to check Manu Options - Feature 2, Scenario -2
     
   When User is in Dashboard page
     Then there are valid Legend options '<legendOptions>'
         
    Examples: 
        |legendOptions               |
        |Administration              |

Login.feature

Feature: Login to HRM Application 
  
   @ValidCredentials
   Scenario: Login with valid credentials - Feature 1, Scenario -1
      
    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 sucessfully

Step 5 – Create the Step Definition class or Glue Code

Below is the Step Definition for FlightBooking.feature.

public class FlightBookingDefinitions {
	
	@Given("I want to book one way flight ticket from Dublin to London on 22nd July for 2 adults and 2 kids")
	   public void singleTrip(){
	        System.out.println("I want to book one way flight ticket from Dublin to London on 22nd July for 2 adults and 2 kids");
	   }
	 
	@Then("TripAdvisor should provide me options to book flight ticket")
	   public void TripAdvisor(){
	        System.out.println("TripAdvisor should provide me options to book flight ticket");
	   }
	
	@Given("I want to book roundtrip flight ticket from Dublin to India on 30th June and return 10th July for 2 adults and 1 kid")
	   public void roundTrip(){
	        System.out.println("I want to book one way flight ticket from Dublin to London on 22nd July for 2 adults and 2 kids");
	   }
}

Below is the StepDefinition for TrainBooking.feature.

public class TrainBookingDefinitions {

	@Given("I want to book one way train ticket from Dublin to Cork on 10th June for 2 adults and 2 kids")
	   public void hotelWithoutBreakfast(){
	        System.out.println("I want to book one way train ticket from Dublin to Cork on 10th June for 2 adults and 2 kids");
	   }
	 
	@Then("IrishRail should provide me options to book train ticket for the specified date")
	   public void Trivago(){
	        System.out.println("IrishRail should provide me options to book train ticket for the specified date");
	   }

}

Below is the StepDefinition for Login.feature.

public class LoginDefinition {

	WebDriver driver;

	@Given("User is on Home page")
	public void userOnHomePage() {

		WebDriverManager.firefoxdriver().setup();
		driver = new FirefoxDriver();
		driver.manage().window().maximize();
		driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);

		driver.get("https://opensource-demo.orangehrmlive.com/");
	}

	@When("User enters username as {string}")
	public void entersUsername(String userName) throws InterruptedException {

		System.out.println("Username Entered");
		driver.findElement(By.name("txtUsername")).sendKeys(userName);

	}

	@When("User enters password as {string}")
	public void entersPassword(String passWord) throws InterruptedException {

		System.out.println("Password Entered");
		driver.findElement(By.name("txtPassword")).sendKeys(passWord);

		driver.findElement(By.id("btnLogin")).submit();
	}

	@Then("User should be able to login sucessfully")
	public void sucessfullLogin() throws InterruptedException {

		String newPageText = driver.findElement(By.id("welcome")).getText();
		System.out.println("newPageText :" + newPageText);
		Assert.assertTrue(newPageText.contains("Welcome"));

		driver.quit();

	}
}

Below is the StepDefinition for HomePage.feature.

public class HomePageDefinition {

	WebDriver driver;

	@Given("User Navigates to HRM login page")
	public void userOnHomePage() {

        WebDriverManager.firefoxdriver().setup();
		driver = new FirefoxDriver();
		driver.manage().window().maximize();
		driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);
		driver.get("https://opensource-demo.orangehrmlive.com/");
	}

	@Given("User login with valid credentials")
	public void entersCredentials() throws InterruptedException {
		Thread.sleep(1000);

		driver.findElement(By.name("txtUsername")).sendKeys("Admin");
		driver.findElement(By.name("txtPassword")).sendKeys("admin123");
		driver.findElement(By.id("btnLogin")).submit();

	}

	@When("User is in Dashboard page")
	public void verifyDashboardPage() {

		String dashboardTitle = driver.findElement(By.id("welcome")).getText();
		Assert.assertTrue(dashboardTitle.contains("Welcome"));

	}

	@Then("there are valid QuickLaunch options {string}")
	public void verifyQuickLinks(String options) throws InterruptedException {

		switch (options) {
		case "Assign Leave":
			String linkOne = driver
					.findElement(By.xpath(
							"//*[@id='dashboard-quick-launch-panel-menu_holder']/table/tbody/tr/td[1]/div/a/span"))
					.getText();
			Assert.assertEquals(linkOne, options);
			Thread.sleep(1000);
			break;
		case "Leave List ":
			String linkTwo = driver
					.findElement(By.xpath(
							"//*[@id='dashboard-quick-launch-panel-menu_holder']/table/tbody/tr/td[2]/div/a/span"))
					.getText();
			Assert.assertEquals(linkTwo, options);
			Thread.sleep(1000);
			break;
		case "Timesheets":
			String linkThree = driver
					.findElement(By.xpath(
							"//*[@id='dashboard-quick-launch-panel-menu_holder']/table/tbody/tr/td[3]/div/a/span"))
					.getText();
			Assert.assertEquals(linkThree, options);
			Thread.sleep(1000);
			break;
		default:
			break;
		}

		driver.quit();

	}

	@Then("there are valid Legend options {string}")
	public void verifyMenuOptions(String options) throws InterruptedException {

		switch (options) {
		case "Not assigned to Subunits":
			String linkOne = driver
					.findElement(
							By.xpath("//*[@id='div_legend_pim_employee_distribution_legend']/table/tbody/tr[1]/td[2]"))
					.getText();
			Assert.assertEquals(linkOne, options);
			Thread.sleep(1000);
			break;
		case "Administration":
			String linkTwo = driver
					.findElement(
							By.xpath("//*[@id='div_legend_pim_employee_distribution_legend']/table/tbody/tr[2]/td[2]"))
					.getText();
			Assert.assertEquals(linkTwo, options);
			Thread.sleep(1000);
			break;
		case "Client Services":
			String linkThree = driver
					.findElement(
							By.xpath("//*[@id='div_legend_pim_employee_distribution_legend']/table/tbody/tr[3]/td[2]"))
					.getText();
			Assert.assertEquals(linkThree, options);
			Thread.sleep(1000);
			break;
		default:
			break;

		}
		driver.quit();
	}
}

Step 6 – Create a Test Runner to run the tests

import org.junit.runner.RunWith;

import io.cucumber.junit.Cucumber;
import io.cucumber.junit.CucumberOptions;

@RunWith(Cucumber.class)

@CucumberOptions(features= {"src/test/resources"}, glue= {"com.cucumber.parallel_demo"})
public class RunCucumberTest {

}

Step 7 – Execute the tests from the command line

mvn test

Below is the execution screen. There are four feature files. Out of four files, two are simple feature files (FlightBooking and TrainBooking ) whereas another two Feature files are complex (Login and Home). These feature files contain the below number of scenarios.

FlightBooking – 2 Scenarios
TrainBooking – 1 Scenario
Login – 1 Scenario
Home – 4 Scenarios

Feature files are executed in alphabetical order. So, the sequence in which non-parallel tests will run is FlightBooking -> Home ->Login -> TrainBooking

But, as we have executed tests parallelly, so the feature files will run in this order (depending on the least number of scenarios will be executed first) TrainBooking -> FlightBooking -> Login -> Home

All the tests of a particular feature file are executed together as feature files are run in parallel, not scenarios.

Step 8 – Cucumber Report Generation

To get Cucumber Test Reports, add cucumber.properties under src/test/resources and add the below instruction in the file.

cucumber.publish.enabled=true

Below is the image of the Cucumber Report generated using Cucumber Service.

Parallel Testing with Dependency Injection

When using Cucumber, if you want to share state between multiple step definition files, you’ll need to use dependency injection (DI). There are several options: PicoContainer, Spring, OpenEJB, etc. If you’re not already using DI, then I recommend PicoContainer. Otherwise, use the one that’s already in use, because you should only have one.

To perform Dependency Injection in Cucumber, we need to add a cucumber-picocontainer dependency to POM.xml.

<!-- Dependency Injection -->
      <dependency>
         <groupId>io.cucumber</groupId>
         <artifactId>cucumber-picocontainer</artifactId>
         <version>${cucumber.picocontainer.version}</version>
         <scope>test</scope>
      </dependency>

Next, create a new class that holds the common data. For example:

public class ApplicationHooks {

	private WebDriver driver;

	@Before
	public void setUp() {
		setDriver();
	}

	public void setDriver() {

		WebDriverManager.chromedriver().setup();
		driver = new ChromeDriver();
		driver.manage().window().maximize();

	}

	public WebDriver getDriver() {
		return driver;
	}

	@After
	public void tearDown() {
		getDriver().quit();
	}

}

Then, in each of your step definition files that you want to use this common data, you can add a constructor that takes StepData as an argument. This is where the injection occurs. For example:

LoginDefinition

public class LoginDefinition {

	private ApplicationHooks hooks;

	public LoginDefinition(ApplicationHooks hooks) {

		this.hooks = hooks;
	}

	@Given("User is on Home page")
	public void userOnHomePage() {

		hooks.getDriver().get("https://opensource-demo.orangehrmlive.com/");
	}

	@When("User enters username as {string}")
	public void entersUsername(String userName) throws InterruptedException {

		System.out.println("Username Entered");
		hooks.getDriver().findElement(By.name("txtUsername")).sendKeys(userName);

	}

	@When("User enters password as {string}")
	public void entersPassword(String passWord) throws InterruptedException {

		System.out.println("Password Entered");
		hooks.getDriver().findElement(By.name("txtPassword")).sendKeys(passWord);

		hooks.getDriver().findElement(By.id("btnLogin")).submit();
	}

	@Then("User should be able to login sucessfully")
	public void sucessfullLogin() throws InterruptedException {

		String newPageText = hooks.getDriver().findElement(By.id("welcome")).getText();
		System.out.println("newPageText :" + newPageText);
		Assert.assertTrue(newPageText.contains("Welcome"));

	}
}

HomePageDefinition

public class HomePageDefinition {

	ApplicationHooks hooks;

	public HomePageDefinition(ApplicationHooks hooks) {
		this.hooks = hooks;
	}

	@Given("User Navigates to HRM login page")
	public void userOnHomePage() {

		hooks.getDriver().get("https://opensource-demo.orangehrmlive.com/");
	}

	@Given("User login with valid credentials")
	public void entersCredentials() throws InterruptedException {

		Thread.sleep(1000);

		hooks.getDriver().findElement(By.name("txtUsername")).sendKeys("Admin");
		hooks.getDriver().findElement(By.name("txtPassword")).sendKeys("admin123");
		hooks.getDriver().findElement(By.id("btnLogin")).submit();

	}

	@When("User is in Dashboard page")
	public void verifyDashboardPage() {

		String dashboardTitle = hooks.getDriver().findElement(By.id("welcome")).getText();
		Assert.assertTrue(dashboardTitle.contains("Welcome"));

	}

	@Then("there are valid QuickLaunch options {string}")
	public void verifyQuickLinks(String options) throws InterruptedException {

		switch (options) {
		case "Assign Leave":
			String linkOne = hooks.getDriver()
					.findElement(By.xpath(
							"//*[@id='dashboard-quick-launch-panel-menu_holder']/table/tbody/tr/td[1]/div/a/span"))
					.getText();
			Assert.assertEquals(linkOne, options);
			Thread.sleep(1000);
			break;
		case "Leave List ":
			String linkTwo = hooks.getDriver()
					.findElement(By.xpath(
							"//*[@id='dashboard-quick-launch-panel-menu_holder']/table/tbody/tr/td[2]/div/a/span"))
					.getText();
			Assert.assertEquals(linkTwo, options);
			Thread.sleep(1000);
			break;
		case "Timesheets":
			String linkThree = hooks.getDriver()
					.findElement(By.xpath(
							"//*[@id='dashboard-quick-launch-panel-menu_holder']/table/tbody/tr/td[3]/div/a/span"))
					.getText();
			Assert.assertEquals(linkThree, options);
			Thread.sleep(1000);
			break;
		default:
			break;
		}

	}

	@Then("there are valid Legend options {string}")
	public void verifyMenuOptions(String options) throws InterruptedException {

		switch (options) {
		case "Not assigned to Subunits":
			String linkOne = hooks.getDriver()
					.findElement(
							By.xpath("//*[@id='div_legend_pim_employee_distribution_legend']/table/tbody/tr[1]/td[2]"))
					.getText();
			Assert.assertEquals(linkOne, options);
			Thread.sleep(1000);
			break;
		case "Administration":
			String linkTwo = hooks.getDriver()
					.findElement(
							By.xpath("//*[@id='div_legend_pim_employee_distribution_legend']/table/tbody/tr[2]/td[2]"))
					.getText();
			Assert.assertEquals(linkTwo, options);
			Thread.sleep(1000);
			break;
		case "Client Services":
			String linkThree = hooks.getDriver()
					.findElement(
							By.xpath("//*[@id='div_legend_pim_employee_distribution_legend']/table/tbody/tr[3]/td[2]"))
					.getText();
			Assert.assertEquals(linkThree, options);
			Thread.sleep(1000);
			break;
		default:
			break;

		}
	}
}

Now, lets the test execution begins

Let us go to the Cucumber Report which is displayed on the screen.

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

In the next tutorial, I explained Parallel Testing in Cucumber with TestNG.

4 thoughts on “Parallel Testing in Cucumber with JUnit

  1. Hi
    Thanks for creating this page. it has answered by 1 problem.
    However, is it possible to reduce the code duplicate of browser initialization using pico container.
    Could you please share an example of
    multiple step definitions using pico container
    and parallel execution of feature file

    Like

    1. Thank you for reading this page. I have updated this page with running multiple step definitions using pico container. Have a look and let me know your opinion.

      Like

      1. Thanks a lot.
        I am facing one challenge in my current project.
        My feature file contains multiple scenario and each scenario open a driver window.
        However, my execution require driver and data to be shared from 1st scenario to 2nd till last one.
        In hook, if i do not pass driver as static webdriver. it does not execute my scenario as per above line.
        However, if i pass static webdriver, then my execution stop after completion of 1st feature file.
        As soon as driver tries to launch for 2nd feature file. It gives error
        Diver session is null and can not call afterhook driver.close/ driver.quit.

        my feature file looks like.

        Feature: Purchase a product from amazon / ebay channel

        @Start
        Scenario: Successful Login with Valid Credentials
        Given Browser has been launched
        When User navigate to loginpage
        And User enters valid credentials
        And Clicks on SignIn button
        Then User should be able to see homepage

        Scenario: Search for the product
        Given User is on homepage
        When search for product using advance search
        Then user should be able to see product
        And open the product details page

        Scenario: add to cart
        Given User is on product details page
        When select color and size of product
        And click add to card
        Then product should be added to cart

        Scenario: payment and checkout

        @teardown
        scenario: logout
        Given User has successfully purchased product
        When user click at logout
        Then User should get successfully logged out of portal

        Could you please suggest what should i do to resolve the parallel execution in such case.

        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