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.

Use a TestNG ITestListener and capture the browser in onTestFailure(ITestResult), before teardown quits the WebDriver. Then save the image somewhere your build publishes and add a report link or use your reporting library’s attachment API. Capturing the file and attaching it to a report are separate steps: TestNG does not provide one universal image-attachment call for every HTML reporter.

How the failure screenshot flow works

A reliable setup has four parts: TestNG signals a failed test, the listener finds that test’s WebDriver, Selenium captures an image, and the report or artifact publisher makes the image accessible. The listener callback is ITestListener.onTestFailure; Selenium’s TakesScreenshot interface supplies the capture operation. See the TestNG documentation and the Selenium Java API for TakesScreenshot.

  1. Keep a way to retrieve the WebDriver instance belonging to the failed test.
  2. Capture from that driver in onTestFailure, while its session is still open.
  3. Save the image under a deterministic, report-accessible directory with a unique filename.
  4. Attach it using the reporting library’s API, or create a relative link in the report and publish the image alongside it.
  5. Register the listener and verify the published report can still resolve the image path.

Implement a TestNG listener

The example below shows a complete listener structure, but two project-specific methods are intentionally left for you to connect: driverFor and attachToReport. Driver storage and report attachment vary by project; there is no safe universal implementation for either. The capture, file copy, directory creation, and error handling are explicit.

import java.io.File;
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;
import org.openqa.selenium.WebDriverException;
import org.testng.ITestListener;
import org.testng.ITestResult;

public final class FailureScreenshotListener implements ITestListener {
  @Override
  public void onTestFailure(ITestResult result) {
    WebDriver driver = driverFor(result); // Connect to your driver manager.
    if (driver == null) {
      result.getTestContext().getReporter().log(
          "Screenshot not captured: no WebDriver was available.");
      return;
    }

    try {
      File temporary = ((TakesScreenshot) driver)
          .getScreenshotAs(OutputType.FILE);
      Path destination = screenshotPathFor(result); // Unique, published path.
      Files.createDirectories(destination.getParent());
      Files.copy(temporary.toPath(), destination,
          StandardCopyOption.REPLACE_EXISTING);
      attachToReport(result, destination); // Use your report library or link.
    } catch (WebDriverException | IOException captureError) {
      // Record this as a secondary diagnostic; do not replace the test failure.
      result.getTestContext().getReporter().log(
          "Screenshot capture failed: " + captureError.getMessage());
    }
  }

  private WebDriver driverFor(ITestResult result) {
    throw new UnsupportedOperationException("Connect to project driver storage");
  }

  private Path screenshotPathFor(ITestResult result) {
    throw new UnsupportedOperationException("Choose a report-accessible path");
  }

  private void attachToReport(ITestResult result, Path image) {
    throw new UnsupportedOperationException("Connect report attachment or link");
  }
}

The placeholders deliberately throw rather than silently suggesting they are ready to run: replace them with the storage and reporting integration used by your project. The Selenium API also permits output such as OutputType.BASE64, which can suit a report library that accepts image data instead of a file. Selenium documents WebDriverException on capture failure and UnsupportedOperationException when a driver does not support screenshots.

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

Find the driver for the failed test

Use the same driver instance that executed the failed test. A project may expose it through a base test class, dependency injection, a driver manager, or thread-local storage. The listener receives an ITestResult, so your project can associate that result with its test instance or execution context. Do not assume a single mutable static driver is correct when tests run concurrently: one test could otherwise capture another test’s browser.

Choose a collision-resistant path

A method name alone is not a reliable filename. Data-provider invocations, retries, repeated suite runs, methods with the same name in different classes, and parallel workers can collide. Include the fully qualified class and method plus a safe invocation identifier or unique suffix. Sanitize characters that are invalid in filenames on the operating systems used by your build. Keep screenshots beneath the directory your test runner or CI system collects as an artifact.

Do not mask the original failure

Screenshot capture is diagnostic work after the test has failed. Catch capture-related exceptions and report them separately; do not throw them in a way that replaces the assertion or exception already recorded in result.getThrowable(). A missing screenshot should not turn the underlying test failure into a different failure that is harder to diagnose.

Attach the image to the report

Writing a PNG to disk does not automatically embed it in a report. Choose one of these approaches based on the report your project actually generates:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • Third-party HTML report: call its screenshot-attachment API from attachToReport. Confirm whether it accepts a file path, bytes, or Base64 and whether it copies or embeds the image.
  • Plain HTML or a published artifact: emit a relative link such as screenshots/SomeTest_testCase_123.png and publish that directory alongside the HTML. A link to a local path on a developer’s computer will not work for other report readers.
  • TestNG’s built-in output: TestNG documents its report output directory and index.html entry point, as well as Reporter.log and XML reporting. Those facilities do not establish a universal built-in image attachment API. If you use Reporter.log, verify how your chosen report consumes the logged text and make the image available at a resolvable path.

TestNG’s project documentation covers report output and reporting options at testng.org. The exact report integration call depends on the library and version in your project; check that library’s documentation rather than assuming that a TestNG listener can attach an image in the same way everywhere.

Register the listener

TestNG supports registration in testng.xml or with @Listeners. XML makes the intended suite scope visible in configuration. The annotation applies at suite level, so use it with care if the class participates in a broader suite than you intend.

Register in testng.xml

<suite name="UI suite">
  <listeners>
    <listener class-name="com.example.FailureScreenshotListener"/>
  </listeners>
  <test name="Browser tests">
    <classes>
      <class name="com.example.LoginTest"/>
    </classes>
  </test>
</suite>

Use the listener’s fully qualified class name. Ensure the listener class is compiled and on the test runtime classpath when TestNG loads the suite file.

Register with @Listeners

import org.testng.annotations.Listeners;

@Listeners(FailureScreenshotListener.class)
public class LoginTest {
  // Test methods
}

Because this annotation applies at suite level, verify which tests inherit the listener’s scope in your TestNG setup. TestNG listener and registration behavior is described in its official documentation.

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

Account for teardown, parallel tests, and retries

Capture before the browser closes

The callback needs a live browser session. If an @AfterMethod or other teardown quits the driver before the failure callback can use it, the listener will not be able to capture the page. Check the order of your framework’s listener callbacks and teardown hooks, and arrange lifecycle ownership so the driver remains available through failure capture. If your own teardown is responsible for capturing, make sure it only runs for the intended failure state and does not hide the original result.

Isolate parallel executions

TestNG supports parallel execution, but it does not choose your project’s driver association or filename strategy. Use the project’s thread-aware or otherwise execution-scoped driver storage, and ensure each invocation writes a distinct file. Confirm that the report attachment points to the artifact produced by that same invocation.

Decide what retries and other outcomes mean

onTestFailure handles the failure callback; it does not mean every non-success outcome. TestNG distinguishes failures, timeouts, skips, and failures within a success-percentage allowance, and retry analyzers may cause a test to be run again. Decide whether you want evidence from each failed attempt or only the final outcome. If you also want screenshots for timeouts or other states, implement the relevant listener callbacks deliberately and confirm that the browser is still available at that point. The TestNG 7.11.0 ITestListener API reference documents the callback distinctions.

Know what the screenshot represents

TakesScreenshot is the Selenium interface for capturing a screenshot through a driver or an HTML element, with output available in different forms. The interface refers to conformant WebDriver behavior; Selenium notes that non-conformant implementations may return a best-effort image of a page, window, frame, or display. Do not promise a full-page screenshot merely because the call succeeded. The image scope depends on the browser, driver, implementation, and capture method. For the same reason, a failure screenshot is evidence of the rendered state at capture time, not necessarily a complete record of the browser’s earlier state.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Use Selenide if it already fits your project

For projects already using Selenide, its documentation says screenshots are automatically taken on failures of Selenide checks, with a default location of build/reports/tests. It documents Configuration.reportsFolder for changing the directory and a TestNG ScreenShooter listener for broader screenshot behavior, including non-Selenide assertions and success screenshots. Check the behavior against your installed version and report integration before relying on it as a drop-in replacement. See Selenide screenshot documentation.

Troubleshoot missing or unusable screenshots

Symptom Likely cause What to check or change
No callback output or image Listener was not loaded, or its class is not on the test runtime classpath. Check the XML listener class name or annotation, then inspect the TestNG startup output for listener-loading errors.
Driver is null or already closed The listener cannot reach the correct driver, or teardown ran first. Use execution-scoped driver lookup and keep the session alive through capture.
One parallel test gets another test’s image Shared mutable driver state or colliding filenames. Use thread-aware driver storage and include invocation identity in each path.
Capture throws an exception The browser or driver cannot take screenshots, or the session is no longer usable. Log the capture error as secondary diagnostic information; check driver support and lifecycle rather than replacing the test’s original throwable.
The report shows a broken image The screenshot was not published, or its link is absolute or points to a temporary location. Publish the screenshot directory with the report and use a relative path that remains valid after artifact collection.
The screenshot is not full page The screenshot API or browser implementation captures a viewport, window, or another best-effort scope. Verify the supported capture behavior for the exact browser and driver; do not infer full-page support from TakesScreenshot alone.
Retries overwrite earlier evidence The filename omits attempt or invocation identity. Add a unique attempt/invocation suffix or preserve each attempt in its own directory.

Or skip the browser setup

If your goal is to capture a URL as an image or PDF without managing Selenium and WebDriver, ScreenshotNeo is a separate screenshot API and MCP server—not a replacement for TestNG’s failure callback or a direct attachment method for an existing Selenium session. A one-call request can capture a URL:

curl -G "https://api.screenshotneo.com/v1/shot" 
  -d access_key=YOUR_API_KEY 
  --data-urlencode url=https://stripe.com 
  -o shot.webp

See the ScreenshotNeo API documentation for request options and response behavior. Before a capture, it can accept consent banners and remove more than 60 known consent platforms, newsletter popups, and chat widgets; each of those steps can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the page verdict and billing status in headers. Its MCP server offers take_screenshot, get_page_info, and capture_pdf for AI agents and other MCP clients. The Free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots, with every feature available on every plan. These URL captures are useful for independent page capture, but they do not automatically attach an image to a TestNG result. Sign up for 1,000 free screenshots a month with no card.

Frequently Asked Questions

Can I add a screenshot link to TestNG’s default report?

You can publish a relative link to the image, but TestNG’s built-in reporting does not define a universal image-attachment API. Verify how your report renders logged text and ensure the image ships with the report.

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

Does onTestFailure run for skipped or timed-out tests?

Not as a general rule. TestNG has distinct callbacks for failures, timeouts, and skips; implement the callback matching the outcome you want to capture.

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.