TestNG Default Reports and Emailable Reports

7 min read

Every TestNG run generates reports automatically — no configuration required. The HTML files that appear in test-output/ after mvn test are ready to read, share, or attach to a ticket before you've written a single line of reporting code. Knowing what those files contain, how to customise the output directory, and how to add per-test log messages turns the default report from "something that exists" into a useful communication tool. This lesson covers the three default output files, Reporter.log() for inline logging, and the limitations that explain why teams eventually add ExtentReports or Allure.

What TestNG generates automatically

After every mvn test (or IntelliJ suite run), TestNG writes four items to test-output/:

File / directoryWhat it contains
index.htmlFull interactive report: suite summary, per-class breakdown, method timings, exception stack traces, DataProvider parameters
emailable-report.htmlSingle-file compact report: summary table, failed test names and error messages — designed to be emailed
testng-results.xmlMachine-readable XML: all pass/fail/skip results with timings — consumed by Jenkins, GitHub Actions, and other CI tools
junitreports/JUnit-compatible XML files per class — for CI systems that expect the JUnit XML format

Open index.html in a browser after a run. The left panel lists every <test> block from your testng.xml; clicking one shows every class and every method in that block, with a green/red/orange indicator and duration in milliseconds. Failed tests expand to show the full stack trace. DataProvider-backed tests show the parameter values for the invocation that failed.

The emailable report

emailable-report.html is the file to send to stakeholders and attach to Jira. It is a single self-contained HTML file with no external resources — no JavaScript CDN, no CSS files, just inline styles and embedded content. Open it on any machine without a server and it renders correctly.

The report contains three sections: a header summary with total/passed/failed/skipped counts and duration; a table of all test methods grouped by status; and an expandable detail section for every failure showing the exception message and first few lines of the stack trace.

Changing the output directory

By default TestNG writes to test-output/ in the project root. Change it in testng.xml:

<suite name="Regression Suite" output-dir="target/testng-reports">

Or from Maven via Surefire:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>3.2.5</version>
    <configuration>
        <suiteXmlFiles>
            <suiteXmlFile>src/test/resources/testng.xml</suiteXmlFile>
        </suiteXmlFiles>
        <reportsDirectory>${project.build.directory}/testng-reports</reportsDirectory>
    </configuration>
</plugin>

Putting reports under target/ means they are cleaned by mvn clean and never accidentally committed. Add test-output/ to .gitignore (the default location) and target/ is already excluded by the standard .gitignore.

Reporter.log() — adding inline messages

org.testng.Reporter.log() attaches a message to the current test's section in index.html. Messages appear inline under the test method — valuable for debugging failures without re-running in a debugger:

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.testng.Assert;
import org.testng.Reporter;
import org.testng.annotations.Test;
 
public class LoginTest extends com.mycompany.tests.base.BaseTest {
 
    @Test(groups = {"smoke"}, description = "Valid credentials navigate to inventory")
    public void validLoginSucceeds() {
        Reporter.log("Navigating to login page: " + driver().getCurrentUrl());
 
        driver().findElement(By.id("user-name")).sendKeys("standard_user");
        Reporter.log("Entered username: standard_user");
 
        driver().findElement(By.id("password")).sendKeys("secret_sauce");
        driver().findElement(By.id("login-button")).click();
        Reporter.log("Clicked login button");
 
        boolean onInventory = driver().getCurrentUrl().contains("/inventory.html");
        Reporter.log("On inventory page: " + onInventory);
 
        Assert.assertTrue(onInventory, "Expected inventory URL after login");
    }
}

Reporter.log(msg, true) also echoes the message to System.out — useful when you want the same message in the console and the HTML report. Without the true flag, messages are HTML-only.

testng-results.xml — machine-readable output

The testng-results.xml file is what CI tools consume. Its structure looks like:

<testng-results skipped="0" failed="1" ignored="0" total="5" passed="4">
    <suite duration-ms="3421" name="Regression Suite">
        <test duration-ms="3200" name="All Tests">
            <class name="com.mycompany.tests.tests.LoginTest">
                <test-method status="PASS" name="validLoginSucceeds" duration-ms="1200"/>
                <test-method status="FAIL" name="invalidLoginShowsError" duration-ms="800">
                    <exception class="org.openqa.selenium.NoSuchElementException">
                        <message>...</message>
                    </exception>
                </test-method>
            </class>
        </test>
    </suite>
</testng-results>

Jenkins' TestNG plugin reads this XML to produce trend graphs and per-run dashboards. GitHub Actions does not natively parse TestNG XML, but third-party actions like dorny/test-reporter can read it and post inline PR annotations.

Report anatomy at a glance

Limitations of the default report

The default report is functional but basic: plain HTML styling, no charts or trend data, screenshots require manual attachment (they won't appear inline unless you add a listener), no history across runs, and no filter/search interface on large suites. Teams that need more reach for ExtentReports (self-contained rich HTML) or Allure (interactive server-rendered reports with history). The next lesson covers both. Start with the default report — add a richer tool only when the default is genuinely insufficient.

⚠️ Common mistakes

  • Not adding test-output/ to .gitignore. The default output directory is at the project root. TestNG regenerates it on every run — committing it pollutes the repo with binary HTML diffs. Add test-output/ and screenshots/ to .gitignore from day one.
  • Sending index.html to stakeholders without the full test-output/ directory. index.html loads assets from sibling files inside test-output/. Opening it alone in a browser shows a broken partial page. Send emailable-report.html — it is truly standalone.
  • Using Reporter.log() for every line of test code. Flooding the report with 20 log messages per test makes failure diagnosis harder, not easier. Log meaningful state transitions: URL navigated to, key elements found or not found, assertion context. Aim for 3–5 messages per test method.

🎯 Practice task

Read and customise the default reports. 20–30 minutes.

  1. Run mvn clean test on your TestNG project. Open test-output/index.html and test-output/emailable-report.html in a browser. Explore both — find a passed test, a failed test (force one with Assert.fail()), and a skipped test.
  2. Add Reporter.log() to three steps in one test method. Re-run. Open index.html, click through to the test — confirm your messages appear beneath the test name.
  3. Change output-dir in testng.xml to target/test-reports. Run. Confirm the report appears in the new location and test-output/ is no longer created.
  4. Find the XML. Open target/test-reports/testng-results.xml (or test-output/testng-results.xml if you haven't moved it). Find your forced-failure test and read the <exception> element. This is exactly what Jenkins' TestNG plugin parses.
  5. Stretch — add a suite-level description. In testng.xml add verbose="2" and run again. The console now prints every method name as it runs — compare the noise level to verbose="1" and verbose="0". Pick the right level for development vs CI.

Next lesson: ExtentReports and Allure — richer reports with screenshots, charts, and history that replace the default report for anything beyond a personal project.

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