Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Capture the browser in TestNG’s ITestListener.onTestFailure(ITestResult), before your test teardown quits the driver, then attach the resulting bytes or a durable image file through your reporting library. The listener gives you the failure event; Selenium supplies the image; ExtentReports, Allure, or another report adapter determines how that image appears in the final report.
The reliable failure-screenshot flow
A useful implementation has four separate responsibilities:
- TestNG calls a listener when a test method fails.
- Your framework finds the WebDriver belonging to that failed test.
- Selenium captures bytes, Base64, or a temporary file while the browser session is still alive.
- The report library attaches those bytes or references a copied file.
Keeping these responsibilities separate prevents a common mistake: writing a message with TestNG’s Reporter.log and expecting that text entry to become an image attachment. TestNG’s HTML and XML reports can contain log text, but an inline image requires the attachment mechanism of the report library you use.
Prerequisites and lifecycle decisions
- A Java TestNG project with Selenium WebDriver.
- A driver registry that can identify the browser for the failing test, including when tests run in parallel.
- A report output directory that remains available until the report is opened or published.
- A listener registered with TestNG through
testng.xmlor@Listeners.
Do not assume a single static driver is safe. In parallel execution, the listener can receive a failure from one test while another test is using a different browser. Associate each driver with the test execution or thread that owns it, and remove the association after the test is complete.
Build a driver registry that the listener can use
The listener needs a stable lookup path. A minimal thread-local registry works when each test owns one driver on its execution thread:
package example;
import org.openqa.selenium.WebDriver;
public final class DriverStore {
private static final ThreadLocal<WebDriver> CURRENT = new ThreadLocal<>();
private DriverStore() {}
public static void set(WebDriver driver) {
CURRENT.set(driver);
}
public static WebDriver get() {
return CURRENT.get();
}
public static void clear() {
CURRENT.remove();
}
}
Set the value immediately after creating the driver and clear it only after all failure capture work has finished. If your framework moves test work between threads, use a map keyed by the specific ITestResult, test instance, or another execution identifier instead of relying on ThreadLocal.
Capture a screenshot in onTestFailure
Selenium’s Java API exposes TakesScreenshot.getScreenshotAs(OutputType<X>). The byte form is convenient for attachment APIs:
package example;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
public final class FailureCapture {
private FailureCapture() {}
public static byte[] bytes(WebDriver driver) {
if (!(driver instanceof TakesScreenshot)) {
throw new IllegalStateException("This WebDriver does not support screenshots");
}
return ((TakesScreenshot) driver).getScreenshotAs(OutputType.BYTES);
}
}
OutputType.BASE64 returns encoded image data when an integration expects Base64. OutputType.FILE returns a temporary file. Selenium documents that this file is deleted when the JVM exits, so copy it into a report-results directory if the report will refer to it later.
Free tools Windows power users keep installed
One-click scans. No signup required.
Complete TestNG listener with a durable image file
This listener copies Selenium’s temporary file to a stable, report-relative directory and logs the resulting path. The publishPathToReport method is deliberately separate: replace it with the attachment call for your report library.
package example;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.time.Instant;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
import org.testng.ITestListener;
import org.testng.ITestResult;
import org.testng.Reporter;
public class FailureScreenshotListener implements ITestListener {
private final Path screenshotDirectory = Path.of("target", "failure-screenshots");
@Override
public void onTestFailure(ITestResult result) {
WebDriver driver = DriverStore.get();
if (driver == null) {
Reporter.log("No driver was available for " + result.getName(), true);
return;
}
try {
Files.createDirectories(screenshotDirectory);
String safeName = result.getTestClass().getName().replaceAll("[^a-zA-Z0-9._-]", "_")
+ "-" + result.getName().replaceAll("[^a-zA-Z0-9._-]", "_")
+ "-" + Instant.now().toEpochMilli() + ".png";
Path destination = screenshotDirectory.resolve(safeName);
Path temporary = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE).toPath();
Files.copy(temporary, destination, StandardCopyOption.REPLACE_EXISTING);
publishPathToReport(result, destination);
Reporter.log("Failure screenshot: " + destination, true);
} catch (Exception captureError) {
Reporter.log("Screenshot capture failed: " + captureError, true);
}
}
private void publishPathToReport(ITestResult result, Path image) {
// Call your report library's file/path attachment API here.
}
}
The listener catches capture errors so a secondary screenshot problem does not hide the original assertion failure. Keep the original failure as the primary result and log the capture exception for diagnosis.
Register the listener
Using @Listeners
import org.testng.annotations.Listeners;
@Listeners(FailureScreenshotListener.class)
public class CheckoutTest {
// test methods
}
Using testng.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="UI suite">
<listeners>
<listener class-name="example.FailureScreenshotListener"/>
</listeners>
<test name="browser tests">
<classes>
<class name="example.CheckoutTest"/>
</classes>
</test>
</suite>
Use the suite registration when you want the listener applied consistently across many test classes; use the annotation when ownership should be visible beside a particular test class.
Attach the image to the report you publish
ExtentReports path-based attachment
ExtentReports v4 documents addScreenCaptureFromPath("screenshot.png"). In a listener that has access to your current Extent test object, attach the copied path:
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
extentTest.addScreenCaptureFromPath(image.toString());
The generated file-based report references the image with an HTML <img> element. Keep the image at a location that remains resolvable when the HTML report is moved or opened on another machine. A report-relative directory is safer than a temporary operating-system path.
Allure attachments
Allure’s Selenium guidance demonstrates attaching PNG bytes with the image MIME type. A small attachment method can return the captured data:
import io.qameta.allure.Attachment;
public final class AllureFailureAttachment {
private AllureFailureAttachment() {}
@Attachment(value = "Failure screenshot", type = "image/png")
public static byte[] attach(byte[] screenshot) {
return screenshot;
}
}
Call AllureFailureAttachment.attach(FailureCapture.bytes(driver)) from onTestFailure. Allure’s automatic-failure example in the Selenium guide is written for JUnit 5; TestNG projects must use the Allure TestNG adapter and its version-compatible setup. Verify the adapter’s attachment API rather than copying JUnit extension wiring into a TestNG suite.
TestNG’s built-in report
Reporter.log("Failure screenshot: ...") adds text and a path to TestNG’s generated output. It does not, by itself, establish an inline image attachment. If you need the image rendered in the report, use the selected report integration’s image API and publish the image file with the report artifacts.
Do these 3 things before closing this tab:
1Fix the driver behind crashes, sound loss and screen glitches2Repair Windows errors before they cause bigger problems3Scan for outdated or missing drivers - takes under a minuteRank #4
Make teardown order safe
The browser must still be usable when onTestFailure runs. If an @AfterMethod hook calls driver.quit() before the listener can access the session, Selenium cannot capture the page. TestNG does not provide a universal lifecycle recipe that makes every framework’s teardown ordering identical, so verify the actual ordering in your project.
- Keep the driver in
DriverStoreuntil capture and report attachment complete. - Do not clear or quit the driver in an earlier failure hook unless that hook also performs the capture.
- Use a fallback capture in your framework’s teardown only when your verified callback order requires it; avoid creating duplicate attachments.
- Always quit and clear the driver after capture, including when capture itself throws.
Parallel tests and unique artifacts
Parallel runs expose two additional failure modes: attaching the wrong browser image and overwriting another test’s file. Use a test-specific or thread-specific driver lookup, and include class, method, invocation, and a timestamp or unique identifier in the filename. If a data-driven method can run more than once, include the invocation number from ITestResult where your framework exposes it.
Keep report artifacts isolated per build (for example, a cleaned target directory). When reports are assembled in CI, publish the entire screenshot directory alongside the HTML or Allure results rather than only the report index.
Common failures and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| No screenshot is created | The listener was not registered, or the driver lookup returned null. | Check @Listeners/testng.xml and log the test name plus the driver association. |
NullPointerException in the listener |
The driver was never stored, was cleared too early, or the test runs on another thread. | Register the driver immediately after creation and use a lookup keyed to the actual test execution. |
| “Session is closed” or invalid-session error | Teardown quit the browser before capture. | Move capture ahead of quit, or capture in the hook that still owns the live session. |
| Report shows a broken image | The report references a temporary or absolute path unavailable where the report is viewed. | Copy OutputType.FILE to a durable, report-relative directory and publish that directory. |
| Two failures show the same image | Parallel tests reused one static driver or one filename. | Use test-specific driver ownership and unique filenames. |
| Only text appears in TestNG HTML | Reporter.log was used without a report attachment API. |
Attach bytes or a saved path through ExtentReports, Allure, or your chosen integration. |
| Allure attachment code does not compile | The example targets a different adapter or Allure version. | Check the TestNG adapter’s version-specific attachment method and MIME-type signature. |
Performance, storage, and reliability considerations
- Capturing only on failure avoids the storage and I/O cost of screenshots for passing tests.
- Bytes avoid managing a temporary path during the attachment call; files are useful when the report library requires a path or when artifacts must be inspected independently.
- Copy files into a build-scoped directory and clean old build outputs to prevent unbounded artifact growth.
- Do not let screenshot failure replace the test’s assertion or exception. Record capture diagnostics and preserve the original failure.
- For remote drivers, the screenshot is returned through Selenium’s driver command; retain the returned data before the session is closed.
Or skip the browser setup
If you need a screenshot service for pages outside the failing Selenium session, ScreenshotNeo captures a URL with one request. Its clean-shot steps accept cookie or consent banners and remove more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. It also offers an MCP server with take_screenshot, get_page_info, and capture_pdf for AI clients.
Recommended Free Tools
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 documentation for request options. The free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
Best Value
FAQ
Should I use IReporter instead?
Use IReporter when you need to inspect completed suite results while constructing a report. It runs after suite execution, so the browser may already be closed; it is not the natural callback for capturing the live failure page.
Is Selenium’s temporary screenshot file safe to archive directly?
No. Copy it to a durable report directory during the test run. Selenium documents that the temporary file is deleted when the JVM exits.
Can a failure screenshot prove what caused the failure?
It records the visible browser state at capture time. Pair it with the assertion message, stack trace, URL, and relevant logs; the image alone does not establish the underlying cause.
Frequently Asked Questions
Should I use IReporter instead?
Use IReporter for post-suite report construction. Because it runs after execution, the browser may already be closed; ITestListener is the appropriate live-failure callback.
Is Selenium’s temporary screenshot file safe to archive directly?
No. Copy it to a durable report directory during the run because Selenium deletes the temporary file when the JVM exits.
Can a failure screenshot prove what caused the failure?
It shows the visible browser state at capture time. Keep the assertion, stack trace, URL, and logs with it; the image alone does not prove the root cause.
Quick Recap
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.




