Driver FixRecommendedSound, Wi-Fi or graphics acting up? Check drivers firstFind missing or outdated drivers fast.Check DriversOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsClean PCRecommendedOne scan can reveal what keeps slowing WindowsLook for cleanup and repair opportunities.Run Scan×
Skip to content
Java

Capture WebDriver Screenshots When Running Parallel Tests with TestNG

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

To capture the correct browser image in a parallel TestNG suite, give every executing test its own WebDriver, store that driver by thread (for example, with ThreadLocal<WebDriver>), capture through Selenium’s TakesScreenshot API, and write each file under a collision-proof name. A TestNG listener can apply the policy automatically after failures or other selected outcomes.

What “parallel” means in TestNG

Parallel execution is not one fixed behavior. TestNG assigns work according to the parallel value in the suite XML, while thread-count limits the number of worker threads allocated to that suite.

Mode Work assigned concurrently What shares a thread
methods Test methods Nothing is assumed to share a thread; methods can execute on different workers.
tests Top-level <test> blocks Methods inside one <test> block run on one thread; separate blocks may run concurrently.
classes Test classes Methods in one class share a thread; classes can run on separate threads.
instances Object instances Methods on one instance share a thread; different instances may run concurrently.

For example, this suite permits up to four workers and runs methods independently:

<suite name="Parallel UI" parallel="methods" thread-count="4">
  <test name="Chrome tests">
    <classes>
      <class name="example.LoginTest"/>
      <class name="example.CheckoutTest"/>
    </classes>
  </test>
</suite>

Choose the mode deliberately. A driver associated with a class may be safe for classes mode but unsafe when methods from that class are split across workers. Your driver lifecycle and screenshot lookup must match the mode actually configured.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Keep one WebDriver per executing thread

Do not put one mutable static driver in a parallel test base. Two methods can navigate it to different pages at the same time, so a screenshot may show the other test’s state. Instead, create a driver for the current worker and retrieve that same driver whenever a test or listener needs it.

package example;

import java.time.Duration;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ThreadGuard;

public abstract class ParallelTestBase {
    private static final ThreadLocal<WebDriver> DRIVER = new ThreadLocal<>();

    protected void startDriver() {
        WebDriver raw = new ChromeDriver();
        // ThreadGuard detects accidental cross-thread calls; it does not replace ThreadLocal.
        DRIVER.set(ThreadGuard.protect(raw));
        getDriver().manage().timeouts().implicitlyWait(Duration.ofSeconds(5));
    }

    protected WebDriver getDriver() {
        WebDriver driver = DRIVER.get();
        if (driver == null) {
            throw new IllegalStateException("No WebDriver is registered for thread "
                    + Thread.currentThread().getName());
        }
        return driver;
    }

    protected void stopDriver() {
        WebDriver driver = DRIVER.get();
        try {
            if (driver != null) {
                driver.quit();
            }
        } finally {
            DRIVER.remove();
        }
    }
}

Selenium’s ThreadGuard documentation says, “ThreadGuard checks that a driver is called only from the same thread that created it.” It also states that ThreadGuard “does not replace the need for using ThreadLocal to manage drivers when running parallel.” It detects misuse; it does not create drivers, take screenshots, or clean up your storage.

Capture a screenshot with Selenium’s Java API

TakesScreenshot returns the output type requested by your code. For a file artifact, request OutputType.FILE, then copy it to a durable directory whose name cannot collide with another worker.

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;

public final class ScreenshotFiles {
    private ScreenshotFiles() {}

    public static Path save(WebDriver driver, Path directory, String testId)
            throws IOException {
        Files.createDirectories(directory);
        Path temporary = ((TakesScreenshot) driver)
                .getScreenshotAs(OutputType.FILE).toPath();
        String safe = testId.replaceAll("[^A-Za-z0-9._-]", "_");
        String filename = safe + "-" + Thread.currentThread().getId()
                + "-" + System.nanoTime() + ".png";
        Path target = directory.resolve(filename);
        return Files.copy(temporary, target, StandardCopyOption.REPLACE_EXISTING);
    }
}

The thread ID helps distinguish workers; the monotonic value prevents repeated invocations with the same test name from overwriting one another. For stronger traceability, include a sanitized class name, method name, invocation number, parameter value, and a timestamp or generated UUID. Keep the original test identity in the report metadata as well as in the filename.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Wire capture into TestNG’s lifecycle

A listener is useful when the rule is “capture after a failure” or another selected result. The listener must obtain the driver belonging to the thread executing the callback. One practical pattern is to expose a registry backed by ThreadLocal and have the listener call it.

package example;

import java.io.IOException;
import java.nio.file.Path;
import org.openqa.selenium.WebDriver;
import org.testng.ITestListener;
import org.testng.ITestResult;

public final class FailureScreenshotListener implements ITestListener {
    private static final Path ROOT = Path.of("build", "screenshots");

    @Override
    public void onTestFailure(ITestResult result) {
        capture(result, "FAIL");
    }

    // Enable these only if your policy requires every outcome.
    // @Override public void onTestSuccess(ITestResult result) { capture(result, "PASS"); }
    // @Override public void onTestSkipped(ITestResult result) { capture(result, "SKIP"); }

    private void capture(ITestResult result, String outcome) {
        WebDriver driver = DriverRegistry.current();
        if (driver == null) {
            result.setAttribute("screenshotError", "No driver for callback thread");
            return;
        }
        String id = result.getTestClass().getName() + "-"
                + result.getMethod().getMethodName() + "-" + outcome;
        Object[] parameters = result.getParameters();
        if (parameters.length > 0) {
            id += "-" + java.util.Arrays.deepToString(parameters);
        }
        try {
            Path file = ScreenshotFiles.save(driver, ROOT, id);
            result.setAttribute("screenshotPath", file.toString());
        } catch (IOException | RuntimeException e) {
            result.setAttribute("screenshotError", e.toString());
        }
    }
}

Register it either on the test class or in the suite configuration:

import org.testng.annotations.Listeners;

@Listeners(FailureScreenshotListener.class)
public class LoginTest extends ParallelTestBase {
    // tests and @BeforeMethod/@AfterMethod go here
}

Or add the listener in testng.xml:

<suite name="Parallel UI" parallel="tests" thread-count="4">
  <listeners>
    <listener class-name="example.FailureScreenshotListener"/>
  </listeners>
  <test name="smoke">...</test>
</suite>

TestNG provides listener interfaces and result objects, but exact callback ordering and report attachment APIs depend on the TestNG version and the reporting framework. Verify those details against the versions pinned by your build. The example records the path on ITestResult; adapt that point to Allure, ExtentReports, or your CI publisher without changing driver ownership.

Choose when and where to capture

Failure-only capture

Use onTestFailure when artifacts are primarily for diagnosis. It minimizes disk use and keeps reports readable. Capture before teardown quits the browser; if teardown runs first, the driver may already be unusable.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

All outcomes

Enable success and skipped callbacks when you need visual evidence for every result, for example in visual-regression triage. Expect substantially more files and storage. A listener should never silently replace one outcome’s image with another.

Selected tests

Filter by class, method annotation, group, or a system property inside the listener. Keep the filter deterministic so a rerun produces the same artifact policy.

Local artifacts versus a report system

Write first to a local or CI workspace, then attach or upload the completed file. A unique path is still required when a reporting system accepts byte streams: concurrent tests can otherwise race while creating temporary files or attachment names.

Lifecycle ordering that avoids blank or missing images

  1. Create and register the driver in @BeforeMethod (or the lifecycle level that matches your selected parallel mode).
  2. Run the test using only the current thread’s driver.
  3. Let the listener capture while the page is still available.
  4. Attach or publish the copied artifact.
  5. Quit the driver and call ThreadLocal.remove() in @AfterMethod or the corresponding teardown.

If you use parallel="methods", a method-scoped driver is generally the least surprising arrangement. With parallel="tests" or classes, you may choose a broader lifecycle, but the lookup must still return the driver belonging to the callback’s worker.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Common failures and fixes

Symptom Likely cause Fix
Screenshot shows another test’s page A shared static WebDriver is being used. Store one driver per worker with ThreadLocal; never read a global mutable driver.
ThreadGuard exception A driver was called from a thread different from its creator. Resolve the driver from the current thread and keep callbacks on that thread. ThreadGuard does not manage driver storage.
No driver in the listener Driver setup ran at a different lifecycle scope, or the registry was cleared before capture. Register before the test and capture before teardown; log the worker name and lifecycle events.
Files overwrite each other Filename contains only the method name. Add class, parameters or invocation identity plus a worker or UUID component.
Zero-byte or missing file The temporary file was moved while another operation was still using it, or the browser was already closed. Copy it immediately, check file size, and capture before quit().
Listener appears not to run It is not registered, or the selected callback does not match the outcome. Register with @Listeners or XML and verify the callback with a small log statement.
Intermittent stale page The assertion fires before navigation or asynchronous content settles. Wait for a meaningful selector or condition before the assertion; capture the post-failure state intentionally.

Performance, reliability and storage

  • Set thread-count according to available browser, CPU and memory capacity. More workers increase contention and do not guarantee faster suites.
  • Use failure-only capture for routine CI and retain artifacts according to your build policy. All-outcome capture can fill workspaces quickly.
  • Copy screenshots to a per-build directory, then publish that directory once. Avoid many workers writing the same archive concurrently.
  • Keep screenshots small enough for your report system, but do not resize before diagnosing layout or rendering failures.
  • Record the URL, browser, viewport, test identity and outcome alongside the file. These fields make a screenshot useful when retries or parameterized invocations run together.
  • On retry, include the retry number in the artifact name; otherwise the second attempt can hide the first failure.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

When the requirement is a clean image of a URL rather than an interaction-heavy Selenium test, ScreenshotNeo provides a single screenshot API call. It accepts the consent banner like a visitor and removes more than 60 known consent platforms, newsletter popups and chat widgets before capture; each step can be disabled. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads and cache hits cost nothing, and the response identifies the result with X-Page-Verdict and X-Billed headers. Its MCP server exposes take_screenshot, get_page_info and capture_pdf to Claude, Cursor and other MCP clients.

For the full parameter list and output details, see the ScreenshotNeo documentation.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

ScreenshotNeo also supports full-page lazy-image capture, CSS-selector elements, dark mode, 12 device presets and custom viewports, retina scale, PDF settings, HTML/CSS rendering, custom JavaScript and CSS, clicks, selector or network-idle waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed image links, asynchronous webhooks, bulk capture of 100 URLs per call, usage reporting and an OpenAPI specification. Parameter names used by other screenshot APIs also work, which can simplify migration.

The Free plan includes 1,000 screenshots each month without a card. Paid plans start at $5 for 3,000 shots; yearly billing provides two months free. Every feature is included on every plan. Create a free ScreenshotNeo account to try it.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

FAQ

Can I reuse one driver if tests only take screenshots?

Not safely when those tests run concurrently. Navigation and capture are mutable browser operations, so each concurrent test needs its corresponding driver.

Does a screenshot prove which thread produced it?

Only if your artifact metadata or filename records that identity. Add worker, invocation and retry information explicitly.

Should screenshots be taken in @AfterMethod instead of a listener?

Either can work. A listener centralizes a result policy; an @AfterMethod gives direct access to the test instance. In both cases, capture before the driver is quit and use the current thread’s driver.

Frequently Asked Questions

Can I reuse one driver if tests only take screenshots?

Not safely when those tests run concurrently. Navigation and capture are mutable browser operations, so each concurrent test needs its corresponding driver.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Does a screenshot prove which thread produced it?

Only if your artifact metadata or filename records that identity. Add worker, invocation and retry information explicitly.

Should screenshots be taken in @AfterMethod instead of a listener?

Either can work. A listener centralizes a result policy; an @AfterMethod gives direct access to the test instance. In both cases, capture before the driver is quit and use the current thread’s driver.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Read next

Recommended PC Tool
Recommended PC Tool
Outdated Drivers Are Slowing You DownFree scan - exact matches
PC Slower Than It Used to Be?Free scan - under a minute

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.