0
# Event Listeners and Hooks
1
2
TestNG's comprehensive event system provides hooks for customizing test behavior through listener interfaces. This enables custom reporting, logging, test modification, and integration with external systems during test execution.
3
4
## Capabilities
5
6
### Test Execution Listeners
7
8
Core listener interfaces for monitoring test execution events and implementing custom behavior during test lifecycle.
9
10
```java { .api }
11
/**
12
* Primary listener interface for test execution events
13
*/
14
public interface ITestListener {
15
16
// Test method events
17
default void onTestStart(ITestResult result) {}
18
default void onTestSuccess(ITestResult result) {}
19
default void onTestFailure(ITestResult result) {}
20
default void onTestSkipped(ITestResult result) {}
21
default void onTestFailedButWithinSuccessPercentage(ITestResult result) {}
22
default void onTestFailedWithTimeout(ITestResult result) {}
23
24
// Test context events
25
default void onStart(ITestContext context) {}
26
default void onFinish(ITestContext context) {}
27
}
28
29
/**
30
* Suite-level listener for suite execution events
31
*/
32
public interface ISuiteListener {
33
default void onStart(ISuite suite) {}
34
default void onFinish(ISuite suite) {}
35
}
36
37
/**
38
* Configuration method listener for setup/teardown events
39
*/
40
public interface IConfigurationListener {
41
default void onConfigurationSuccess(ITestResult itr) {}
42
default void onConfigurationFailure(ITestResult itr) {}
43
default void onConfigurationSkip(ITestResult itr) {}
44
default void beforeConfiguration(ITestResult tr) {}
45
}
46
47
/**
48
* Class-level listener for test class events
49
*/
50
public interface IClassListener {
51
default void onBeforeClass(ITestClass testClass) {}
52
default void onAfterClass(ITestClass testClass) {}
53
}
54
```
55
56
**Usage Examples:**
57
58
```java
59
import org.testng.ITestListener;
60
import org.testng.ITestResult;
61
import org.testng.ITestContext;
62
import org.testng.ISuiteListener;
63
import org.testng.ISuite;
64
65
public class CustomTestListener implements ITestListener {
66
67
@Override
68
public void onStart(ITestContext context) {
69
System.out.println("Test execution started: " + context.getName());
70
System.out.println("Included groups: " + Arrays.toString(context.getIncludedGroups()));
71
System.out.println("Excluded groups: " + Arrays.toString(context.getExcludedGroups()));
72
}
73
74
@Override
75
public void onTestStart(ITestResult result) {
76
System.out.println("Starting test: " + result.getMethod().getMethodName());
77
System.out.println("Test class: " + result.getTestClass().getName());
78
System.out.println("Groups: " + Arrays.toString(result.getMethod().getGroups()));
79
}
80
81
@Override
82
public void onTestSuccess(ITestResult result) {
83
long duration = result.getEndMillis() - result.getStartMillis();
84
System.out.println("✓ Test passed: " + result.getMethod().getMethodName()
85
+ " (" + duration + "ms)");
86
}
87
88
@Override
89
public void onTestFailure(ITestResult result) {
90
System.out.println("✗ Test failed: " + result.getMethod().getMethodName());
91
System.out.println("Parameters: " + Arrays.toString(result.getParameters()));
92
93
Throwable throwable = result.getThrowable();
94
if (throwable != null) {
95
System.out.println("Error: " + throwable.getMessage());
96
throwable.printStackTrace();
97
}
98
99
// Take screenshot, log additional info, etc.
100
takeScreenshotOnFailure(result);
101
}
102
103
@Override
104
public void onTestSkipped(ITestResult result) {
105
System.out.println("⊘ Test skipped: " + result.getMethod().getMethodName());
106
107
Throwable throwable = result.getThrowable();
108
if (throwable != null) {
109
System.out.println("Reason: " + throwable.getMessage());
110
}
111
}
112
113
@Override
114
public void onFinish(ITestContext context) {
115
System.out.println("Test execution finished: " + context.getName());
116
System.out.println("Passed: " + context.getPassedTests().size());
117
System.out.println("Failed: " + context.getFailedTests().size());
118
System.out.println("Skipped: " + context.getSkippedTests().size());
119
120
// Generate custom reports
121
generateCustomReport(context);
122
}
123
124
private void takeScreenshotOnFailure(ITestResult result) {
125
// Screenshot logic for web tests
126
if (result.getInstance() instanceof WebTest) {
127
WebTest webTest = (WebTest) result.getInstance();
128
webTest.takeScreenshot(result.getMethod().getMethodName());
129
}
130
}
131
132
private void generateCustomReport(ITestContext context) {
133
// Custom reporting logic
134
System.out.println("Generating custom test report...");
135
}
136
}
137
138
public class CustomSuiteListener implements ISuiteListener {
139
140
@Override
141
public void onStart(ISuite suite) {
142
System.out.println("Suite started: " + suite.getName());
143
System.out.println("Output directory: " + suite.getOutputDirectory());
144
145
// Initialize suite-level resources
146
initializeDatabaseConnection();
147
setupTestEnvironment();
148
}
149
150
@Override
151
public void onFinish(ISuite suite) {
152
System.out.println("Suite finished: " + suite.getName());
153
154
// Collect overall statistics
155
Map<String, ISuiteResult> results = suite.getResults();
156
int totalPassed = 0, totalFailed = 0, totalSkipped = 0;
157
158
for (ISuiteResult result : results.values()) {
159
ITestContext context = result.getTestContext();
160
totalPassed += context.getPassedTests().size();
161
totalFailed += context.getFailedTests().size();
162
totalSkipped += context.getSkippedTests().size();
163
}
164
165
System.out.println("Suite Summary:");
166
System.out.println(" Total Passed: " + totalPassed);
167
System.out.println(" Total Failed: " + totalFailed);
168
System.out.println(" Total Skipped: " + totalSkipped);
169
170
// Cleanup suite-level resources
171
cleanupDatabaseConnection();
172
teardownTestEnvironment();
173
}
174
175
private void initializeDatabaseConnection() {
176
// Database setup logic
177
}
178
179
private void setupTestEnvironment() {
180
// Environment setup logic
181
}
182
183
private void cleanupDatabaseConnection() {
184
// Database cleanup logic
185
}
186
187
private void teardownTestEnvironment() {
188
// Environment teardown logic
189
}
190
}
191
```
192
193
### Custom Reporting Listeners
194
195
Specialized listeners for generating custom reports and integrating with external reporting systems.
196
197
```java { .api }
198
/**
199
* Interface for custom test reporters
200
*/
201
public interface IReporter {
202
void generateReport(List<XmlSuite> xmlSuites, List<ISuite> suites, String outputDirectory);
203
}
204
205
/**
206
* Interface for invoked method tracking
207
*/
208
public interface IInvokedMethodListener {
209
default void beforeInvocation(IInvokedMethod method, ITestResult testResult) {}
210
default void afterInvocation(IInvokedMethod method, ITestResult testResult) {}
211
default void beforeInvocation(IInvokedMethod method, ITestResult testResult, ITestContext context) {}
212
default void afterInvocation(IInvokedMethod method, ITestResult testResult, ITestContext context) {}
213
}
214
215
/**
216
* Interface for execution-level events
217
*/
218
public interface IExecutionListener {
219
default void onExecutionStart() {}
220
default void onExecutionFinish() {}
221
}
222
```
223
224
**Usage Examples:**
225
226
```java
227
public class CustomReporter implements IReporter {
228
229
@Override
230
public void generateReport(List<XmlSuite> xmlSuites, List<ISuite> suites, String outputDirectory) {
231
System.out.println("Generating custom HTML report...");
232
233
try {
234
// Create custom HTML report
235
String reportPath = outputDirectory + "/custom-report.html";
236
PrintWriter writer = new PrintWriter(new FileWriter(reportPath));
237
238
writer.println("<html><head><title>Test Report</title></head><body>");
239
writer.println("<h1>Test Execution Report</h1>");
240
241
for (ISuite suite : suites) {
242
writer.println("<h2>Suite: " + suite.getName() + "</h2>");
243
244
Map<String, ISuiteResult> results = suite.getResults();
245
for (ISuiteResult result : results.values()) {
246
ITestContext context = result.getTestContext();
247
248
writer.println("<h3>Test: " + context.getName() + "</h3>");
249
writer.println("<p>Duration: " +
250
(context.getEndDate().getTime() - context.getStartDate().getTime()) + "ms</p>");
251
252
// Passed tests
253
writer.println("<h4>Passed Tests (" + context.getPassedTests().size() + ")</h4>");
254
writer.println("<ul>");
255
for (ITestResult testResult : context.getPassedTests().getAllResults()) {
256
writer.println("<li style='color: green;'>" +
257
testResult.getMethod().getMethodName() + "</li>");
258
}
259
writer.println("</ul>");
260
261
// Failed tests
262
writer.println("<h4>Failed Tests (" + context.getFailedTests().size() + ")</h4>");
263
writer.println("<ul>");
264
for (ITestResult testResult : context.getFailedTests().getAllResults()) {
265
writer.println("<li style='color: red;'>" +
266
testResult.getMethod().getMethodName());
267
if (testResult.getThrowable() != null) {
268
writer.println("<br/>Error: " + testResult.getThrowable().getMessage());
269
}
270
writer.println("</li>");
271
}
272
writer.println("</ul>");
273
}
274
}
275
276
writer.println("</body></html>");
277
writer.close();
278
279
System.out.println("Custom report generated: " + reportPath);
280
281
} catch (IOException e) {
282
System.err.println("Failed to generate custom report: " + e.getMessage());
283
}
284
}
285
}
286
287
public class MethodInvocationListener implements IInvokedMethodListener {
288
289
@Override
290
public void beforeInvocation(IInvokedMethod method, ITestResult testResult, ITestContext context) {
291
String methodName = method.getTestMethod().getMethodName();
292
String className = method.getTestMethod().getTestClass().getName();
293
294
System.out.println("Before: " + className + "." + methodName);
295
296
if (method.isTestMethod()) {
297
System.out.println(" Type: Test Method");
298
System.out.println(" Groups: " + Arrays.toString(method.getTestMethod().getGroups()));
299
} else if (method.isConfigurationMethod()) {
300
System.out.println(" Type: Configuration Method");
301
}
302
303
// Log method parameters
304
Object[] parameters = testResult.getParameters();
305
if (parameters != null && parameters.length > 0) {
306
System.out.println(" Parameters: " + Arrays.toString(parameters));
307
}
308
}
309
310
@Override
311
public void afterInvocation(IInvokedMethod method, ITestResult testResult, ITestContext context) {
312
String methodName = method.getTestMethod().getMethodName();
313
long duration = testResult.getEndMillis() - testResult.getStartMillis();
314
315
System.out.println("After: " + methodName + " (" + duration + "ms)");
316
System.out.println(" Status: " + getStatusName(testResult.getStatus()));
317
318
if (testResult.getThrowable() != null) {
319
System.out.println(" Exception: " + testResult.getThrowable().getClass().getSimpleName());
320
}
321
}
322
323
private String getStatusName(int status) {
324
switch (status) {
325
case ITestResult.SUCCESS: return "PASSED";
326
case ITestResult.FAILURE: return "FAILED";
327
case ITestResult.SKIP: return "SKIPPED";
328
case ITestResult.SUCCESS_PERCENTAGE_FAILURE: return "SUCCESS_PERCENTAGE_FAILURE";
329
default: return "UNKNOWN";
330
}
331
}
332
}
333
```
334
335
### Test Transformation and Interception
336
337
Advanced listener interfaces for modifying test behavior, annotations, and execution flow.
338
339
```java { .api }
340
/**
341
* Interface for transforming annotations at runtime
342
*/
343
public interface IAnnotationTransformer {
344
default void transform(ITestAnnotation annotation, Class testClass,
345
Constructor testConstructor, Method testMethod) {}
346
default void transform(IConfigurationAnnotation annotation, Class testClass,
347
Constructor testConstructor, Method testMethod) {}
348
default void transform(IDataProviderAnnotation annotation, Method method) {}
349
default void transform(IFactoryAnnotation annotation, Method method) {}
350
}
351
352
/**
353
* Interface for method interception and modification
354
*/
355
public interface IMethodInterceptor {
356
List<IMethodInstance> intercept(List<IMethodInstance> methods, ITestContext context);
357
}
358
359
/**
360
* Interface for hookable test methods
361
*/
362
public interface IHookable {
363
void run(IHookCallBack callBack, ITestResult testResult);
364
}
365
366
/**
367
* Interface for configurable configuration methods
368
*/
369
public interface IConfigurable {
370
void run(IConfigureCallBack callBack, ITestResult testResult);
371
}
372
```
373
374
**Usage Examples:**
375
376
```java
377
public class AnnotationTransformer implements IAnnotationTransformer {
378
379
@Override
380
public void transform(ITestAnnotation annotation, Class testClass,
381
Constructor testConstructor, Method testMethod) {
382
383
// Modify test timeout based on method name
384
if (testMethod != null && testMethod.getName().contains("Slow")) {
385
annotation.setTimeOut(30000); // 30 seconds for slow tests
386
}
387
388
// Add groups based on class name
389
if (testClass != null && testClass.getName().contains("Integration")) {
390
String[] currentGroups = annotation.getGroups();
391
String[] newGroups = Arrays.copyOf(currentGroups, currentGroups.length + 1);
392
newGroups[currentGroups.length] = "integration";
393
annotation.setGroups(newGroups);
394
}
395
396
// Enable retry for specific tests
397
if (testMethod != null && testMethod.isAnnotationPresent(Flaky.class)) {
398
annotation.setRetryAnalyzer(FlakyTestRetryAnalyzer.class);
399
}
400
}
401
402
@Override
403
public void transform(IDataProviderAnnotation annotation, Method method) {
404
// Make all data providers parallel by default
405
annotation.setParallel(true);
406
}
407
}
408
409
public class CustomMethodInterceptor implements IMethodInterceptor {
410
411
@Override
412
public List<IMethodInstance> intercept(List<IMethodInstance> methods, ITestContext context) {
413
System.out.println("Intercepting " + methods.size() + " methods");
414
415
List<IMethodInstance> result = new ArrayList<>();
416
417
for (IMethodInstance method : methods) {
418
ITestNGMethod testMethod = method.getMethod();
419
420
// Skip methods marked with @Ignore annotation
421
if (testMethod.getConstructorOrMethod().getMethod().isAnnotationPresent(Ignore.class)) {
422
System.out.println("Skipping ignored method: " + testMethod.getMethodName());
423
continue;
424
}
425
426
// Run only smoke tests in CI environment
427
if (System.getProperty("CI") != null) {
428
String[] groups = testMethod.getGroups();
429
if (!Arrays.asList(groups).contains("smoke")) {
430
System.out.println("Skipping non-smoke test in CI: " + testMethod.getMethodName());
431
continue;
432
}
433
}
434
435
result.add(method);
436
}
437
438
// Sort methods by priority
439
result.sort((m1, m2) -> {
440
int priority1 = m1.getMethod().getPriority();
441
int priority2 = m2.getMethod().getPriority();
442
return Integer.compare(priority1, priority2);
443
});
444
445
System.out.println("Filtered to " + result.size() + " methods");
446
return result;
447
}
448
}
449
450
public class HookableTest implements IHookable {
451
452
@Override
453
public void run(IHookCallBack callBack, ITestResult testResult) {
454
System.out.println("Before test execution hook");
455
456
// Custom setup before test
457
setupTestData();
458
459
try {
460
// Execute the actual test
461
callBack.runTestMethod(testResult);
462
463
// Custom validation after test
464
validateTestResults();
465
466
} catch (Exception e) {
467
System.out.println("Test execution failed: " + e.getMessage());
468
throw e;
469
} finally {
470
// Custom cleanup after test
471
System.out.println("After test execution hook");
472
cleanupTestData();
473
}
474
}
475
476
@Test
477
public void hookableTestMethod() {
478
System.out.println("Executing hookable test method");
479
Assert.assertTrue(true);
480
}
481
482
private void setupTestData() {
483
System.out.println("Setting up test data in hook");
484
}
485
486
private void validateTestResults() {
487
System.out.println("Validating test results in hook");
488
}
489
490
private void cleanupTestData() {
491
System.out.println("Cleaning up test data in hook");
492
}
493
}
494
```
495
496
### Data Provider Listeners
497
498
Specialized listeners for data provider events and error handling.
499
500
```java { .api }
501
/**
502
* Listener for data provider events
503
*/
504
public interface IDataProviderListener {
505
default void beforeDataProviderExecution(IDataProviderMethod dataProviderMethod,
506
ITestNGMethod method, ITestContext iTestContext) {}
507
default void afterDataProviderExecution(IDataProviderMethod dataProviderMethod,
508
ITestNGMethod method, ITestContext iTestContext) {}
509
}
510
511
/**
512
* Interface for retry analyzers
513
*/
514
public interface IRetryAnalyzer {
515
boolean retry(ITestResult result);
516
}
517
518
/**
519
* Interface for data provider retry logic
520
*/
521
public interface IRetryDataProvider {
522
boolean retry(IDataProviderMethod dataProviderMethod);
523
}
524
```
525
526
**Usage Examples:**
527
528
```java
529
public class DataProviderListener implements IDataProviderListener {
530
531
@Override
532
public void beforeDataProviderExecution(IDataProviderMethod dataProviderMethod,
533
ITestNGMethod method, ITestContext context) {
534
System.out.println("Executing data provider: " + dataProviderMethod.getName() +
535
" for test: " + method.getMethodName());
536
}
537
538
@Override
539
public void afterDataProviderExecution(IDataProviderMethod dataProviderMethod,
540
ITestNGMethod method, ITestContext context) {
541
System.out.println("Data provider completed: " + dataProviderMethod.getName());
542
}
543
}
544
545
public class FlakyTestRetryAnalyzer implements IRetryAnalyzer {
546
private int retryCount = 0;
547
private static final int MAX_RETRY_COUNT = 3;
548
549
@Override
550
public boolean retry(ITestResult result) {
551
if (retryCount < MAX_RETRY_COUNT) {
552
retryCount++;
553
System.out.println("Retrying test: " + result.getMethod().getMethodName() +
554
" (attempt " + retryCount + " of " + MAX_RETRY_COUNT + ")");
555
return true;
556
}
557
return false;
558
}
559
}
560
```
561
562
## Types
563
564
```java { .api }
565
// Listener factory interface
566
public interface ITestNGListenerFactory {
567
ITestNGListener createListener(Class<? extends ITestNGListener> listenerClass);
568
}
569
570
// Invoked method interface
571
public interface IInvokedMethod {
572
ITestNGMethod getTestMethod();
573
Object getInstance();
574
long getDate();
575
boolean isTestMethod();
576
boolean isConfigurationMethod();
577
}
578
579
// Hook callback interfaces
580
public interface IHookCallBack {
581
void runTestMethod(ITestResult testResult);
582
Object[] getParameters();
583
}
584
585
public interface IConfigureCallBack {
586
void runConfigurationMethod(ITestResult testResult);
587
Object[] getParameters();
588
}
589
590
// Method instance for interceptors
591
public interface IMethodInstance {
592
ITestNGMethod getMethod();
593
Object getInstance();
594
}
595
596
// Data provider method interface
597
public interface IDataProviderMethod {
598
String getName();
599
Method getMethod();
600
Object getInstance();
601
}
602
603
// Custom annotations for examples
604
@Retention(RetentionPolicy.RUNTIME)
605
@Target(ElementType.METHOD)
606
public @interface Flaky {
607
}
608
609
@Retention(RetentionPolicy.RUNTIME)
610
@Target(ElementType.METHOD)
611
public @interface Ignore {
612
}
613
```