Parallel Testing in Cucumber with TestNG

HOME

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

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 TestNG and Maven test execution plugins by setting the data provider parallel option to true.

In TestNG, the scenarios and rows in a scenario outline are executed in multiple threads. One can use either Maven Surefire or Failsafe plugin for executing the runners. In this tutorial, I’m using the Maven Surefire plugin.

Pre-Requisite

  1. Java is installed
  2. Maven is installed
  3. TestNG is installed
  4. Eclipse or IntelliJ Java IDE installed

This framework consists of:

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

Steps to create a project for parallel Testing in Cucumber

  1. Create a Maven project.
  2. Update the Properties section in Maven pom.xml
  3. Add Cucumber and TestNG dependencies to the project.
  4. Add Surefire plugin configuration to the build section to the POM.
  5. Create 2 feature files under src/test/resources – Login.feature and HomePage.feature.
  6. Create the Step Definition classes for both feature files or Glue Code.
  7. Create a Cucumber TestNG Runner class.
  8. Report Generation
  9. Execute the test from Command-Line.
  10. Execute the test from TestNG Runner.
  11. Difference between parallel and non-parallel tests

Detailed Step Description

Step 1 – Create a Maven project

Create a Maven project in your favorite IDE using the cucumber archetype. To know more about this, click here.

Step 2 – Update 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>
   <testng.version>7.4.0</testng.version>
   <maven.surefire.plugin.version>3.0.0-M5</maven.surefire.plugin.version>
 </properties>

Step 3 – Add Cucumber, Selenium, and TestNG dependencies to the project

Add below mentioned Cucumber-Java and Cucumber-TestNG and Selenium-java dependencies to the project.

<dependencies>

    <!--Cucumber Dependencies -->
    <dependency>
      <groupId>io.cucumber</groupId>
      <artifactId>cucumber-java</artifactId>
      <version>${cucumber.version}</version>
    </dependency>
  
    <dependency>
      <groupId>io.cucumber</groupId>
      <artifactId>cucumber-testng</artifactId>
      <version>${cucumber.version}</version>
      <scope>test</scope>
     </dependency>
  
    <!-- Selenium Dependency -->
    <dependency>
      <groupId>org.seleniumhq.selenium</groupId>
      <artifactId>selenium-java</artifactId>
      <version>${selenium.version}</version>
    </dependency>

    <!-- TestNG Dependency -->
    <dependency>
      <groupId>org.testng</groupId>
      <artifactId>testng</artifactId>
      <version>${testng.version}</version>
      <scope>test</scope>
    </dependency>  

</dependencies>
  

Step 4 – 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>
</plugin>

Step 5 – Create 2 feature files under src/test/resources – Login.feature and HomePage.feature

Feature File is an entry point to the Cucumber tests.

The first keyword in the Feature file is the Feature keyword, followed by: and short text that describes the feature.

To know more about the Feature file, please refer to this tutorial.

Below are the sample feature files.

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

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              |

Step 6 – Create the Step Definition classes for both feature files or Glue Code.

Below is the Step Definition for Login.feature.

public class LoginDefinition {

	WebDriver driver;

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

		System.out.println("Thread ID - " + Thread.currentThread().getId());
		System.setProperty("webdriver.gecko.driver",
				"C:\\Users\\Vibha\\Software\\geckodriver-v0.26.0-win64\\geckodriver.exe");

		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);
		assertThat(newPageText, containsString("Welcome"));

		driver.quit();

	}

Below is the Step Definition for HomePage.feature.

public class HomePageDefinitions {

	WebDriver driver;

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

		System.out.println("Thread ID - " + Thread.currentThread().getId());
		System.setProperty("webdriver.gecko.driver",
				"C:\\Users\\Vibha\\Software\\geckodriver-v0.26.0-win64\\geckodriver.exe");

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

	@And("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();
		assertThat(dashboardTitle, containsString("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 7 – Create a Cucumber TestNG Runner class

Add a cucumber runner by extending the AbstractTestNGCucumberTests class and overriding the scenarios method. Set the parallel option value to true for the DataProvider annotation.

import org.testng.annotations.DataProvider;

import io.cucumber.testng.AbstractTestNGCucumberTests;
import io.cucumber.testng.CucumberOptions;

@CucumberOptions(tags = "", features = "src/test/resources/features", glue = "com.example.Cucumber_TestNGDemo.definitions")

public class CucumberRunnerTests extends AbstractTestNGCucumberTests {

	@Override
	@DataProvider(parallel = true)
	public Object[][] scenarios() {
		return super.scenarios();
	}

}

Step 8 – Report Generation

Add cucumber.properties under src/test/resources and add the below instruction in the file.

cucumber.publish.enabled=true

Step 9 – Execute the test from Command Line

Use the below-mentioned command in the command prompt to run the tests.

mvn clean test

Step 10 – Execute the tests from TestNG Runner

Go to the Runner class and right-click Run As TestNG Test. The tests will run as TestNG tests.

Step 11 – Difference between parallel and non-parallel tests

Parallel Tests

All the tests are started at the same time, so they share different threads. The way tests are executed is different in them. With non-parallel tests, all the scenarios of the same feature are executed together, and then the scenarios of another feature file. Whereas in parallel tests, all the tests are started at the same time, so there won’t be any specific order.

All the scenarios and rows of the scenario outlines are executed in different threads.

Non Parallel Tests

Parallel Test Report – The execution time to complete the tests is less in parallel test execution as compared to the non-parallel one.

Non-Parallel Tests Report

Congratulations on making it through this tutorial and hope you found it useful! Happy Learning!! Cheers!!

There is another tutorial that shows Parallel Testing in Cucumber with JUnit.

4 thoughts on “Parallel Testing in Cucumber with TestNG

  1. This example presumes that all of a scenario’s steps are found in a single step-definition class, which has its own test runner.
    If my Scenario Outline has:
    * “Given I add to my basket” defined in FruitSteps.java
    * “When I pay for my order” defined in CheckoutSteps.java
    * “Then my total payment is $” defined in CheckoutAssertions.java
    …where should the WebDriver be declared, so that each thread has its own WebDriver, but that WebDriver is accessible by all the definition classes?

    Like

      1. A few things (because what is testing all about, if not telling people that there’s something wrong with the thing they made?):

        – The site your tests are running against has evidently changed, because almost none of your element selectors work.
        – The Pre-requisites don’t mention downloading a suitable webdriver.
        – The webdriver location path in the code is specific to your own computer. WebDriverManager (as used in that Page Object Model post you suggested) with Chrome works great though.
        – in the LoginDefinition class, you use assertThat() and containsString(), but they’re not declared anywhere. (In HomePageDefinition, you use the working org.testng method Assert.assertEquals() ).
        – Because driver.quit() is called at the end of the final step of each script, the browser and the webdriver only close if the test passes. When a test fails (and there were many, until I fixed the driver location and selectors), you quickly end up with many unsupervised webdrivers taking up system resources.
        – Please include the package and import declarations of the classes, and a screenshot of the full file tree at the end, for people to check against their own structure.

        But! After fixing the various issues, it ran four tests at once (though without adapting it to a page-object model yet), so I’m very happy indeed. Many thanks 🙂

        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