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();
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;
}
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();
}
}
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();
}
}
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");
}
}
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.");
}
}
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>
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
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)