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 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 ?
Here's why ThreadLocal webdriver is important
- Thread Safety
- Parallel Execution
- Resource Management
- Scenario Isolation
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
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>
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.
- 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
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:
#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.publish.enabled=true
I hope this helped you a bit ! If you liked it do subscribe my YouTube channel for interesting tech updates.