Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
Capture the screenshot in TestNG’s failure callback, while the failing test’s WebDriver session is still open, then attach the image to your report. Selenium can return a temporary file, bytes, or Base64 data; ExtentReports can attach either a persistent path or Base64, including media on a specific failure log. The implementation below shows a thread-safe listener pattern, report lifecycle, artifact handling, troubleshooting, and an alternative when you need screenshots of public web pages rather than the live browser session.
What the workflow must do
- Keep one WebDriver associated with each test instance (and, for parallel suites, each executing thread).
- Register a TestNG listener that receives
ITestResultinonTestFailure. - Call
TakesScreenshot.getScreenshotAsbefore teardown quits the driver. - Persist the image or encode it as Base64.
- Attach it to the matching Extent test or to the failure log, then flush the report.
Selenium documents the screenshot contract in its TakesScreenshot Java API. TestNG supplies the failure lifecycle through its listener APIs; its documentation is at testng.org.
Choose an attachment format and location
| Choice | How it works | Advantages | Risks or costs |
|---|---|---|---|
| Persistent file path | Copy Selenium’s temporary file into a run directory and give Extent the resulting path. | Images remain independent build artifacts and keep report payloads smaller. | The HTML report references an <img> path; copying or publishing the report without that image breaks the link. |
| Base64 | Request Base64 (or bytes and encode them) and pass the data to Extent’s Base64 API. | No separate path to maintain; convenient for a self-contained report. | Many large screenshots can make the report substantially larger. |
| Test-level attachment | Attach media to the Extent test object. | Simple overview of the test’s artifacts. | It may not sit beside the exact failure message when a test has several log entries. |
| Failure-log attachment | Create media with MediaEntityBuilder and pass it to the failure log call. |
Places the image next to the error text that caused it. | Requires the correct Extent API for the dependency version in your build. |
The OutputType Java API defines FILE, BYTES, and BASE64. A FILE result is temporary and can be deleted when the JVM exits, so never publish only that original path.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Implement a TestNG listener with ExtentReports
The following is an integration pattern rather than a drop-in framework. Replace the three project-specific methods with your driver registry, screenshot storage, and Extent test lookup. A registry keyed by test instance (or a thread-safe context) is safer than one mutable static driver when tests run concurrently.
import com.aventstack.extentreports.ExtentTest;
import com.aventstack.extentreports.MediaEntityBuilder;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
import org.testng.ITestListener;
import org.testng.ITestResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.time.Instant;
public final class ScreenshotListener implements ITestListener {
@Override
public void onTestFailure(ITestResult result) {
WebDriver driver = driverFor(result.getInstance());
ExtentTest test = extentTestFor(result);
if (driver == null || test == null) {
return; // Preserve the original failure if framework context is unavailable.
}
try {
byte[] png = ((TakesScreenshot) driver)
.getScreenshotAs(OutputType.BYTES);
Path path = savePngForThisTest(result, png);
test.fail("Test failed",
MediaEntityBuilder.createScreenCaptureFromPath(
path.toString()).build());
} catch (RuntimeException captureError) {
// Log captureError without replacing the test's original exception.
test.warning("Screenshot capture failed: " + captureError.getMessage());
}
}
private Path savePngForThisTest(ITestResult result, byte[] png) {
String method = result.getMethod().getMethodName()
.replaceAll("[^A-Za-z0-9_.-]", "_");
String unique = method + "-" + Instant.now().toEpochMilli() + ".png";
Path dir = Path.of("target", "screenshots");
try {
Files.createDirectories(dir);
Path output = dir.resolve(unique);
Files.write(output, png);
return output.toAbsolutePath();
} catch (Exception e) {
throw new IllegalStateException("Cannot save screenshot", e);
}
}
private WebDriver driverFor(Object testInstance) {
// Return the driver owned by this test instance or execution context.
throw new UnsupportedOperationException("Implement driver lookup");
}
private ExtentTest extentTestFor(ITestResult result) {
// Return the Extent test created for this ITestResult.
throw new UnsupportedOperationException("Implement Extent lookup");
}
}
Using BYTES avoids a second read of Selenium’s temporary file. If your project already has a stable file utility, you can instead request OutputType.FILE and copy it with Files.copy before the JVM exits. For Base64, request OutputType.BASE64 and use the Base64 method exposed by your ExtentReports version.
Attach to a test or to the failure message
ExtentReports version 4 documents both test-level screenshot methods and log-level media in its Java documentation. A log attachment follows this shape:
String path = savedScreenshot.toString();
extentTest.fail("Assertion failed",
MediaEntityBuilder.createScreenCaptureFromPath(path).build());
For a test-level artifact, use the equivalent addScreenCaptureFromPath(path) or addScreenCaptureFromBase64String(base64) method on the Extent test object available in your dependency. Match capitalization and signatures to the version in your build; do not assume every Extent release exposes identical overloads.
Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minutePC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Register the listener and finish the report
Registering with TestNG
Enable the listener through @Listeners(ScreenshotListener.class), the suite XML listener declaration, or your existing framework wiring. If the callback never runs, the listener is not registered or the failure is being handled by a different runner. The Extent TestNG adapter is documented as an ITestListener integration in its version 4 adapter documentation, which also describes extent.properties reporter configuration.
Rank #2
import org.testng.annotations.Listeners;
@Listeners(ScreenshotListener.class)
public class CheckoutTest {
// Your @BeforeMethod creates the driver and Extent test.
// Your @Test performs browser actions.
// Your @AfterMethod quits the driver after listeners have captured failure state.
}
Ordering teardown
Do not call driver.quit() before the failure callback can execute. A common arrangement is to create the driver in @BeforeMethod, capture in onTestFailure, and quit in @AfterMethod. If your framework’s teardown order differs, move capture into an earlier failure hook or retain the driver until the listener has run.
Flushing and publishing
Call the reporter’s flush() once the suite has finished. Extent documents flush() as writing reporter output. For file-based attachments, publish the HTML report and the screenshot directory together, preserving the relative path from the final report file. A CI archive that contains only HTML will show broken image links.
Parallel tests and unique artifacts
- Never use one shared mutable static WebDriver for concurrent methods. Map
result.getInstance(), a test identifier, or a thread-local context to its own driver. - Include a unique run, class, method, and timestamp (or UUID) in each filename. Otherwise two workers can overwrite one another.
- Keep the Extent test lookup thread-safe; a plain global “current test” variable can attach one worker’s image to another worker’s failure.
- Use a per-run directory such as
target/screenshots/<run-id>and clean it at the start of a run, not while tests are still writing.
Diagnose missing screenshots
The callback never executes
Confirm the listener is enabled with an annotation, suite configuration, or framework adapter. Verify the test actually reaches a failed state rather than being skipped or aborted before WebDriver creation.
Free tools Windows power users keep installed
One-click scans. No signup required.
“No such session” or a closed-driver exception
The browser was quit before capture, or the driver crashed. Move capture earlier, prevent premature teardown, and keep the original exception as the primary failure.
Unsupported screenshot operation
Selenium documents WebDriverException and UnsupportedOperationException cases for screenshot calls. Check that the driver implements TakesScreenshot, that the session is alive, and that the browser supports the operation. Catch the capture error so diagnostics do not mask the assertion that failed.
The report shows a broken image
Inspect the generated HTML to see the exact src path. Resolve it relative to the report’s final directory, then archive that image at the same relative location. Absolute workstation paths usually fail on another CI machine.
The temporary file vanished
Copy an OutputType.FILE result immediately, or request BYTES and write those bytes yourself. Selenium describes the file output as temporary and removable when the JVM exits.
Quick wins for a faster PC:
Scan for outdated or missing drivers - takes under a minuteDriver Scan →Repair Windows errors before they cause bigger problemsFix Now →The wrong test receives the image
Audit driver and Extent registries for shared state. Key records by the actual TestNG instance and execution, and test with two methods running in parallel before enabling high concurrency.
Rank #4
The report becomes too large
Prefer persistent files for large suites, limit captures to failures, and avoid Base64 when an external artifact directory is acceptable. Resize screenshots only if your diagnostic needs allow it; changing the browser viewport can hide the layout problem you are trying to investigate.
Use existing integrations when they fit
If your project already uses Selenide, its screenshots documentation describes automatic screenshots on failure and TestNG ScreenShooter support, with an option to capture successful tests as well. This reduces custom listener code, but verify the Selenide version, output directory, and compatibility with your report publisher.
TestNG also writes testng-failed.xml after suite failures for rerunning failed methods. That rerun mechanism is separate from screenshot capture: use the listener or integration to add visual artifacts, and use the generated XML to reproduce the failure.
Do these 3 things before closing this tab:
1Fix the driver behind crashes, sound loss and screen glitches2Clear out junk files and repair common Windows errors3Scan for outdated or missing drivers - takes under a minuteOr skip the browser setup
If you need a clean screenshot of a public URL—not the exact authenticated, in-progress WebDriver state—ScreenshotNeo returns an image or PDF through one GET request. It is useful for documentation, smoke checks, and report attachments where launching Selenium would be unnecessary. It does not replace a Selenium screenshot of a live test session.
Best Value
See the ScreenshotNeo API documentation for parameters and response headers. A cURL capture is:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python:
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)
Node.js:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
Before capture, ScreenshotNeo accepts consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed as clean shots, and response headers identify the page verdict and billing result. It also offers an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. Every plan includes the features; 1,000 shots per month are free without a card, and paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
Operational and cost considerations
- Capture only on failure unless successful-test images are required; this reduces disk use and report size.
- Write to the CI workspace and archive the directory with the report. Set retention appropriate to your organization’s debugging and privacy requirements.
- Be careful with screenshots containing credentials, personal data, payment details, or customer records. Redact or mask sensitive fields before publishing artifacts.
- Keep the capture call short and defensive. A screenshot is secondary diagnostics; it must not turn a real test failure into a listener failure.
- For flaky pages, preserve the failure screenshot and the exception/URL together so a rerun can be compared with the original state.
Practical decision guide
| Situation | Recommended approach |
|---|---|
| You need the exact browser state, cookies, and authenticated page at failure. | Capture from the live WebDriver in onTestFailure; save a unique file and attach it to the failure log. |
| You want a portable, self-contained report and the suite is small. | Use Selenium bytes/Base64 and Extent’s Base64 attachment API, watching report size. |
| You publish reports and artifacts separately. | Use a persistent file path and preserve the image’s relative location beside the HTML. |
| You already use Selenide. | Evaluate its TestNG ScreenShooter integration before adding custom listener code. |
| You need scheduled or on-demand screenshots of public URLs without browser-driver setup. | Use ScreenshotNeo’s one-call API; it is not a substitute for a live Selenium session. |
Frequently Asked Questions
Can I capture a screenshot in an @AfterMethod instead of a listener?
Yes, if the method receives ITestResult and runs before the driver is quit. A listener centralizes the behavior and also works across test classes, but the essential requirement is an active session at capture time.
Does a Selenium screenshot automatically appear in a TestNG HTML report?
No. Selenium produces the image; TestNG and the selected reporter need explicit listener or integration code to associate and publish it.
Should I use PNG or JPEG?
Selenium’s standard examples return PNG bytes or files. Keep PNG for crisp UI text unless your report pipeline deliberately converts formats.
Will a file-path attachment be embedded in every Extent report format?
No. Extent’s file-based reporters reference the saved image path. The HTML and image must remain together at the published location.
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.
Recommended Free Tools

