Reporting Layer — ExtentReports, Allure, Custom Reporters

8 min read

After every CI run, someone asks: "Did the tests pass?" If the answer is "yes, except for these 3 — here's the screenshot of each failure, which step it failed on, and how long the suite took," that person gets what they need in 30 seconds. If the answer is "here's the Jenkins console output," that person opens a wall of text and extracts nothing useful. The reporting layer is not optional, and it's not just for management. A good report is your first debugging tool when something fails overnight. This lesson covers what each major reporter provides, when to use which, and how to wire a reporter into a framework without coupling it to your test code.

What a report must provide

A minimal useful report answers five questions:

  1. Which tests passed, which failed, which were skipped?
  2. For failed tests: what assertion failed, and what was the expected vs actual value?
  3. For failed tests: what did the browser look like at the moment of failure (screenshot)?
  4. How long did the suite take, and which tests were the slowest?
  5. What environment, browser, and build number produced these results?

CLI output answers question 1 partially and questions 2–5 not at all. Every serious team needs at least question 3.

Default reports — functional, ugly

TestNG generates test-output/index.html automatically. JUnit generates Surefire XML (target/surefire-reports). pytest generates a terminal summary. All three provide pass/fail counts and exception messages.

They share the same limitations: no screenshots, no step-by-step breakdown, no trend data, no charts, and no format a non-engineer would open voluntarily. They're suitable for CI tools (Jenkins, GitHub Actions parse Surefire XML) but not for human stakeholders.

ExtentReports

ExtentReports generates a self-contained HTML file with charts, test names, pass/fail status, environment info, attached screenshots, and custom tags. One HTML file you can email, upload to S3, or attach to a Jira ticket.

// ExtentReports wiring in a TestNG Listener
public class ExtentReportListener implements ITestListener {
    private static ExtentReports extent;
    private static final ThreadLocal<ExtentTest> test = new ThreadLocal<>();
 
    @Override
    public void onStart(ISuite suite) {
        ExtentSparkReporter reporter = new ExtentSparkReporter("reports/extent.html");
        reporter.config().setTheme(Theme.DARK);
        reporter.config().setDocumentTitle("Test Run Report");
        extent = new ExtentReports();
        extent.attachReporter(reporter);
        extent.setSystemInfo("Environment", Config.env());
        extent.setSystemInfo("Browser", Config.browser());
    }
 
    @Override
    public void onTestStart(ITestResult result) {
        ExtentTest extentTest = extent.createTest(result.getMethod().getMethodName());
        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());
        // Capture and attach screenshot
        String screenshot = ScreenshotHelper.captureBase64(DriverManager.getDriver());
        test.get().addScreenCaptureFromBase64String(screenshot, "Failure screenshot");
    }
 
    @Override
    public void onFinish(ISuite suite) {
        extent.flush();
    }
}

Register the listener in testng.xml:

<listeners>
    <listener class-name="com.mycompany.tests.listeners.ExtentReportListener"/>
</listeners>

Zero changes to test code. The listener attaches to TestNG's lifecycle and builds the report as tests run.

Allure — cross-language, trend-aware

Allure supports Java, Python, JavaScript, Ruby, and .NET. Its key differentiators: trend charts across runs, retry tracking, step-level breakdown with @Step annotations, and integration with test management tools (TestRail, Jira Xray via Allure TestOps).

// Java — Allure annotations enrich the report
@Test
@Feature("Authentication")
@Story("Login with valid credentials")
@Severity(SeverityLevel.CRITICAL)
public void adminCanLogin() {
    loginPage.navigateTo();
    loginPage.loginAs(Users.admin());
    assertTrue(dashboardPage.isDisplayed());
}
 
// In the page object — @Step appears as a step in the report
@Step("Log in as {email}")
public void loginAs(String email, String password) {
    find(emailInput).sendKeys(email);
    find(passwordInput).sendKeys(password);
    find(submitButton).click();
}
# Python — Allure annotations with pytest
import allure
 
@allure.feature("Authentication")
@allure.story("Login")
def test_admin_login(login_page, dashboard_page):
    with allure.step("Navigate to login page"):
        login_page.navigate()
    with allure.step("Submit admin credentials"):
        login_page.login(Users.admin_email(), Users.admin_password())
    with allure.step("Verify dashboard loaded"):
        assert dashboard_page.is_displayed()

Allure generates a data folder during the run; allure serve or allure generate converts it to an HTML dashboard. This two-step process is its main practical disadvantage over ExtentReports — you can't just email one HTML file; you need either a server or the allure generate step in CI.

Reporting options — same test results, different experience

CLI / Surefire XML

  • Pass/fail count and exception message only

  • No screenshots on failure

  • No trend data or history

  • Unreadable by non-engineers

  • Good for CI tool parsing only

ExtentReports

  • Single-file HTML — shareable by email or link

  • Screenshots attached on failure

  • Environment and system info embedded

  • Charts for pass/fail/skip distribution

  • No trend across runs without extra setup

Allure

  • Step-level breakdown with @Step annotations

  • Cross-run trend charts and history

  • Retry tracking, flakiness detection

  • Java, Python, JS, Ruby, .NET support

  • Requires allure-serve or CI plugin to view

Built-in reporters — Playwright and Cypress

Modern tools include professional reporters out of the box, reducing the need for third-party libraries:

Playwright HTML reporter — generates a full HTML report with screenshots, video recordings, and network traces. One command in playwright.config.ts:

reporter: [
  ["html", { outputFolder: "playwright-report", open: "never" }],
  ["junit", { outputFile: "test-results/results.xml" }],  // also emit XML for CI
],

Cypress Cloud — Cypress's paid reporting service with analytics, parallelism management, and flakiness detection. For open-source, cypress-mochawesome-reporter generates a comparable local HTML report.

Choosing the right reporter

SituationRecommendation
Java + Selenium + TestNGExtentReports or Allure
Python + pytestAllure (best ecosystem fit) or pytest-html
PlaywrightBuilt-in HTML reporter
CypressMochawesome or Cypress Cloud
Cucumber (any language)Cucumber HTML plugin or Allure
Need trend data across runsAllure
Need a single shareable HTML fileExtentReports
Need test management integrationAllure TestOps or custom JUnit XML integration

Always emit JUnit XML in addition to the human-readable format. CI tools (Jenkins, GitHub Actions) parse JUnit XML to display test results natively. The human-readable report is for debugging; the XML is for the CI system.

Custom reporters — when standard tools don't fit

Some teams need outputs that standard reporters don't support: Slack notifications on failure, TestRail result uploads, custom dashboards with team-specific metrics. A custom ITestListener (TestNG) or Reporter (pytest plugin) can augment any standard reporter:

// Sends a Slack message for every failed test — TestNG Listener
@Override
public void onTestFailure(ITestResult result) {
    String message = String.format(":red_circle: FAILED: %s | %s | %s",
        result.getName(), Config.env(), result.getThrowable().getMessage());
    SlackClient.send(message);
}

Custom reporters should augment, not replace. Keep the standard Allure or ExtentReports output as the primary report; add integrations on top.

⚠️ Common mistakes

  • Flushing ExtentReports in @AfterMethod instead of @AfterSuite. If extent.flush() is called after every test, the HTML is overwritten repeatedly and only the last test's data survives. Flush once, at suite teardown.
  • Taking screenshots from the wrong thread. In parallel execution, DriverManager.getDriver() in a listener must return the correct thread's driver — which is why ThreadLocal in DriverManager is non-negotiable before adding parallel-safe reporting.
  • Only having the human-readable report. If Allure serves locally but the CI pipeline has no allure generate step, the report never reaches the team. Always verify that reports are generated and accessible after every CI run — build the CI step before you need it.

🎯 Practice task

Wire up a reporter and verify it captures failures — 35 minutes.

  1. Add ExtentReports. Add the extentreports dependency to your project. Create ExtentReportListener implementing ITestListener (TestNG) or the equivalent. Register it. Run the suite and open reports/extent.html — all tests should appear.
  2. Wire screenshots on failure. In onTestFailure, call ScreenshotHelper.captureBase64(DriverManager.getDriver()) and attach to the Extent test. Deliberately break one test (wrong selector) and re-run. Open the report — the failure should show a screenshot of the browser state.
  3. Add environment metadata. In onStart, add extent.setSystemInfo("Environment", Config.env()), "Browser", and "Build". Run the suite. Verify the System Info tab in the report shows the correct values.
  4. Also emit JUnit XML. Configure TestNG (or your runner) to additionally write Surefire XML. Verify the XML file appears in target/surefire-reports. This is what CI tools consume — having both formats lets the CI display pass/fail and lets stakeholders see the full report.
  5. Stretch — Allure comparison. Add the allure-testng (or allure-pytest) dependency. Add @Feature and @Story annotations to three tests. Run the suite, then allure serve target/allure-results. Compare the Allure dashboard to your ExtentReports dashboard. Identify one capability each has that the other lacks.

Next lesson: test data management — strategies for creating, sharing, and cleaning up the data your tests depend on, at scale.

// tip to track lessons you complete and pick up where you left off across devices.