Extent Reports Java Guide: Beautiful HTML Test Reports for Selenium

Extent Reports Java Guide: Beautiful HTML Test Reports for Selenium

Extent Reports is one of the most widely used open-source test reporting libraries in the Java ecosystem. It generates rich HTML reports from your Selenium, TestNG, or JUnit test runs — complete with pass/fail status, screenshots, system information, and categorized logs.

This guide covers setting up Extent Reports 5.x with a Java/Selenium/TestNG stack, customizing the output, attaching screenshots on failure, and integrating the report into CI pipelines.

What Is Extent Reports?

Extent Reports is a Java library (and available for .NET) that produces self-contained HTML reports from test execution data. Unlike JUnit XML — which requires a separate rendering tool — Extent Reports generates a standalone HTML file you can open in any browser without a server.

Key features:

  • Spark reporter — responsive HTML with charts and status breakdown
  • PDF reporter — printable summary for stakeholders
  • Klov reporter — sends data to MongoDB for trend dashboards
  • Log levels — pass, fail, skip, info, warning — attached to each test step
  • Screenshots — embedded in the HTML report as base64 images
  • System info — OS, browser, environment details in the report header
  • Tagging and categorization — group tests by feature, module, or label

Setup

Maven Dependency

Add Extent Reports 5.x to your pom.xml:

<dependency>
    <groupId>com.aventstack</groupId>
    <artifactId>extentreports</artifactId>
    <version>5.1.1</version>
</dependency>

For TestNG integration, also add:

<dependency>
    <groupId>org.testng</groupId>
    <artifactId>testng</artifactId>
    <version>7.8.0</version>
    <scope>test</scope>
</dependency>

Basic Report Setup

The minimum code to create a report:

import com.aventstack.extentreports.ExtentReports;
import com.aventstack.extentreports.ExtentTest;
import com.aventstack.extentreports.reporter.ExtentSparkReporter;

public class ReportManager {
    private static ExtentReports extent;
    private static ExtentSparkReporter sparkReporter;

    public static ExtentReports getInstance() {
        if (extent == null) {
            sparkReporter = new ExtentSparkReporter("test-output/ExtentReport.html");
            sparkReporter.config().setDocumentTitle("Automation Test Report");
            sparkReporter.config().setReportName("Selenium Test Suite");
            
            extent = new ExtentReports();
            extent.attachReporter(sparkReporter);
            extent.setSystemInfo("OS", System.getProperty("os.name"));
            extent.setSystemInfo("Java Version", System.getProperty("java.version"));
            extent.setSystemInfo("Browser", "Chrome");
            extent.setSystemInfo("Environment", "QA");
        }
        return extent;
    }
}

Integration with TestNG

The cleanest approach for TestNG is implementing ITestListener to hook into TestNG's lifecycle events automatically.

TestNG Listener

import com.aventstack.extentreports.ExtentReports;
import com.aventstack.extentreports.ExtentTest;
import com.aventstack.extentreports.Status;
import org.testng.ITestContext;
import org.testng.ITestListener;
import org.testng.ITestResult;

public class ExtentTestNGListener implements ITestListener {
    private static ExtentReports extent = ReportManager.getInstance();
    private static ThreadLocal<ExtentTest> extentTest = new ThreadLocal<>();

    @Override
    public void onTestStart(ITestResult result) {
        ExtentTest test = extent.createTest(result.getMethod().getMethodName());
        extentTest.set(test);
    }

    @Override
    public void onTestSuccess(ITestResult result) {
        extentTest.get().log(Status.PASS, "Test passed");
    }

    @Override
    public void onTestFailure(ITestResult result) {
        extentTest.get().log(Status.FAIL, result.getThrowable());
        // Attach screenshot on failure
        try {
            String screenshotPath = ScreenshotUtil.capture(result.getMethod().getMethodName());
            extentTest.get().addScreenCaptureFromPath(screenshotPath);
        } catch (Exception e) {
            extentTest.get().log(Status.WARNING, "Could not capture screenshot: " + e.getMessage());
        }
    }

    @Override
    public void onTestSkipped(ITestResult result) {
        extentTest.get().log(Status.SKIP, "Test skipped: " + result.getThrowable());
    }

    @Override
    public void onFinish(ITestContext context) {
        extent.flush();
    }
}

Register the Listener

In testng.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="Automation Suite">
    <listeners>
        <listener class-name="com.yourpackage.ExtentTestNGListener"/>
    </listeners>
    <test name="Smoke Tests">
        <classes>
            <class name="com.yourpackage.tests.LoginTest"/>
            <class name="com.yourpackage.tests.CheckoutTest"/>
        </classes>
    </test>
</suite>

Screenshot Capture Utility

Screenshots are what make Extent Reports actually useful for debugging. Here's a utility class:

import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;

public class ScreenshotUtil {
    private static WebDriver driver;

    public static void setDriver(WebDriver webDriver) {
        driver = webDriver;
    }

    public static String capture(String testName) throws Exception {
        TakesScreenshot ts = (TakesScreenshot) driver;
        File source = ts.getScreenshotAs(OutputType.FILE);
        
        String timestamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
        String destination = "test-output/screenshots/" + testName + "_" + timestamp + ".png";
        
        File destFile = new File(destination);
        destFile.getParentFile().mkdirs();
        FileUtils.copyFile(source, destFile);
        
        return destination;
    }
}

For embedding screenshots as base64 (no file dependency):

public static String captureBase64() {
    TakesScreenshot ts = (TakesScreenshot) driver;
    return ts.getScreenshotAs(OutputType.BASE64);
}

// In your listener:
extentTest.get().addScreenCaptureFromBase64String(ScreenshotUtil.captureBase64());

Adding Detailed Step Logs

Beyond pass/fail status, Extent Reports lets you log individual steps within a test:

@Test
public void loginTest() {
    ExtentTest test = ReportManager.getInstance()
        .createTest("Login Test", "Validates user login with valid credentials");
    
    test.log(Status.INFO, "Navigating to login page");
    driver.get("https://example.com/login");
    
    test.log(Status.INFO, "Entering username");
    driver.findElement(By.id("username")).sendKeys("testuser");
    
    test.log(Status.INFO, "Entering password");
    driver.findElement(By.id("password")).sendKeys("password123");
    
    test.log(Status.INFO, "Clicking login button");
    driver.findElement(By.id("login-btn")).click();
    
    String currentUrl = driver.getCurrentUrl();
    if (currentUrl.contains("/dashboard")) {
        test.log(Status.PASS, "Login successful - redirected to dashboard");
    } else {
        test.log(Status.FAIL, "Login failed - unexpected URL: " + currentUrl);
    }
}

Categorizing Tests

Extent Reports supports assigning categories, authors, and devices to tests. This enables filtering in the report:

ExtentTest test = extent.createTest("Checkout Test")
    .assignCategory("E2E", "Checkout")
    .assignAuthor("QA Team")
    .assignDevice("Chrome/Windows");

In the Spark report, you can then filter by category to see all checkout-related tests, or by author to view a team member's test results.

Configuring the Spark Reporter

The Spark reporter accepts several configuration options:

ExtentSparkReporter sparkReporter = new ExtentSparkReporter("test-output/report.html");
sparkReporter.config().setDocumentTitle("My Test Suite");
sparkReporter.config().setReportName("Sprint 42 Regression");
sparkReporter.config().setTheme(Theme.DARK);  // DARK or STANDARD
sparkReporter.config().setTimeStampFormat("MMM dd, yyyy HH:mm:ss");
sparkReporter.config().setEncoding("utf-8");
sparkReporter.config().setOfflineMode(true);  // embed all assets in HTML

setOfflineMode(true) is important for CI pipelines where the report will be viewed in environments without internet access. It embeds Bootstrap and other dependencies into the HTML file.

CI/CD Integration

Jenkins

In your Jenkins pipeline, publish the HTML report using the HTML Publisher plugin:

post {
    always {
        publishHTML([
            allowMissing: false,
            alwaysLinkToLastBuild: true,
            keepAll: true,
            reportDir: 'test-output',
            reportFiles: 'ExtentReport.html',
            reportName: 'Extent HTML Report'
        ])
    }
}

GitHub Actions

- name: Run tests
  run: mvn test

- name: Upload Extent Report
  uses: actions/upload-artifact@v3
  if: always()
  with:
    name: extent-report
    path: test-output/ExtentReport.html

Parallel Test Execution

For parallel test runs with TestNG, the ThreadLocal<ExtentTest> pattern ensures each thread gets its own test object:

private static ThreadLocal<ExtentTest> extentTest = new ThreadLocal<>();

// In onTestStart:
ExtentTest test = extent.createTest(result.getMethod().getMethodName());
extentTest.set(test);

// Access from test:
public static ExtentTest getTest() {
    return extentTest.get();
}

Each thread updates its own ExtentTest instance, preventing race conditions in the report data.

Common Issues

Report not generating: Call extent.flush() at the end of the suite. Without it, data in memory is never written to disk.

Screenshots not appearing: Check that the file path is relative to where the report HTML sits, or use base64 embedding to avoid path issues entirely.

Empty report on CI: Ensure test-output/ directory is writable. Some CI environments have restricted temp directories.

Duplicate test entries in parallel runs: If you're not using ThreadLocal, multiple threads will write to the same ExtentTest instance. Use the ThreadLocal pattern described above.

Extent Reports vs Allure

Feature Extent Reports Allure
Setup complexity Low Medium
Report appearance Good Excellent
Step logging Manual API calls Annotation-based
Historical trends Via Klov (MongoDB) Built-in with server
CI artifact support HTML file Needs Allure server
Screenshot support Built-in Plugin
Java focus Yes Multi-language

Allure produces more polished reports and has better historical trend support. Extent Reports is simpler to set up and works well when you need a standalone HTML artifact.

Summary

Extent Reports remains a practical choice for Java/Selenium teams that want rich HTML test reports without a separate reporting server. The listener-based TestNG integration keeps test code clean, the offline mode produces portable report files for CI artifact storage, and the screenshot API gives enough context to debug failures without reproducing them locally.

For teams already invested in Java and Selenium, adding Extent Reports is a 30-minute investment that pays off every time you need to share test results with stakeholders who don't have access to your CI system.

Read more

Start now free