Getting Started
Since the main activity of any devops team is around building and refining application code & tests, reporting generally becomes an afterthought. But, with the Extent framework, it doesn't have to be. Extent Reporting Framework is widely adopted and used in many test frameworks. With its API-style usage, and a wealth of available adapters, listeners and examples, you can get started with beautiful and incredibly descriptive reports in no time.
Community vs Pro Editions
Unlike version 3, there are no differences in the underlying ExtentReports API - both community and professional users enjoy a full-featured API, with the exception of a few reporters. To see which reporters are available in your edition, visit this page. In this documentation website, reporters available only to the professional edition only are marked pro
Migrating from Version 3
If you are migrating from version 3, please note that the core usage remains the same. See the list of breaking changes here.
Download
Extent Core is a required dependency for all formatters or reporters.
Nuget
GitHub
Reporters
Extent allows creation of tests, nodes, events and assignment of tags, devices, authors, environment values etc. This information can be printed to multiple destinations. In our context, a reporter defines the destination.
You can use one or more reporters to create different types of reports. reporters are available for BDD, non-BDD and both - choose one of the reporters from the navigation menu at the top of this page to learn more.
- ExtentAventReporter pro
- ExtentBDDReporter pro
- ExtentCardsReporter pro
- ExtentEmailReporter pro
- ExtentHtmlReporter (version-4)
- ExtentV3HtmlReporter (version-3)
- ExtentKlovReporter
- ExtentLoggerReporter
- ExtentTabularReporter pro
Usage
This section focuses upon the core usage of the framework. Extent framework follows the Observer pattern and each reporter attached becomes an observer and notified of any changes such as creation of a test, assignment of a category to the test, adding an event etc.
Attaching Reporters
To enable or start a reporter, use AttachReporter
:
var extent = new ExtentReports();
extent.AttachReporter(reporterType);
A real world example attaching 2 reporters:
var avent = new ExtentAventReporter("/user/build/");
var klov = new ExtentKlovReporter("project", "build");
var extent = new ExtentReports();
extent.AttachReporter(avent);
extent.AttachReporter(klov);
// or:
extent.AttachReporter(avent, klov);
Flush
Flush
writes/updates the test information of your reporter to the destination type.
- ExtentAventReporter: Builds multiple linked output files in HTML format
- ExtentBDDReporter: Builds multiple linked output files in HTML format
- ExtentCardsReporter: Builds multiple linked output files in HTML format
- ExtentEmailReporter: Builds an emailable HTML file
- ExtentHtmlReporter (version-3): Builds a single HTML file with multiple views (SPA)
- ExtentKlovReporter: Updates MongoDb upon invocation of each
TestListener
event - ExtentLoggerReporter: Builds multiple linked output files in HTML format
- ExtentTabularReporter: Builds multiple linked output files in HTML format
Creating Tests
To create tests, use CreateTest
. This method returns a ExtentTest
object.
var test = extent.CreateTest("MyFirstTest");
test.Pass("details");
// short-hand
extent.CreateTest("MyFirstTest").Pass("details");
// description
extent.CreateTest("MyFirstTest", "Test Description").Pass("details");
Creating Nodes
To create nodes, use CreateNode
. This method returns a ExtentTest
object.
ExtentReports creates a test (CreateTest
) and ExtentTest creates a node (CreateNode
).
var test = extent.CreateTest("Test"); // level = 0
var node = test.CreateNode("Node"); // level = 1
node.Pass("details");
// short-hand
extent.CreateTest("MyFirstTest").CreateNode("MyFirstChildTest").Pass("details");
// description
var node = test.CreateNode("MyFirstChildTest", "Node Description");
Each test or node created is assigned a level in the test hierarchy. The top-most test has a level value of 0
, and level is incremented each time a new step in the hierarchy is achieved.
test (level 0)
-
child (level 1)
grandchild (level 2)
A BDD example
Feature (level 0)
-
Scenario (level 1)
Given (level 2)
When (level 2)
Then (level 2)
Creating BDD Tests
To create gherkin-style tests, use the gherkin classes or model names. The example below shows creation of such tests directly using gherkin classes:
// feature
var feature = extent.CreateTest<Feature>("Refund item");
// scenario
var scenario = feature.CreateNode<Scenario>("Jeff returns a faulty microwave");
// steps
scenario.CreateNode<Given>("Jeff has bought a microwave for $100").Pass("pass");
scenario.CreateNode<And>("he has a receipt").Pass("pass");
scenario.CreateNode<When>("he returns the microwave").Pass("pass");
scenario.CreateNode<Then>("Jeff should be refunded $100").Fail("fail");
You can also pass the gherkin keyword directly when creating tests using the GherkinKeyword
instance.
// feature
var feature = extent.CreateTest(new GherkinKeyword("Feature"), "Refund item");
// scenario
var scenario = feature.CreateNode(new GherkinKeyword("Scenario"), "Jeff returns a faulty microwave");
// steps
scenario.CreateNode(new GherkinKeyword("Given"), "Jeff has bought a microwave for $100").Pass("pass");
scenario.CreateNode(new GherkinKeyword("And"), "he has a receipt").Pass("pass");
scenario.CreateNode(new GherkinKeyword("When"), "he returns the microwave").Pass("pass");
scenario.CreateNode(new GherkinKeyword("Then"), "Jeff should be refunded $100").Fail("fail");
A ClassNotFoundException
is thrown if the supplied keyword is not found.
// Typo, FFeature instead of Feature
// ClassNotFoundException
var feature = extent.CreateTest(new GherkinKeyword("FFeature"), "Refund item");
Selecting a Gherkin Dialect
If you are using a dialect other than the default en
or English keywords, you can now specify a gherkin dialect. The API uses the same source for dialects as defined in the gherkin spec. To specify your dialect, simply add this before starting to create tests:
// German
extent.GherkinDialect = "de";
// Norwegian
extent.GherkinDialect = "no";
Removing Tests
Use RemoveTest
to remove a test which was created using CreateTest
or CreateNode
.
var test = extent.CreateTest("Test").Fail("reason");
extent.RemoveTest(test);
var test = extent.CreateTest("Test");
var node = test.CreateNode("Node").Fail("reason");
extent.RemoveTest(node);
Log Events
To create an event for a test or node, use test.Log(Status.Pass, "details")
or test.Pass("details")
. The below status are available but the most commonly used are Pass
, Fail
and Skip
.
- Fatal
- Fail
- Error
- Warning
- Skip
- Pass
- Info
- Debug
var test = extent.CreateTest("TestName");
test.Log(Status.Pass, "pass");
test.Pass("pass");
test.Log(Status.Fail, "fail");
test.Fail("fail");
A test will never be marked with Info
and Debug
status. These are only available to indicate the type of log, and a test with only these status will be marked as Pass
. Fatal
has the highest level in the hierarchy and the lowest possible (and default) status in the hierarchy is Pass
.
pass < skip < warning < error < fail < fatal
Logging Exceptions
Exceptions
can be passed directly when logging events.
Exception e;
test.Fail(e);
To enable the Defects tab in some reporters, use this approach to log exceptions instead of passing a string representing the exception.
Assign Tags
You can assign tags or categories to tests using AssignCategory
.
test.AssignCategory("tag");
test.AssignCategory("tag-1", "tag-2", ..);
// usage
extent.CreateTest("Test").AssignCategory("tag-1").Pass("details");
Assigning tags enables the Tag view in BasicFileReporter
reporters.
Assign Devices
You can assign devices to tests using AssignDevice
.
test.AssignDevice("device-name");
test.AssignDevice("device-1", "device-2", ..);
// usage
extent.CreateTest("Test").AssignDevice("device-name").Pass("details");
Assign Authors
You can assign categories to tests using AssignAuthor
.
test.AssignAuthor("author");
test.AssignAuthor("author-1", "author-2", ..);
// usage
extent.CreateTest("MyFirstTest").AssignAuthor("aventstack").Pass("details");
Screenshots
You can add file and base64 snapshots to your tests. Both methods are described in this section.
Screenshots - Tests
To add screenshots to tests, use AddScreenCaptureFromPath
.
Note: This method does not "attach" or "affix" the screenshot the file-based (BasicFileReporter
) formatters/reporters, such as the Avent, HTML, Email, Logger etc. Instead, the image is saved on disk and is referenced in the report using the <img>
tag.
test.Fail("details").AddScreenCaptureFromPath("screenshot.png");
Screenshots - Logs
Adding a screenshot to a log is a little different as logs do not accept image paths directly. The path must be provided using MediaEntityBuilder
.
var mediaModel =
MediaEntityBuilder.CreateScreenCaptureFromPath("screenshot.png").Build();
test.Fail("details", mediaModel);
// or:
test.Fail("details",
MediaEntityBuilder.CreateScreenCaptureFromPath("screenshot.png").Build());
Screenshots
Base64 Screenshots - Tests
To add base64 screenshots to tests, use AddScreenCaptureFromBase64String
.
test.AddScreenCaptureFromBase64String("base64String");
Base64 Screenshots - Logs
To add base64 screenshots to logs, use CreateScreenCaptureFromBase64String
.
test.Fail("details",
MediaEntityBuilder.CreateScreenCaptureFromBase64String("base64String").Build());
test.Log(Status.Fail, "details",
MediaEntityBuilder.CreateScreenCaptureFromBase64String("base64String").Build());
Inserting HTML
Simply insert custom HTML in the logs by using an HTML tag:
test.Log(Status.Info, "Usage: <b>BOLD TEXT</b>");
Adding System Info
It is possible to add system or environment info for your using using the AddSystemInfo
method. This automatically adds system information to all started reporters.
extent.AddSystemInfo("os", "win7");
Creating system info also enables the System or Environment view in some reporters.
Configuration
The core Extent API supports several configuration options as detailed in this section.
Analysis Strategy
Avent support configuring the analysis strategy by selecting one of the 3 available strategies:
- AnalysisStrategy.Test (default)
- AnalysisStrategy.Class
- AnalysisStrategy.BDD
AnalysisStrategy.Test
Generates stats based on maximum 2 levels - Tests and Steps (children). If none of the Tests in the run-session do not contain any Steps, stats will be based on 1 level only. In the examples below, AnalysisStrategy is not explicitly set since TEST is the default strategy. To explicitly set the strategy use:
extent.AnalysisStrategy = AnalysisStrategy.Test;
If there are multiple levels, the bottom-most or the leaf test will be considered as Step. See example-3 for more information.
Example 1
var parent = extent.CreateTest("Parent");
parent.CreateNode("Regression").Pass("pass");
parent.CreateNode("Unit").Fail("fail");
In the above example, the stats on the dashboard are viewed as:
// 2 charts: Tests, Steps
Total Tests = 1, tests passed = 0, tests failed = 1 // Parent (fail)
Total Steps = 2, steps passed = 1, steps failed = 1 // Regression (pass), Unit (fail)
Example 2
Now consider a different example without child tests:
var parent1 = extent.CreateTest("Parent1");
parent1.Pass("pass");
var parent2 = extent.CreateTest("Parent2");
parent2.Fail("fail");
In this example, since there are no children or steps, the dashboard displays 1 chart as:
// 1 chart: Tests
Total Tests = 2, tests passed = 1, tests failed = 1
Example 3
Finally, if there are more than 2 levels, the top-most and the bottom-most (leaf) levels will only be considered.
var parent = extent.CreateTest("Parent");
var child = parent.CreateNode("Child");
child.CreateNode("Regression").Pass("pass");
child.CreateNode("Unit").Fail("fail");
child.CreateNode("Integration").Pass("pass");
// you will see 2 charts: Tests, Steps
Total Tests = 1, tests passed = 0, tests failed = 1 // counts Parent
Total Steps = 3, steps passed = 2, steps failed = 1 // counts Regression, Unit, Integration
AnalysisStrategy.BDD
It is not required to explicitly set the AnalysisStrategy
for BDD tests, it is configured internally if a BDD test is started.
Markup Helpers
A few helpers are provided to allow:
- Code block
- Table
- Label
Code block
var test = extent.CreateTest("Code Blocks");
// xml
var code = "<root>" +
"\n <Person>" +
"\n <Name>Joe Doe</Name>" +
"\n <StartDate>2007-01-01</StartDate>" +
"\n <EndDate>2009-01-01</EndDate>" +
"\n <Location>London</Location>" +
"\n </Person> " +
"\n <Person>" +
"\n <Name>John Smith</Name>" +
"\n <StartDate>2012-06-15</StartDate>" +
"\n <EndDate>2014-12-31</EndDate>" +
"\n <Location>Cardiff</Location>" +
"\n </Person>" +
"\n</root>";
var m = MarkupHelper.CreateCodeBlock(code, CodeLanguage.Xml);
test.Pass(m);
// json
var json = "{'foo' : 'bar', 'foos' : ['b','a','r'], 'bar' : {'foo':'bar', 'bar':false,'foobar':1234}}";
test.Pass(MarkupHelper.CreateCodeBlock(json, CodeLanguage.Json));
// java chars
code = "\n\t\n\t\tText\n\t\n";
m = MarkupHelper.CreateCodeBlock(code);
test.Pass(m);
Resulting markup:
Label
var text = "extent";
var m = MarkupHelper.CreateLabel(text, ExtentColor.Blue);
test.Pass(m);
// or
test.Log(Status.Pass, m);
Table
String[][] data = {
{ "Header1", "Header2", "Header3" },
{ "Content.1.1", "Content.2.1", "Content.3.1" },
{ "Content.1.2", "Content.2.2", "Content.3.2" },
{ "Content.1.3", "Content.2.3", "Content.3.3" },
{ "Content.1.4", "Content.2.4", "Content.3.4" }
};
var m = MarkupHelper.CreateTable(data);
test.Pass(m);
// or
test.Log(Status.Pass, m);
Examples
Basic Usage
public class Main {
public static void main(String[] args) {
// start reporters
var reporter = new ExtentHtmlReporter("path/to/directory/");
// create ExtentReports and attach reporter(s)
var extent = new ExtentReports();
extent.AttachReporter(htmlReporter);
// creates a test
var test = extent.CreateTest("MyFirstTest", "Sample description");
// log(Status, details)
test.Log(Status.Info, "This step shows usage of log(status, details)");
// info(details)
test.Info("This step shows usage of info(details)");
// log with snapshot
test.Fail("details",
MediaEntityBuilder.CreateScreenCaptureFromPath("screenshot.png").Build());
// test with snapshot
test.AddScreenCaptureFromPath("screenshot.png");
// calling flush writes everything to the log file
extent.Flush();
}
}
Helper Classes
ExtentService
public class ExtentService
{
private static readonly Lazy _lazy =
new Lazy(() => new ExtentReports());
public static ExtentReports Instance { get { return _lazy.Value; } }
static ExtentService()
{
var reporter = new ExtentHtmlReporter(TestContext.CurrentContext.TestDirectory);
reporter.Config.Theme = Theme.Standard;
Instance.AttachReporter(reporter);
}
private ExtentService()
{
}
}
ExtentTestManager
public class ExtentTestManager
{
private static Dictionary _parentTestMap = new Dictionary();
private static ThreadLocal _parentTest = new ThreadLocal();
private static ThreadLocal _childTest = new ThreadLocal();
private static readonly object _synclock = new object();
// creates a parent test
public static ExtentTest CreateTest(string testName, string description = null)
{
lock (_synclock)
{
_parentTest.Value = ExtentService.Instance.CreateTest(testName, description);
return _parentTest.Value;
}
}
// creates a node
// node is added to the parent using the parentName
// if the parent is not available, it will be created
public static ExtentTest CreateMethod(string parentName, string testName, string description = null)
{
lock (_synclock)
{
ExtentTest parentTest = null;
if (!_parentTestMap.ContainsKey(parentName))
{
parentTest = ExtentService.Instance.CreateTest(testName);
_parentTestMap.Add(parentName, parentTest);
} else
{
parentTest = _parentTestMap[parentName];
}
_parentTest.Value = parentTest;
_childTest.Value = parentTest.CreateNode(testName, description);
return _childTest.Value;
}
}
public static ExtentTest CreateMethod(string testName)
{
lock (_synclock)
{
_childTest.Value = _parentTest.Value.CreateNode(testName);
return _childTest.Value;
}
}
public static ExtentTest GetMethod()
{
lock (_synclock)
{
return _childTest.Value;
}
}
public static ExtentTest GetTest()
{
lock (_synclock)
{
return _parentTest.Value;
}
}
}