How to Capture Screenshots in Selenium TestNG
Capture a Selenium screenshot automatically when a TestNG test fails with ITestListener, safe filenames, parallel execution support, and CI artifacts.
Use TestNG’s ITestListener.onTestFailure(ITestResult) callback. In that method, obtain the WebDriver associated with the failing test, cast it to Selenium’s TakesScreenshot, call getScreenshotAs(OutputType.FILE), and copy the result to a unique artifact path. Register the listener in testng.xml or with @Listeners.
This approach captures the browser state at the moment TestNG reports the failure and keeps screenshot collection centralized across the suite.
Prerequisites
- Java, Selenium WebDriver, and TestNG are available to the test project.
- Your tests can retrieve the WebDriver used by the failing test.
- The test process can write to an artifacts directory.
Selenium exposes screenshots through the TakesScreenshot API. TestNG’s listener documentation describes listener registration and failure callbacks.
Complete Java listener implementation
The following example uses a project-specific DriverManager. Replace that lookup with the mechanism your tests use. The listener logs capture errors separately so a screenshot problem does not replace the original assertion failure.
package example;
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.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.time.Instant;
import java.util.UUID;
public final class ScreenshotListener implements ITestListener {
@Override
public void onTestFailure(ITestResult result) {
WebDriver driver = DriverManager.getDriver();
if (driver == null) {
System.err.println("Screenshot skipped: no WebDriver for " + result.getName());
return;
}
if (!(driver instanceof TakesScreenshot)) {
System.err.println("Screenshot skipped: driver does not support TakesScreenshot");
return;
}
String safeTestName = result.getName().replaceAll("[^a-zA-Z0-9._-]", "_");
String fileName = safeTestName + "-" + Instant.now().toEpochMilli()
+ "-" + UUID.randomUUID() + ".png";
Path target = Paths.get("artifacts", "screenshots", fileName);
try {
Files.createDirectories(target.getParent());
File source = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
Files.copy(source.toPath(), target, StandardCopyOption.REPLACE_EXISTING);
System.out.println("Screenshot saved: " + target.toAbsolutePath());
} catch (RuntimeException | IOException captureError) {
System.err.println("Screenshot capture failed for " + result.getName()
+ ": " + captureError.getMessage());
}
}
}
DriverManager.getDriver() is intentionally not defined by Selenium or TestNG. It must return the driver belonging to the current test. A typical thread-local manager looks like this:
package example;
import org.openqa.selenium.WebDriver;
public final class DriverManager {
private static final ThreadLocal<WebDriver> CURRENT = new ThreadLocal<>();
private DriverManager() {}
public static void setDriver(WebDriver driver) {
CURRENT.set(driver);
}
public static WebDriver getDriver() {
return CURRENT.get();
}
public static void removeDriver() {
CURRENT.remove();
}
}
Set the driver before the test starts and remove it after the test finishes. The exact setup depends on whether your suite uses @BeforeMethod, a factory, a base class, or a dependency-injection container.
Register the listener
Option 1: testng.xml
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="UI suite">
<listeners>
<listener class-name="example.ScreenshotListener" />
</listeners>
<test name="Checkout tests">
<classes>
<class name="example.CheckoutTest" />
</classes>
</test>
</suite>
Use this when the listener should apply to every class launched by the suite file.
Option 2: @Listeners
package example;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
@Listeners(ScreenshotListener.class)
public final class LoginTest {
@Test
public void invalidPasswordShowsError() {
// test steps
}
}
Use the annotation when the listener belongs to one test class or a selected group of classes.
A complete test lifecycle with driver cleanup
package example;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
public abstract class SeleniumTestBase {
@BeforeMethod
public void startBrowser() {
WebDriver driver = new ChromeDriver();
DriverManager.setDriver(driver);
}
@AfterMethod(alwaysRun = true)
public void stopBrowser() {
WebDriver driver = DriverManager.getDriver();
try {
if (driver != null) {
driver.quit();
}
} finally {
DriverManager.removeDriver();
}
}
}
Keep the browser alive until onTestFailure has run. If teardown quits the driver before the listener executes, the listener cannot capture the failing page. TestNG listener and teardown ordering can vary with suite design, so verify the lifecycle in your own runner.
Driver screenshots versus element screenshots
A driver screenshot captures the browser view supported by the driver. Selenium also allows an individual element to implement TakesScreenshot:
WebElement price = driver.findElement(By.cssSelector("[data-testid='price']"));
File source = ((TakesScreenshot) price).getScreenshotAs(OutputType.FILE);
Path target = Paths.get("artifacts", "price.png");
Files.copy(source.toPath(), target, StandardCopyOption.REPLACE_EXISTING);
Choose an element screenshot when the failure concerns one component and a full browser view would add noise. Element capture can fail when the driver or browser does not support that operation.
What screenshot scope should you expect?
Do not assume every implementation returns a full-page image. Selenium documents best-effort behavior for non-W3C-conformant drivers, including the entire page, current window, visible portion of the current frame, or the display containing the browser. A driver may also throw WebDriverException or UnsupportedOperationException when capture fails or is unsupported. Check the browser and remote-driver behavior used by your CI environment.
Parallel tests and artifact naming
Parallel TestNG runs make a shared static driver unsafe: one test can overwrite another test’s driver reference, and two failures can write the same filename. Use a ThreadLocal<WebDriver> or another test-to-driver mapping and include enough identity in each path.
| Problem | Safer choice |
|---|---|
| Shared browser variable | Thread-local or per-test driver storage |
| Duplicate test names | Add timestamp, UUID, invocation number, or run ID |
| Missing parent directory | Call Files.createDirectories before copying |
| Artifacts mixed across jobs | Prefix paths with build, shard, or suite identifiers |
Attach screenshots to CI reports
The listener creates files, but files can disappear when a CI workspace is discarded. Configure your CI system or test reporter to publish artifacts/screenshots after the test command, including when tests fail. The exact configuration is CI-specific. Always print the absolute path so a local run and a CI log both identify the artifact.
Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
ClassCastException when casting the driver |
The implementation does not implement TakesScreenshot. |
Check with instanceof and use a driver/browser that supports screenshots. |
UnsupportedOperationException |
The selected driver or element does not support capture. | Use a supported implementation or capture a supported scope; keep the original test failure. |
WebDriverException during capture |
The browser session ended, disconnected, or rejected the command. | Capture before quitting the driver, inspect remote-driver logs, and retry only if your failure policy allows it. |
| No image appears after a failure | Listener was not registered, the callback could not find the driver, or the process lacks write access. | Verify XML or annotation registration, log the driver lookup, create the directory, and check filesystem permissions. |
| Wrong browser appears in the image | A shared driver reference was overwritten during parallel execution. | Use thread-local or per-test driver association. |
| Only the viewport is captured | Full-page capture is not guaranteed by every driver. | Confirm the driver’s supported screenshot behavior; use page-specific scrolling or browser capabilities only when supported. |
| Screenshot failure hides the assertion | Capture exceptions escaped from the listener. | Catch and log capture errors inside onTestFailure; never replace ITestResult‘s original throwable. |
| Files overwrite each other | Filename uses only the test method name. | Add a timestamp, UUID, invocation number, or CI run identifier. |
| Screenshot is blank or stale | Capture occurs before the page finishes rendering or after teardown. | Wait for the page state needed by the test and capture while the failing session is still available. |
Performance, reliability, and cost considerations
- Performance: each capture adds browser and filesystem work. Capture on failure by default; avoid capturing every passing test unless a diagnostic requirement justifies it.
- Reliability: treat screenshots as diagnostic evidence. Keep the test’s original exception and record capture failures separately.
- Storage: retain only the artifacts needed by your debugging and compliance policy. Large suites can produce many images during repeated retries.
- Remote browsers: network latency and session disconnects make capture less reliable than in-process runs. Publish artifacts from the machine that actually received the screenshot file.
- Security: screenshots can contain credentials, personal data, or tokens rendered by the application. Restrict artifact access and redact sensitive test data before capture where possible.
Or skip the browser setup
ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns PNG, JPEG, WebP, or PDF output, so you can capture a URL without managing a Selenium browser. Cookie and consent banners are accepted and removed before capture, along with more than 60 known consent platforms, newsletter popups, and chat widgets. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. Its MCP server lets Claude, Cursor, and other MCP clients use take_screenshot, get_page_info, and capture_pdf.
See the ScreenshotNeo API documentation for authentication and options.
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 includes full-page capture with lazy images loaded, CSS-selector element capture, custom JavaScript and CSS, waits, request blocking, headers, cookies, user agents, geolocation, timezone, PDFs, caching, signed links, asynchronous jobs, webhooks, bulk capture, and a usage API. The Free plan includes 1,000 screenshots each month with no card; paid plans start at $5 for 3,000 screenshots. Create a free ScreenshotNeo account.
FAQ
Does TestNG take screenshots automatically?
No. Add an ITestListener and capture the driver in onTestFailure, then register the listener.
Can I capture a screenshot for skipped tests?
You can implement onTestSkipped, but use it only when a skipped test has a useful browser state. A skipped test may never create a browser session.
Can the listener change the test result?
It should not. Save the image and log secondary errors while preserving the original failure reported by TestNG.
Where should screenshots be stored?
Use a dedicated artifacts directory with unique names, then publish that directory through your CI system.
Is a full-page screenshot guaranteed?
No. Screenshot scope is implementation-dependent, especially for non-W3C-conformant drivers. Verify the behavior of the browser and driver used by your suite.


