Implement a class that implements the TestNG listener interface for the event you need, register it with TestNG, and use the callback to act on the test result. For Selenium failure screenshots, an ITestListener can capture the failing test’s WebDriver session—but save the screenshot before teardown calls quit().
Choose the listener that matches the event
TestNG provides several interfaces for changing or observing its behavior. Choose by lifecycle scope rather than using one listener for every task.
As an Amazon Associate I earn from qualifying purchases.
| Need | Interface | When it applies |
|---|---|---|
| React to test method start, pass, failure, or skip as execution proceeds | ITestListener |
Receives test outcome notifications during execution. |
| Handle suite start and finish | ISuiteListener |
Provides suite boundary callbacks. |
| Observe class processing boundaries | IClassListener |
Runs around class processing. |
| Observe setup or teardown configuration outcomes | IConfigurationListener |
Reports configuration method invocation and whether it passes, fails, or is skipped. |
| Build an aggregate report after execution | IReporter |
Receives run information after suites have run. |
| Modify supported annotations before tests execute | IAnnotationTransformer |
Must be registered early, before TestNG parses annotations. |
For example, use ITestListener for live progress messages and failure artifacts, but choose IReporter if the report needs the completed run’s results. TestNG’s listener documentation describes the available interfaces and their purposes.
Implement an ITestListener
For test-level events, create a Java class implementing ITestListener and override only the callbacks the suite needs. The following minimal example logs a failure; it is runnable once placed in the project’s source tree with TestNG on the classpath. It does not depend on Selenium.
#1 Best Overall
package com.example;
import org.testng.ITestListener;
import org.testng.ITestResult;
public class LoggingListener implements ITestListener {
@Override
public void onTestFailure(ITestResult result) {
System.err.println("FAILED: " + result.getName());
if (result.getThrowable() != null) {
result.getThrowable().printStackTrace();
}
}
@Override
public void onTestSuccess(ITestResult result) {
System.out.println("PASSED: " + result.getName());
}
@Override
public void onTestSkipped(ITestResult result) {
System.out.println("SKIPPED: " + result.getName());
}
}
The exact dependency and version depend on your project’s Java, Selenium, and TestNG setup; verify them against the versions already in use rather than copying an assumed version into a build file.
Register the listener with TestNG
For an explicit suite-wide setup, add the listener to the suite XML file and use the listener’s fully qualified class name:
Rank #2
<suite name="UI suite">
<listeners>
<listener class-name="com.example.LoggingListener" />
</listeners>
<test name="Browser tests">
<classes>
<class name="com.example.LoginTest" />
</classes>
</test>
</suite>
Run the suite using the project’s existing TestNG runner or build configuration. A successful registration produces the listener’s output when the matching test events occur.
Use @Listeners when its scope is appropriate
TestNG also supports the @Listeners annotation on a test class:
Rank #3
package com.example;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
@Listeners(LoggingListener.class)
public class LoginTest {
@Test
public void loginWorks() {
// Test steps
}
}
TestNG documents this annotation as applying to the entire suite file as though it were configured in testng.xml. It may therefore have broader scope than a class-only registration suggests. If particular classes must be excluded, implement that filtering deliberately or use another registration arrangement.
Other supported registration routes
TestNG also supports programmatic listener registration and Java ServiceLoader discovery. ServiceLoader can make a listener available across projects, but classpath contents then affect test behavior; document that dependency so maintainers know where the listener comes from.
Rank #4
Special case: IAnnotationTransformer
Do not register IAnnotationTransformer with @Listeners. TestNG warns that it will be ignored there because it must be available before annotation parsing. Use suite XML or another supported early registration path instead.
Recommended Free Tools
Capture a Selenium screenshot when a test fails
Selenium’s Java API exposes screenshots through TakesScreenshot. In the failure callback, retrieve the WebDriver associated with the failing test, capture the image, and copy the temporary file to a durable artifact location before the driver is closed. TestNG does not prescribe how your test framework stores or shares its driver, so adapt the lookup below to your own driver lifecycle.
Best Value
package com.example;
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.testng.ITestListener;
import org.testng.ITestResult;
public class ScreenshotListener implements ITestListener {
@Override
public void onTestFailure(ITestResult result) {
WebDriver driver = DriverStore.current(); // Replace with your framework's driver lookup.
if (!(driver instanceof TakesScreenshot)) {
System.err.println("No screenshot-capable driver for " + result.getName());
return;
}
try {
File temporary = ((TakesScreenshot) driver)
.getScreenshotAs(OutputType.FILE);
Path directory = Path.of("target", "screenshots");
Files.createDirectories(directory);
Path destination = directory.resolve(safeName(result.getName()) + ".png");
Files.copy(temporary.toPath(), destination,
StandardCopyOption.REPLACE_EXISTING);
System.out.println("Failure screenshot: " + destination.toAbsolutePath());
} catch (IOException | RuntimeException e) {
System.err.println("Could not save screenshot for " + result.getName());
e.printStackTrace();
}
}
private static String safeName(String value) {
return value.replaceAll("[^A-Za-z0-9._-]", "_");
}
}
DriverStore.current() is project-specific, not a TestNG or Selenium method. Replace it with the mechanism your tests use to associate a driver with the current test. Selenium’s Java screenshot example uses getScreenshotAs(OutputType.FILE) and saves the returned temporary file; its screenshot documentation also demonstrates closing the driver after capture.
Keep screenshots tied to the correct test
- Save or copy the temporary screenshot immediately; do not rely on its temporary location as a durable artifact.
- Use a unique filename if retries, data-driven tests, or parallel execution can produce more than one result with the same method name. Include a stable test identifier or invocation data as appropriate.
- In parallel runs, isolate driver state per test or thread. A single shared global driver can cause the callback to capture another test’s browser.
- Arrange teardown so the failure callback can use the session before
quit()closes it. - Consider returning screenshot bytes with
OutputType.BYTESif your artifact system accepts a byte stream rather than a file; Selenium also documents base64 output.
These are integration choices: the TestNG callback supplies the result, while your test framework is responsible for driver ownership and artifact storage.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Or skip the browser setup
If you need a screenshot of a page rather than a screenshot tied to a live Selenium session, ScreenshotNeo offers a one-request screenshot API and an MCP server for AI agents. Its API can return PNG, JPEG, WebP, or PDF; the API key is passed as access_key. See the ScreenshotNeo documentation for request options.
curl -G "https://api.screenshotneo.com/v1/shot"
-d access_key=YOUR_API_KEY
--data-urlencode url=https://stripe.com
-o shot.webp
Before capture, it accepts consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each of those steps can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and responses indicate the page verdict and billing status in headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. The free plan includes 1,000 shots per month without a card; paid plans start at $5 for 3,000 shots. Sign up for ScreenshotNeo’s free plan.
Troubleshoot common listener and screenshot problems
| Symptom | Likely cause | What to check or change |
|---|---|---|
| No listener output appears | The listener is not registered, or its class name is wrong. | Confirm the fully qualified name in testng.xml, that the XML suite is the one being run, and that the listener class is on the test runtime classpath. |
| The listener runs more broadly than expected | @Listeners has suite-file scope in TestNG’s documented behavior. |
Use explicit suite XML registration or add deliberate filtering in the listener. |
| An annotation transformer has no effect | It was registered with @Listeners, which TestNG says ignores it. |
Register IAnnotationTransformer early through suite XML or another supported early path. |
| Screenshot capture fails or returns no useful image | The driver is already closed, is not associated with this result, or does not support screenshots. | Capture during failure handling before teardown; verify the per-test driver lookup and check that the driver implements TakesScreenshot. |
| Parallel failures overwrite screenshots or capture the wrong browser | Artifact names or driver state are shared between tests. | Use per-test or per-thread driver storage and unique artifact names that distinguish invocations. |
| A screenshot exists only temporarily | The returned file was not copied to persistent storage. | Create the artifact directory and copy the file before the test process or temporary-file lifecycle ends. |
| The failure screenshot is missing but the test report shows failure | The listener’s capture or file-copy code may itself have thrown an exception. | Log capture and filesystem exceptions separately, and confirm the process can write to the chosen artifact directory. |
Use the listener lifecycle deliberately
Keep each callback focused on its event: use suite callbacks for suite boundaries, class callbacks for class processing, configuration callbacks for setup and teardown results, and test callbacks for individual outcomes. For aggregated reports that can wait until execution is complete, use IReporter rather than trying to make a live listener serve both purposes.
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.




