DEV Community

Cover image for Selenium Parallel testing using Java ThreadLocal and TestNG
Shoeib Shargo
Shoeib Shargo

Posted on

Selenium Parallel testing using Java ThreadLocal and TestNG

No, writing just parallel = “tests” & thread-count =”2" in your testng.xml file won’t cut it. That is because Selenium WebDriver is not thread-safe by default. In a non-multithreaded environment, we keep our WebDriver reference static to make it thread-safe. But the problem occurs when we try to achieve parallel execution of our tests within the framework. Every thread you create to parallelize your tests tries to overwrite the WebDriver reference since there could be only one instance of static WebDriver reference.

To overcome this problem, we can take help from the ThreadLocal class of Java. Wondering what is ThreadLocal? Simply put, it enables you to create a generic/ThreadLocal type of object which can only be read and written (via its get and set method) by the same thread, so if two threads are trying to read and write a ThreadLocal object concurrently, one thread would not see the modification of the ThreadLocal object done by the other thread, thus, making the thread-local object thread-safe.

A typical thread-local object is made private static and its class provides initialValue, get, set, and remove methods. The following example shows how you can create a Generic type thread-local object

//create a generic thread-local object private static ThreadLocal<String> myThreadLocal = new ThreadLocal<String>(); //set thread-local value myThreadLocal.set("Hello ThreadLocal"); //get thread-local value String threadLocalValue = myThreadLocal.get(); //remove thread-local value for the current thread myTheadlocal.remove(); 
Enter fullscreen mode Exit fullscreen mode

Now that we have seen how ThreadLocal works, we can go ahead and implement it in our Selenium framework making the WebDriver session exclusive to each thread. Let’s create a BroweserManager class to manage the WebDriver instance alongside browsers.

public class BrowserManager { public static WebDriver doBrowserSetup(String browserName){ WebDriver driver = null; if (browserName.equalsIgnoreCase("chrome")){ //steup chrome browser WebDriverManager.chromedriver().setup(); //Add options for --headed or --headless browserlaunch ChromeOptions chromeOptions = new ChromeOptions(); chromeOptions.addArguments("-headed"); //initialize driver for chrome driver = new ChromeDriver(chromeOptions); //maximize window driver.manage().window().maximize(); //add implicit timeout driver.manage() .timeouts() .implicitlyWait(Duration.ofSeconds(30)); } return driver; } 
Enter fullscreen mode Exit fullscreen mode

After that, it’s time to create our BaseTest class where we will create a static WebDriver as a ThreadLocal and then set the driver to its object which will be accessible throughout the test with the help of our BrowserManager class.

public class BaseTest { protected static ThreadLocal<WebDriver> threadLocalDriver = new ThreadLocal<>(); @BeforeTest public void Setup(){ WebDriver driver = BrowserManager.doBrowserSetup("chrome"); //set driver threadLocalDriver.set(driver); System.out.println("Before Test Thread ID: "+Thread.currentThread().getId()); //get URL getDriver().get("https://www.linkedin.com/"); } //get thread-safe driver public static WebDriver getDriver(){ return threadLocalDriver.get(); } @AfterTest public void tearDown(){ getDriver().quit(); System.out.println("After Test Thread ID: "+Thread.currentThread().getId()); threadLocalDriver.remove(); } } 
Enter fullscreen mode Exit fullscreen mode

Inside @BeforeTest annotation we have called the doBrowserSetup() static method from BrowserManager class which returned a WebDriver reference with ChromeDriver initialized in it. Then we have set the returned WebDriver reference in our threadLocalDriver object. Now that our threadLocalDriver is set making the WebDriver reference thread-safe, we can use it across the test framework.

Inside @AfterTest annotation we are just quitting the browser and then removing the current thread’s value (in this case, returned WebDriver reference from the doBrowserSetup() static method) from the threadLocalDriver object.

Let’s complete the test by adding Test classes to it. We will run a login test on a demo site.

package Pages; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.FindBy; import org.openqa.selenium.support.PageFactory; public class Login { WebDriver driver; @FindBy(className = "login") WebElement linkLogin; @FindBy(id = "email") WebElement txtEmail; @FindBy(id = "passwd") WebElement txtPassword; @FindBy(id = "SubmitLogin") WebElement btnSignIn; @FindBy(xpath = "//span[contains(text(),'viva test')]") WebElement lblUserName; @FindBy(xpath = "//li[contains(text(),'Invalid email address.')]") WebElement lblInvalidEmail; @FindBy(xpath = "//li[contains(text(),'Authentication failed.')]") WebElement lblInvalidPassword; public Login(WebDriver driver){ this.driver = driver; PageFactory.initElements(driver, this); } //valid email and valid password public String doLogin(String email, String password){ linkLogin.click(); txtEmail.sendKeys(email); txtPassword.sendKeys(password); btnSignIn.click(); return lblUserName.getText(); } //Invalid email public String loginWithInvalidPassword(String email, String password){ linkLogin.click(); txtEmail.sendKeys(email); txtPassword.sendKeys(password); btnSignIn.click(); return lblInvalidPassword.getText(); } } 
Enter fullscreen mode Exit fullscreen mode

Now create a test class based on the above class i.e. LoginTestRunner1.

package TestRunners; import Base.BaseTest; import Pages.Login; import Utils.BrowserManager; import Utils.JsonReader; import org.json.simple.parser.ParseException; import org.testng.Assert; import org.testng.annotations.Test; import java.io.IOException; public class LoginTestRunner1 extends BaseTest { @Test public void loginTest() throws IOException, ParseException { getDriver().get("http://automationpractice.com/"); Login login = new Login(getDriver()); //valid email and valid password String user = login.doLogin("test_viva@test.com", "123456"); Assert.assertEquals(user, "viva test"); } } 
Enter fullscreen mode Exit fullscreen mode

Create LoginTestRunner2 class:

package TestRunners; import Base.BaseTest; import Pages.Login; import Utils.BrowserManager; import Utils.JsonReader; import org.json.simple.parser.ParseException; import org.testng.Assert; import org.testng.annotations.Test; import java.io.IOException; public class LoginTestRunner2 extends BaseTest { @Test public void loginWithInvalidEmailTest() throws IOException, ParseException { getDriver().get("http://automationpractice.com/"); Login login = new Login(getDriver()); //invalid email and valid pass String lblInvalidEmail = login.loginWithInvalidEmail("viva@te.com", "123456"); Assert.assertEquals(lblInvalidEmail, "Authentication failed."); } } 
Enter fullscreen mode Exit fullscreen mode

Finally, let’s write parallel = “tests” & thread-count =”2" in our testng.xml file.

<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd"> <suite verbose="1" name="E-commerce portal automation" parallel="tests" thread-count="2" > <test name="invalid email login" > <classes> <class name="TestRunners.LoginTestRunner2"/> </classes> </test> <test name="valid email login"> <classes> <class name="TestRunners.LoginTestRunner"/> </classes> </test> </suite> 
Enter fullscreen mode Exit fullscreen mode

Now run the test. See the output below:

Before Test Thread ID: 15 Before Test Thread ID: 14 After Test Thread ID: 15 After Test Thread ID: 14 BUILD SUCCESSFUL in 48s 4 actionable tasks: 4 executed 
Enter fullscreen mode Exit fullscreen mode

That’s it. WebDriver instance has now become thread-safe, and as the output shows it is being used by the multiple threads concurrently thanks to ThreadLocal class.

Top comments (0)