In previous post, we see some common mistakes which we do while running selenium automation scripts in parallel. As we know now that object variables are shared by threads created by same object, we need a way to keep object variables unique to each threads so that we don’t get any data inconsistency when reading or writing same variables.
To achieve the same we can use ThreadLocal class of Java. Let me give you a basic idea about it and you will understand how class name itself gives you a hint about it.
If you want that a variable should be accessed by same thread by which it is created, we can use ThreadLocal variables. This class makes variables thread safe. Each thread of same object will have separate copy of object variables.
Official Javadoc defines ThreadLocal class as :-
This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get
or set
method) has its own, independently initialized copy of the variable. ThreadLocal
instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID). Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal
instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).
ThreadLocal class provides below methods:-
- initialValue() :- Returns the current thread’s “initial value” for this thread-local variable
- set(T value) :- Sets the current thread’s copy of this thread-local variable to the specified value
- get() :- Returns the value in the current thread’s copy of this thread-local variable. If the variable has no value for the current thread, it is first initialized to the value returned by an invocation of the
initialValue()
method. - remove() :- Removes the current thread’s value for this thread-local variable.
Just to summarize, A ThreadLocal variable could be initialized with an initial value by overriding initialValue() method. Use set() method to set the value for variable , get() to get it and remove() to remove it. There are plenty of examples given on ThreadLocal on internet. Refer JournalDev and Jenkov. These two posts are great.
Let’s rewrite the same RegisterUser class from previous post with a ThreadLocal variable.
Since class variable “resgiteredUserName” will be shared among threads so lets have it as a ThreadLocal variable.
package ThreadLocalUsageInSelenium; import java.util.Random; /* * This class has a method to register user. Implementing this class to run as a Thread. */ public class ThreadLocalRegisterUser implements Runnable{ ThreadLocalthreadLocal = new ThreadLocal (){ @Override protected String initialValue() { return "No Value"; } }; String registeredUserName; @Override public void run() { createUser(); try { getUserWithDelay(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } private void createUser() { System.out.println(Thread.currentThread().getName()+" Before starting registration, value of registeredUserName :"+threadLocal.get()); System.out.println(Thread.currentThread().getName()+" Registering a user."); registeredUserName = Thread.currentThread().getName()+" User"+ new Random().nextInt(999); threadLocal.set(registeredUserName); System.out.println(Thread.currentThread().getName()+" After registration, value of registeredUserName :"+threadLocal.get()); } private void getUserWithDelay() throws InterruptedException { Thread.sleep(5000); System.out.println(Thread.currentThread().getName()+" After some delay , value of registeredUserName :"+threadLocal.get()); } }
Test Class :-
package ThreadLocalUsageInSelenium; public class ResgisterUserTestThreadLocal { public static void main(String[] args) throws InterruptedException { ThreadLocalRegisterUser registerUser = new ThreadLocalRegisterUser(); // Creating two threads and both are using same instance of ThreadLocalRegisterUser class Thread thread1 = new Thread(registerUser); Thread thread2 = new Thread(registerUser); // Starting first thread thread1.start(); // Giving a pause Thread.sleep(2000); // Starting another thread thread2.start(); } }
Run above class and observe output.
Thread-0 Before starting registration, value of registeredUserName :No Value Thread-0 Registering a user. Thread-0 After registration, value of registeredUserName :Thread-0 User591 Thread-1 Before starting registration, value of registeredUserName :No Value Thread-1 Registering a user. Thread-1 After registration, value of registeredUserName :Thread-1 User546 Thread-0 After some delay , value of registeredUserName :Thread-0 User591 Thread-1 After some delay , value of registeredUserName :Thread-1 User546
Observe that value of registeredUserName is not reused by another thread as both threads have their own copy of object variable named “registeredUserName”.
Let’s rewrite Selenium example from previous post using ThreadLocal class.
When we run @Test annotated methods in parallel, each test methods will be run by a different thread. Both threads will be sharing WebDriver class variable. So let’s declare it as ThreadLocal.
WebDriverFactory class:-
package ThreadLocalUsageInSelenium; import org.openqa.selenium.WebDriver; import org.openqa.selenium.chrome.ChromeDriver; import io.github.bonigarcia.wdm.WebDriverManager; public class WebDriverFactory { private WebDriver driver; public void setDriver() { WebDriverManager.chromedriver().setup(); driver = new ChromeDriver(); } public WebDriver getDriver() { return driver; } }
Test Class:-
package ThreadLocalUsageInSelenium; import org.openqa.selenium.WebDriver; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; public class WebDriverTest2 { // I am not using static ThreadLocal variable here bcz I have kept it in test class where it is being used. // If we keep it in another class, you can use it as static with static setter & getter methods. private ThreadLocalwebdriver = new ThreadLocal (); @BeforeMethod public void setUpBrowser() { WebDriverFactory webDriverFactory = new WebDriverFactory(); webDriverFactory.setDriver(); webdriver.set(webDriverFactory.getDriver()); } @Test public void test1() { webdriver.get().get("https://www.google.com/"); System.out.println("Title printed by "+Thread.currentThread().getName()+webdriver.get().getTitle()); webdriver.get().close(); } @Test public void test2() { webdriver.get().get("https://www.facebook.com/"); System.out.println("Title printed by "+Thread.currentThread().getName()+webdriver.get().getTitle()); webdriver.get().close(); } }
Run above tests parallel using testng xml as below:-
Output:-
Starting ChromeDriver 80.0.3987.106 (f68069574609230cf9b635cd784cfb1bf81bb53a-refs/branch-heads/3987@{#882}) on port 8466 Only local connections are allowed. Please protect ports used by ChromeDriver and related test frameworks to prevent access by malicious code. Starting ChromeDriver 80.0.3987.106 (f68069574609230cf9b635cd784cfb1bf81bb53a-refs/branch-heads/3987@{#882}) on port 11901 Only local connections are allowed. Please protect ports used by ChromeDriver and related test frameworks to prevent access by malicious code. Title printed by TestNG-test=Test-1Google Title printed by TestNG-test=Test-2Facebook β log in or sign up Suite Total tests run: 2, Passes: 2, Failures: 0, Skips: 0
Both threads have control on browser instance initiated by them now and there will be no session overriding by threads.
Note:- Since I am running test methods in parallel so I need to apply Thread Safe at TestNG class level. If you are running classes or tests in parallel, you need to identify shared resource and make them thread safe. We will see this example in next post.
You can download code form this repo.
If you have any doubt, feel free to comment below.
If you like my posts, please like, comment, share and subscribe.
#ThanksForReading
#HappyLearning
You can find all Selenium related post here.
You can find all API manual and automation related posts here.
You can find frequently asked Java Programs here.
Thanks Amod for this blog really appreciate !! π
Hello Amod,
Thanks for such detailed content . i was able to achieve parallel execution due to our post . Great work .All the Best π Hearty Thanks
Hello Amod, Thanks for this article.
I am trying to implement parallel execution in my Junit 5 Automation framework (@Execution(ExecutionMode.concurrent)) but though it opens 2 browsers for my test cases but it executes only 1 test case in browser 1 and once it is complete then only it goes to the other browser for testcase 2. What can be the issue here ?
Test Cases flow :
1. BrowserDriver .java class has the ThreadLocal webdriver variable and browser launch methods and also the getDriver method.
2. Test cases are in a separate class which extends the BaseTestCase.java class.
3. @BeforeEach / @AfterEach, @BeforeAll/@AfterAll – are all present in BaseTestCase .java
4. Browser initiation , Data Load all happens in @BeforeEach Method.
Can you pls help me why am i seeing this issue ?
Hi,
Only using ThreadLocal doesnβt solve all issues in parallel execution. If you are running all test methods in parallel, make sure all initialisation happen for each method call.
Hello Amod,
Thanks for a wonderful article. After referring this article, I tried to implement the same in my sample project. I was unable to run it successfully. The actions are getting clubbed on single browser and thus unstable. Is this approach feasible in Page Object Model ?
Thanks,
Deven J.
Can you please share your project using git!