extentreports
Configures ExtentReports v5 for Java / .NET test runs - wires `ExtentSparkReporter` (HTML), `attachReporter`, `createTest`, the log-level chain (`info`/`pass`/`warning`/`skip`/`fail`), screenshots via `MediaEntityBuilder`, hierarchical `createNode` parent/child tests, and category / author / device labels. Outputs a static HTML report alongside JUnit XML for CI artifact upload. Use when a JVM project (or .NET via `extentreports-dotnet`) wants a richer per-test report than JUnit XML and the team is on the Aventstack ExtentReports stack.
extentreports
Overview
ExtentReports is a Java / .NET test reporting library - ExtentReports holds the run; pluggable reporters (ExtentSparkReporter for HTML, ExtentKlovReporter for the Klov server) consume the events and produce the artifact (extent-wiki).
Project status note (extent-readme): "ExtentReports is being sunset and will be replaced by ChainTest Framework." Teams already on ExtentReports v5 should plan a future migration; new projects should evaluate ChainTest first.
This skill covers the v5 API as documented in the official wiki.
When to use
If the team is starting fresh, evaluate ChainTest (the announced successor per extent-readme) before adopting ExtentReports. For framework-agnostic richer reporting, allure-reports covers similar ground with broader language support.
Step 1 - Install
Maven:
<dependency>
<groupId>com.aventstack</groupId>
<artifactId>extentreports</artifactId>
<version>5.1.2</version>
</dependency>Per extent-readme, v5.1.2 is the latest release as of 2024-06-26. Pin a version explicitly; the project is in maintenance mode pending ChainTest.
Step 2 - The canonical init pattern
Per the extent-wiki complete example:
import com.aventstack.extentreports.ExtentReports;
import com.aventstack.extentreports.MediaEntityBuilder;
import com.aventstack.extentreports.Status;
import com.aventstack.extentreports.markuputils.MarkupHelper;
import com.aventstack.extentreports.reporter.ExtentSparkReporter;
public class Main {
public static void main(String[] args) {
ExtentReports extent = new ExtentReports();
ExtentSparkReporter spark = new ExtentSparkReporter("target/Spark/Spark.html");
extent.attachReporter(spark);
// ... createTest calls ...
extent.flush();
}
}Three load-bearing pieces:
extent.flush() at the end of the run writes the report to disk. Without flush(), no file is written - the most common new-user mistake.
Step 3 - Create tests + log levels
Per extent-wiki:
extent.createTest("LogLevels")
.info("info")
.pass("pass")
.warning("warn")
.skip("skip")
.fail("fail");The chain reads top-to-bottom in the report. info is neutral; pass / fail / skip set the test's overall status; warning surfaces a yellow flag without changing status.
Each createTest returns an ExtentTest; calls on it accumulate log entries. Multiple createTest calls on the same extent produce multiple test entries in the report.
Step 4 - Hierarchical tests (parent / child)
Per extent-wiki:
extent.createTest("ParentWithChild")
.createNode("Child")
.pass("This test is created as a toggle as part of a child test of 'ParentWithChild'");The parent appears as a collapsible toggle in the report; children nest underneath. Useful for grouping per-suite tests under a suite-level node, or per-step interactions under a per-test node.
Step 5 - Screenshots + media
Per extent-wiki:
extent.createTest("ScreenCapture")
.addScreenCaptureFromPath("extent.png")
.pass(MediaEntityBuilder.createScreenCaptureFromPath("extent.png").build());Two patterns:
The latter pattern is preferred for failure screenshots: capture the screenshot inside the test framework's @AfterEach failure hook and pass it to .fail(...) so the failure log includes the visual evidence inline.
Step 6 - Categories / authors / devices
Per extent-wiki:
extent.createTest("Tags").assignCategory("MyTag")
.pass("The test 'Tags' was assigned by the tag MyTag");
extent.createTest("Authors").assignAuthor("TheAuthor")
.pass("This test 'Authors' was assigned by a special kind of author tag.");
extent.createTest("Devices").assignDevice("TheDevice")
.pass("This test 'Devices' was assigned by a special kind of devices tag.");These metadata fields drive the report's filter sidebar - by tag, author, device - making the report navigable when there are hundreds of tests. Use:
Step 7 - Exception capture
Per extent-wiki:
extent.createTest("Exception")
.fail(new RuntimeException("A runtime exception occurred!"));Passing an exception to .fail(...) captures the message + full stack trace in the report. Wire this into the test framework's failure hook so every failed test gets the trace inline.
Step 8 - Code blocks
Per extent-wiki:
extent.createTest("CodeBlock").generateLog(
Status.PASS,
MarkupHelper.createCodeBlock(CODE1, CODE2));MarkupHelper.createCodeBlock(...) produces syntax-highlighted JSON / SQL / code blocks in the report - useful for capturing the request body that triggered a failure.
Step 9 - Wire into a JUnit 5 / TestNG run
JUnit 5 with a per-test extension:
public class ExtentTestWatcher implements TestWatcher, BeforeAllCallback, AfterAllCallback {
private static ExtentReports extent;
@Override
public void beforeAll(ExtensionContext ctx) {
extent = new ExtentReports();
extent.attachReporter(new ExtentSparkReporter("target/Spark/Spark.html"));
}
@Override
public void testSuccessful(ExtensionContext ctx) {
extent.createTest(ctx.getDisplayName()).pass("OK");
}
@Override
public void testFailed(ExtensionContext ctx, Throwable cause) {
extent.createTest(ctx.getDisplayName()).fail(cause);
}
@Override
public void afterAll(ExtensionContext ctx) {
extent.flush(); // critical
}
}Register via @ExtendWith(ExtentTestWatcher.class) on the test class.
Step 10 - CI artifact upload
- run: ./mvnw -B verify
- name: Upload Extent report
if: always()
uses: actions/upload-artifact@v4
with:
name: extent-report
path: target/Spark/
retention-days: 30if: always() is critical - Extent matters most on failure runs.
Anti-patterns
| Anti-pattern | Why it fails | Fix |
|---|---|---|
Skipping extent.flush() | No HTML file written; CI artifact step uploads an empty directory. | Always call flush() in the suite-level teardown (Step 9). |
| One ExtentReports instance per test class (separate HTML files) | Reports fragment across the run; reviewer has to open N files. | One per run; use Categories to navigate (Step 6). |
| Logging assertion details without screenshots on UI tests | Failure context is missing the visual; debugging requires reproducing. | MediaEntityBuilder on every .fail(...) (Step 5). |
| ExtentReports as a substitute for JUnit XML in CI gating | The HTML is for humans; CI gates need machine-readable XML. | Emit both; gate on JUnit XML; surface Extent as artifact. |
| Hard-coded category / author strings | Drift; renames don't propagate; filter list grows polluted. | Author a small enum / constants class; reference centrally. |
| Adopting ExtentReports for a new project in 2026+ | Project is sunset per extent-readme; you'll migrate later. | Evaluate ChainTest (announced successor) or allure-reports. |
| Manually computing pass/fail counts vs trusting the reporter | Reporter aggregates from the events; manual count drifts. | Read the extent.flush()-emitted extent.json for programmatic access. |