Answer
Extent Reports in Selenium TestNG
Dependency (pom.xml)
XML
<dependency>
<groupId>com.aventstack</groupId>
<artifactId>extentreports</artifactId>
<version>5.1.1</version>
</dependency>
ExtentManager — Singleton Report Instance
Java
// utils/ExtentManager.java
public class ExtentManager {
private static ExtentReports extent;
public static ExtentReports getInstance() {
if (extent == null) {
ExtentSparkReporter spark = new ExtentSparkReporter("test-output/ExtentReport.html");
spark.config().setDocumentTitle("AutomateQA Test Report");
spark.config().setReportName("Regression Suite");
spark.config().setTheme(Theme.DARK);
extent = new ExtentReports();
extent.attachReporter(spark);
extent.setSystemInfo("OS", System.getProperty("os.name"));
extent.setSystemInfo("Browser", ConfigReader.getBrowser());
extent.setSystemInfo("Env", ConfigReader.getBaseUrl());
}
return extent;
}
}
TestNG Listener
Java
// listeners/TestListener.java
public class TestListener implements ITestListener, ISuiteListener {
private static ExtentReports extent = ExtentManager.getInstance();
private static ThreadLocal<ExtentTest> test = new ThreadLocal<>();
@Override
public void onTestStart(ITestResult result) {
String testName = result.getMethod().getMethodName();
String desc = result.getMethod().getDescription();
ExtentTest extentTest = extent.createTest(testName, desc);
// Add categories from TestNG groups
for (String group : result.getMethod().getGroups()) {
extentTest.assignCategory(group);
}
test.set(extentTest);
}
@Override
public void onTestSuccess(ITestResult result) {
test.get().pass("✅ Test PASSED");
}
@Override
public void onTestFailure(ITestResult result) {
test.get().fail(result.getThrowable());
// Attach screenshot
String path = ScreenshotUtil.capture(result.getName());
test.get().addScreenCaptureFromPath(path, "Failure Screenshot");
}
@Override
public void onTestSkipped(ITestResult result) {
test.get().skip("⚠️ Test SKIPPED: " + result.getThrowable().getMessage());
}
@Override
public void onFinish(ISuite suite) {
extent.flush(); // write HTML file
System.out.println("Report: test-output/ExtentReport.html");
}
}
Register Listener in testng.xml
XML
<suite name="Suite">
<listeners>
<listener class-name="com.automateqa.listeners.TestListener"/>
</listeners>
<test name="Regression">
<classes>
<class name="com.automateqa.tests.LoginTest"/>
<class name="com.automateqa.tests.DashboardTest"/>
</classes>
</test>
</suite>
ScreenshotUtil
Java
// utils/ScreenshotUtil.java
public class ScreenshotUtil {
public static String capture(String testName) {
File src = ((TakesScreenshot) DriverManager.getDriver())
.getScreenshotAs(OutputType.FILE);
String dir = "test-output/screenshots/";
String name = testName + "_" + LocalDateTime.now()
.format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss")) + ".png";
String path = dir + name;
try {
new File(dir).mkdirs();
FileUtils.copyFile(src, new File(path));
} catch (IOException e) {
e.printStackTrace();
}
return path;
}
}
Log Within Tests (Optional)
Java
// In any test — log steps to the Extent report
@Test
public void testLogin() {
ExtentTestLogger.log("Navigating to login page");
loginPage.visit();
ExtentTestLogger.log("Entering credentials");
loginPage.login("alice@test.com", "Pass@123");
ExtentTestLogger.log("Verifying dashboard");
Assert.assertTrue(dashPage.isLoaded());
}
// Where ExtentTestLogger accesses ThreadLocal test:
public class ExtentTestLogger {
public static void log(String msg) {
TestListener.getCurrentTest().info(msg);
}
}
