extentreports
Configures ExtentReports v5 for a JVM (or .NET via `extentreports-dotnet`) test run: wires `ExtentSparkReporter`, `attachReporter`, `createTest`, the `info`/`pass`/`warning`/`skip`/`fail` log chain, screenshots via `MediaEntityBuilder`, hierarchical `createNode` parent/child tests, and category/author/device labels, emitting a static HTML report alongside JUnit XML for CI artifact upload. Use when a suite on the Aventstack ExtentReports stack wants a richer per-test HTML narrative than JUnit XML gives; for code-coverage reporting use jacoco-analysis, and for hosted cross-run flakiness analytics use currents-integration.
Install with skills.sh (any agent)
npx skills add testland/qa --skill extentreportsextentreports
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 (opens in new window)).
Project status note (extent-readme (opens in new window)): "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 (opens in new window)) before adopting ExtentReports. For framework-agnostic richer reporting, allure-reports covers similar ground with broader language support.
How to use
Install
Maven:
<dependency>
<groupId>com.aventstack</groupId>
<artifactId>extentreports</artifactId>
<version>5.1.2</version>
</dependency>Per extent-readme (opens in new window), v5.1.2 is the latest release as of 2024-06-26. Pin a version explicitly; the project is in maintenance mode pending ChainTest.
Initialize the report
Per the extent-wiki (opens in new window) 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.
Create tests and log levels
Per extent-wiki (opens in new window):
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.
Screenshots and media
Per extent-wiki (opens in new window):
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.
Worked example
A minimal runnable report with one passing test, the log-level chain, and a screenshot inlined on the pass entry:
import com.aventstack.extentreports.ExtentReports;
import com.aventstack.extentreports.MediaEntityBuilder;
import com.aventstack.extentreports.reporter.ExtentSparkReporter;
public class Demo {
public static void main(String[] args) {
ExtentReports extent = new ExtentReports();
extent.attachReporter(new ExtentSparkReporter("target/Spark/Spark.html"));
extent.createTest("checkout adds item")
.info("navigated to cart")
.pass("item added",
MediaEntityBuilder.createScreenCaptureFromPath("cart.png").build());
extent.flush(); // writes target/Spark/Spark.html - omit it and nothing is written
}
}Open target/Spark/Spark.html in a browser: the test shows the info and pass log lines top-to-bottom with the screenshot inlined on the pass entry.
Operating in CI
ExtentReports HTML is a human artifact, not a CI gate - keep emitting JUnit XML for the gate and upload the Spark HTML alongside it. Register the reporter through a JUnit 5 TestWatcher (or TestNG listener) so every pass/fail is logged and flush() runs in the suite teardown, then upload target/Spark/ with if: always() (Extent matters most on failure runs). The lifecycle extension and the artifact-upload step are in references/api-and-ci-wiring.md.
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 (see Operating in CI). |
| 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 (see the richer-API reference). |
| Logging assertion details without screenshots on UI tests | Failure context is missing the visual; debugging requires reproducing. | MediaEntityBuilder on every .fail(...) (see Screenshots and media). |
| 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 (opens in new window); 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. |
Limitations
References
ExtentReports richer API and test-lifecycle wiring
View source (opens in new window)ExtentReports richer API and test-lifecycle wiring
Deep reference for the extentreports SKILL.md. Consult for the richer report API (hierarchical tests, category / author / device labels, exception capture, code blocks) and for wiring the reporter into a JUnit 5 / TestNG run plus CI artifact upload.
Hierarchical tests (parent / child)
Per extent-wiki (opens in new window):
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.
Categories, authors, devices
Per extent-wiki (opens in new window):
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:
Exception capture
Per extent-wiki (opens in new window):
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.
Code blocks
Per extent-wiki (opens in new window):
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.
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.
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. The HTML is for humans; keep emitting JUnit XML as the machine-readable CI gate (extent-readme (opens in new window)) and surface the Spark HTML alongside it.
Related skills
allure-reports
Configures Allure Report (test-runner adapter install, `allure-results` directory wiring, `categories.json` for failure classification, `history-trend.json` retention via the copy-history-between-runs pattern), runs the Allure CLI to convert `allure-results` to a static HTML site, and uploads the report as a CI artifact. Use when the team needs richer test reporting than JUnit XML - step-level attachments, per-test history, retry tracking, and severity / epic / feature labeling across framework-agnostic adapters (pytest, Jest, JUnit, TestNG, NUnit, Mocha). As a rich static HTML report generator, it is the open-source alternative to extentreports (JVM/.NET per-test HTML narrative); for hosted cross-run flakiness analytics rather than a static per-run report use currents-integration.
cobertura-analysis
Parses Cobertura XML coverage reports (the JVM-canonical format originally from the cobertura-cobertura tool, also emitted by JaCoCo `--coverage-xml`, coverage.py `--xml`, Istanbul / Jest `cobertura` reporter, gocover-cobertura, and dotnet's `coverlet`). Walks the coverage-04 DTD structure (coverage → packages → classes → methods → lines + conditions), computes per-file deltas, and emits PR-time gating verdicts. Use when the existing CI emits Cobertura XML - typical for JVM-heavy stacks and tools that ship Cobertura as a default reporter.
coverage-diff-reporter
Builds a per-PR coverage delta report from any pair of LCOV / Cobertura / JSON coverage outputs (current run + baseline from the merge target) - emits a per-file table with line% / branch% deltas, called-out new files, hidden drops (overall +0.1pp but one file -8pp), and a single-line PR-comment summary. Use when the team has coverage in CI but needs human-readable PR feedback that points at the specific file the reviewer should focus on, not just an aggregate number.
coverage-py-analysis
Configures coverage.py for Python projects - wires `coverage run` (replacing `python` for instrumentation), enables branch coverage via the `--branch` flag or `branch = True` config, manages the `.coverage` data file (single-process and `combine` for parallel pytest-xdist runs), authors `.coveragerc` with `source` / `omit` / `fail_under`, and emits the format the downstream tool needs (`coverage report` for terminal, `coverage xml` for Cobertura, `coverage html` for human review, `coverage lcov` for SaaS, `coverage json` for programmatic post-processing). Use for any Python test stack (pytest, unittest, nose) that needs PR-time coverage signal.
currents-integration
Wires Currents.dev cross-run test analytics into a Playwright suite: installs `@currents/playwright`, authors `currents.config.ts` (env-sourced `recordKey` + `projectId`), registers `currentsReporter()`, enables trace/video/screenshot artifacts, and runs via `npx pwc` so per-test traces stream to the Currents dashboard with over-time flakiness, slowest-test, and pass-rate trends. Use when a Playwright suite needs hosted cross-run suite-health analytics; for a static per-run report use extentreports or allure-reports, and to sync results into Jira test management use zephyr-integration or xray-integration.
jacoco-analysis
Configures JaCoCo for JVM projects (Java / Kotlin / Scala / Groovy) - wires the runtime agent via `jacoco-maven-plugin` `prepare-agent`, generates per-build reports (HTML / XML / CSV) via the `report` goal, gates the build via the `check` goal with element / limit / minimum rules, parses the six native counters (instructions, branches, lines, methods, classes, cyclomatic complexity), and converts JaCoCo XML to LCOV / Cobertura when downstream tools need a different format. Use when the JVM build is Maven / Gradle and the team wants the canonical JVM coverage tool - or to convert JaCoCo output for cross-language coverage aggregation.
jest-coverage-analysis
Configures Jest's built-in coverage (Istanbul-instrumented `babel` provider or V8-native `v8` provider), wires the right `coverageReporters` for downstream consumption (`lcov` for SaaS / cross-tool, `cobertura` for Jenkins, `text-summary` for terminal, `html` for human review), authors per-file `coverageThreshold` rules that focus the gate on critical paths (vs the global-only foot-gun), and parses the per-file JSON output for PR-time deltas. Use when the project tests with Jest (or Vitest, which uses the same Istanbul/V8 provider) and the team needs PR-time coverage signal that's both local-runnable and CI-gateable.
junit-xml-analysis
Parses JUnit-format XML reports (the de-facto interchange format every CI ingests - Jenkins, GitHub Actions, GitLab, Buildkite, CircleCI) into structured, machine-readable per-suite and per-case metrics tables (passed / failed / errored / skipped, time, classname, message, stack), groups failures by classname for trend analysis, and distinguishes "new failures vs flakes" by cross-referencing the `flakyFailure` and `rerunFailure` rerun elements. Use when the downstream consumer is a dashboard, script, or aggregator - not when the goal is a human-readable prose summary (use test-run-summary-author for that). Single-run, in-XML aggregation only; for cross-run cross-environment roll-ups, use a cross-run test-suite aggregator.
lcov-analysis
Parses LCOV `.info` text files (the de-facto coverage interchange format produced by gcov, llvm-cov, Coverage.py via `py2lcov`, JaCoCo via `xml2lcov`, Devel::Cover, Jest via `lcov` reporter, NYC, and most others). Extracts per-file line / function / branch metrics from the canonical record keywords (TN/SF/FN/FNDA/FNF/FNH/BRDA/BRF/BRH/DA/LH/LF), computes the diff vs a baseline, and emits per-file gating verdicts. Use for PR coverage gates that don't depend on a specific language runtime.
test-coverage-targeter
Builds a "what to test next" recommendation by combining a coverage report (LCOV / Cobertura / coverage.py JSON / Jest JSON / JaCoCo XML) with the PR's `git diff`, ranking uncovered branches by risk × cost - risk weighted by McCabe cyclomatic complexity and code-churn frequency, cost weighted by the unit-test pyramid layer (unit tests cheaper than integration than E2E). Emits a prioritized list with concrete file:line targets and the test layer recommended for each. Use when a team has the budget to write 5 - 10 new tests and needs help picking which uncovered code to target first instead of blindly chasing 100% coverage.
test-run-summary-author
Build-an-X workflow that turns a structured test-run artifact (JUnit XML, Allure JSON, TestRail / Xray / Zephyr export) plus optional release context (version, build URL, deploy target) into a narrative markdown summary for release notes, an exec status update, or a stand-up Slack post. Distinct from the per-framework parsers junit-xml-analysis / allure-reports / coverage-diff-reporter, which emit structured tabular reports: this skill takes the same data and writes the human-readable narrative. Use when a manager needs a draft release note or stand-up summary from a single run; for cross-run trend analytics use currents-integration.
testrail-integration
Syncs test runs / results / cases between an automated test suite and TestRail (Gurock / Idera) - opens a Test Run for the build (`add_run`), batches per-case results back via `add_results_for_cases` (preferred over per-test `add_result_for_case` - N+1 API calls vs 1), maps the test framework's pass/fail/skip to TestRail status IDs, and attaches build URL + version + elapsed time. Use when the team's test management is standalone TestRail (not a Jira app) and automated suites must update it without a human copy-paste step; when the TCM is instead a Jira app use xray-integration (Xray) or zephyr-integration (Zephyr Scale), and for hosted cross-run flakiness analytics rather than TCM sync use currents-integration.
xray-integration
Imports CI test results into Xray for Jira - authenticates via the `client_id` + `client_secret` → JWT exchange (Cloud) or PAT / Basic (Server), posts to the format-specific `/api/v2/import/execution/*` endpoint (`/junit` for JUnit XML, `/cucumber` for Cucumber JSON, `/nunit` / `/testng` / `/robot` for the others), and maps automated results to existing Xray Test issues via the `xray-junit-extensions` `@XrayTest(key="...")` annotation. Use when the team uses the Xray Jira app to manage Test, Test Set, and Test Execution issue types and CI must keep those execution issues in sync; for the other Jira TCM app use zephyr-integration (Zephyr Scale), for standalone non-Jira TestRail use testrail-integration, and for hosted cross-run flakiness analytics rather than TCM sync use currents-integration.
zephyr-integration
Syncs automated test results to Zephyr Scale for Jira (formerly TM4J / SmartBear / Adaptavist): picks the product variant (Scale Cloud / Squad / Enterprise), authenticates with a long-lived API token as a Bearer header, opens a Test Cycle per build, posts executions via `POST /testexecutions` (or bulk JUnit via `/automations/executions/junit`), and maps test methods to Zephyr Test Cases via `@TestCaseKey`-style annotations. Use when the team's Jira test management is Zephyr Scale; for the Xray Jira app use xray-integration, for standalone TestRail use testrail-integration, and for over-time flakiness analytics rather than TCM sync use currents-integration.