How to Use ThreadLocal in Java With Selenium WebDriver
Vipul Gupta
Posted On: May 7, 2024
85005 Views
19 Min Read
ThreadLocal in Java is a powerful tool for managing thread-specific data, and its application in Selenium testing can significantly enhance test stability and efficiency. Encapsulating thread-specific variables within ThreadLocal objects helps manage the WebDriver instances efficiently to avoid concurrency issues in multi-threaded environments.
It helps achieve this by providing an independent WebDriver instance per thread. This ensures that each thread has its own isolated WebDriver instance and variables copy, preventing thread interference or data corruption and improving test stability.
This blog will explore the fundamentals of ThreadLocal in Java and demonstrate practical examples of its implementation in Selenium test automation, highlighting its role in ensuring thread safety and improving test reliability.
So, let’s get started!
TABLE OF CONTENTS
- What is ThreadLocal?
- Advantages of Using ThreadLocal
- ThreadLocal Class Methods
- Challenges in Parallel Execution without ThreadLocal
- Demonstration: Using ThreadLocal in Java Using Selenium WebDriver
- Demonstration: Using ThreadLocal in Java with RemoteWebDriver
- Key Considerations When Using ThreadLocal
- Frequently Asked Questions (FAQs)
What is ThreadLocal?
In Java, ThreadLocal is a class that facilitates the usage of thread-local variables. This means it allows the user to store data specific to a particular thread independently. Each thread that accesses a ThreadLocal variable accesses its unique copy of the variable, thus ensuring that changes made by any thread are specific to variables of that thread and do not affect the value of the same variable in another thread.
This concept is instrumental when working with multithreading use cases like parallel testing in Selenium. This makes our system thread-safe, protecting each thread from unintended modifications.
Thread-safe is a term used to describe a piece of code, data structure, or system that can be safely used and modified by multiple threads concurrently without causing synchronization issues.
Using ThreadLocal in Java helps isolate the data and other information specific to individual tests at the test case level, ensuring each test has its local copy. This means we have a separate thread for each WebDriver instance. It prevents cross-thread interference, thus enabling us to execute test cases in parallel without worrying about maintaining test data and flow.
Advantages of Using ThreadLocal
Using ThreadLocal in Selenium Java for parallel execution provides several advantages. A few of them that make it the preferred choice for managing concurrent tests without any hiccups are:
- Thread Isolation
- Per-Thread Storage
- Thread Safety
- Concurrent Performance
Using ThreadLocal helps to isolate data or state information specific to an individual thread. This helps to ensure thread safety in parallel test execution where multiple threads are executing concurrently, and we need to ensure that each test case thread has its local copy of data and results.
ThreadLocal internally associates each variable to a thread whenever a thread is created. It provides each thread with a copy of the variables. This copy is unique to a thread, and any value modifications to variables of one thread have no impact on the value of the same variable in another thread.
There are several ways to implement synchronization mechanisms in automation testing using sleep() and implicit/explicit waits. ThreadLocal provides a way to avoid the need for these by giving each thread a copy of the data.
Tests can be executed parallelly using ThreadLocal without complex synchronization, allowing for better resource utilization and faster test execution. Doing so without ThreadLocal results in environment setup complexities, increased test dependency issues, and reduced ability to debug and troubleshoot parallel tests.
ThreadLocal Class Methods
ThreadLocal instances are typically private static fields in classes and hold an implicit reference to its copy of a thread-local variable as long as the thread is accessible. These values can be accessed or modified via a combination of methods. After a thread has exited its execution, all its copies of thread-local instances are subject to garbage collection and must be removed to prevent memory leaks. If not taken care of properly, these memory leaks lead to load on the system, resulting in slower execution and inconsistent results.
ThreadLocal in Java provides below methods to achieve the same:
Methods | Usage |
---|---|
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. This is essential to prevent memory leaks. |
Challenges in Parallel Execution without ThreadLocal
Before we understand ThreadLocal implementation using Selenium with Java, it is imperative to understand the problem with a practical example.
So, let’s start with the project setup.
Project Setup
For demonstration purposes in this blog, we have taken Eclipse IDE. You can use any other of your choice, and the setup will remain the same.
The same project setup will be used to demonstrate the usage of ThreadLocal in Java with Selenium WebDriver in the following section of this blog. We will only add a new package and Java test class files to showcase the changes in the same code. Therefore, carefully follow the setup on your machine to prevent any errors during execution.
Step 1. Create a new Maven Project and name it as ThreadLocal.
Step 2. Under src/test/java package, add a new package, WithoutThreadLocal. This is the package in which we add our test classes to demonstrate the problems we might encounter when executing in parallel without keeping thread safety in mind.
Step 3. Inside this new package, add three Java class files and name them as:
- BaseTest.java: This will have the code to launch and close the browser.
- TestLambdaTestPlayground.java: First test case file.
- TestLambdaTestEcommerce.java: Second test case file.
Once you perform all these steps, the project structure so far should look something like below:
As our project uses Selenium and TestNG along with WebDriverManager to launch the browser, add the latest stable versions of these dependencies inside the pom.xml for best execution results.
The updated pom.xml should look like this.
With this, our basic project setup is completed. The following steps and configurations will be specific to the static WebDriver use case, which we use to understand the problem in parallel execution before proceeding to the solution.
In this example, we see how using a static WebDriver without ThreadLocal, in case of parallel execution, could lead to erroneous and uncertain results, impacting our automation framework’s overall performance.
Test Scenario
|
Add the first Java class file as BaseTest.java.
Code Walkthrough: BaseTest.java
Step 1. Create an object of WebDriver. This will be used to perform browser actions.
Step 2. Add the first method as setDriver() and annotate it with the @BeforeClass annotation in TestNG to invoke it before each test class.
![ChromeDriver](https://www.lambdatest.com/blog/wp-content/uploads/2024/05/ChromeDriver.png)
In this method, we create the instance of a new ChromeDriver to launch the Chrome browser and execute the test case. This is assigned to the WebDriver object created in the previous step.
![WebDriver](https://www.lambdatest.com/blog/wp-content/uploads/2024/05/WebDriver.png)
Add a print statement to log the thread ID and browser reference information. This information will help check whether both threads had their independent thread variable in case of parallel execution.
Step 3. Add another method, such as closeBrowser(), and annotate it with @AfterClass annotation. This method will log the information like the previous one and close the driver instance after completing the test case.
Now, we add the test class files one by one.
Code Walkthrough: TestLambdaTestPlayground.java
Step 1. Add the first Java test class file and extend BaseTest.
Step 2. Add the test method as testLambdaTestPlayground() and annotate it with @Test annotation.
Step 3. Add another print statement to log the thread, browser information and test name. This will help differentiate between test logs after all are executed in parallel runs.
Step 4. Navigate to the LambdaTest Selenium Playground website.
Step 5. Add another log to print the title with the thread ID.
Code Walkthrough: TestLambdaTestEcommerce.java
This second test class, TestLambdaTestEcommerce, is very similar to the previous one, with only the difference in the website we navigate is a different title. This is used to differentiate the results when we execute with and without ThreadLocal in Java implementation.
Finally, update the default testng.xml. Since the execution will be parallel, update your testng.xml to specify the parallel execution mode and mention the test class names. One important thing to note here is that we renamed it to withoutThreadLocalTestng.xml to differentiate it from the one we added in the next steps to execute cases with ThreadLocal in Java implementation.
Execution Results
Now that we are ready with the code to see how it behaves in parallel execution with ThreadLocal, let’s execute the same.
To run the test cases in parallel, execute your withoutThreadLocalTestng.xml. Right-click on it, then go to Run As > TestNG suite.
Your console results would look like this:
As you can see from the output screenshot above, we did get two different threads with IDs 17 and 18, but since we didn’t use ThreadLocal variables, our thread safety was compromised.
Both the threads refer to the same driver reference, which is evident from the same reference ID 7a288b4db365a77d3222d009ad42ebf3, and as a result, the title printed by both threads is also the same. This is the incorrect result, as the title should have been different for the websites we used in both cases. This is the problem we face when working with parallel execution and not keeping thread safety in mind to provide independent variables to each thread.
Demonstration: Using ThreadLocal in Java With Selenium WebDriver
Having understood the thread-safety challenges parallel execution could pose without ThreadLocal, let us now update the same automation code to use ThreadLocal in Java with Selenium WebDriver. This would help us to see how thread safety can be implemented to prevent cross-communication between independent threads. These changes showcase how ThreadLocal in Java helps run test cases in a thread-safe manner, making our suite more robust and less prone to failures.
We start by updating BaseTest.java and then make corresponding changes to the test files one by one. Let us see how these ThreadLocal changes are to be implemented.
Code Walkthrough: BaseTest.java
Step 1. In this file, we start by replacing the static WebDriver object, with a ThreadLocal variable having WebDriver data type and initialize same.
Current code:
![WebDriver object](https://www.lambdatest.com/blog/wp-content/uploads/2024/05/WebDriver-object.png)
Updated Code with ThreadLocal:
This driver variable will help to ensure thread safety by providing an independent thread for each test execution. This means it would give two thread IDs, each containing a copy of the instantiated browser instance for all parallel executing test cases, unlike the previous implementation, which is prone to errors.
Step 2. Inside the setDriver() function, we use the set() method of ThreadLocal to assign the ChromeDriver reference to this driver thread local variable.
Current code:
![setDriver](https://www.lambdatest.com/blog/wp-content/uploads/2024/05/setDriver.png)
Updated Code with ThreadLocal:
In the logging step, we update the driver variable to call the getDriver() method to get the browser reference information.
Step 3. Add a new function to this class as getDriver(). This is used to return the driver variable reference using the get() method of ThreadLocal.
Step 4. Similarly, update the logging step on the closeBrowser() method to use getDriver() to get the driver reference information.
Next, add a step to close the web driver and then remove the ThreadLocal driver variable to prevent memory leaks.
Current code:
![Code with Thread](https://www.lambdatest.com/blog/wp-content/uploads/2024/05/Code-with-Thread.png)
Updated Code with ThreadLocal:
Using the remove() function helps to prevent memory leaks by clearing the assigned memory to the ThreadLocal variable. Thus, it prevents degradation in system performance by maintaining available memory for execution.
Code Walkthrough: TestLambdaTestPlayground.java
The code for both the test classes remains the same as earlier. The only change is how we refer to the driver reference object to get its information. While working with ThreadLocal, it is fetched using the getDriver() function of BaseTest.
Code Walkthrough: TestLambdaTestEcommerce.java
Similarly, in this test case, we updated the code to get the driver’s reference.
Finally, add another TestNG XML as withThreadLocalTestng.xml to execute the cases with ThreadLocal implementation.
Execution Results
Let us execute the cases using this XML and analyze the results to see the difference. This execution will help us understand how using ThreadLocal in Java with Selenium WebDriver helps to ensure thread safety and provides concrete results without thread interference.
Executing the same would give you results like the one below.
From the screenshot above, you can now notice that both threads have different browser references for executing the test case. Also, since both the cases on parallel execution have their independent thread-local browser variable, there is no interference, and both execute the test cases successfully and print the correct page title.
Yellow color represents Thread 18 : c2ec0c28c1a5cf9ae23acf6c42ff3604 : Your Store
Blue color represents Thread 17 : 1dafb6143e36c8ea690da15fe907722d : Selenium Grid Online | Run Selenium Test On Cloud
This shows how using ThreadLocal in Java with Selenium WebDriver helps to create more robust automation testing frameworks with parallel execution to give quicker and better results.
Demonstration: Using ThreadLocal in Java with RemoteWebDriver
Another advantage of using ThreadLocal in Java with Selenium is its easy integration with RemoteWebDriver. Using RemoteWebDriver allows you to utilize the powers of the cloud grid, including speedy and parallel test execution in a more organized way.
Using RemoteWebDriver with ThreadLocal to run concurrently ensures enhanced thread safety, performance, and simplified test management.
In this blog, the LambdaTest cloud Selenium Grid is used to achieve this. LambdaTest is an AI-powered test orchestration and execution platform that enables users to perform automation testing for web and mobile applications on a wide range of over 3000+ real browsers, devices, and operating system combinations.
Subscribe to the LambdaTest YouTube Channel and stay updated with the latest tutorials around Selenium, automated testing, and more.
Let us look at how easily the demonstration examples we discussed can be modified to use LambdaTest RemoteWebDriver. To achieve this, we only need to modify the BaseTest.java to use RemoteWebDriver with ThreadLocal. The updated file would look like this:
Code Walkthrough: WithThreadLocal.BaseTest.java
As you can notice, we modified the setDriver() function only to use the RemoteWebDriver instead of the local grid. The other two functions remain the same. Let us look at these changes respective to using a cloud grid.
Step 1. Create an object of ThreadLocal class with RemoteWebDriverType.
Step 2. Create your account on the LambdaTest platform and fetch your username and access key for the LambdaTest Account from the Password & Security section to connect to the cloud grid for test execution.
Alternatively, you can configure these as environment variables and directly fetch them in code.
For Windows:
1 2 |
set LT_USERNAME=LT_USERNAME set LT_ACCESS_KEY=LT_ACCESS_KEY |
For macOS and Linux:
1 2 |
export LT_USERNAME=LT_USERNAME export LT_ACCESS_KEY=LT_ACCESS_KEY |
Step 3. Create a variable as status and initialize to value failed. This will set the test case status at the end of the execution on the LambdaTest dashboard.
Step 4. Create an object of the ChromeOptions class as we will be using Chrome browser for executing the test cases. This object can be used to set the OS and browser versions.
You can also choose a different browser, platform, and version from the list of supported OS and browsers from LambdaTest and modify the code.
Step 5. To specify the additional browser capabilities required by the LambdaTest platform to support test execution on their Selenium cloud Grid, use a HashMap type variable. This will help identify the dashboard test results using the build name and other details.
We can fetch the required browser capabilities for the LambdaTest platform by navigating to the Automation Capabilities Generator. This helps by offering ready-to-use code for setting up browser capabilities that can be used in execution.
Step 6. Use the Selenium RemoteWebDriver to connect to the LambdaTest remote grid using your credentials and the ChromeOptions object containing all specified browser capabilities. Finally, pass this value to the set() method of the ThreadLocal to provide the current thread its unique value of this thread-local variable.
These changes showcase how easy it is to use ThreadLocal in Java with RemoteWebDriver to support execution on the cloud grid.
Let us look at one scenario of test executions in parallel with RemoteWebDriver and ThreadLocal and how the results look on the LambdaTest dashboard.
For this, add two more test classes with code like the one below:
Code Walkthrough: TestSimpleFormSeleniumPlayground.java
Step 1. Navigate to LambdaTest Selenium Playground. We keep logging the action executed on each step along with the thread ID for better logging when multiple cases are executed in parallel.
Step 2. Click on Simple Form Demo.
Step 3. Enter data as Lambdatest and click the Get Checked Value button.
Step 4. Finally, fetch the response value and print the same on the console along with the thread ID, just like in other steps. Then, update the status to passed as there are no errors, and we can showcase the same on the dashboard.
Code Walkthrough: TestOrderFlowEcommercePlayground.java
Step 1. Navigate to the LambdaTest Ecommerce Playground. Start logging actions on each step along with the thread ID, like in the previous test case.
Step 2. Enter the search keyword as iPhone in the search text box and click the Search button.
Step 3. From the loaded product list, fetch the price of the first one and print it on the console along with the thread ID. Finally, update the status as passed on the LambdaTest dashboard.
To execute the test cases in parallel, use the same testng.xml, and update the test class names, and run as TestNG.
Output:
You can see from the console logs that both the test cases are executed in parallel with different threads. From the thread ID, it is clear which step is executed by which thread, and the steps for one test are executed by one thread only. This shows the successful implementation of ThreadLocal in Java with RemoteWebDriver.
We can see the results of this execution on the LambdaTest Dashboard and under the Automation tab.
![Info](https://www.lambdatest.com/blog/wp-content/uploads/2023/04/blog_info.png)
Run your Selenium Java tests on over 3000+ desktop browsers. Try LambdaTest Today!
Key Considerations When Using ThreadLocal
We have now seen and understood that ThreadLocal in Java is powerful for ensuring thread safety and concurrent execution. But, despite its immense usage, it does have certain limitations that need to be kept in mind and handled properly in test automation for the best results.
A robust exception-handling strategy is required while working with ThreadLocal in Java to prevent unforeseen challenges in parallel test executions.
It is also crucial to understand the application’s synchronization and resource sharing as ThreadLocal essentially helps to simplify thread management and does not solve all the thread safety problems. Understanding resourcing for the application under test allows you to design automation test cases better and prevent any added complexity to the framework.
For those delving into test automation with Selenium Java, mastering concepts like ThreadLocal is important. Cloud-based testing platforms like LambdaTest offer a comprehensive Selenium Java 101 certification, providing in-depth knowledge and hands-on experience using Selenium WebDriver with Java for web automation testing.
Enroll in the LambdaTest Selenium Java 101 certification today to validate your skills and expertise in Selenium Java testing.
Conclusion
With this, we have reached the end of this blog on using ThreadLocal in Java with Selenium WebDriver. We have learned about ThreadLocal in Java and how it helps implement thread safety in the case of complex automation frameworks where parallel execution becomes a critical requirement. We also saw the advantages and methods ThreadLocal provides to implement it easily, but at the same time, there are things to be careful of. Finally, we saw how using RemoteWebDriver with ThreadLocal offers added advantages to a cloud grid. So, this is time for you to get started and make your automation frameworks thread-safe using ThreadLocal with Selenium Java to make your runs in parallel, quicker, and more stable without any worries.
Frequently Asked Questions (FAQs)
What is ThreadLocal in Java?
In Java, ThreadLocal is a class that provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get() or set(Object) methods) has its own independently initialized copy of the variable.
What is the advantage of ThreadLocal?
The primary advantage of ThreadLocal in Java lies in its ability to provide thread isolation, ensuring that each thread accessing a ThreadLocal variable gets its independent copy. This eliminates the need for synchronization mechanisms like locks, improving performance and reducing the risk of concurrency bugs.
ThreadLocal is particularly useful when data needs to be scoped to a specific thread, such as in web applications handling user sessions or in multi-threaded environments managing database connections. By simplifying thread-local data management and reducing contention for shared resources, ThreadLocal facilitates cleaner, more efficient, and safer multi-threaded code.
Got Questions? Drop them on LambdaTest Community. Visit now