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

Trace Viewer in Playwright

In Playwright, Trace Viewer is a powerful debugging and analysis tool that allows you to visually inspect the execution of your test cases. It records what happened during the test run and provides a timeline-like interface where you can see each step, the DOM state at that moment, console logs, network activities, and even screenshots. Think of it as a "flight recorder" for your Playwright tests.


What is Trace Viewer?

  • The Trace Viewer is a UI-based tool provided by Playwright to replay and analyze the execution of automated tests.
  • It captures the entire test lifecycle: from test start to finish.

The trace includes:

  • Actions performed (click, type, navigate, etc.)
  • DOM snapshots (the exact state of the page when the action was executed)
  • Screenshots (taken before and after each action)
  • Network requests & responses (API calls, resources loaded, etc.)
  • Console logs and errors
  • Test steps and timings (how long each action took)

Importance of Trace Viewer

The Trace Viewer is extremely important in test automation for the following reasons:

1. Debugging Failures Efficiently
  • When a test fails, simply looking at the error message might not explain why it failed.
  • With the Trace Viewer, you can replay the test step-by-step and see exactly what happened in the browser at the point of failure.
  • For example, if a button click failed, you can check: Was the button visible?, Was it overlapped by another element?, Did the page load fully before the click?

2. Reproducibility of Issues
  • Often, tests fail on CI/CD pipelines but not locally.
  • With traces, you can download the trace file from the pipeline and open it in your local Trace Viewer.
  • This makes it possible to reproduce and analyze issues that occur in different environments without rerunning the test multiple times.

3. Visual Timeline of Test Execution
  • The Trace Viewer provides a timeline view of the test execution.
  • You can see: Which actions were taken, How long each action took, Whether any unnecessary delays or retries happened.
  • This helps in optimizing tests for performance and speed.

4. Step-by-Step DOM State & Snapshots
  • For each test step, Playwright records: A snapshot of the DOM, A before/after screenshot.
  • This allows you to time-travel through the test and understand how the UI looked at each step.

5. Network Analysis
  • The Trace Viewer shows all network requests made during the test.
  • You can analyze: API calls and their responses, Timing of requests (how fast or slow). Any failed or blocked requests.
  • This helps in debugging issues related to backend services or data fetching.

6. Error Diagnosis
  • Besides console errors, the Trace Viewer captures JavaScript errors, timeouts, and Playwright-specific errors.
  • This provides a more holistic view of what caused the failure—whether it was a UI issue, a script issue, or a network issue.

7. Improving Test Reliability
  • By studying the trace, you can detect flaky test patterns such as: Clicking before an element is visible, Not waiting for network responses, Timing issues due to animations or slow rendering.
  • With these insights, you can improve synchronization and make your tests more stable.















Steps to Use Trace Viewer in Java

  • Start tracing before test execution.
  • Stop tracing after the test and export the trace file.
  • View trace using npx playwright show-trace trace.zip in terminal.

Website used to automate: https://playwright.dev














Java Code Example


import com.microsoft.playwright.*;

public class TraceViewerExample {
    public static void main(String[] args) {
        try (Playwright playwright = Playwright.create()) {
            Browser browser = playwright.chromium().launch(new BrowserType.LaunchOptions().setHeadless(false));
            BrowserContext context = browser.newContext();

            // Start tracing
            context.tracing().start(new Tracing.StartOptions()
                .setScreenshots(true)
                .setSnapshots(true));

            Page page = context.newPage();
            page.navigate("https://playwright.dev");

            // Perform some actions
            page.locator("text=Docs").click();
            page.locator("button[class='DocSearch DocSearch-Button']").click();
            page.locator("input[placeholder='Search docs']").fill("trace viewer");
            page.keyboard().press("Enter");

            // Stop tracing and export it
            context.tracing().stop(new Tracing.StopOptions().setPath(Paths.get("trace.zip")));

            browser.close();
        }
    }
}


Code explanation:

(a) After creating BrowserContext object, start tracing by code: 
     context.tracing().start(new Tracing.StartOptions()
     .setScreenshots(true).setSnapshots(true));
(c) Create Page object and perform actions on web elements
(d) Stop tracing and export the trace.


Code responsible for Trace Viewer:







Viewing the Trace

After executing the above code, a file named trace.zip is created.

To view it:

npx playwright show-trace trace.zip

This opens an interactive UI in your browser where you can step through every action.