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.
private static ThreadLocal<WebDriver> driver = new ThreadLocal<>();
- Override the DataProvider annotation with parallel=true in CucumberRunnerTest that extends AbstractTestNGCucumberTests class.
public class CucumberRunnerTest extends AbstractTestNGCucumberTests {
@Override
@DataProvider(parallel = true)
public Object[][] scenarios() {
return super.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"?>
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="Suite" parallel="true" data-provider-thread-count="1" verbose="2">
<test name="Test">
<classes>
<class name="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 @LoginFeature Feature: Login page validations Background: Given User is on login page @Login Scenario: Login to Orange HRM When User enters username "Admin" And User enters password "admin123" And User clicks on Login button Then User should navigate to Orange HRM home page @ForgotPassword Scenario: Forgot password link verification Then User verifies Forgot password link display
pom.xml
<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 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"> <suite name="Suite" parallel="true" data-provider-thread-count="1" verbose="2"> <test name="Test"> <classes> <class name="runners.CucumberRunnerTest" /> </classes> </test> <!-- Test --> </suite> <!-- Suite -->
WebDriverFactory.java
package driverfactory; import org.openqa.selenium.WebDriver; import org.openqa.selenium.chrome.ChromeDriver; import org.openqa.selenium.chrome.ChromeOptions; import org.openqa.selenium.edge.EdgeDriver; public final class WebDriverFactory { private static ThreadLocal<WebDriver> driver = new ThreadLocal<>(); public static void setDriver() { 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(); } else if (browser.equalsIgnoreCase("edge")) { System.setProperty("webdriver.edge.driver", "E:\\Drivers\\msedgedriver.exe"); driver.set(new EdgeDriver()); getDriver().manage().window().maximize(); getDriver().manage().deleteAllCookies(); } } public static WebDriver getDriver() { return driver.get(); } public static void closeDriver() { driver.get().quit(); driver.remove(); } }
Hooks.java
package hooks; import java.net.InetAddress; import java.net.UnknownHostException; import driverfactory.WebDriverFactory; import io.cucumber.java.After; import io.cucumber.java.AfterAll; import io.cucumber.java.Before; import io.cucumber.java.Scenario; public class Hooks { @Before(order = 0) public void before(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) public void after() throws Exception { WebDriverFactory.closeDriver(); } @AfterAll(order = 0) public static void afterAll() 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() { @Override public void run() { 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; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.FindBy; public class LoginPageFactory { public WebDriver driver; public LoginPageFactory(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 action public void enterUsername(String uname) { userName.sendKeys(uname); } // Method that performs Password action public void enterPassword(String pwd) { passWord.sendKeys(pwd); } // Method that performs Login action public void clickLogin() { loginButton.click(); } // Method that performs Forgot Password link verification public boolean isForgotPasswordLinkPresent() { return forgotPassword.isDisplayed(); } }
CucumberRunnerCLI.java
package runners; import io.cucumber.core.cli.*; public class CucumberRunnerCLI { public static void main(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; import org.testng.annotations.DataProvider; import io.cucumber.testng.AbstractTestNGCucumberTests; import io.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) public class CucumberRunnerTest extends AbstractTestNGCucumberTests { @Override @DataProvider(parallel = true) public Object[][] scenarios() { return super.scenarios(); } }
LoginStepDef.java
package stepdef; import org.openqa.selenium.support.PageFactory; import org.testng.Assert; import driverfactory.WebDriverFactory; import io.cucumber.java.en.Given; import io.cucumber.java.en.Then; import io.cucumber.java.en.When; import pagefactory.LoginPageFactory; public class LoginStepDef { LoginPageFactory login = PageFactory.initElements(WebDriverFactory.getDriver(), LoginPageFactory.class); @Given("User is on login page") public void user_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}") public void user_enters_username(String username) { login.enterUsername(username); } @When("User enters password {string}") public void user_enters_password(String password) { login.enterPassword(password); } @When("User clicks on Login button") public void user_clicks_on_login_button() throws InterruptedException { login.clickLogin(); Thread.sleep(3000); } @Then("User should navigate to Orange HRM home page") public void user_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") public void user_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.
No comments:
Post a Comment