Capture the screenshot in a TestNG failure callback, before teardown quits the WebDriver, then attach either a persistent image path or Base64 data to your report. Selenium provides the image; TestNG supplies the failure lifecycle; your reporting library (such as ExtentReports) renders the attachment. The essential workflow is:
- Keep each test’s driver available to the listener that receives
ITestResult. - Call
getScreenshotAswhile the session is alive. - Persist the image (if using
OutputType.FILE) and attach it to the matching test or failure log. - Flush the report and publish the report together with any referenced image files.
What you need before adding screenshots
- A Java Selenium test suite running under TestNG.
- A WebDriver lifecycle that does not quit the browser until failure listeners have run.
- A report implementation that supports media, such as ExtentReports, or your existing TestNG reporting integration.
- A per-test driver lookup that is safe when tests execute in parallel. A single mutable static driver can attach one test’s browser image to another test.
Selenium’s TakesScreenshot API exposes getScreenshotAs. The Java OutputType API supports FILE, BYTES, and BASE64. TestNG’s listener callback is the right trigger because it receives the failed test result while your framework can still locate that test’s driver.
Choose how the image will reach the report
Persistent file path
Use OutputType.FILE when screenshots should remain independent CI artifacts or when your report expects an image filename. Selenium returns a temporary file; copy it into a unique directory such as target/screenshots/<run-id>. The temporary file is documented as removable when the JVM exits, so retaining only its original path can produce a broken report.
Extent’s file-based reporters reference the saved image with an HTML <img> element; they do not automatically embed the file. Keep the image at the relative or absolute path used by the final report, including when an archive or CI publisher copies the report elsewhere.
Recommended Free Tools
Base64 data
OutputType.BYTES gives raw bytes that you can save yourself, while OutputType.BASE64 gives encoded data. ExtentReports provides Base64 attachment methods for tests and logs. Base64 avoids a separate image reference, but many screenshots can make the HTML report substantially larger.
Test attachment or failure-log attachment
A test-level image is useful when the report’s test view should always show the final browser state. A log-level image puts the screenshot beside the exact failure message. In ExtentReports, build log media with MediaEntityBuilder; use the test’s screen-capture method when the image belongs to the overall test entry. These are different API shapes, so select the one that matches how readers navigate your report.
Implement a custom TestNG failure listener
1. Make driver lookup explicit
The listener below deliberately uses project-specific methods. Your driver may be held in a base test, a thread-local registry, dependency-injection container, or another framework object. The listener receives result.getInstance(), which lets you identify the test object associated with the failure.
public final class ScreenshotListener implements ITestListener {
@Override
public void onTestFailure(ITestResult result) {
WebDriver driver = driverFor(result.getInstance());
if (driver == null) {
return; // Do not hide the original assertion failure.
}
try {
byte[] png = ((TakesScreenshot) driver)
.getScreenshotAs(OutputType.BYTES);
String screenshotPath = savePngForThisTest(result, png);
ExtentTest test = extentTestFor(result);
test.fail("Test failed",
MediaEntityBuilder
.createScreenCaptureFromPath(screenshotPath)
.build());
} catch (WebDriverException | UnsupportedOperationException captureError) {
// Record captureError in your test logs; preserve the original failure.
}
}
private WebDriver driverFor(Object testInstance) {
// Project-specific: return this test's driver, not a shared mutable driver.
throw new UnsupportedOperationException("Implement driver lookup");
}
private String savePngForThisTest(ITestResult result, byte[] png) {
// Project-specific: create a unique filename and write png to run output.
throw new UnsupportedOperationException("Implement screenshot storage");
}
private ExtentTest extentTestFor(ITestResult result) {
// Project-specific: return the Extent test mapped to this result.
throw new UnsupportedOperationException("Implement report lookup");
}
}
This is an implementation pattern, not a drop-in universal class: match the capitalization and signatures to the ExtentReports dependency in your build. A practical savePngForThisTest implementation should create directories, sanitize the class and method names, add a timestamp or unique identifier, and write the bytes with Files.write. Use a path relative to the report directory when the report will be moved between machines.
Free tools Windows power users keep installed
One-click scans. No signup required.
Rank #2
2. Register the listener
TestNG must load the listener. You can annotate a test class or suite with @Listeners(ScreenshotListener.class), list it in testng.xml, or register it through your build/framework wiring. If the callback never runs, verify that registration method is actually included in the suite being executed.
3. Keep teardown after capture
Do not call driver.quit() in a teardown phase that runs before the failure callback. If your framework has an @AfterMethod, listener ordering, or custom extension that closes the session early, move the quit operation later or capture in the earlier lifecycle hook. A closed session can throw instead of returning an image.
Attach an image with ExtentReports
ExtentReports’ Java documentation supports path and Base64 capture. A path-based test attachment looks like this:
extentTest
.fail("Checkout assertion failed")
.addScreenCaptureFromPath("target/screenshots/run-42/CheckoutTest_payments_1697041234.png");
For a failure message and image in one log entry, use the media builder:
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →extentTest.fail(
"Checkout assertion failed",
MediaEntityBuilder
.createScreenCaptureFromPath("target/screenshots/run-42/CheckoutTest_payments_1697041234.png")
.build());
When your report API exposes Base64 methods, pass the encoded value instead of maintaining a file reference:
String base64 = ((TakesScreenshot) driver)
.getScreenshotAs(OutputType.BASE64);
extentTest.addScreenCaptureFromBase64String(base64);
Confirm the exact method available in your ExtentReports version. The version-4 TestNG adapter documentation describes the official adapter, properties-based reporter setup, and its ITestListener integration at the adapter documentation.
Flush the report and preserve its assets
Call the reporter’s flush() at the end of the suite or in the report manager’s finalization hook. Extent documents flush() as writing reporter output. For path attachments, publish the HTML and screenshot directory as one artifact. If a CI job copies only the HTML file, the browser will request an image that no longer exists. Open the report from its final published location, not only from the original workspace, to verify relative paths.
Example report lifecycle
@AfterSuite(alwaysRun = true)
public void closeReport() {
extent.flush();
}
Keep the lifecycle owner singular: multiple managers flushing or replacing the reporter can create incomplete output or mismatched test objects.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Rank #4
Handle parallel execution correctly
Parallel TestNG runs make driver ownership the hardest part. Associate the driver with the test instance or thread, and map the same identity to the Extent test. Ensure screenshot filenames are unique across workers; class and method names alone collide when retry analyzers or data providers run the same method repeatedly. Include a run identifier, invocation index, or UUID. Avoid a global mutable WebDriver field unless access is synchronized and ownership is unambiguous; synchronization can prevent corruption but cannot make the wrong browser logically correct.
Common failures and precise fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| No screenshot appears | Listener was not registered or the callback is not in the executed suite. | Check @Listeners, testng.xml, and adapter wiring; add a temporary log in onTestFailure. |
invalid session id or screenshot exception |
Teardown quit the driver before capture, or the session cannot capture. | Capture before quit; adjust lifecycle ordering; catch the capture exception so the assertion remains the reported failure. |
| Wrong browser image in a failure | Shared static driver or non-thread-safe registry in parallel execution. | Resolve the driver from ITestResult and the owning test/thread; test with parallel workers. |
| Report shows a broken image icon | The temporary Selenium file was not copied, or the report moved without its image directory. | Write to stable run output and publish that directory beside the HTML report. |
Capture call throws UnsupportedOperationException |
The active driver implementation does not support screenshots. | Use a screenshot-capable WebDriver and handle the exception without masking the original test failure. |
| Report is excessively large | Many high-resolution images embedded as Base64. | Prefer external files, capture only failures, or apply your artifact retention policy. |
Selenium documents WebDriverException and UnsupportedOperationException as possible screenshot failures in the TakesScreenshot API.
Use Selenide when it already owns your screenshots
If your project uses Selenide, its screenshots documentation describes automatic screenshots on test failure and TestNG ScreenShooter support, with an option to include successful tests. This can remove custom listener code. Check the output directory and compatibility for the Selenide version in your build before relying on it in CI; you still need to preserve the generated artifacts with the report.
Rerun failures separately from capturing images
TestNG writes testng-failed.xml after suite failures so failed methods can be rerun, as described in the TestNG documentation. That rerun mechanism does not attach screenshots by itself. Keep the listener/report integration for visual evidence, and use the generated suite to reproduce the failing method with a fresh browser session.
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallBest Value
Or skip the browser setup
ScreenshotNeo provides a website screenshot API and MCP server when the thing you need is a screenshot of a URL rather than the live Selenium session inside a test. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP tools—take_screenshot, get_page_info, and capture_pdf—work with Claude, Cursor, and other MCP clients.
One GET request returns PNG, JPEG, WebP, or PDF. See the ScreenshotNeo documentation for the complete option list, including full-page and selector captures, device presets, custom waits, headers, cookies, JavaScript, blocking rules, signed links, asynchronous jobs, bulk capture, and usage information.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; every feature is included on every plan. Create a free ScreenshotNeo account to try the API.
Practical decision checklist
- Choose a file path when CI must retain images as separate artifacts or reports are routinely moved.
- Choose Base64 when a self-contained report is more valuable than a small HTML payload.
- Attach at test level for a general browser-state image; attach to a failure log for message-level context.
- Use a custom listener for complete lifecycle and naming control; use an existing adapter or Selenide support when reducing setup matters more.
- Always test the published report, parallel mode, retries, and a deliberately failing test.
Frequently Asked Questions
Does TestNG take the screenshot itself?
No. Selenium’s WebDriver implementation captures the image through TakesScreenshot; TestNG provides the failure callback where your listener invokes it.
Can I keep Selenium’s temporary FILE path?
No. Copy the file into stable run output before the JVM exits, then reference that copied path from the report.
Why is my screenshot attached to the wrong test in parallel mode?
The listener is resolving a shared or incorrectly scoped driver. Map each ITestResult to its own driver and report object, and generate unique filenames for invocations.
Are TestNG failed-test reruns enough to preserve screenshots?
No. testng-failed.xml reruns methods; screenshot persistence still depends on your listener and report artifact handling.
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.

