Click here to view javadocs.
View this link for all active contributors.
2.41.2
<!-- pom.xml -->
<dependency>
<groupId>com.relevantcodes</groupId>
<artifactId>extentreports</artifactId>
<version>2.41.2</version>
</dependency>
Version 2.41.2 launched on 10/20/2016. Here is what's new:
replaceExisting
causing exceptions while parsingThis section demonstrates basic methods that you will be using with this library.
To initialize the report, you must create an instance of ExtentReports
. It is possible to initialize in the following different ways:
ExtentReports(String filePath, Boolean replaceExisting, DisplayOrder displayOrder, NetworkMode networkMode, Locale locale)
ExtentReports(String filePath, Boolean replaceExisting, DisplayOrder displayOrder, NetworkMode networkMode)
ExtentReports(String filePath, Boolean replaceExisting, DisplayOrder displayOrder, Locale locale)
ExtentReports(String filePath, Boolean replaceExisting, DisplayOrder displayOrder)
ExtentReports(String filePath, Boolean replaceExisting, NetworkMode networkMode, Locale locale)
ExtentReports(String filePath, Boolean replaceExisting, NetworkMode networkMode)
ExtentReports(String filePath, NetworkMode networkMode)
ExtentReports(String filePath, Boolean replaceExisting, Locale locale)
ExtentReports(String filePath, Boolean replaceExisting)
ExtentReports(String filePath, Locale locale)
ExtentReports(String filePath)
%reportFolder%/extentreports
and report will be accessible without internet connectivity.properties
file as shown here.
To create an offline report, useNetworkMode.OFFLINE
in your initialization:
ExtentReports(String filePath, Boolean replaceExisting, DisplayOrder displayOrder, NetworkMode networkMode, Locale locale)
ExtentReports(String filePath, Boolean replaceExisting, DisplayOrder displayOrder, NetworkMode networkMode)
ExtentReports(String filePath, Boolean replaceExisting, NetworkMode networkMode, Locale locale)
ExtentReports(String filePath, Boolean replaceExisting, NetworkMode networkMode)
ExtentReports(String filePath, NetworkMode networkMode)
// usage:
ExtentReports extent = ExtentReports("file-path", NetworkMode.OFFLINE);
To start a custom reporter, use the following method:
extent.startReporter(ReporterType);
Presently, only 1 additional custom reporter is provided, of type DB
to write all execution information to a sqlite database.
Once your session is complete and you are ready to write all logs to the report, simply call the flush()
method.
// writing everything to document
extent.flush();
To append to a report you had previously created, simply mark replaceExisting = false
and new tests will be appended to the same report.
ExtentReports extent = new ExtentReports(file-path, false);
To start tests, a new instance of ExtentTest
must be created. To end, simply call endTest(testInstance)
.
// new instance
ExtentReports extent = new ExtentReports(file-path, replaceExisting);
// starting test
ExtentTest test = extent.startTest("Test Name", "Sample description");
// step log
test.log(LogStatus.PASS, "Step details");
// ending test
extent.endTest(test);
// writing everything to document
extent.flush();
There are 2 ways logs can be created: one that creates 3 columns and other that creates 4. Always use only 1 type of log for the test otherwise the table will become malformed.
// creates 3 columns in table: TimeStamp, Status, Details
log(LogStatus, details);
log(LogStatus logStatus, Throwable t);
// creates 4 columns in table: TimeStamp, Status, StepName, Details
log(LogStatus, stepName, details);
log(LogStatus logStatus, String stepName, Throwable t);
You can know the current status of the test during execution by calling the getRunStatus()
method.
LogStatus status = test.getRunStatus();
You can assign categories to tests using assignCategory(String... params)
method:
test.assignCategory("Regression");
test.assignCategory("Regression", "ExtentAPI");
test.assignCategory("Regression", "ExtentAPI", "category-3", "cagegory-4", ..);
Or simply assign them when you start your test:
ExtentTest test = extent
.startTest("Categories")
.assignCategory("Regression", "ExtentAPI");
Assign the author of the test (similar to assignCategory
above):
test.assignAuthor("Anshoo");
test.assignAuthor("Anshoo", "Viren");
To add a test node as a child of another test, use the appendChild
method.
ExtentTest parent = extent.startTest("Parent");
ExtentTest child1 = extent.startTest("Child 1");
child1.log(LogStatus.INFO, "Info");
ExtentTest child2 = extent.startTest("Child 2");
child2.log(LogStatus.PASS, "Pass");
parent
.appendChild(child1)
.appendChild(child2);
extent.endTest(parent);
Simply insert any custom HTML in the logs by using an HTML tag:
extent.log(LogStatus.INFO, "HTML", "Usage: BOLD TEXT");
To add a screen-shot, simply call addScreenCapture
. This method returns the HTML with tag which can be used anywhere in the log details.
test.log(LogStatus.INFO, "Snapshot below: " + test.addScreenCapture("screenshot-path"));
Relative paths starting with .
and /
are supported. If you using an absolute path, file:///
will be automatically be appended for the image to load.
To insert a Base64 screenshot, simply use the string with addBase64ScreenShot
method:
test.log(LogStatus.INFO, "Snapshot below: " + test.test.addBase64ScreenShot("base64String"));
To add a screencast/recording of your test run, use the addScreencast
method anywhere in the log details:
test.log(LogStatus.INFO, "Screencast below: " + test.addScreencast("screencast-path"));
Relative paths starting with .
and /
are supported. If you using an absolute path, file:///
will be automatically be appended for the screencst to load.
It is possible to add system or environment info for your using using the addSystemInfo
method. It is an overloaded method that allows you to add via a Map
or string pairs.
By default, the OS
, User Name
, Java Version
and Host Name
will be available. You do not have to add these 4 items manually.
extent.addSystemInfo("Selenium Version", "2.46");
extent.addSystemInfo("Environment", "Prod");
Map sysInfo = new HashMap();
sysInfo.put("Selenium Version", "2.46");
sysInfo.put("Environment", "Prod");
extent.addSystemInfo(sysInfo);
Call close()
at the very end of your session to clear all resources. If any of your test ended abruptly causing any side-affects (not all logs sent to ExtentReports, information missing), this method will ensure that the test is still appended to the report with a warning message.
extent.close();
You should call close()
only once, at the very end (in @AfterSuite
for example) as it closes the underlying stream. Once this method is called, calling any Extent method will throw an error.
Internally, this method does everything that flush()
does by writing to the text file. Here are a few differences between close
and flush:
:
endTest
), close
will still show them in the report with a warning sign. Calling Flush
will only write tests to the report once they are endedClose
does not write SystemInfo to the report (flush
does)flush
any number of times for a report session but close
only once, because:If you are using ExtentX as the report server, it will be required to provide the host & port of MongoDB to connect:
// if MongoDB on localhost, port: 27017
extent.x();
// if MongoDB on custom host & port
extent.x(host, port);
Extent uses the same way to connect to MongoDB as used by MongoClient class. To see additional connection options, see MongoClient javadoc. The MongoClient connect options are used in x()
the same way:
// from MongoClient docs:
MongoClient mongoClient1 = new MongoClient();
MongoClient mongoClient1 = new MongoClient("localhost");
MongoClient mongoClient2 = new MongoClient("localhost", 27017);
MongoClient mongoClient4 = new MongoClient(new ServerAddress("localhost"));
// uses same options as MongoClient
extent.x();
extent.x("localhost");
extent.x("localhost", 27017);
extent.x(new ServerAddress("localhost"));
This section shows basic examples to get started with ExtentReports 2.0. For multi-threaded execution models, see section Creating Parallel Runs.
A simple example with a static method.
import com.relevantcodes.extentreports.ExtentReports;
import com.relevantcodes.extentreports.ExtentTest;
import com.relevantcodes.extentreports.LogStatus;
public class Main {
private static ExtentReports extent;
public static void main(String[] args) {
extent = new ExtentReports("file-path", true);
// creates a toggle for the given test, adds all log events under it
ExtentTest test = extent.startTest("My First Test", "Sample description");
// log(LogStatus, details)
test.log(LogStatus.INFO, "This step shows usage of log(logStatus, details)");
// report with snapshot
String img = test.addScreenCapture("img-path");
test.log(LogStatus.INFO, "Image", "Image example: " + img);
// end test
extent.endTest(test);
// calling flush writes everything to the log file
extent.flush();
}
}
See this article by Richard Kieft showing JUnit usage with @Rule
annotation.
A simple example that uses a global ExtentReports instance showing 2 tests that check for Google search button text. The test with the assert will fail.
public class SingleLogTest extends BaseExample {
@Test
public void passTest() {
test = extent.startTest("passTest");
test.log(LogStatus.PASS, "Pass");
Assert.assertEquals(test.getRunStatus(), LogStatus.PASS);
}
@Test
public void intentionalFailure() throws Exception {
test = extent.startTest("intentionalFailure");
throw new Exception("intentional failure");
}
}
public class ExtentManager {
private static ExtentReports extent;
public synchronized static ExtentReports getReporter(String filePath) {
if (extent == null) {
extent = new ExtentReports(filePath, true);
extent
.addSystemInfo("Host Name", "Anshoo")
.addSystemInfo("Environment", "QA");
}
return extent;
}
}
public abstract class BaseExample {
protected ExtentReports extent;
protected ExtentTest test;
final String filePath = "Extent.html";
@AfterMethod
protected void afterMethod(ITestResult result) {
if (result.getStatus() == ITestResult.FAILURE) {
test.log(LogStatus.FAIL, result.getThrowable());
} else if (result.getStatus() == ITestResult.SKIP) {
test.log(LogStatus.SKIP, "Test skipped " + result.getThrowable());
} else {
test.log(LogStatus.PASS, "Test passed");
}
extent.endTest(test);
extent.flush();
}
@BeforeSuite
public void beforeSuite() {
extent = ExtentManager.getReporter(filePath);
}
@AfterSuite
protected void afterSuite() {
extent.close();
}
}
If you use ExtentReports and do not want to use it like a normal logger, it is also possible to use it as a listener for TestNG to still be able to generate a beautiful report with dashboards. See this post for more information.
This section is going to be helpful for users that use a multi-threaded execution model.
Virender has created a tutorial to show a few ways of using Extent in a parallel execution model. View it here
Depending on how your tests are structured, usage may differ for parallel classes and methods. If you require a single report for the run session, only use one instance of ExtentReports, as shown via ExtentManager
class below.
Below is a sample testng.xml
file for running parallel classes.
<?xml version="1.0" encoding="UTF-8"?>
<suite name="Extent Parallel Test" parallel="classes" thread-count="2">
<test name="ExtentWithParallelClasses">
<classes>
<class name="ParallelClass1" />
<class name="ParallelClass2" />
</classes>
</test>
</suite>
Note that the test
instance for each class is local. That's because each class runs in its own thread, so each thread must have its own instance of test object.
public class ParallelClass1 extends BaseClass {
@Test
public void parallelClass1TestResultMustEqualPass() {
ExtentTestManager.getTest().log(LogStatus.PASS, "Log from threadId: " + Thread.currentThread().getId());
ExtentTestManager.getTest().log(LogStatus.INFO, "Log from threadId: " + Thread.currentThread().getId());
Assert.assertEquals(ExtentTestManager.getTest().getRunStatus(), LogStatus.PASS);
}
}
public class ParallelClass2 extends BaseClass {
@Test
public void parallelClass2TestResultMustEqualFail() throws Exception {
ExtentTestManager.getTest().log(LogStatus.FAIL, "Log from threadId: " + Thread.currentThread().getId());
ExtentTestManager.getTest().log(LogStatus.INFO, "Log from threadId: " + Thread.currentThread().getId());
throw new Exception("intentional failure");
}
}
public class ExtentTestManager {
static Map extentTestMap = new HashMap();
static ExtentReports extent = ExtentManager.getReporter();
public static synchronized ExtentTest getTest() {
return extentTestMap.get((int) (long) (Thread.currentThread().getId()));
}
public static synchronized void endTest() {
extent.endTest(extentTestMap.get((int) (long) (Thread.currentThread().getId())));
}
public static synchronized ExtentTest startTest(String testName) {
return startTest(testName, "");
}
public static synchronized ExtentTest startTest(String testName, String desc) {
ExtentTest test = extent.startTest(testName, desc);
extentTestMap.put((int) (long) (Thread.currentThread().getId()), test);
return test;
}
}
public class ExtentManager {
static ExtentReports extent;
final static String filePath = "Extent.html";
public synchronized static ExtentReports getReporter() {
if (extent == null) {
extent = new ExtentReports(filePath, true);
}
return extent;
}
}
public abstract class BaseClass {
@BeforeMethod
public void beforeMethod(Method method) {
ExtentTestManager.startTest(method.getName());
}
@AfterMethod
protected void afterMethod(ITestResult result) {
if (result.getStatus() == ITestResult.FAILURE) {
ExtentTestManager.getTest().log(LogStatus.FAIL, result.getThrowable());
} else if (result.getStatus() == ITestResult.SKIP) {
ExtentTestManager.getTest().log(LogStatus.SKIP, "Test skipped " + result.getThrowable());
} else {
ExtentTestManager.getTest().log(LogStatus.PASS, "Test passed");
}
ExtentManager.getReporter().endTest(ExtentTestManager.getTest());
ExtentManager.getReporter().flush();
}
protected String getStackTrace(Throwable t) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
t.printStackTrace(pw);
return sw.toString();
}
}
Below is a sample testng.xml
file for running parallel methods.
<?xml version="1.0" encoding="UTF-8"?>
<suite name="Extent Parallel Test" parallel="methods">
<test name="ExtentWithParallelMethods" allow-return-values="true">
<classes>
<class name="ParallelMethodsTest" />
</classes>
</test>
</suite>
An important thing to note when executing parallel methods is that, since each @Test
method runs in its own thread, you must start and end your tests in the same @Test
itself.
@Test
public class ParallelMethodsTest {
private final String filePath = "ParallelMethodsTest.html";
@BeforeSuite
public void beforeSuite() {
extent = ExtentManager.getReporter(filePath);
}
@Test
public void parallelTest01() {
ExtentTest test = ExtentTestManager.startTest(Thread.currentThread().getStackTrace()[1].getMethodName());
test.log(LogStatus.INFO, "Log from threadId: " + Thread.currentThread().getId());
test.log(LogStatus.INFO, "Log from threadId: " + Thread.currentThread().getId());
ExtentTestManager.endTest();
}
@Test
public void parallelTest02() {
ExtentTest test = ExtentTestManager.startTest(Thread.currentThread().getStackTrace()[1].getMethodName());
test.log(LogStatus.ERROR, "Log from threadId: " + Thread.currentThread().getId());
test.log(LogStatus.ERROR, "Log from threadId: " + Thread.currentThread().getId());
ExtentTestManager.endTest();
}
@AfterMethod
public void afterEachTest(ITestResult result) {
if (!result.isSuccess()) {
test.log(LogStatus.FAIL, result.getThrowable());
}
extent.endTest(test);
extent.flush();
}
}
public class ExtentTestManager { // new
static Map extentTestMap = new HashMap();
private static ExtentReports extent = ExtentManager.getReporter();
public static synchronized ExtentTest getTest() {
return extentTestMap.get((int) (long) (Thread.currentThread().getId()));
}
public static synchronized void endTest() {
extent.endTest(extentTestMap.get((int) (long) (Thread.currentThread().getId())));
}
public static synchronized ExtentTest startTest(String testName) {
return startTest(testName, "");
}
public static synchronized ExtentTest startTest(String testName, String desc) {
ExtentTest test = extent.startTest(testName, desc);
extentTestMap.put((int) (long) (Thread.currentThread().getId()), test);
return test;
}
}
public class ExtentManager {
private static ExtentReports extent;
public synchronized static ExtentReports getReporter(String filePath) {
if (extent == null) {
extent = new ExtentReports(filePath, true, NetworkMode.ONLINE);
extent
.addSystemInfo("Host Name", "Anshoo")
.addSystemInfo("Environment", "QA");
}
return extent;
}
public synchronized static ExtentReports getReporter() {
return extent;
}
}
Extent currently supports the following localized versions of the document:
new Locale("en-US");
new Locale("es-ES");
New configuration items are added in most new versions, so be sure to use the latest config.xml for your report.
<?xml version="1.0" encoding="UTF-8"?>
<extentreports>
<configuration>
<!-- report theme -->
<!-- standard, dark -->
<theme>standard</theme>
<!-- document encoding -->
<!-- defaults to UTF-8 -->
<encoding>UTF-8</encoding>
<!-- protocol for script and stylesheets -->
<!-- defaults to https -->
<protocol>https</protocol>
<!-- title of the document -->
<documentTitle>ExtentReports 2.0</documentTitle>
<!-- report name - displayed at top-nav -->
<reportName>Automation Report</reportName>
<!-- report headline - displayed at top-nav, after reportHeadline -->
<reportHeadline></reportHeadline>
<!-- global date format override -->
<!-- defaults to yyyy-MM-dd -->
<dateFormat>yyyy-MM-dd</dateFormat>
<!-- global time format override -->
<!-- defaults to HH:mm:ss -->
<timeFormat>HH:mm:ss</timeFormat>
<!-- custom javascript -->
<scripts>
<![CDATA[
$(document).ready(function() {
});
]]>
</scripts>
<!-- custom styles -->
<styles>
<![CDATA[
]]>
</styles>
</configuration>
</extentreports>
The configuration file can be saved as one of your project resources of kept externally.
To use this configuration, in your Java code, call loadConfig
:
loadConfig(File configFile);
loadConfig(Class clazz, String fileName);
loadConfig(Class clazz, String basePackagePath, String fileName);
Examples:
// file location: "C:\HelloWorld\extent-config.xml"
loadConfig(new File("C:\HelloWorld\extent-config.xml"));
// loadConfig(Class clazz, String fileName);
// clazz: com.relevantcodes.extentreports.ExtentReports
// packagePath of clazz: com/relevantcodes/extentreports
// final path: com/relevantcodes/extentreports/extent-config.xml
loadConfig(ExtentReports.class, "extent-config.xml");
// loadConfig(Class clazz, String basePackagePath, String fileName;
// clazz: com.relevantcodes.extentreports.ExtentReports
// packagePath of clazz: com/relevantcodes/extentreports
// basePackagePath: "resources"
// final path: com/relevantcodes/extentreports/resources/extent-config.xml
loadConfig(ExtentReports.class, "resources", "extent-config.xml");
<extentreports>
<configuration>
<!-- document encoding -->
<!-- defaults to UTF-8 -->
<encoding>UTF-8</encoding>
<!-- protocol for script and stylesheets -->
<!-- defaults to https -->
<protocol>https</protocol>
<!-- title of the document -->
<documentTitle>ExtentReports 2.0</documentTitle>
<!-- report name - displayed at top-nav -->
<reportName>Automation Report</reportName>
<!-- report headline - displayed at top-nav, after reportHeadline -->
<reportHeadline></reportHeadline>
<!-- global date format override -->
<!-- defaults to yyyy-MM-dd -->
<dateFormat>yyyy-MM-dd</dateFormat>
<!-- global time format override -->
<!-- defaults to HH:mm:ss -->
<timeFormat>HH:mm:ss</timeFormat>
<!-- custom javascript -->
<scripts>
<![CDATA[
$(document).ready(function() {
});
]]>
</scripts>
<!-- custom styles -->
<styles>
<![CDATA[
]]>
</styles>
</configuration>
</extentreports>
The configuration file can be saved as one of your project resources of kept externally.
To use this configuration, in your Java code, call loadConfig
:
loadConfig(File configFile);
loadConfig(Class clazz, String fileName);
loadConfig(Class clazz, String basePackagePath, String fileName);
Examples:
// file location: "C:\HelloWorld\extent-config.xml"
loadConfig(new File("C:\HelloWorld\extent-config.xml"));
// loadConfig(Class clazz, String fileName);
// clazz: com.relevantcodes.extentreports.ExtentReports
// packagePath of clazz: com/relevantcodes/extentreports
// final path: com/relevantcodes/extentreports/extent-config.xml
loadConfig(ExtentReports.class, "extent-config.xml");
// loadConfig(Class clazz, String basePackagePath, String fileName;
// clazz: com.relevantcodes.extentreports.ExtentReports
// packagePath of clazz: com/relevantcodes/extentreports
// basePackagePath: "resources"
// final path: com/relevantcodes/extentreports/resources/extent-config.xml
loadConfig(ExtentReports.class, "resources", "extent-config.xml");
All customization is done at the report level and by using the config()
method of ExtentReports. Remember, you can chain these methods as config()
is fluent as shown below:
extent.config()
.documentTitle("Title of the Report")
.reportName("ExtentReports")
.reportHeadline("A short headline.")
.insertCustomStyles(".test { border:2px solid #444; }");
You can use your custom CSS directly in the document or load a custom css-stylesheet.
// custom styles
String style = ".test { border: 2px solid #444; }";
extent.config().insertCustomStyles(style);
// custom stylesheet
extent.config().addCustomStylesheet("C:\\css.css");
Just like having the ability to change document styles, you can also add your own custom scripts.
extent.config().insertJs("$('.test').click(function(){ alert('test clicked'); });");
It is possible to change the title of the report file using:
extent.config().documentTitle("ExtentReports - DocumentTitle");
To set the report name, simply call the reportName()
method:
extent.config().reportName("ExtentReports - ReportName");
The max length allowed for report-name is 20 characters.
You can remove or add your own summary by using the following config:
extent.config().reportHeadline("ExtentReports - ReportHeadline");
The max length allowed for report-headline is 70 characters.
Extent - Reporting API and Server Copyright (C) 2016 Anshoo Arora, RelevantCodes.com All rights reserved
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESSOR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER ORCONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHERIN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.