QA How-To
Java for Testers: TestNG listeners (2026)
Learn java testers TestNG listeners with lifecycle hooks, parallel-safe evidence capture, registration options, reporting patterns, and runnable examples.
22 min read | 2,880 words
TL;DR
Java testers TestNG listeners are event hooks for observing test, configuration, method, suite, and execution lifecycle changes. Implement only the interfaces you need, keep shared state thread-safe, register the listener consistently, and capture evidence before cleanup without changing the underlying test result.
Key Takeaways
- Choose the narrowest TestNG listener interface that matches the event you need.
- Keep assertions and business steps in tests, while listeners handle cross-cutting observation and evidence.
- Register listeners through one deliberate mechanism so local, IDE, Maven, and CI runs behave consistently.
- Capture screenshots and other failure artifacts before WebDriver teardown, with unique parallel-safe filenames.
- Treat listener objects as shared infrastructure unless you have proved their instance scope, and use thread-safe state.
- Test listeners with synthetic TestNG runs and forced failures instead of trusting report output by inspection.
- Make listener failures visible without allowing optional reporting code to mask the original test result.
Java testers TestNG listeners provide a clean way to observe a TestNG run and apply cross-cutting behavior such as logging, screenshots, metrics, audit records, and custom reporting. A listener should describe what happened around a test, not contain the business steps or decide that a failed assertion is actually a pass.
The difficult part is not implementing onTestFailure. It is choosing the correct lifecycle callback, registering it everywhere, handling configuration failures, and remaining correct when TestNG runs methods in parallel. This guide builds that mental model and a small listener package using real TestNG APIs.
TL;DR
| Need | Best starting interface | Important caution |
|---|---|---|
| Test pass, fail, skip events | ITestListener |
Configuration failures use different callbacks |
| Before and after every invoked method | IInvokedMethodListener |
Test and configuration methods both appear |
| Suite start and finish | ISuiteListener |
A suite can contain several <test> blocks |
| Whole TestNG process boundary | IExecutionListener |
Keep startup and shutdown small and reliable |
| Change annotations during discovery | IAnnotationTransformer |
Register it early, not with @Listeners |
| Custom report after execution | IReporter |
Treat result collections as read-only evidence |
A production listener should be deterministic, thread-safe, inexpensive, and unable to hide the original failure. Start with one observable requirement, such as attaching a screenshot when a UI test fails, then verify the complete lifecycle with a controlled failing test.
1. Java Testers TestNG Listeners and the Event Model
TestNG discovers tests, builds suites and method invocations, runs configuration methods, runs test methods, calculates results, and generates reports. Listener interfaces expose selected points in that sequence. They are callbacks invoked by TestNG, so your code does not call onTestFailure directly during a normal run.
The major event scopes are different. IExecutionListener surrounds the complete TestNG execution. ISuiteListener surrounds an ISuite. ITestListener observes test result events associated with an ITestContext. IInvokedMethodListener surrounds each invoked test or configuration method. IConfigurationListener reports configuration outcomes. IReporter receives completed suites when TestNG generates reports.
This distinction prevents common design errors. A failed @BeforeMethod may prevent the test body from running, so expecting onTestFailure to capture every setup problem is unsafe. An IInvokedMethodListener sees configuration methods, but must inspect IInvokedMethod.isTestMethod() or isConfigurationMethod() before applying test-specific logic. A suite finish callback is suitable for closing a suite-scoped client, but not for quitting a browser owned by one test method.
Listeners work best for cross-cutting concerns. Logging a stable case ID, recording duration, publishing an artifact path, and updating a run summary are cross-cutting. Clicking a checkout button, creating order data, and asserting the total belong to test or domain code. If a listener needs to know product workflow details, its responsibility is probably too broad.
For the related runner concepts, review the TestNG annotations and lifecycle guide. The listener model becomes much easier once configuration scope and ordering are explicit.
2. Set Up a Runnable TestNG Listener Example in Java
A listener can be a normal Java class that implements one or more TestNG interfaces. The following Maven configuration uses a real TestNG 7 release and Java 17 language level. If your project manages a newer compatible TestNG release, keep that centrally managed version.
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>testng-listener-demo</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.release>17</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>7.11.0</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Create a small listener under src/test/java/example/listeners/ConsoleResultListener.java:
package example.listeners;
import org.testng.ITestListener;
import org.testng.ITestResult;
public final class ConsoleResultListener implements ITestListener {
@Override
public void onTestStart(ITestResult result) {
System.out.printf("START %s%n", qualifiedName(result));
}
@Override
public void onTestSuccess(ITestResult result) {
System.out.printf("PASS %s durationMs=%d%n",
qualifiedName(result), durationMs(result));
}
@Override
public void onTestFailure(ITestResult result) {
System.err.printf("FAIL %s durationMs=%d cause=%s%n",
qualifiedName(result), durationMs(result),
result.getThrowable());
}
private static String qualifiedName(ITestResult result) {
return result.getMethod().getQualifiedName();
}
private static long durationMs(ITestResult result) {
return result.getEndMillis() - result.getStartMillis();
}
}
Register it on a test class:
package example.tests;
import example.listeners.ConsoleResultListener;
import org.testng.Assert;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
@Listeners(ConsoleResultListener.class)
public class PriceTest {
@Test
public void totalIncludesTax() {
Assert.assertEquals(108, 100 + 8);
}
}
Run mvn test. The listener observes the result without changing the assertion or surrounding it with a try and catch block.
3. Use ITestListener for Result and Context Events
ITestListener is the usual entry point because it exposes test start, success, failure, skip, success-percentage failure, and context start and finish callbacks. ITestResult is the evidence object. It includes the test method, parameters, instance, throwable, timing, status, and an attribute map. ITestContext provides the current <test> context, result maps, included methods, and suite reference.
Use result attributes to attach small metadata for another listener or report adapter. Names should be namespaced to avoid collisions. For example:
@Override
public void onTestStart(ITestResult result) {
result.setAttribute("qajobfit.workerThread",
Thread.currentThread().getName());
}
Do not store large screenshots directly in attributes when a path or artifact identifier is enough. A byte array retained for every result can increase heap usage in a long suite. Write the file through an artifact service, then attach a safe relative path. Never attach tokens, passwords, full cookies, or unredacted customer data.
TestNG can invoke onTestFailedButWithinSuccessPercentage when successPercentage and repeated invocation settings allow some failures. That outcome is neither a normal pass nor a final failure. If your organization does not use success percentages, log it explicitly rather than merging it into success. Also handle onTestFailedWithTimeout if you need timeout-specific classification. The default method ultimately represents a failed result, but a separate callback can improve diagnostics.
At onFinish(ITestContext), use getPassedTests(), getFailedTests(), and getSkippedTests() to build a summary. Avoid removing results or rewriting their statuses to make a dashboard green. A custom dashboard can calculate a logical outcome while retaining raw events. If retries are enabled, preserve every attempt and label a passed retry as flaky evidence. The TestNG retry analyzer guide explains that relationship.
4. Observe Configuration Methods and Every Invocation
A test listener does not replace configuration failure handling. IConfigurationListener provides onConfigurationSuccess, onConfigurationFailure, and onConfigurationSkip. Use it when setup and teardown failures must appear as first-class infrastructure events. A failed login fixture and a failed business assertion have different owners and should not share a vague failure label.
IInvokedMethodListener is useful for uniform before and after behavior around both tests and configurations. The callback receives IInvokedMethod plus ITestResult. Filter by method type before recording metrics.
package example.listeners;
import org.testng.IInvokedMethod;
import org.testng.IInvokedMethodListener;
import org.testng.ITestResult;
public final class InvocationTimingListener
implements IInvokedMethodListener {
private static final String START_NANOS =
"qajobfit.invocationStartNanos";
@Override
public void beforeInvocation(IInvokedMethod method,
ITestResult result) {
result.setAttribute(START_NANOS, System.nanoTime());
}
@Override
public void afterInvocation(IInvokedMethod method,
ITestResult result) {
Object value = result.getAttribute(START_NANOS);
if (!(value instanceof Long start)) {
return;
}
long elapsedNanos = System.nanoTime() - start;
String type = method.isTestMethod()
? "test" : "configuration";
System.out.printf("INVOKED type=%s method=%s ms=%.3f%n",
type, method.getTestMethod().getQualifiedName(),
elapsedNanos / 1_000_000.0);
}
}
System.nanoTime() is appropriate for elapsed time because it is monotonic within the process. Use wall-clock timestamps for correlation with external logs, not duration math. The attribute belongs to the current result, so simultaneous invocations do not share a timer.
An invoked-method listener can also establish logging context, but always clear thread-bound logging state in afterInvocation inside a finally block. TestNG reuses worker threads. Context left behind can give the next test the wrong case ID. Avoid opening browser sessions in a generic listener unless that listener is explicitly the owner of UI lifecycle and its configuration-failure behavior has been tested. Normal @BeforeMethod and @AfterMethod(alwaysRun = true) methods are often clearer.
5. Choose Suite, Execution, and Reporting Listeners Correctly
Use ISuiteListener for resources that truly belong to one suite, such as a suite result directory or a suite-scoped read-only metadata snapshot. onStart(ISuite) runs before suite tests, and onFinish(ISuite) runs after them. Do not assume only one suite exists in a TestNG execution.
IExecutionListener has onExecutionStart() and onExecutionFinish(). It is suited to lightweight process-wide initialization, final metrics flushing, or validation that required runtime configuration exists. Keep it resilient. If a final network publish fails, log and retain the local report. Do not overwrite a real test failure with an opaque reporting exception. If reporting is a release gate, model that as a separate explicit pipeline step.
IReporter.generateReport(...) runs after suites complete and receives XML suite definitions, suite results, and an output directory. It is a report generator, not a live event stream. Use it to build HTML, JSON, or another summary from completed evidence. Escape test names and throwable messages before writing HTML. Normalize output paths and never use a test-supplied string as an unrestricted filesystem path.
| Interface | Scope | Typical QA use | Poor use |
|---|---|---|---|
ITestListener |
Test result and <test> context |
Status, failure evidence, result metadata | Browser business workflow |
IConfigurationListener |
Configuration result | Setup and teardown diagnostics | Replacing configuration methods |
IInvokedMethodListener |
Every invocation | Timing, logging context, universal tracing | Unfiltered test-only logic |
ISuiteListener |
One suite | Suite directory, aggregate resource | Per-method driver cleanup |
IExecutionListener |
Full TestNG run | Global validation, final flush | Slow remote orchestration |
IReporter |
Completed suites | Custom static report | Live screenshot capture |
One class can implement several interfaces, but splitting unrelated concerns is easier to test. A timing listener, artifact listener, and report generator can evolve independently. Use composition when they need the same safe helper.
6. Register TestNG Listeners Without Surprises
TestNG supports several registration paths. @Listeners is visible beside the test class and works well for class-oriented behavior. A <listeners> entry in testng.xml applies to the suite. Maven Surefire can pass listener classes through TestNG properties. The Java service provider mechanism can discover listeners packaged with META-INF/services/org.testng.ITestNGListener. Programmatic runners can call TestNG.addListener(...).
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="Regression" parallel="methods" thread-count="4">
<listeners>
<listener class-name="example.listeners.ConsoleResultListener"/>
<listener class-name="example.listeners.InvocationTimingListener"/>
</listeners>
<test name="Checkout">
<packages>
<package name="example.tests"/>
</packages>
</test>
</suite>
Choose one authoritative registration path for mandatory framework listeners. Registering the same class through an annotation and suite XML can produce duplicate callbacks, duplicate screenshots, and doubled metrics. Add an integration test that counts one start and one terminal event for a known test.
Annotation transformers are the important exception. IAnnotationTransformer changes annotations while TestNG discovers tests. Because TestNG needs it early, do not register a transformer through @Listeners. Use suite XML, the command line, service loading, or programmatic registration. This matters for retry analyzers and dynamically assigned groups.
Inherited @Listeners behavior can also surprise teams. If a base class declares listeners, subclasses receive them. That can be helpful for a framework base class but hidden to a test author. Document mandatory listeners and verify IDE runs match Maven and CI. A listener that exists only in testng.xml will not run when a developer executes one class directly unless the IDE uses that suite.
7. Capture Selenium Screenshots on Failure Safely
Screenshot listeners fail when they cannot reach the correct driver, run after teardown, or overwrite files in parallel. Define ownership first. The test lifecycle owns one driver per invocation. The listener obtains that invocation's driver through a narrow provider and captures evidence before quit(). If your teardown quits first, move capture earlier or call a dedicated artifact service from teardown.
This listener uses a small interface implemented by UI test classes. It does not depend on a static global driver.
package example.listeners;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.UUID;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
import org.testng.ITestListener;
import org.testng.ITestResult;
public final class ScreenshotListener implements ITestListener {
public interface DriverProvider {
WebDriver driver();
}
@Override
public void onTestFailure(ITestResult result) {
Object instance = result.getInstance();
if (!(instance instanceof DriverProvider provider)) {
return;
}
WebDriver driver = provider.driver();
if (!(driver instanceof TakesScreenshot camera)) {
return;
}
byte[] png = camera.getScreenshotAs(OutputType.BYTES);
Path directory = Path.of("target", "test-artifacts");
String safeMethod = result.getMethod().getMethodName()
.replaceAll("[^A-Za-z0-9._-]", "_");
Path destination = directory.resolve(
safeMethod + "-" + UUID.randomUUID() + ".png");
try {
Files.createDirectories(directory);
Files.write(destination, png);
result.setAttribute("qajobfit.screenshot",
destination.toString());
} catch (IOException error) {
System.err.println("Screenshot write failed: " + error);
}
}
}
The Selenium APIs are real: TakesScreenshot.getScreenshotAs(OutputType.BYTES). The unique suffix prevents concurrent collisions. The path is generated by the framework, not arbitrary test input. Production code should guard a provider whose setup failed, redact sensitive pages where policy requires it, and capture browser logs or page source separately.
The listener catches only artifact-write failure. It does not change the result status. If getScreenshotAs itself can throw in your environment, wrap the complete optional capture path while preserving the original throwable in ITestResult. Pair this design with a ThreadLocal WebDriver lifecycle when TestNG methods run in parallel.
8. Make Listener State Parallel-Safe and Bounded
Assume a listener instance can receive callbacks from multiple worker threads. Fields such as int passed, ArrayList<Result> results, SimpleDateFormat formatter, or a mutable report writer are unsafe without coordination. Use LongAdder or AtomicInteger for counters, concurrent collections for bounded metadata, immutable event records, and thread-safe date and time APIs.
package example.listeners;
import java.util.concurrent.atomic.LongAdder;
import org.testng.ITestListener;
import org.testng.ITestResult;
public final class CountListener implements ITestListener {
private final LongAdder passed = new LongAdder();
private final LongAdder failed = new LongAdder();
@Override
public void onTestSuccess(ITestResult result) {
passed.increment();
}
@Override
public void onTestFailure(ITestResult result) {
failed.increment();
}
public long passedCount() {
return passed.sum();
}
public long failedCount() {
return failed.sum();
}
}
Thread safety is more than avoiding exceptions. Event ordering across workers is nondeterministic. Do not infer a global test sequence from callback arrival. Store each event with test ID, invocation ID, attempt, thread, and timestamp, then sort or aggregate during report generation. A start event for test B can occur before the pass event for test A.
Bound memory. A listener that retains every screenshot byte, page source, response body, and stack trace can exhaust the JVM during a large regression. Write artifacts incrementally, keep identifiers in memory, limit captured response size, and redact secrets. Use a queue and dedicated writer only when required, with backpressure and a guaranteed flush strategy. A background writer that silently drops the last events at JVM shutdown is not reliable reporting.
Do not synchronize one large callback method around network upload. That serializes parallel tests and couples result timing to the report service. Prefer local durable artifacts during execution and an explicit publish step afterward.
9. Test and Debug Custom TestNG Listeners
A listener deserves tests because it can affect every scenario. Unit-test pure helpers such as filename sanitization, status mapping, redaction, and duration formatting. Then run TestNG programmatically against controlled sample classes and inspect captured events.
package example.listeners;
import java.util.concurrent.CopyOnWriteArrayList;
import org.testng.ITestListener;
import org.testng.ITestResult;
import org.testng.TestNG;
import org.testng.annotations.Test;
public class ListenerIntegrationCheck {
public static final class Sample {
@Test public void passes() { }
@Test public void fails() { throw new AssertionError("expected"); }
}
public static void main(String[] args) {
var events = new CopyOnWriteArrayList<String>();
ITestListener listener = new ITestListener() {
@Override public void onTestSuccess(ITestResult result) {
events.add("PASS:" + result.getName());
}
@Override public void onTestFailure(ITestResult result) {
events.add("FAIL:" + result.getName());
}
};
TestNG testng = new TestNG();
testng.setUseDefaultListeners(false);
testng.setTestClasses(new Class<?>[] { Sample.class });
testng.addListener(listener);
testng.run();
if (!(events.contains("PASS:passes")
&& events.contains("FAIL:fails"))) {
throw new AssertionError("Unexpected events: " + events);
}
}
}
The sample intentionally fails one TestNG test, so the TestNG run reports failure while the outer main verifies listener evidence. In a normal build, place fixture classes where their intentional failure does not make the main test phase fail, or drive them from a dedicated integration-test module.
Add cases for skipped tests, failed @BeforeMethod, failed @AfterMethod, DataProvider rows, retries, timeouts, and method parallelism. Force screenshot capture to throw and prove the original assertion remains available. Run the same selected test through the IDE-equivalent class path and suite XML to expose registration gaps.
When callbacks appear duplicated, search every registration mechanism and inherited base class. When no screenshot exists, compare event timing with teardown and confirm the listener sees the test instance that owns the driver. When counts disagree, inspect retries and configuration failures before blaming concurrency.
10. Java Testers TestNG Listeners Design Checklist
Start with a written event contract: event name, source callback, required fields, thread expectations, failure behavior, and retention. A test.failed event might require case ID, qualified method, parameters represented by safe IDs, attempt number, throwable type, artifact paths, environment, and build revision. It should never require a full password-bearing request dump.
Keep callbacks fast. Compute small metadata and persist locally. Put slow aggregation in IReporter or a post-test build step. Preserve the TestNG status and throwable. If a listener exception is unavoidable, add context and make the failure mode deliberate rather than letting a NullPointerException obscure the test.
Use stable identifiers. result.getName() can be too short when classes contain similar methods. Prefer getQualifiedName() plus a safe parameter case ID. Do not use Arrays.toString(parameters) if parameters include secrets or mutable objects. Test filenames on all supported operating systems.
Decide ownership of teardown. Evidence capture must occur while the browser or client is alive, and resource cleanup must still run if capture fails. One reliable sequence is test failure -> listener records event -> teardown captures final resource-specific evidence if needed -> teardown quits -> reporter aggregates. Document any retry interaction because each attempt needs a distinct artifact.
Finally, monitor the observer. Track listener exceptions, dropped event count, publish failures, artifact size, and missing start or finish pairs. Reporting infrastructure is part of test reliability. It should have health signals instead of failing silently.
Interview Questions and Answers
Q: What is a TestNG listener?
A TestNG listener implements callback interfaces that TestNG invokes during discovery or execution. I use listeners for cross-cutting observation such as logs, artifacts, metrics, and reports. Test steps and assertions remain in test or domain code.
Q: What is the difference between ITestListener and IInvokedMethodListener?
ITestListener focuses on test result and context events. IInvokedMethodListener surrounds every invoked test and configuration method, so I filter the method type before applying logic.
Q: How do you capture a screenshot on failure?
I obtain the invocation-owned driver, call Selenium's TakesScreenshot, and write to a unique safe artifact path before teardown quits the session. Capture errors are logged but never replace the original test throwable.
Q: Are TestNG listeners thread-safe?
TestNG does not make custom mutable fields safe. I assume callbacks can arrive concurrently, use thread-safe counters or collections, and avoid relying on global callback order.
Q: How can listeners be registered?
Common choices are @Listeners, testng.xml, build-tool properties, service loading, or programmatic addListener. I select one mandatory path and test for duplicate registration.
Q: Why should I not register IAnnotationTransformer with @Listeners?
The transformer must be available while TestNG reads annotations. @Listeners is discovered too late for that role, so I use suite XML, service loading, command-line, or programmatic registration.
Q: How do you handle a listener failure?
For optional evidence, I catch the narrow infrastructure exception, log it with the test ID, and preserve the original result. Critical publication happens as an explicit build step with its own status and retry policy.
Q: How do you test a listener?
I unit-test pure transformations and run a small programmatic TestNG suite containing controlled pass, fail, skip, configuration failure, retry, and parallel cases. I assert the exact events and verify cleanup even when artifact capture fails.
Common Mistakes
- Putting browser actions or business assertions inside listener callbacks.
- Assuming
onTestFailurereceives failed setup and teardown methods. - Registering the same listener through annotations and suite configuration.
- Registering
IAnnotationTransformerwith@Listeners. - Sharing
ArrayList, report writers, formatters, or integer counters across parallel callbacks. - Capturing screenshots after the WebDriver session has already quit.
- Using one filename such as
failure.pngfor parallel methods and retries. - Logging complete parameters, cookies, environment variables, or response bodies without redaction.
- Changing failed results to skipped or passed to improve a report.
- Performing slow remote uploads synchronously in every callback.
- Allowing an optional reporting exception to mask the original product failure.
- Testing only successful methods and missing configuration, skip, retry, and timeout paths.
Conclusion
Java testers TestNG listeners are most valuable as small, reliable observers of a well-defined lifecycle. Choose the narrowest interface, register it consistently, keep state parallel-safe, preserve raw results, and capture invocation-specific evidence while resources are still alive.
Implement one listener around a controlled failure first. Prove exactly one callback sequence, a unique artifact, successful teardown, and correct behavior under parallel execution. Once that foundation is trustworthy, richer reports and metrics can grow without turning the listener layer into hidden test logic.
Interview Questions and Answers
What is a TestNG listener?
A TestNG listener is a class that implements one or more TestNG callback interfaces. TestNG invokes it at defined lifecycle events. I use it for cross-cutting observation and keep product workflow in test code.
Compare ITestListener and IInvokedMethodListener.
`ITestListener` reports test outcomes and context boundaries. `IInvokedMethodListener` runs before and after every invoked test or configuration method. I filter `isTestMethod()` and `isConfigurationMethod()` so metrics and setup diagnostics are classified correctly.
How would you capture failure screenshots in parallel TestNG tests?
Each invocation owns a separate driver. The listener resolves that driver, captures bytes with `TakesScreenshot`, and writes a filename containing a safe test ID plus a unique suffix. Capture happens before quit, and capture errors never replace the assertion failure.
How do you register listeners?
I can use `@Listeners`, suite XML, build configuration, service loading, or programmatic `addListener`. I choose one mandatory registration path, document IDE behavior, and test that each callback fires once.
Why is IAnnotationTransformer special?
It must be registered before annotation discovery. Therefore I do not use `@Listeners` for it. I register it through XML, service loading, the command line, or the programmatic runner.
How do you make a custom listener parallel-safe?
I treat the listener as shared and avoid mutable non-thread-safe fields. I use result-local attributes, immutable event records, concurrent collections, and atomic counters. I also avoid inferring global order from concurrent callbacks.
What happens when a BeforeMethod fails?
The test body may not run and can be skipped, while the configuration receives its own failed result. I use `IConfigurationListener` or invoked-method callbacks to capture setup failure evidence rather than relying only on `onTestFailure`.
Should listeners perform assertions?
Generally no. Assertions express scenario expectations and belong in tests or assertion helpers. A listener can validate its own required infrastructure, but it should not hide scenario behavior or reinterpret a product result.
Frequently Asked Questions
What are TestNG listeners used for?
TestNG listeners observe execution events and support cross-cutting behavior such as logging, screenshots, metrics, and reports. They should not contain business workflows or replace test assertions.
Which TestNG listener captures failed tests?
`ITestListener.onTestFailure(ITestResult)` is the common callback for a failed test method. Configuration failures should also be observed with `IConfigurationListener` when setup and teardown evidence matters.
How do I register a TestNG listener?
Use `@Listeners`, a `<listeners>` entry in `testng.xml`, build-tool properties, Java service loading, or `TestNG.addListener`. Pick one authoritative mechanism for mandatory listeners to avoid duplicate callbacks.
Can a TestNG listener take a Selenium screenshot?
Yes. In `onTestFailure`, obtain the current invocation's live WebDriver and use `TakesScreenshot`. Write a unique parallel-safe file before teardown quits the session, and preserve the original failure if capture fails.
Do TestNG listeners work with parallel tests?
The callbacks work, but custom mutable listener state must be thread-safe. Avoid assumptions about global callback ordering and use stable invocation IDs, concurrent structures, and unique artifact paths.
Why is my TestNG listener called twice?
The class may be registered through more than one mechanism, such as both `@Listeners` and `testng.xml`, or inherited from a base class. Audit every registration path and add an integration check for exactly one start and terminal event.
Can a listener change a TestNG result?
Some APIs expose mutable result status, but changing raw failures to improve reporting destroys evidence. Preserve the TestNG outcome and calculate any separate logical or flaky status in the reporting layer.
Related Guides
- Java for Testers: Builder pattern for test data (2026)
- Java for Testers: Generics for frameworks (2026)
- Java for Testers: Maven profiles for suites (2026)
- Java for Testers: retry analyzer in TestNG (2026)
- Java for Testers: TestNG DataProvider (2026)
- Java for Testers: TestNG groups and parallel (2026)