How To Generate Test Report In NUnit?
Tags: nunit, csharp, selenium
Published: 2021-02-08 09:25:10

Test reports are an integral part of any activity related to testing, whether it is automation testing or manual testing. Test reports help track how the activities related to automation testing have evolved over a period of time. The same principle also applies when using NUnit/xUNit/MSTest for [automated browser testing](https://www.lambdatest.com/selenium-automation?utm_source=dev&utm_medium=Blog&utm_campaign=Himanshu-8022021&utm_term=Himanshu). Of the lot, NUnit is the most-used test automation framework for all .Net languages. NUnit reports can serve as a considerable value addition to the tests performed using the said framework.

![Reporting in NUnit](https://www.lambdatest.com/blog/wp-content/uploads/2020/12/image3.gif)

Let’s look at NUnit report generation and how seamlessly you can integrate the NUnit reporting tool in the test implementation. As far as Selenium C# is concerned, Extent and Allure are the preferred NUnit reporting tools. Both are third-party tools that have to be installed from the NuGet gallery, either from the Visual Studio UI or using the Package Manager Console commands.

Extent Report is a more popular NUnit report generator. By the end of this blog, you would be comfortable integrating Extent Reports in the Selenium C# test implementation. So, let’s get started.

 
## Introduction to Extent Reports

ExtentReport is a popular multi-language test reporting tool that gives in-depth information about the status of the tests. The Extent framework lets you create interactive and detailed test reports. Apart from the .NET framework, ExtentReport is also available for the Java language. ExtentReports (or Extent Reports) is an open-source reporting library, and version 4.0 of ExtentReports is licensed under Apache 2.0.

The Extent Framework source code is available on [GitHub](https://github.com/extent-framework/); the implementation of ExtentReports 4 .NET Core is available [here](https://github.com/extent-framework/extentreports-csharp). As of writing this article, ExtentReports 4.0 was the latest version of ExtentReports for the .NET framework. Since ExtentReports 3.x is no longer maintained, it is recommended to migrate from ExtentReports 3.x to ExtentReports 4.x.

In case you are using [SpecFlow for automation testing with Selenium C#](https://www.lambdatest.com/blog/specflow-tutorial-for-automation-testing/?utm_source=dev&utm_medium=Blog&utm_campaign=Himanshu-8022021&utm_term=Himanshu), you have the flexibility to integrate ExtentReports 4.x in the SpecFlow BDD tests for HTML test report generation. Extent Reports follows a freemium pricing approach, which essentially means that it is available in Community (or Free) and Pro (or Paid) Editions. As per the [official documentation of ExtentReports](http://www.extentreports.com/docs/versions/4/net/index.html#community-vs-pro), there are no differences in the underlying ExtentReports API. The community & pro users can leverage the advantage of features offered by full-featured APIs.

For the demonstration, we have used the Community Edition of ExtentReports 4.x.

### Advantages of using Extent Reports

There are several advantages of using Extent Reports; the major ones are below:

- Easy to setup and integrate ExtentReports with Selenium C# frameworks like NUnit, MSTest, and xUnit and Selenium Java frameworks like TestNG, JUnit, etc.
- Helps generate super-customizable HTML test reports that help visualize the status of the tests executed on the Selenium Grid.
- Stepwise and Pie-chart representations in the NUnit test report provide top-level information on how the tests have fared (i.e., how many passed/failed) on the execution front.
- It lets you customize the report’s look & feel through an XML based configuration file where you can input details such as report theme (Standard/Dark), report title, document title, etc.
- It makes it easy to track multiple test cases that are a part of a single test suite.
- It can be used with ease even when the test scenarios are executed parallel on a Selenium Grid.
- Each test case is accompanied by vital information like logs, results, and the overall time to execute the test.
- It lets you capture the ScreenShot with every test step. The captured ScreenShots can help identify the exact step at which the test has failed.
- Methods OnNodeAdded, OnTestStarted, OnScreenCaptureAdded, etc., let you perform actions (e.g., adding relevant logs) when that particular ‘event’ (i.e., test has started, screenshot is captured, the test is removed, etc.) has occurred in the tests.

Extent Reports 4 is a significant upgrade over Extent Reports 3, as a lot of new features are introduced in version 4.x of ExtentReports.

### New Features introduced in Extent Reports 4

In case you are currently using Extent Reports 3 as the NUnit reporting tool, you might want to upgrade to Extent Reports 4. In Extent Reports, vital related information like tests, nodes, events, and assignment of tags, devices, environment values, etc., can be printed to ‘multiple destinations.’ These destinations are referred to as ‘reporter.’

Here are the major feature upgrades in Extent Reports 4, most of which are also available for Community Edition of this NUnit report generator:

- Extent Reports 4 provides reporters for BDD/Gherkin, non-BDD, and both. Here are some of the reporters available with Extent Reports 4:
- ExtentAventReporter
- ExtentBDDReporter
- ExtentCardsReporter
- ExtentTabularReporter, and many [more](http://www.extentreports.com/docs/versions/4/net/#reporters).
- LoggerReporter that eases the navigation from Category (or Bug) view to the Test view.
- Provision for adding [base64 screenshots](http://www.extentreports.com/docs/versions/4/net/#base64-screenshots-logs) to logs through the _CreateScreenCaptureFromBase64String_ method of MediaEntityBuilder class.
- Numerous layout improvements for the BDD view.
- Support for adding a Gherkin dialect other than ‘en’ or English keywords. The API for setting the dialects is inline with the dialect defined in the Gherkin spec. For example, you can set the Gherkin dialect to German (or de) by using the command extent._GherkinDialect = “de”;_ in the test code.
- The _ExtentHtmlReporter_ method, which is newly introduced in Extent Reports 4, is used for creating HTML reports. ExtentV3HtmlReporter method was earlier used for creating HTML reports with Extent Reports 3.

For [migrating from ExtentReports 3 to ExtentReports 4](http://www.extentreports.com/docs/versions/4/net/breaking-changes.html), it is recommended to remove ChartLocation from the setup code as it is no longer available in Extent Reports 4.


## How To Use Extent Reports With NUnit And Selenium WebDriver?

The Extent framework follows an Observer pattern where the reporter (e.g., _ExtentHTMLReporter_) attached to the framework becomes an ‘observer’ and is notified of changes like the creation of logs and logs, etc. Some of the notified changes result in content updation in the report. Though the Extent framework offers numerous capabilities, we would require only certain classes for report generation in NUnit.

### Sneak Peek Of The Extent Framework For Report Generation

Here are the two major classes that are frequently used when creating NUnit report using the Extent framework:

- ExtentHtmlReporter (for version 4.x) or ExtentV3HtmlReporter (for version 3.x)
- ExtentReports
- ExtentTest
- MediaEntityBuilder

We would be using the _ExtentHtmlReporter_ method in the code as our demo will be with ExtentReports 4.

**The ‘ExtentHtmlReporter’ class**

The _ExtentHtmlReporter_ class is used for generating Extent Report in the HTML format in the path specified as the argument.

![Generating Extent Report using ExtentHtmlReporter class](https://www.lambdatest.com/blog/wp-content/uploads/2020/12/test-report-nunit.png)

It supports two reporter configurations – Default and SPA (Single-Page Applications).

![NUnit reporter configurations](https://www.lambdatest.com/blog/wp-content/uploads/2020/12/image1-4.png)

It also provides the flexibility to load the report configurations from a configuration XML file. The config XML file lets you set the theme, document encoding format (e.g., UTF-8), document title, report name, report headline, and more.

**Syntax (or sample usage)**

```
String reportPath = @";Folder-Path\\report.html";
String configPath = @";Folder-Path\\report-config.xml";
 
 
var htmlReporter = new ExtentHtmlReporter(reportPath);
htmlReporter.LoadConfig(configPath);
```

The first step is creating an object of the class ExtentHtmlReporter. The optional step is loading the custom report configurations from the configuration XML file. Now that we have started the reporter (i.e., Extent HTML Reporter), the next step is creating the ExtentReports and attaching the reporter(s).

**The ‘ExtentReports’ class**

The ExtentReports class is primarily used for logging the test steps in the generated HTML report, the path of which was configured using the instance of the ExtentHtmlReporter class.

![logging the test steps in generated HTML report](https://www.lambdatest.com/blog/wp-content/uploads/2020/12/ExtentHtmlReporter-class.png)

The **AttachReporter** method of the ExtentReports class is used for attaching the specified reporter.

```
var _extent = new ExtentReports();
/* Attach reporter(s) */
_extent.AttachReporter(htmlReporter);
```

The **AddSystemInfo** method is used for adding system or environment-related information to the report. This information is automatically added to the started reporters (i.e., ExtentHtmlReporter).

```
_extent.AddSystemInfo("Host Name", "Cloud-based Selenium Grid on LambdaTest");
_extent.AddSystemInfo("Environment", "Test Environment");
_extent.AddSystemInfo("UserName", "Himanshu Sheth");
extent.AddSystemInfo("os", "Windows 10");
```

The Flush method writes (or updates) the specified reporter’s test information (i.e., Extent HTML Reporter) to the destination type. In most scenarios, the Flush method should be called only ‘once’ in the [OneTimeTearDown] attribute available in the NUnit framework.

`_extent.Flush();`

**Syntax (or sample usage)**

```
public static ExtentReports _extent;
_extent = new ExtentReports();
 
_extent.AttachReporter(htmlReporter);
_extent.AddSystemInfo("Host Name", "Cloud-based Selenium Grid on LambdaTest");
_extent.AddSystemInfo("Environment", "Test Environment");
_extent.AddSystemInfo("UserName", "Himanshu Sheth");
```
 

**The ‘ExtentTest’ Class**

The _ExtentTest_ class contains methods for the following tasks:

- Creating tests
- Creating nodes
- Log test steps in the HTML report
- Assigning Device Category
- Adding Screen Capture from a Base64 String
- Adding Screen Capture from a local Path

The **CreateTest** method is an overloaded method that is used for BDD and non-BDD tests. For non-BDD tests, the _CreateTest_ method is used for setting the test name and test description (optional). It returns an _ExtentTest_ object. Sample usage of the CreateTest method is below:

```
_test = _extent.CreateTest("Testing Google Search for LambdaTest");
```

When creating tests using the Gherkin instance, the Gherkin keyword can be directly passed in the CreateTest method.

```
var feature = extent.CreateTest(new GherkinKeyword("Feature"), "Browser version");
```

The **Log** method of the ExtentTest class is used for logging the test status along with a description that needs to be printed on the resultant report. The commonly used statuses are Pass, Fail, and Skip. In totality, the following statuses are available:

- Pass
- Fail
- Fatal
- Error
- Warning
- Info
- Skip
- Debug

In terms of hierarchy, Fatal has the highest level in the hierarchy, and Pass has the lowest (and default) status in the hierarchy. Shown below is the hierarchy from Lowest to Highest:

**Pass \< Skip \< Warning \< Error \< Fail \< Fatal**

The **RemoveTest** method is used to remove a test created using the CreateTest method or [CreateNode](http://www.extentreports.com/docs/versions/4/net/#create-nodes) method.

```
var _test = _extent.CreateTest("Test").Fail("reason");
extent.RemoveTest(_test);
```

Screenshots serve as huge value addition in the NUnit test reports. Before ExtentReports 4, the traditional methodology for capturing page screenshots had to be used. With ExtentReports 4, you can add files and base64 snapshots to the tests.

The **AddScreenCaptureFromPath** method lets you add screenshots to the tests. The image is saved on the disk and referenced using the `< img >` attribute in the report. Screenshots can be added to the logs, irrespective of whether the test has passed or failed.

```
test.Fail("Test information").AddScreenCaptureFromPath("screenshot-name.png");
```

**Syntax (or sample usage)**

```
public ExtentTest _test;
 
_test = _extent.CreateTest("Testing Google Search for LambdaTest");
_test.Log(Status.Fail, "Fail");
test.Log(Status.Fail, "fail");
```

**The ‘MediaEntityBuilder’ Class**

The MediaEntityBuilder class in the Extent framework lets you add screenshots to logs, as the logs do not accept image paths directly. The **CreateScreenCaptureFromPath** method of MediaEntityBuilder class should be used for adding a base64 encoded screenshot to the report.

![MediaEntityBuilder class](https://www.lambdatest.com/blog/wp-content/uploads/2020/12/CreateScreenCaptureFromPath.png)

The **Build** method is used along with **CreateScreenCaptureFromPath** so that it returns a MediaEntityModelProvider.

We would be covering both the ways (traditional screenshot capturing mechanism and CreateScreenCaptureFromBase64String method in ExtentReports 4) for capturing page screenshots in the demonstration of this NUnit reporting tool.

**Syntax (or sample usage)**

```
var mediaModel = 
MediaEntityBuilder.CreateScreenCaptureFromPath("screenshot-name.png").Build();
test.Fail("Test details", mediaModel);
```

Now that we have covered the capabilities of ExtentReports 4 as an NUnit report generator let’s look at how we can use Extent Reports with NUnit and Selenium.

## Generate Reports In NUnit And Selenium WebDriver

For demonstrating generating Extent Reports in NUnit and Selenium WebDriver, we use the example shown in the [Page Object Model Tutorial with Selenium C#](https://www.lambdatest.com/blog/page-object-model-tutorial-selenium-csharp/?utm_source=dev&utm_medium=Blog&utm_campaign=Himanshu-8022021&utm_term=Himanshu).

Here is the test scenario and browser/OS combination on which the tests are executed:

1. Go to ‘Google.’
2. Search for ‘LambdaTest.’
3. Click on the first search result.
4. Assert if the title of the newly opened webpage does not match with the expected title.

| Browser | Browser version | Platform/Operating System |
| --- | --- | --- |
| Chrome | 72.0 | Windows 10 |
| Internet Explorer | 11.0 | Windows 10 |
| Safari | 11.0 | macOS High Sierra |

To get started, we create a new NUnit test project by navigating to _New -\> Project -\> NUnit Test Project (.Net Core)_.

Once the project is created, install the following packages using the Package Manager (PM) Console. Run the following commands on the PM console to install Selenium WebDriver, PageObjects, and other packages.

```
Install-Package DotNetSeleniumExtras
Install-Package DotNetSeleniumExtras.PageObjects
Install-Package DotNetSeleniumExtras.PageObjects.Core
Install-Package DotNetSeleniumExtras.WaitHelpers
```

With the basic setup complete, install packages – ExtentReports.Core and ExtentReports from the PM console. At the time of this article, the latest version of Extent Reports (Core) and ExtentReports in the NuGet gallery were [1.0.3](https://www.nuget.org/packages/ExtentReports.Core/1.0.3) and [4.1.0](https://www.nuget.org/packages/ExtentReports/4.1.0).

Run the following commands on the PM Console:

```
Install-Package ExtentReports.Core -Version 1.0.3
Install-Package ExtentReports -Version 4.1.0
```

Here are the installation screenshots from the PM console:

![installing screenshots from the PM console](https://www.lambdatest.com/blog/wp-content/uploads/2020/12/PM-console.png)

![PM console ExtentReports](https://www.lambdatest.com/blog/wp-content/uploads/2020/12/PM-console-1.png)

The status of the installed packages is obtained using the Get-Package PM command:

```
PM> Get-Package
 
Id                                  Versions
--                                  --------
nunit                               {3.12.0}
NUnit3TestAdapter                   {3.13.0}
DotNetSeleniumExtras.PageObjects... {3.12.0}
Selenium.Support                    {3.141.0}
Microsoft.NET.Test.Sdk              {16.2.0}
DotNetSeleniumExtras.PageObjects    {3.11.0}
Selenium.WebDriver                  {3.141.0}
ExtentReports.Core			     {1.0.3}
ExtentReports	                 {4.1.0}
```

The project structure should look as shown below:

![project structure](https://www.lambdatest.com/blog/wp-content/uploads/2020/12/image5-1-1.png)

**Page Classes created in \< project\_folder \>/Src/PageObjects/Pages**

```csharp
using System;
using OpenQA.Selenium;
using OpenQA.Selenium.Remote;
using NUnit.Framework;
using System.Threading;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using OpenQA.Selenium.Support.UI;
using SeleniumExtras.PageObjects;
 
namespace POMExample.PageObjects
{
    class HomePage
    {
        private IWebDriver driver;
        private WebDriverWait wait;
 
        public HomePage(IWebDriver driver)
        {
            this.driver = driver;
            wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
            PageFactory.InitElements(driver, this);
        }
 
        [FindsBy(How = How.Name, Using = "q")]
        [CacheLookup]
        private IWebElement elem_search_text;
 
        [FindsBy(How = How.Name, Using = "btnI")]
        [CacheLookup]
        private IWebElement elem_submit_button;
 
        [FindsBy(How = How.Id, Using = "hplogo")]
        [CacheLookup]
        private IWebElement elem_logo_img;
 
        public void goToPage(String test_url)
        {
            driver.Navigate().GoToUrl(test_url);
        }
 
        // Returns the Page Title
        public String getPageTitle()
        {
            return driver.Title;
        }
 
        // Returns the search string
        public String getSearchText()
        {
            return elem_search_text.Text;
        }
 
        // Checks whether the Logo is displayed properly or not
        public bool getWebPageLogo()
        {
            return elem_logo_img.Displayed;
        }
 
        public SearchPage test_search(string input_search)
        {
            elem_search_text.SendKeys(input_search);
            elem_search_text.Submit();
            return new SearchPage(driver);
        }
    }
}
```
```csharp
using System;
using OpenQA.Selenium;
using OpenQA.Selenium.Remote;
using NUnit.Framework;
using System.Threading;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using OpenQA.Selenium.Support.UI;
using SeleniumExtras.PageObjects;
 
namespace POMExample.PageObjects
{
    class SearchPage
    {
        private IWebDriver driver;
        Int32 timeout = 10000; // in milliseconds
 
        public SearchPage(IWebDriver driver)
        {
            this.driver = driver;
            PageFactory.InitElements(driver, this);
        }
 
        [FindsBy(How = How.XPath, Using = "//span[.='LambdaTest: Most Powerful Cross Browser Testing Tool Online']")]
        private IWebElement elem_first_result;
 
        async void async_delay()
        {
            await Task.Delay(50);
        }
 
        public FinalPage click_search_results()
        {
            var wait = new WebDriverWait(driver, TimeSpan.FromMilliseconds(timeout));
 
            // Wait for the page to load
            wait.Until(d => ((IJavaScriptExecutor)d).ExecuteScript("return document.readyState").Equals("complete"));
 
            elem_first_result.Click();
 
            async_delay();
 
            return new FinalPage(driver);
        }
    }
}
```
```csharp
using System;
using OpenQA.Selenium;
using OpenQA.Selenium.Remote;
using NUnit.Framework;
using System.Threading;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using OpenQA.Selenium.Support.UI;
using SeleniumExtras.PageObjects;
 
namespace POMExample.PageObjects
{
    class FinalPage
    {
        private IWebDriver driver;
        Int32 timeout = 10000; // in milliseconds
 
        public FinalPage(IWebDriver driver)
        {
            this.driver = driver;
            PageFactory.InitElements(driver, this);
        }
 
        [FindsBy(How = How.XPath, Using = "/html/body/div[1]/header/div[3]/nav/a/img")]
        private IWebElement elem_lt_logo;
 
        public String getPageTitle()
        {
            return driver.Title;
        }
 
        // Checks whether the LambdaTest Logo is displayed properly or not
        public bool getLTPageLogo()
        {
            return elem_lt_logo.Displayed;
        }
 
        public void load_complete()
        {
            var wait = new WebDriverWait(driver, TimeSpan.FromMilliseconds(timeout));
 
            // Wait for the page to load
            wait.Until(d => ((IJavaScriptExecutor)d).ExecuteScript("return document.readyState").Equals("complete"));
        }
    }
}
```

**Test Code created in \< project\_folder \>/Src/Test/Scripts**

```csharp
using System;
using OpenQA.Selenium;
using OpenQA.Selenium.Remote;
using POMExample.PageObjects;
using NUnit.Framework;
using System.Threading;
using System.Collections.Generic;
using System.Threading.Tasks;
using OpenQA.Selenium.Support.UI;
using SeleniumExtras.PageObjects;
using AventStack.ExtentReports;
using AventStack.ExtentReports.Reporter;
using System.Text;
using OpenQA.Selenium.Chrome;
using System.IO;
using NUnit.Framework.Interfaces;
 
namespace POMExample
{
    [TestFixture("chrome", "86.0", "Windows 10")]
    [TestFixture("internet explorer", "11.0", "Windows 10")]
    [TestFixture("Safari", "11.0", "macOS High Sierra")]
 
    [Parallelizable(ParallelScope.All)]
 
    public class ExtentReportTests
    {
        String search_string = "LambdaTest";
        String web_page_title = "Google";
        ThreadLocal<IWebDriver> driver = new ThreadLocal<IWebDriver>();
        private String browser;
        private String version;
        private String os;
 
        public static ExtentReports _extent;
        public ExtentTest _test;
        public String TC_Name;
 
        public ExtentReportTests(String browser, String version, String os)
        {
            this.browser = browser;
            this.version = version;
            this.os = os;
        }
 
        [OneTimeSetUp]
        protected void ExtentStart()
        {
            var path = System.Reflection.Assembly.GetCallingAssembly().CodeBase;
            var actualPath = path.Substring(0, path.LastIndexOf("bin"));
            var projectPath = new Uri(actualPath).LocalPath;
            Directory.CreateDirectory(projectPath.ToString() + "Reports");
 
            Console.WriteLine(projectPath.ToString());
            var reportPath = projectPath + "Reports\\Index.html";
            Console.WriteLine(reportPath);
            /* For Version 3 */
            /* var htmlReporter = new ExtentV3HtmlReporter(reportPath); */
            /* For version 4 --> Creates Index.html */
            var htmlReporter = new ExtentHtmlReporter(reportPath);
            _extent = new ExtentReports();
            _extent.AttachReporter(htmlReporter);
            _extent.AddSystemInfo("Host Name", "Cloud-based Selenium Grid on LambdaTest");
            _extent.AddSystemInfo("Environment", "Test Environment");
            _extent.AddSystemInfo("UserName", "Himanshu Sheth");
            htmlReporter.LoadConfig(projectPath + "report-config.xml");
        }
 
        [SetUp]
        public void Init()
        {
            String username = "user-name";
            String accesskey = "access-key";
            String gridURL = "@hub.lambdatest.com/wd/hub";
 
            DesiredCapabilities capabilities = new DesiredCapabilities();
 
            capabilities.SetCapability("user", username);
            capabilities.SetCapability("accessKey", accesskey);
            capabilities.SetCapability("browserName", browser);
            capabilities.SetCapability("version", version);
            capabilities.SetCapability("platform", os);
 
            driver.Value = new RemoteWebDriver(new Uri("https://" + username + ":" + accesskey + gridURL), capabilities, TimeSpan.FromSeconds(600));
 
            System.Threading.Thread.Sleep(2000);
        }
 
        [Test]
        public void SearchLT_Google()
        {
            String test_url = "https://www.google.com";
            String expected_PageTitle = "Most Powerful Cross Browser Testing Tool Online | LambdaTest";
            String result_PageTitle;
            Console.WriteLine("SearchLT_Google");
 
            String context_name = TestContext.CurrentContext.Test.Name + " on " + browser + " " + version + " " + os;
            TC_Name = context_name;
 
            _test = _extent.CreateTest(context_name);
 
            HomePage home_page = new HomePage(driver.Value);
            home_page.goToPage(test_url);
            home_page.test_search(search_string);
 
            SearchPage search_page = new SearchPage(driver.Value); ;
            FinalPage final_page = search_page.click_search_results();
 
            result_PageTitle = final_page.getPageTitle();
 
            final_page.load_complete();
            Assert.AreEqual(result_PageTitle, expected_PageTitle, "Search Test Passed");
        }
 
        [OneTimeTearDown]
        protected void ExtentClose()
        {
            Console.WriteLine("OneTimeTearDown");
            _extent.Flush();
        }
 
        [TearDown]
        public void Cleanup()
        {
            bool passed = TestContext.CurrentContext.Result.Outcome.Status == NUnit.Framework.Interfaces.TestStatus.Passed;
            var exec_status = TestContext.CurrentContext.Result.Outcome.Status;
            var stacktrace = string.IsNullOrEmpty(TestContext.CurrentContext.Result.StackTrace) ? ""
            : string.Format("{0}", TestContext.CurrentContext.Result.StackTrace);
            Status logstatus = Status.Pass;
            String screenShotPath, fileName;
 
            Console.WriteLine("TearDown");
 
            DateTime time = DateTime.Now;
            fileName = "Screenshot_" + time.ToString("h_mm_ss") + TC_Name + ".png";
 
            switch (exec_status)
            {
                case TestStatus.Failed:
                    logstatus = Status.Fail;
                    /* The older way of capturing screenshots */
                    screenShotPath = Capture(driver.Value, fileName);
                    /* Capturing Screenshots using built-in methods in ExtentReports 4 */
                    var mediaEntity = CaptureScreenShot(driver.Value, fileName);
                    _test.Log(Status.Fail, "Fail");
                    /* Usage of MediaEntityBuilder for capturing screenshots */  
                    _test.Fail("ExtentReport 4 Capture: Test Failed", mediaEntity);
                    /* Usage of traditional approach for capturing screenshots */
                    _test.Log(Status.Fail, "Traditional Snapshot below: " + _test.AddScreenCaptureFromPath("Screenshots\\" + fileName));
                    break;
                case TestStatus.Passed:
                    logstatus = Status.Pass;
                    /* The older way of capturing screenshots */
                    screenShotPath = Capture(driver.Value, fileName);
                    /* Capturing Screenshots using built-in methods in ExtentReports 4 */
                    mediaEntity = CaptureScreenShot(driver.Value, fileName);
                    _test.Log(Status.Pass, "Pass");
                    /* Usage of MediaEntityBuilder for capturing screenshots */
                    _test.Pass("ExtentReport 4 Capture: Test Passed", mediaEntity);
                    /* Usage of traditional approach for capturing screenshots */
                    _test.Log(Status.Pass, "Traditional Snapshot below: " + _test.AddScreenCaptureFromPath("Screenshots\\" + fileName));
                    break;
                case TestStatus.Inconclusive:
                    logstatus = Status.Warning;
                    break;
                case TestStatus.Skipped:
                    logstatus = Status.Skip;
                    break;
                default:
                    break;
            }
            _test.Log(logstatus, "Test: " + TC_Name + " Status:" + logstatus + stacktrace);
 
            try
            {
                ((IJavaScriptExecutor)driver.Value).ExecuteScript("lambda-status=" + (passed ? "passed" : "failed"));
            }
            finally
            {
                driver.Value.Quit();
            }
        }
 
        public static string Capture(IWebDriver driver, String screenShotName)
        {
            ITakesScreenshot ts = (ITakesScreenshot)driver;
            Screenshot screenshot = ts.GetScreenshot();
            var pth = System.Reflection.Assembly.GetCallingAssembly().CodeBase;
            var actualPath = pth.Substring(0, pth.LastIndexOf("bin"));
            var reportPath = new Uri(actualPath).LocalPath;
            Directory.CreateDirectory(reportPath + "Reports\\" + "Screenshots");
            var finalpth = pth.Substring(0, pth.LastIndexOf("bin")) + "Reports\\Screenshots\\" + screenShotName;
            var localpath = new Uri(finalpth).LocalPath;
            screenshot.SaveAsFile(localpath, ScreenshotImageFormat.Png);
            return reportPath;
        }
 
        public MediaEntityModelProvider CaptureScreenShot(IWebDriver driver, String screenShotName)
        {
            ITakesScreenshot ts = (ITakesScreenshot)driver;
            var screenshot = ts.GetScreenshot().AsBase64EncodedString;
 
            return MediaEntityBuilder.CreateScreenCaptureFromBase64String(screenshot, screenShotName).Build();
        }
    }
}
```

**Extent Report configuration :\< project\_folder \>/report-config.xml** 
 
```
<?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>Report Demonstration with NUnit and Extent Reports</documentTitle>
 
   <!-- report name - displayed at top-nav -->
   <reportName>Report Demonstration with NUnit and Extent Reports</reportName>
 
   <!-- report headline - displayed at top-nav, after reportHeadline -->
   <reportHeadline>Report Demonstration with NUnit and Extent Reports</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>
```

This is how the final project structure in Visual Studio 2019 looks like:

![final project structure in Visual Studio](https://www.lambdatest.com/blog/wp-content/uploads/2020/12/image10-2-1.png)

**Code WalkThrough [\< project\_folder \>/Src/Test/Scripts/test\_POM.cs]**

For a code walkthrough of the POM related implementation, you can refer to the Implementation section in the [Page Object Model](https://www.lambdatest.com/blog/page-object-model-tutorial-selenium-csharp/?utm_source=dev&utm_medium=Blog&utm_campaign=Himanshu-8022021&utm_term=Himanshu) tutorial for Selenium C#. We would limit this code walkthrough for the NUnit report or Extent Report related implementation in the test code.

**Lines (11) – (12):**

```
using AventStack.ExtentReports;
using AventStack.ExtentReports.Reporter;
```

**Lines (20)- (22):**

The TestFixture annotation (or attribute) is used for passing the browser and OS combinations to the test case.

```
[TestFixture("chrome", "86.0", "Windows 10")]
[TestFixture("internet explorer", "11.0", "Windows 10")]
[TestFixture("Safari", "11.0", "macOS High Sierra")]
```

**Line (24):**

The test scenario is tested in parallel on different browser and OS combinations. The _Parallelizable_ annotation is used with the _ParallelScope_.All options for executing the tests and their descendants in parallel.

```
[Parallelizable(ParallelScope.All)]
```

**Lines (35) – (36):**

Variables of type ExtentReports and ExtentTest are created. These would be further used in creating the NUnit report.

```
public static ExtentReports _extent;
public ExtentTest _test;
```

**Lines (46) – (54):**

The basic prerequisites for Extent Report creation is added under the [_OneTimeSetUp_] attribute. The method under this attribute will be called before executing any test in the fixture.

The _GetCallingAssembly_ method returns the Assembly object of the method that has invoked ExtentStart (i.e., the currently executing method). The Uri ._LocalPath_ converts the file path into a URI-style path.

The generated NUnit report will be stored in the Reports folder, and the report name is set to Index.html.

```
[OneTimeSetUp]
protected void ExtentStart()
{
     var path = System.Reflection.Assembly.GetCallingAssembly().CodeBase;
     var actualPath = path.Substring(0, path.LastIndexOf("bin"));
     var projectPath = new Uri(actualPath).LocalPath;
 
     Directory.CreateDirectory(projectPath.ToString() + "Reports");
     Console.WriteLine(projectPath.ToString());
     var reportPath = projectPath + "Reports\\Index.html";
```

**Lines (60):**

Start the ExtentHTMLReporter file in the location (i.e. reportPath) specified in the earlier step.

```
var htmlReporter = new ExtentHtmlReporter(reportPath);
```

**Line(61) – (62):**

Create an instance of the ExtentReports class and enable (or start) the Extent HTML reporter using the _AttachReporter_ method.

```
_extent = new ExtentReports();
_extent.AttachReporter(htmlReporter);
```

**Lines (63) – (65):**

Necessary system or environment related information is added using the AddSystemInfo method to the started HTML reporter.

```
_extent.AddSystemInfo("Host Name", "Cloud-based Selenium Grid on LambdaTest");
_extent.AddSystemInfo("Environment", "Test Environment");
_extent.AddSystemInfo("UserName", "Himanshu Sheth");
```

**Line(66):**

The custom report configuration is loaded using the report configuration XML file (i.e., report-config.xml).

```
htmlReporter.LoadConfig(projectPath + "report-config.xml");
```

**Line (84):**

The Remote WebDriver object is instantiated using LambdaTest’s cloud-based Selenium Grid remote address, desired browser capabilities, and command timeout.

```
driver.Value = new RemoteWebDriver(new Uri("https://" + username + ":" + accesskey + gridURL), capabilities, TimeSpan.FromSeconds(600));
```

**Lines (89) – (98):**

The _TestContext_ class in NUnit lets tests access information about the execution context. The _CurrentContext_ property of _TestContext_ class is used for getting the context of the currently executing test (i.e., SearchLT\_Google). The test case’s name is appended with the browser name, browser version, and platform to differentiate the tests executing on different browser & OS combinations.

```
[Test]
public void SearchLT_Google()
{
	..............................................
	..............................................
	..............................................
	String context_name = TestContext.CurrentContext.Test.Name + " on " + browser + " " + version + " " + os;
      String TC_Name = context_name;
	..............................................
	..............................................
}
```
 

**Line (100):**

The test is created using the _CreateTest_ method of the Extent framework. The Test case name is passed as the argument to the CreateTest method.

```
_test = _extent.CreateTest(context_name);
```

The remaining part of the implementation under the [Test] attribute is POM related, for which you can refer to the [POM in Selenium C# blog](https://www.lambdatest.com/blog/page-object-model-tutorial-selenium-csharp/).

**Line (126):**

The execution status of the test scenario is obtained using the Result property in the current context.

```
[TearDown]
public void Cleanup()
{
	var exec_status = TestContext.CurrentContext.Result.Outcome.Status;
	..............................................
	..............................................
	..............................................
}
```
 

**Line (135):**

For demonstration, we capture the page screenshot even for the PASS scenario. The filename will be a combination of the test case name, browser & OS combination, and timestamp when the snapshot was generated.

```
fileName = "Screenshot_" + time.ToString("h_mm_ss") + TC_Name + ".png";
```

**Lines (137) – (172):**

Based on the execution status (i.e., Passed, Failed, Skipped, etc.) of the test scenario, the next set of actions are performed. For demonstration, we capture screenshots in the Extent report for _TestStatus.Passed_ and _TestStatus.Failed_ statuses.

Log events are added to the Extent Report using the appropriate status (i.e., Pass, Fail, etc.).

 
```
switch (exec_status)
{
	case TestStatus.Failed:
		screenShotPath = Capture(driver.Value, fileName);
            var mediaEntity = CaptureScreenShot(driver.Value, fileName);
		_test.Fail("ExtentReport 4 Capture: Test Failed", mediaEntity);
            _test.Log(Status.Fail, "Traditional Snapshot below: " + _test.AddScreenCaptureFromPath("Screenshots\\" + fileName));
		break;
		.................
		.................
	case TestStatus.Passed:
	      screenShotPath = Capture(driver.Value, fileName);
            var mediaEntity = CaptureScreenShot(driver.Value, fileName);
		_test.Pass("ExtentReport 4 Capture: Test Passed", mediaEntity);
            _test.Log(Status.Pass, "Traditional Snapshot below: " + _test.AddScreenCaptureFromPath("Screenshots\\" + fileName));
		break;
		.................
		.................
	case TestStatus.Inconclusive:
		.................
		.................
	case TestStatus.Skipped:
		.................
		.................
	default:
             break;
}
```

The screenshots of AUT are captured using two different methods (only for demonstration):

- Capture screenshot using GetScreenshot method of [ITakesScreenshot interface](https://www.selenium.dev/selenium/docs/api/dotnet/html/T_OpenQA_Selenium_ITakesScreenshot.htm) (in OpenQA.Selenium Namespace).
- Capture screenshot in base64 encoded string format using CreateScreenCaptureFromBase64String method of the Extent framework. The CreateScreenCaptureFromBase64String method is only available with Extent Reports 4.

We’ll be covering how to capture screenshots in NUnit using Extent Reports in more detail in the subsequent section.

**Lines (115) – (120):**

The Flush method of ExtentReports writes everything to the log file (or HTML report). It is called only ‘once’ in the [_OneTimeTearDown_] method. The report will not be generated if Flush is not called.

```
[OneTimeTearDown]
protected void ExtentClose()
{
	Console.WriteLine("OneTimeTearDown");
      _extent.Flush();
}
```
 
 
### How to capture Screenshots in NUnit Extent Report?

Screenshots of AUT during the test execution helps in identifying the potential issues in the application code. It is recommended to capture screenshots only when the test fails since screenshots might consume a lot of memory. However, for demonstration, we capture screenshots even when the test has passed.

Here are the two ways to capture screenshots in Extent Report:

**Method – 1**

Here we capture a screenshot of the application under test using the _GetScreenshot_ method of the ITakesScreenshot interface. Here is the complete implementation for capturing screenshot:

```
public static string Capture(IWebDriver driver, String screenShotName)
{
	ITakesScreenshot ts = (ITakesScreenshot)driver;
      Screenshot screenshot = ts.GetScreenshot();
      var pth = System.Reflection.Assembly.GetCallingAssembly().CodeBase;
      var actualPath = pth.Substring(0, pth.LastIndexOf("bin"));
      var reportPath = new Uri(actualPath).LocalPath;
      Directory.CreateDirectory(reportPath + "Reports\\" + "Screenshots");
      var finalpth = pth.Substring(0, pth.LastIndexOf("bin")) + "Reports\\Screenshots\\" + screenShotName;
      var localpath = new Uri(finalpth).LocalPath;
      screenshot.SaveAsFile(localpath, ScreenshotImageFormat.Png);
      return reportPath;
}
```

**a.** A variable (i.e., ts) of type ITakesScreenshot is created, and the type-casted WebDriver instance is assigned to it.

 
```
ITakesScreenshot ts = (ITakesScreenshot)driver;
```

**b.** The _GetScreenshot_ method in ITakesScreenshot returns the screenshot object that represents the image of the page on the screen.

```
Screenshot screenshot = ts.GetScreenshot();
```

**c.** The next set of steps create a sub-directory named _Screenshots_ in the Reports directory where the final HTML report will be stored.

```
var pth = System.Reflection.Assembly.GetCallingAssembly().CodeBase;
var actualPath = pth.Substring(0, pth.LastIndexOf("bin"));
var reportPath = new Uri(actualPath).LocalPath;
Directory.CreateDirectory(reportPath + "Reports\\" + "Screenshots");
```

**d.** The _SaveAsFile_ method in Screenshot class (i.e., _Screenshot.SaveAsFile_) saves the screenshot to a file. It overwrites if the file already exists in that particular location. The method takes two arguments – the location where the image will be stored and [ScreenshotImageFormat](https://www.selenium.dev/selenium/docs/api/dotnet/html/T_OpenQA_Selenium_ScreenshotImageFormat.htm) (which is png in our case).

In our case, the same test is executed in parallel across different browser and OS combinations. Hence, the image name includes the test name, browser(and OS combination), & timestamp when the corresponding test was executed.

```
screenshot.SaveAsFile(localpath, ScreenshotImageFormat.Png);
return reportPath;
```

**Usage:** This is how the Capture method is invoked in the test code for saving screenshot on the local disk and adding the same to the HTML report.

```
screenShotPath = Capture(driver.Value, fileName);
_test.Log(Status.Fail, "Traditional Snapshot below: " + _test.AddScreenCaptureFromPath("Screenshots\\" + fileName));
```

**Method – 2 (Only in Extent Reports 4)**

Here we capture a screenshot of the application under test in the base64 string format using the _CreateScreenCaptureFromBase64String_ method in Extent Reports 4. Here is the complete implementation for capturing screenshot:

```
public MediaEntityModelProvider CaptureScreenShot(IWebDriver driver, String screenShotName)
{
	ITakesScreenshot ts = (ITakesScreenshot)driver;
      var screenshot = ts.GetScreenshot().AsBase64EncodedString;
 
      return MediaEntityBuilder.CreateScreenCaptureFromBase64String(screenshot, screenShotName).Build();
}
```

**a.** A variable (i.e., ts) of type ITakesScreenshot is created, and the type-casted WebDriver instance is assigned to it.

```
ITakesScreenshot ts = (ITakesScreenshot)driver;
```

**b.** The screenshot image is captured using the _GetScreenshot_ method of the ITakesScreenshot interface. The value of the screenshot image is fetched as a Base64-encoded string.

```
var screenshot = ts.GetScreenshot().AsBase64EncodedString;
```

**c.** The screenshot image (in Base64-encoded string format) is added to the logs using _CreateScreenCaptureFromBase64String_ method of _MediaEntityBuilder_ class in Extent Reports 4. The Build method returns the screenshot image as _MediaEntityModelProvider_ type.

```
return MediaEntityBuilder.CreateScreenCaptureFromBase64String(screenshot, screenShotName).Build();
```

_ExtentTest.Pass_, _ExtentTest.Fail_, _ExtentTest.Skip_, etc. used for logging respective events in the HTML report have the second argument as _MediaEntityModelProvider_.

**Usage:** This is how the _CaptureScreenShot_ method is invoked in the test code for adding base64 screenshots to logs.

```
var mediaEntity = CaptureScreenShot(driver.Value, fileName); _test.Fail("ExtentReport 4 Capture: Test Failed", mediaEntity);
```

**Execution**

As shown below, the test scenario (_SearchLT\_Google_) is run against different browser and OS combinations in parallel.

![SearchLT_Google](https://www.lambdatest.com/blog/wp-content/uploads/2020/12/SearchLT_Google.png)

![LambdaTest Automation Testing Dashboard](https://www.lambdatest.com/blog/wp-content/uploads/2020/12/SearchLT_Google-1.png)

As seen below, the tests have executed successfully.

![test execution successfully](https://www.lambdatest.com/blog/wp-content/uploads/2020/12/executed-successfully.png)

![Automation Testing Dashboard](https://www.lambdatest.com/blog/wp-content/uploads/2020/12/image15-1-1.png)

The following screenshots are generated in the Reports\Screenshots folder. These screenshots were generated using Method – 1 described in the _Capture Screenshots_ in the _NUnit Extent Report_ section.

![NUnit Extent Report](https://www.lambdatest.com/blog/wp-content/uploads/2020/12/NUnit-Extent-Report.png)

Shown below is the Extent Report snapshot (taken from the report file) generated for the tests:

**Test 1 (Chrome 86.0 on Windows 10):**

![Test Chrome 86.0 on Windows 10](https://www.lambdatest.com/blog/wp-content/uploads/2020/12/Extent-Report-snapshot.png)

**Test 2 (IE 11.0 on Windows 10):**

![TEst IE 11.0 on Windows 10](https://www.lambdatest.com/blog/wp-content/uploads/2020/12/image16-2.png)

**Test 3 (Safari 11.0 on macOS High Sierra):**

![Test Safari 11.0 on macOS High Sierra](https://www.lambdatest.com/blog/wp-content/uploads/2020/12/macOS-High-Sierra.png)

As seen in the report, the base 64 screenshot is shown as “base64 img” with a link to the captured screenshot. The screenshot captured using the _GetScreenshot_ is shown at the bottom of the corresponding test logs.


## Conclusion

![Report Creation](https://www.lambdatest.com/blog/wp-content/uploads/2020/12/image19.gif)

Reports play an integral part in keeping track of the test execution. Extent Report is used as a preferred NUnit reporting tool since the Extent Framework lets you generate customizable HTML test reports. The NUnit report generated using the Extent Framework provides insightful information about the tests, environment values, devices against which tests were conducted, and more. It also lets you create Gherkin-style tests, due to which Extent Reports can be used as an NUnit report generator for BDD tests. In particular, Extent Reports 4 has several new features like the simplified generation of base64 screenshots to be added to the logs, selecting a Gherkin dialect, etc.

Which NUnit reporting tool do you use for your Selenium tests? Do leave your preference in the comments section below.

Happy Automation Testing ☺