Generating Test Reports with TestNG in Java Automation Testing
Using TestNG's Built-in Repotrers
To generate TestNG's default reports, configure the execution settings in your IDE. In the Run/Debug configurations, select the test file and enable the "Use default reporters" option in the Listeners section. When executing a testng.xml file, TestNG automatically creates a folder named test-output in the project structure. This folder contains the generated test reports and associated data files from the test run.
ZTestReport for Custom Reporting
ZTestReport is a custom reporting tool similar to BeautifulReport in Python. There are two methods to integrate it into your TestNG setup.
Method 1: Menual Integration
Add the required dependency to your project's pom.xml file:
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.0</version>
</dependency>
Place the ZTestReport Java files and template files in your project, typically within a utility package. Adjust the paths in the ZTestReport files to point to the correct locations for the template and report generation. Add the ZTestReport listener to your test class and remove any existing report listeners from the testng.xml file. Execute the testng.xml file to produce the custom report.
Method 2: XML Configuration
Alternatively, add the ZTestReport listener directly in the testng.xml file by specifying the class and method names. After adding the listener, run the testng.xml file to generate the report. Both methods yield identical ZTestReport outputs.
ZTestReport templates are available for download from its GitHub repository.
ExtentReports for Advanced Reporting
ExtentReports provides a more feature-rich reporting solution. Follow these steps to set it up.
Step 1: Add Dependencies
Include the necessary dependencies in your Maven project's pom.xml:
<dependencies>
<dependency>
<groupId>com.relevantcodes</groupId>
<artifactId>extentreports</artifactId>
<version>2.41.1</version>
</dependency>
<dependency>
<groupId>com.vimalselvam</groupId>
<artifactId>testng-extentsreport</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>com.aventstack</groupId>
<artifactId>extentreports</artifactId>
<version>3.0.6</version>
</dependency>
</dependencies>
Step 2: Implement a Custom Listneer
Create a listener class that implements the IReporter interface to monitor test outcomes and generate reports. Here is a restructured example:
package util;
import com.aventstack.extentreports.ExtentReports;
import com.aventstack.extentreports.ExtentTest;
import com.aventstack.extentreports.ResourceCDN;
import com.aventstack.extentreports.Status;
import com.aventstack.extentreports.model.TestAttribute;
import com.aventstack.extentreports.reporter.ExtentHtmlReporter;
import com.aventstack.extentreports.reporter.configuration.ChartLocation;
import com.aventstack.extentreports.reporter.configuration.Theme;
import org.testng.*;
import org.testng.xml.XmlSuite;
import java.io.File;
import java.util.*;
public class CustomExtentReporter implements IReporter {
private static final String REPORT_DIR = "test-output/";
private static final String REPORT_FILE = "extent-report.html";
private ExtentReports report;
@Override
public void generateReport(List<XmlSuite> xmlSuites, List<ISuite> suites, String outputDir) {
initializeReport();
boolean multiSuite = suites.size() > 1;
for (ISuite suite : suites) {
Map<String, ISuiteResult> results = suite.getResults();
if (results.isEmpty()) continue;
int totalPass = 0, totalFail = 0, totalSkip = 0;
ExtentTest suiteNode = null;
if (multiSuite) {
suiteNode = report.createTest(suite.getName()).assignCategory(suite.getName());
}
boolean multiResult = results.size() > 1;
for (ISuiteResult suiteResult : results.values()) {
ITestContext context = suiteResult.getTestContext();
ExtentTest resultNode;
if (multiResult) {
resultNode = (suiteNode == null) ? report.createTest(context.getName()) : suiteNode.createNode(context.getName());
} else {
resultNode = suiteNode;
}
if (resultNode != null) {
resultNode.getModel().setName(suite.getName() + " : " + context.getName());
resultNode.assignCategory(context.getName());
resultNode.getModel().setStartTime(context.getStartDate());
resultNode.getModel().setEndTime(context.getEndDate());
int passCount = context.getPassedTests().size();
int failCount = context.getFailedTests().size();
int skipCount = context.getSkippedTests().size();
totalPass += passCount;
totalFail += failCount;
totalSkip += skipCount;
if (failCount > 0) {
resultNode.getModel().setStatus(Status.FAIL);
}
resultNode.getModel().setDescription(String.format("Passed: %d; Failed: %d; Skipped: %d", passCount, failCount, skipCount));
}
addTestResults(resultNode, context.getFailedTests(), Status.FAIL);
addTestResults(resultNode, context.getSkippedTests(), Status.SKIP);
addTestResults(resultNode, context.getPassedTests(), Status.PASS);
}
if (suiteNode != null) {
suiteNode.getModel().setDescription(String.format("Total Passed: %d; Total Failed: %d; Total Skipped: %d", totalPass, totalFail, totalSkip));
if (totalFail > 0) {
suiteNode.getModel().setStatus(Status.FAIL);
}
}
}
report.flush();
}
private void initializeReport() {
File dir = new File(REPORT_DIR);
if (!dir.exists() && !dir.isDirectory()) {
dir.mkdir();
}
ExtentHtmlReporter htmlReporter = new ExtentHtmlReporter(REPORT_DIR + REPORT_FILE);
htmlReporter.config().setResourceCDN(ResourceCDN.EXTENTREPORTS);
htmlReporter.config().setDocumentTitle("API Test Report");
htmlReporter.config().setReportName("API Automation Results");
htmlReporter.config().setChartVisibilityOnOpen(true);
htmlReporter.config().setTestViewChartLocation(ChartLocation.TOP);
htmlReporter.config().setTheme(Theme.STANDARD);
htmlReporter.config().setCSS(".node.level-1 ul{ display:none;} .node.level-1.active ul{display:block;}");
report = new ExtentReports();
report.attachReporter(htmlReporter);
report.setReportUsesManualConfiguration(true);
}
private void addTestResults(ExtentTest parentNode, IResultMap testMap, Status resultStatus) {
String[] categories = new String[0];
if (parentNode != null) {
List<TestAttribute> attrList = parentNode.getModel().getCategoryContext().getAll();
categories = new String[attrList.size()];
for (int i = 0; i < attrList.size(); i++) {
categories[i] = attrList.get(i).getName();
}
}
if (testMap.size() > 0) {
Set<ITestResult> sortedResults = new TreeSet<>(Comparator.comparingLong(ITestResult::getStartMillis));
sortedResults.addAll(testMap.getAllResults());
for (ITestResult result : sortedResults) {
StringBuilder paramName = new StringBuilder();
for (Object param : result.getParameters()) {
paramName.append(param.toString());
}
String testName = paramName.length() > 0 ? (paramName.length() > 50 ? paramName.substring(0, 49) + "..." : paramName.toString()) : result.getMethod().getMethodName();
ExtentTest testNode = (parentNode == null) ? report.createTest(testName) : parentNode.createNode(testName).assignCategory(categories);
for (String group : result.getMethod().getGroups()) {
testNode.assignCategory(group);
}
for (String log : Reporter.getOutput(result)) {
testNode.debug(log);
}
if (result.getThrowable() != null) {
testNode.log(resultStatus, result.getThrowable());
} else {
testNode.log(resultStatus, "Test " + resultStatus.toString().toLowerCase() + "ed");
}
testNode.getModel().setStartTime(new Date(result.getStartMillis()));
testNode.getModel().setEndTime(new Date(result.getEndMillis()));
}
}
}
}
Step 3: Configure TestNG XML
Add the listener class path to your testng.xml file. Execute the testng.xml file, and a test-output folder will be generated containing an HTML report named extent-report.html, wich can be viewed in a browser.