Quantcast
Channel: Pochampalli IT Labs
Viewing all articles
Browse latest Browse all 261

Cucumber scenarios parallel execution with ThreadLocal driver and dynamic dataproviderthreadcount | TestNG, Java Selenium, Maven , POM page factory

$
0
0
Hi, In this post, we will see how to run cucumber scenarios in parallel with TestNG.

What is parallel testing in cucumber?
Parallel testing in Cucumber refers to the ability to execute Cucumber scenarios in parallel, allowing multiple scenarios to run simultaneously and speeding up the overall test execution time.

Consider below factors while implementing parallel execution. 
  • Scenario Independence
  • Thread Safety
  • Tagging Scenarios
  • Synchronization

Software :
Java :  "17.0.7" 2023-04-18 LTS  (java -version)
Maven : Apache Maven 3.9.2  (mvn -version)
Chrome browser: 114.0.5735.110 (Official Build) (64-bit)
Chrome WebDriver: 114.0.5735.90
https://chromedriver.storage.googleapis.com/index.html?path=114.0.5735.90/

cucumber-java 7.12.0
cucumber-testng 7.12.0
selenium-java 4.10.0
maven-surefire-plugin 3.1.2
maven-compiler-plugin 3.11.0

Example: Execute two scenarios in parallel from a single feature file.
i.e.,
1) Verify user is able to login to the application with valid credentials
2) Verify Forgot password link is displayed on login page.

Is it necessary to have testng.xml for parallel execution ?
There could be different approaches to perform parallel execution from command line or jar file or from CI/CD method, in this demo I'd say yes it is necessary to have testng.xml so that we can pass dynamic thread count. 

What is ThreadLocal webdriver in cucumber ? Why it is important ?
A ThreadLocal webdriver refers to an instance of a WebDriver object that is stored in a ThreadLocal variable. 
Each thread running a Cucumber scenario or step can have its own WebDriver instance, isolated from other threads.
This approach ensures thread safety and prevents conflicts when executing scenarios in parallel.

Here's why ThreadLocal webdriver is important
  • Thread Safety
  • Parallel Execution
  • Resource Management
  • Scenario Isolation
The project folder structure: 
Is mandatory to keep all the code in "parallel" folder as in many example over the internet ? 

No, it is not necessary to keep them in "parallel" folder. Similarly to keep the features NO "parallel" folder is required. 

Concepts covered in code base:
  • DriverFactory for chrome and edge - How to pass chrome or edge as command line argument ? 
  • Parallel execution configurations - Where and how to configure parallel execution with TestNG? 
  • POM model with page factory pattern - How to isolate locators and step definitions ? 
  • Cucumber hooks  - What is the recommendation of cucumber hooks over TestNG hooks ?  
  • Reports 
    • Which report is to be used - cucumber reports or extent reports ?  cucumber reports 
    • Where to write email code to send the latest reports ? In JVM shutdown hook that is written in cucumber AfterAll hook
      (Did you ever try to remove or refresh a folder to have the latest reports to be sent over email)
  • How to run the project ?
    • Through command line 
    • Through eclipse run as TestNG
    • Through executable jar file
Would you like to watch few mins demo in a no voice video ? 

Jumping on to the core of this article i.e., parallel testing 

Parallel execution configurations:

(For complete code download project from GitHub or Use this link to download as Zip

  • Declare thread local driver in WebDriverFactory java class and then set, get and remove instances of the driver. 

    privatestatic ThreadLocal<WebDriver> driver = new ThreadLocal<>();

  • Override the DataProvider annotation with parallel=true in CucumberRunnerTest that extends AbstractTestNGCucumberTests class. 

    publicclass CucumberRunnerTest extends AbstractTestNGCucumberTests {


    @Override

    @DataProvider(parallel = true)

    public Object[][] scenarios() {

    returnsuper.scenarios();

    }

    }

  • Create a testng.xml file and give parallel=true and data-provider-thread-count=1 and the runner class as shown below.

    <?xml version="1.0" encoding="UTF-8"?>

    <!DOCTYPEsuite SYSTEM "https://testng.org/testng-1.0.dtd">

    <suitename="Suite"parallel="true"data-provider-thread-count="1"verbose="2">

    <testname="Test">

    <classes>

    <classname="runners.CucumberRunnerTest" />

    </classes>

    </test> <!--

    Test -->

    </suite> <!--

    Suite -->

  • In pom.xml add the testng.xml as suiteXml file  in maven-surefire-plugin.

    <plugin>

    <groupId>org.apache.maven.plugins</groupId>

    <artifactId>maven-surefire-plugin</artifactId>

    <version>3.1.2</version>

    <configuration>

    <suiteXmlFiles>

    <suiteXmlFile>testng.xml</suiteXmlFile>

    </suiteXmlFiles>

    </configuration>

    </plugin>

That's all we need to make cucumber scenarios to run parallel with TestNG as testing framework. 
So, after configurations - How to run the code to see parallel execution ? 

On the command prompt - maven execution
  • Navigate to the the project folder location - you should have the pom.xml in it. 
  • Run below command in Command prompt. 

    mvn -Dbrowser=chrome -Ddataproviderthreadcount=2 test

  • This command opens two chrome browser parallelly as passing dataproviderthreadcount =2 
  • If passed 1 then sequential execution takes place. 
On the command prompt - jar execution 
  • Use the maven-assembly-plugin and Main.run method creation technique to create a jar file
    (scroll down to the complete code section below and see how assembly plugin is configured in pom.xml and how Main.main takes arguments in CucumberRunnerCLI.java class )

  • Now to create executable/runnable jar file give below goal in eclipse 
    (Runs As -> Maven build -> Goals)  (Note: Make sure to clean the project prior assembling)
     
    clean package assembly:single -Dmaven.test.skip=true

  • Running above command downloads required jar files in .m2 repository and creates and copy the jar file in target folder

    [ [1;34mINFO [m] Building jar: E:\Automation\CucumberParallelExecutionTestNG\ target\CucumberParallelExecutionTestNG-0.0.1-SNAPSHOT-jar-with-dependencies.jar

    [ [1;34mINFO [m] [1m------------------------------------------------------------------------ [m

    [ [1;34mINFO [m] [1;32mBUILD SUCCESS

  • Use below command to run the scenarios from jar generated in target folder. 

    java -Ddataproviderthreadcount=1 -Dbrowser=chrome -jar CucumberParallelExecutionTestNG-0.0.1-SNAPSHOT-jar-with-dependencies.jar

In the Eclipse Run As TestNG with Run Configurations

  • Run As -> TestNG -> Run Configurations -> VM Arguments 
     --> -Dbrowser=chrome -Ddataproviderthreadcount=2


This is how one could able to run the scenarios in parallel with TestNG as the testing framework. 


Interesting Research Observations: 
Over the internet there are bunch of suggestions with maven sure fire plugin to play with parallel=method, threadCount=3  and incase of maven fail safe plugin to play with dataproviderthreadcount and value in the properties those didn't work for me at least. 

i.e., None of the below configurations worked to run scenarios parallel. 

maven-surefire-plugin with parallel=true and threadCount=3 DID NOT work<plugin>

<groupId>org.apache.maven.plugins</groupId>

<artifactId>maven-surefire-plugin</artifactId>

<version>3.1.2</version>

<configuration>

<parallel>methods</parallel>

<threadCount>3</threadCount>

<perCoreThreadCount>false</perCoreThreadCount>

<useUnlimitedThreads>false</useUnlimitedThreads>

</configuration>

</plugin>

maven-failsafe-plugin with dataproviderthreadcount property with value DID NOT work.

<plugin>

<groupId>org.apache.maven.plugins</groupId>

<artifactId>maven-failsafe-plugin</artifactId>

<version>2.22.0</version>

<executions>

<execution>

<goals>

<goal>integration-test</goal>

<goal>verify</goal>

</goals>

<configuration>

<properties>

<property>

<name>dataproviderthreadcount</name>

<value>2</value>

</property>

</properties>

</configuration>

</execution>

</executions>

</plugin>

----------------------------------------------------------------------------------------------------------


Code base: 

Login.feature
#Author: Sadakar Pochampalli@LoginFeatureFeature: Login page validationsBackground:    Given User is on login page@LoginScenario: Login to Orange HRM    When User enters username "Admin"And User enters password "admin123"And User clicks on Login buttonThen User should navigate to Orange HRM home page@ForgotPasswordScenario: Forgot password link verification    Then User verifies Forgot password link display

  pom.xml
<projectxmlns="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 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>CucumberParallelExecutionTestNG</groupId><artifactId>CucumberParallelExecutionTestNG</artifactId><version>0.0.1-SNAPSHOT</version><name>CucumberParallelExecutionTestNG</name><description>CucumberParallelExecutionTestNG</description><dependencies><dependency><groupId>io.cucumber</groupId><artifactId>cucumber-java</artifactId><version>7.12.0</version></dependency><!-- https://mvnrepository.com/artifact/io.cucumber/cucumber-java --><dependency><groupId>io.cucumber</groupId><artifactId>cucumber-testng</artifactId><version>7.12.0</version></dependency><dependency><groupId>org.seleniumhq.selenium</groupId><artifactId>selenium-java</artifactId><version>4.10.0</version></dependency><!-- https://mvnrepository.com/artifact/org.testng/testng --><!--	<dependency><groupId>org.testng</groupId><artifactId>testng</artifactId><version>7.8.0</version></dependency>--><dependency><groupId>tech.grasshopper</groupId><artifactId>extentreports-cucumber7-adapter</artifactId><version>1.2.0</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.24</version><scope>provided</scope></dependency><!-- https://mvnrepository.com/artifact/javax.mail/mail --><dependency><groupId>javax.mail</groupId><artifactId>mail</artifactId><version>1.4.7</version></dependency></dependencies><build><pluginManagement><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-surefire-plugin</artifactId><version>3.1.2</version><configuration><suiteXmlFiles><suiteXmlFile>testng.xml</suiteXmlFile></suiteXmlFiles></configuration></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.11.0</version><configuration><source>17</source><target>17</target></configuration></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-assembly-plugin</artifactId><version>3.1.1</version><configuration><archive><manifest><addClasspath>true</addClasspath><mainClass>runners.CucumberRunnerCLI</mainClass></manifest><manifestEntries><Class-Path>.</Class-Path></manifestEntries></archive><descriptorRefs><descriptorRef>jar-with-dependencies</descriptorRef></descriptorRefs></configuration><executions><execution><id>make-assembly</id><phase>package</phase><goals><goal>single</goal></goals></execution></executions></plugin></plugins></pluginManagement></build></project>

testng.xml
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd"><suitename="Suite"parallel="true"data-provider-thread-count="1"verbose="2"><testname="Test"><classes><classname="runners.CucumberRunnerTest"/></classes></test><!--	Test --></suite><!--Suite -->

WebDriverFactory.java
package driverfactory;importorg.openqa.selenium.WebDriver;importorg.openqa.selenium.chrome.ChromeDriver;importorg.openqa.selenium.chrome.ChromeOptions;importorg.openqa.selenium.edge.EdgeDriver;publicfinalclassWebDriverFactory{privatestatic ThreadLocal<WebDriver> driver =new ThreadLocal<>();publicstaticvoidsetDriver(){

		String browser = System.getProperty("browser","chrome");if(browser.equalsIgnoreCase("chrome")){
			System.setProperty("webdriver.chrome.driver","E:\\Drivers\\chromedriver.exe");
			ChromeOptions options =new ChromeOptions();
			options.addArguments("--remote-allow-origins=*");
			driver.set(new ChromeDriver(options));
			getDriver().manage().window().maximize();
			getDriver().manage().deleteAllCookies();}elseif(browser.equalsIgnoreCase("edge")){
			System.setProperty("webdriver.edge.driver","E:\\Drivers\\msedgedriver.exe");
			driver.set(new EdgeDriver());
			getDriver().manage().window().maximize();
			getDriver().manage().deleteAllCookies();}}publicstatic WebDriver getDriver(){return driver.get();}publicstaticvoidcloseDriver(){
		driver.get().quit();
		driver.remove();}}

Hooks.java
package hooks;importjava.net.InetAddress;importjava.net.UnknownHostException;importdriverfactory.WebDriverFactory;importio.cucumber.java.After;importio.cucumber.java.AfterAll;importio.cucumber.java.Before;importio.cucumber.java.Scenario;publicclassHooks{@Before(order =0)publicvoidbefore(Scenario scenaio)throws Exception {

		WebDriverFactory.setDriver();
		System.out.println("Current Thread Name:"+ Thread.currentThread().getName());
		System.out.println("Current Thread ID:"+ Thread.currentThread().getId());}@After(order =0)publicvoidafter()throws Exception {
		WebDriverFactory.closeDriver();}@AfterAll(order =0)publicstaticvoidafterAll()throws UnknownHostException {
		System.out.println("AfterAll - with order=0");

		InetAddress localhost = InetAddress.getLocalHost();
		System.out.println("System IP Address : "+(localhost.getHostAddress()).trim());

		Runtime.getRuntime().addShutdownHook(new Thread(new Runnable(){@Overridepublicvoidrun(){

				System.out.println("Write email code in this method - Shutdown Hook is running and this text prints before JVM shut downs!");}}));
		System.out.println("This text prints before Shutdown hook");}}

LoginPageFactory.java
package pagefactory;importorg.openqa.selenium.WebDriver;importorg.openqa.selenium.WebElement;importorg.openqa.selenium.support.FindBy;publicclassLoginPageFactory{public WebDriver driver;publicLoginPageFactory(WebDriver driver){this.driver= driver;}// Username locator@FindBy(xpath ="//input[@name='username']")public WebElement userName;// Password locator@FindBy(xpath ="//input[@name='password']")public WebElement passWord;// Login button locator@FindBy(xpath ="//button[@type='submit']")public WebElement loginButton;// Forgot password locator@FindBy(xpath ="//p[@class='oxd-text oxd-text--p orangehrm-login-forgot-header']")public WebElement forgotPassword;// Method that performs Username actionpublicvoidenterUsername(String uname){
		userName.sendKeys(uname);}// Method that performs Password actionpublicvoidenterPassword(String pwd){
		passWord.sendKeys(pwd);}// Method that performs Login actionpublicvoidclickLogin(){
		loginButton.click();}// Method that performs Forgot Password link verificationpublicbooleanisForgotPasswordLinkPresent(){return forgotPassword.isDisplayed();}}

CucumberRunnerCLI.java
package runners;importio.cucumber.core.cli.*;publicclassCucumberRunnerCLI{publicstaticvoidmain(String[] args){

		String threadCount = System.getProperty("dataproviderthreadcount","1");
		Main.run(new String[]{"classpath:scenarios","-g","driverfactory","-g","hooks","-g","pagefactory","-g","runners","-g","stepdef","-p","pretty","-p","json:target/cucumber-reports/cucumber.json","-p","html:target/cucumber-reports/cucumber-report.html","-m","--threads",threadCount
				

		}, Thread.currentThread().getContextClassLoader());}}

CucumberRunnerTest.java
package runners;importorg.testng.annotations.DataProvider;importio.cucumber.testng.AbstractTestNGCucumberTests;importio.cucumber.testng.CucumberOptions;@CucumberOptions(

		features ="classpath:scenarios", 
		tags ="@Login or @ForgotPassword", 
		glue ={"driverfactory","hooks","pagefactory","runners","stepdef"}, 
		plugin ={"pretty","json:target/cucumber-reports/cucumber.json","html:target/cucumber-reports/cucucmber-report.html"}, 
		monochrome =true)publicclassCucumberRunnerTestextends AbstractTestNGCucumberTests {@Override@DataProvider(parallel =true)public Object[][]scenarios(){returnsuper.scenarios();}}

LoginStepDef.java
package stepdef;importorg.openqa.selenium.support.PageFactory;importorg.testng.Assert;importdriverfactory.WebDriverFactory;importio.cucumber.java.en.Given;importio.cucumber.java.en.Then;importio.cucumber.java.en.When;importpagefactory.LoginPageFactory;publicclassLoginStepDef{
	LoginPageFactory login = PageFactory.initElements(WebDriverFactory.getDriver(), LoginPageFactory.class);@Given("User is on login page")publicvoiduser_is_on_login_page()throws InterruptedException {

		WebDriverFactory.getDriver().get("https://opensource-demo.orangehrmlive.com/web/index.php/auth/login");
		Thread.sleep(3000);}@When("User enters username {string}")publicvoiduser_enters_username(String username){
		login.enterUsername(username);}@When("User enters password {string}")publicvoiduser_enters_password(String password){
		login.enterPassword(password);}@When("User clicks on Login button")publicvoiduser_clicks_on_login_button()throws InterruptedException {
		login.clickLogin();
		Thread.sleep(3000);}@Then("User should navigate to Orange HRM home page")publicvoiduser_should_navigate_to_orange_hrm_home_page(){

		String expectedURLToNavigate ="https://opensource-demo.orangehrmlive.com/web/index.php/dashboard/index";
		String actualURLNavigated = WebDriverFactory.getDriver().getCurrentUrl();
		Assert.assertEquals(actualURLNavigated, expectedURLToNavigate);}@Then("User verifies Forgot password link display")publicvoiduser_verifies_forgot_password_link_display(){
		Assert.assertTrue(login.isForgotPasswordLinkPresent());}}

cucumber.properties
cucumber.publish.enabled=true

Take another look at the folder structure:

I hope this helped you a bit ! If you liked it do subscribe my YouTube channel for interesting tech updates.  

Viewing all articles
Browse latest Browse all 261

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>