Thread Local in Playwright




What is Thread Local?

  • ThreadLocal is a Java utility class that provides thread-local variables.
  • Each thread accessing a ThreadLocal variable has its own, independent copy of the variable.
  • Changes made to a ThreadLocal variable in one thread do not affect the value of the same variable in another thread.
  • This is particularly useful when working in multi-threaded environments, such as parallel test execution, where shared resources may lead to race conditions or inconsistent results.

Why Thread Local in Playwright Java?
  • Playwright allows parallel execution of tests across multiple threads (browsers, contexts, or pages).
  • If all threads share the same Playwright objects (like BrowserContext or Page), one test could unintentionally interfere with another.
  • To avoid this, we use ThreadLocal storage so that: Each thread maintains its own Browser, Context, and Page. No data leakage or object collision happens across test executions.

How to Handle Thread Local in Playwright Java

Define ThreadLocal Variables
  • For objects like Playwright, Browser, BrowserContext, and Page, you create ThreadLocal instances.
  • This ensures each thread holds its own version of these objects.

Initialize Per Thread
  • When a test thread starts, it initializes its own Playwright, Browser, Context, and Page inside its ThreadLocal storage.
  • Other threads will do the same, but with their own isolated objects.

Accessing Thread-Safe Objects
  • During test execution, instead of directly accessing shared variables, you get the instance from the ThreadLocal.
  • This guarantees that the actions being performed belong only to that thread’s browser/page.

Cleaning Up
  • Once the test finishes, you remove the objects from ThreadLocal storage.
  • This prevents memory leaks and ensures resources are freed.


In Playwright Java, ThreadLocal is often used in test automation frameworks (like TestNG or JUnit) to ensure thread safety when running tests in parallel. ThreadLocal allows each thread to maintain its own isolated instance of a variable — like the Playwright BrowserPage, or BrowserContext.














Use Case:

When you run tests in parallel, sharing a single instance of Browser or Page across threads can cause data corruption or flaky tests. ThreadLocal ensures each test thread uses its own BrowserContext, and Page.


Example: Thread-safe Playwright with ThreadLocal in Java

Website to be automated for showing ThreadLocal concept:
https://www.google.com









PlaywrightFactory.java


Below is the PlaywrightFactory class which consists of Threadlocal class objects of type Playwright, Browser, BrowserContext, and Page classes. It also consists of getter and setter methods of these objects. In the last objects are closed by close() and removed by remove()

import com.microsoft.playwright.*;

public class PlaywrightFactory {
    private static ThreadLocal<Playwright> tlPlaywright = new ThreadLocal<>();
    private static ThreadLocal<Browser> tlBrowser = new ThreadLocal<>();
    private static ThreadLocal<BrowserContext> tlContext = new ThreadLocal<>();
    private static ThreadLocal<Page> tlPage = new ThreadLocal<>();

    public static void initPlaywright() {
        tlPlaywright.set(Playwright.create());
    }

    public static void initBrowser() {
        BrowserType.LaunchOptions options = new BrowserType.LaunchOptions().setHeadless(false);
        tlBrowser.set(getPlaywright().chromium().launch(options));
    }

    public static void initContext() {
        tlContext.set(getBrowser().newContext());
    }

    public static void initPage() {
        tlPage.set(getContext().newPage());
    }

    public static Playwright getPlaywright() {
        return tlPlaywright.get();
    }

    public static Browser getBrowser() {
        return tlBrowser.get();
    }

    public static BrowserContext getContext() {
        return tlContext.get();
    }

    public static Page getPage() {
        return tlPage.get();
    }

    public static void closeAll() {
        getPage().close();
        getContext().close();
        getBrowser().close();
        getPlaywright().close();

        // Remove from ThreadLocal
        tlPage.remove();
        tlContext.remove();
        tlBrowser.remove();
        tlPlaywright.remove();
    }
}



SampleTest.java

In below code, There is a TestNG class which has @BeoreMethod, @Test, @AfterMethod annotations.

@BeforeMethod initialises Playwright, Browser, BrowserContext and Page classes by methods mentioned in PlaywrightFactory class.

@Test will create Page class object and call webpage google.com and print the title

@AfterMethod will close all objects by closell() mentioned in PlaywrightFactory class


import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import com.microsoft.playwright.*;

public class SampleTest {

    @BeforeMethod
    public void setUp() {
        PlaywrightFactory.initPlaywright();
        PlaywrightFactory.initBrowser();
        PlaywrightFactory.initContext();
        PlaywrightFactory.initPage();
    }

    @Test
    public void testGoogleSearch() {
        Page page = PlaywrightFactory.getPage();
        page.navigate("https://www.google.com");
        System.out.println("Page Title: " + page.title());
    }

    @AfterMethod
    public void tearDown() {
        PlaywrightFactory.closeAll();
    }
}

Parallel Execution

To run parallel tests safely:

  • Use TestNG with <parallel="methods"> or <parallel="classes">

  • Each test thread will get its own isolated Playwright Page due to ThreadLocal


Suggested Posts:

1. Playwright with TestNG Integration
2. Automate Login Page in Playwright
3. Comma Selectors in Playwright
4. Trace Viewer in Playwright
5. Handle GET API in Playwright