Extent Reports

HOME

We can create beautiful, interactive, and detailed reports for your tests using the Extent Reports library. Add events, screenshots, tags, devices, authors, or any other relevant information you deem necessary to create a descriptive and visually appealing report that you have complete control over.

Chapter 1  ExtentReports Version 4 with Selenium and TestNG
Chapter 2  ExtentReports Version 4 for Cucumber 5, Selenium, and TestNG
Chapter 3  ExtentReports Version 5 for Cucumber 6 and TestNG
Chapter 4  How to add Screenshot to Cucumber ExtentReports
Chapter 5  ExtentReports Version 5 for Cucumber 6 and JUnit4
Chapter 6  PDF ExtentReport for Cucumber and TestNG
Chapter 7  ExtentReports Version 5 for Cucumber 7 and TestNG
Chapter 8  Extent Reports Version 5 for Cucumber7 and JUnit5
Chapter 9  Gradle – Extent Report Version 5 for Cucumber, Selenium, and TestNG
Chapter 10  Gradle – ExtentReports Version 5 for Cucumber, Selenium and JUnit4
Chapter 11 How to host Extent Report on GitHub Pages with Github Actions – NEW

ExtentReports Version 4 for Cucumber 5, Selenium, and TestNG

HOME

The previous tutorial explained the generation of Allure Report with Cucumber5, Selenium and TestNG in a Maven project. In this tutorial, I will explain the steps to create an Extent Report4 with Cucumber5, Selenium, and TestNG in a Maven project.

Pre-Requisite:

  1. Java 8 or higher is needed for ExtentReport5
  2. Maven
  3. JAVA IDE (like Eclipse, IntelliJ, or so on)
  4. TestNG installed
  5. Cucumber Eclipse plugin (in case using Eclipse)

Project Structure

Step 1 – Add Maven dependencies to the POM

Add ExtentReport dependency.

<!-- Extent Report -->
<dependency>
	<groupId>com.aventstack</groupId>
	<artifactId>extentreports</artifactId>
	<version>${extentreports.version}</version>
</dependency>

Add tech grasshopper maven dependency for Cucumber. The below version of extentreports-cucumber5-adapter dependency needs to be added to the POM, to work with ExtentReports version 4.

<!-- Cucumber ExtentReport Adapter -->
<dependency>
	<groupId>tech.grasshopper</groupId>
	<artifactId>extentreports-cucumber5-adapter</artifactId>
	<version>1.51</version>
</dependency>

If you want to use ExtentReport Version5, then use version – 2.13.0.

The complete POM.xml will look as shown below with other Selenium and TestNG dependencies.

<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<cucumber.version>5.7.0</cucumber.version>
		<extentreports.cucumber5.adapter.version>1.5.1</extentreports.cucumber5.adapter.version>
		<extentreports.version>4.1.7</extentreports.version>
		<selenium.version>3.141.59</selenium.version>
		<webdrivermanager.version>5.2.1</webdrivermanager.version>
		<testng.version>6.14.3</testng.version>
		<apache.common.version>2.4</apache.common.version>		
		<maven.compiler.plugin.version>3.7.0</maven.compiler.plugin.version>
		<maven.surefire.plugin.version>3.0.0-M5</maven.surefire.plugin.version>
		<maven.compiler.source.version>11</maven.compiler.source.version>
		<maven.compiler.target.version>11</maven.compiler.target.version>
	</properties>

	<dependencies>

		<dependency>
			<groupId>io.cucumber</groupId>
			<artifactId>cucumber-java</artifactId>
			<version>${cucumber.version}</version>
		</dependency>

		<dependency>
           <groupId>io.cucumber</groupId>
           <artifactId>cucumber-testng</artifactId>
           <version>${cucumber.version}</version>
           <scope>test</scope>
       </dependency>

		<!-- Cucumber ExtentReport Adapter -->
		<dependency>
			<groupId>tech.grasshopper</groupId>
			<artifactId>extentreports-cucumber5-adapter</artifactId>
			<version>${extentreports.cucumber5.adapter.version}</version>
		</dependency>

		<!-- Extent Report -->
		<dependency>
			<groupId>com.aventstack</groupId>
			<artifactId>extentreports</artifactId>
			<version>${extentreports.version}</version>
		</dependency>
		
		<!-- Selenium -->
		<dependency>
			<groupId>org.seleniumhq.selenium</groupId>
			<artifactId>selenium-java</artifactId>
			<version>${selenium.version}</version>
		</dependency>

		<!-- Web Driver Manager -->
		<dependency>
			<groupId>io.github.bonigarcia</groupId>
			<artifactId>webdrivermanager</artifactId>
			<version>${webdrivermanager.version}</version>
		</dependency>

		<!-- TestNG -->
		<dependency>
			<groupId>org.testng</groupId>
			<artifactId>testng</artifactId>
			<version>${testng.version}</version>
			<scope>test</scope>
		</dependency>

		<!-- Apache Common -->
		<dependency>
			<groupId>org.apache.directory.studio</groupId>
			<artifactId>org.apache.commons.io</artifactId>
			<version>${apache.common.version}</version>
		</dependency>

	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>${maven.compiler.plugin.version}</version>
				<configuration>
					<source>${maven.compiler.source.version}</source> 
					<target>${maven.compiler.target.version}</target> 
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-surefire-plugin</artifactId>
				<version>${maven.surefire.plugin.version}</version>
				<configuration>
					<suiteXmlFiles>
						<suiteXmlFile>testng.xml</suiteXmlFile>
					</suiteXmlFiles>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

Step 2: Create a feature file in src/test/resources

Below is a sample feature file. I have added 2 failed scenarios – @FaceBookLink(Invalid XPath) and @MissingUsername (Incorrect Verification).

Feature: Login to HRM Application 

Background: 
    Given User is on HRMLogin page "https://opensource-demo.orangehrmlive.com/"
  
   @ValidCredentials
   Scenario: Login with valid credentials
     
    When User enters username as "Admin" and password as "admin123"
    Then User should be able to login sucessfully and new page opens
    
   @InvalidCredentials
   Scenario Outline: Login with invalid credentials
     
    When User enters username as "<username>" and password as "<password>"
    Then User should be able to see error message "<errorMessage>"
    
  Examples:
   | username   | password  | errorMessage                      |
   | Admin      | admin12$$ | Invalid credentials               |
   | admin$$    | admin123  | Invalid credentials               |
   | admin123   | Admin     | Invalid credentials               |
   | $$$$$$$    | &&&&&&&&  | Invalid credentials               |
      
  @FaceBookLink
  Scenario: Verfy FaceBook Icon on Login Page
     
    Then User should be able to see FaceBook Icon
    
  @MissingUsername
  Scenario: Verify error message when username is missing
     
    When User enters username as "" and password as "admin123"
    Then User should be able to see error message for empty username as "Empty Username"
   

Step 3: Create extent.properties file in src/test/resources

We need to create the extent.properties file in the src/test/resources folder for the grasshopper extent report adapter to recognize it. Using a property file for reporting is quite helpful if you want to define several different properties.

Let’s enable spark report in an extent properties file:

extent.reporter.avent.start=false
extent.reporter.bdd.start=false
extent.reporter.cards.start=false
extent.reporter.email.start=false
extent.reporter.html.start=true
extent.reporter.klov.start=false
extent.reporter.logger.start=true
extent.reporter.tabular.start=false

extent.reporter.avent.config=
extent.reporter.bdd.config=
extent.reporter.cards.config=
extent.reporter.email.config=
extent.reporter.html.config=
extent.reporter.klov.config=
extent.reporter.logger.config=
extent.reporter.tabular.config=

extent.reporter.avent.out=Reports/AventReport/
extent.reporter.bdd.out=Reports/BddReport/
extent.reporter.cards.out=Reports/CardsReport/
extent.reporter.email.out=Reports/EmailReport/ExtentEmail.html
extent.reporter.html.out=Reports/HtmlReport/ExtentHtml.html
extent.reporter.logger.out=Reports/LoggerReport/
extent.reporter.tabular.out=Reports/TabularReport/

#Screenshot
screenshot.dir=Reports/Screenshots/
screenshot.rel.path=../Screenshots/

Step 4: Create a Helper class in src/main/java

We have used Page Object Model with Cucumber and TestNG.

Create a Helper class where we are initializing the web driver, initializing the web driver wait, defining the timeouts, and creating a private constructor of the class, it will declare the web driver, so whenever we create an object of this class, a new web browser is invoked. We are using a setter and getter method to get the object of Chromedriver with the help of a private constructor itself within the same class.

HelperClass

import java.util.concurrent.TimeUnit;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import io.github.bonigarcia.wdm.WebDriverManager;

public class HelperClass {
	
	private static HelperClass helperClass;
	
	private static WebDriver driver;
    public final static int TIMEOUT = 10;
	
	 private HelperClass() {
		 
			WebDriverManager.chromedriver().setup();
	    	driver = new ChromeDriver();
	        driver.manage().timeouts().implicitlyWait(TIMEOUT,TimeUnit.SECONDS);
	        driver.manage().window().maximize();

	 }      
	    	
    public static void openPage(String url) {
        driver.get(url);
    }
	
	public static WebDriver getDriver() {
		return driver;			
	}
	
	public static void setUpDriver() {
		
		if (helperClass==null) {		
			helperClass = new HelperClass();
		}
	}
	
	 public static void tearDown() {
		 
		 if(driver!=null) {
			 driver.close();
			 driver.quit();
		 }
		 
		 helperClass = null;

	 } 	
}

Step 5: Create Locator classes in src/main/java

Create a locator class for each page that contains the detail of the locators of all the web elements. Here, I’m creating 2 locator classes – LoginPageLocators and HomePageLocators.

LoginPageLocators

import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;

public class LoginPageLocators {

	@FindBy(name = "txtUsername")
    public WebElement userName;
 
    @FindBy(name = "txtPassword")
    public WebElement password;
 
    @FindBy(id = "logInPanelHeading")
    public WebElement titleText;
 
    @FindBy(id = "btnLogin")
    public WebElement login;
 
    @FindBy(id = "spanMessage")
    public  WebElement errorMessage;
    
    @FindBy(xpath = "//*[@id='social-icons']/a[1]/img")
    public  WebElement linkedInIcon;
    
    @FindBy(xpath = "//*[@id='social-icons']/a[6]/img")  //Invalid Xpath
    public  WebElement faceBookIcon;
     
}

HomePageLocators

import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;

public class HomePageLocators {

	@FindBy(xpath = "//*[@id='app']/div[1]/div[2]/div[2]/div/div[1]/div[1]/div[1]/h5")
	public  WebElement homePageUserName;
 
}

Step 6: Create Action classes in src/main/java

Create the action classes for each web page. These action classes contain all the methods needed by the step definitions. In this case, I have created 2 action classes – LoginPageActions and HomePageActions

LoginPageActions

In this class, the very first thing will do is to create the object of the LoginPageLocators class so that we should be able to access all the PageFactory elements. Secondly, create a public constructor of LoginPageActions class.

package com.example.testng.actions;

import org.openqa.selenium.support.PageFactory;
import com.example.testng.locators.LoginPageLocators;
import com.example.testng.utils.HelperClass;

public class LoginPageActions {

LoginPageLocators loginPageLocators = null; 
	
    public LoginPageActions() {

    	this.loginPageLocators = new LoginPageLocators();

		PageFactory.initElements(HelperClass.getDriver(),loginPageLocators);
	}
    
    public void login(String strUserName, String strPassword) {
    	 
        // Fill user name
    	loginPageLocators.userName.sendKeys(strUserName);
 
        // Fill password
    	loginPageLocators.password.sendKeys(strPassword);
 
        // Click Login button
    	loginPageLocators.login.click(); 
    }
 
    //Get the title of Login Page")
    public String getLoginTitle() {
        return loginPageLocators.titleText.getText();
    }
     
    // Get the error message of Login Page
    public String getErrorMessage() {
        return loginPageLocators.errorMessage.getText();
    }
    
    // FaceBook Icon is displayed
    public Boolean getFaceBookIcon() {
   
        return loginPageLocators.faceBookIcon.isDisplayed();
    }
    
    // Get the error message when username is blank
    public String getMissingUsernameText() {
         return loginPageLocators.missingUsernameErrorMessage.getText();
     }
}

HomePageActions

import org.openqa.selenium.support.PageFactory;
import com.example.testng.locators.HomePageLocators;
import com.example.testng.utils.HelperClass;

public class HomePageActions {

	HomePageLocators homePageLocators = null;
   
	public HomePageActions() {
    	
		this.homePageLocators = new HomePageLocators();

		PageFactory.initElements(HelperClass.getDriver(),homePageLocators);
    }
 
    // Get the User name from Home Page
    public String getHomePageText() {
        return homePageLocators.homePageUserName.getText();
    }

}

Step 7: Create a Step Definition file in src/test/java

Create the corresponding Step Definition file of the feature file.

LoginPageDefinitions

import org.testng.Assert;
import com.example.testng.actions.HomePageActions;
import com.example.testng.actions.LoginPageActions;
import com.example.testng.utils.HelperClass;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;

public class LoginPageDefinitions{	

	LoginPageActions objLogin = new LoginPageActions();
    HomePageActions objHomePage = new HomePageActions();
 
    @Given("User is on HRMLogin page {string}")
    public void loginTest(String url) {
    	
    	HelperClass.openPage(url);
 
    }
 
    @When("User enters username as {string} and password as {string}")
    public void goToHomePage(String userName, String passWord) {
 
        // login to application
        objLogin.login(userName, passWord);
 
        // go the next page
        
    }
    
    @Then("User should be able to login sucessfully and new page opens")
    public void verifyLogin() {
 
        // Verify home page
       Assert.assertTrue(objHomePage.getHomePageText().contains("Employee Information"));
 
    }
    
    @Then("User should be able to see error message {string}")
    public void verifyErrorMessage(String expectedErrorMessage) {
 
        // Verify home page
    	Assert.assertEquals(objLogin.getErrorMessage(),expectedErrorMessage);
 
    }
     
    @Then("User should be able to see FaceBook Icon")
    public void verifyFaceBookIcon( ) {
    	
    	Assert.assertTrue(objLogin.getFaceBookIcon());
    }     

    @Then("User should be able to see error message for empty username as {string}")
    public void verifyErrorMessageForEmptyUsername(String expectedErrorMessage) {
    	 
    	Assert.assertEquals(objLogin.getMissingUsernameText(),expectedErrorMessage);
 
    }      
}

Step 8: Create Hook class in src/test/java

Create the hook class that contains the Before and After hooks. @Before hook contains the method to call the setup driver which will initialize the chrome driver. This will be run before any test.

After Hook – Here will call the tearDown method.

import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import com.example.testng.utils.HelperClass;
import io.cucumber.java.After;
import io.cucumber.java.Before;
import io.cucumber.java.Scenario;

public class Hooks {
	
	@Before
    public static void setUp() {

       HelperClass.setUpDriver();
    }
	
	@After
	public static void tearDown(Scenario scenario) {
		//validate if scenario has failed
		if(scenario.isFailed()) {
			final byte[] screenshot = ((TakesScreenshot) HelperClass.getDriver()).getScreenshotAs(OutputType.BYTES);
			scenario.attach(screenshot, "image/png", scenario.getName()); 
		}	
		
		HelperClass.tearDown();
	}
}

Step 9: Create a Cucumber Test Runner class in src/test/java

Add the extent report cucumber adapter to the runner class’s CucumberOption annotation. 

 plugin = {"com.aventstack.extentreports.cucumber.adapter.ExtentCucumberAdapter:"}

This is how your runner class should look after being added to our project. Moreover, be sure to keep the colon “:” at the end.

import io.cucumber.testng.AbstractTestNGCucumberTests;
import io.cucumber.testng.CucumberOptions;
 
@CucumberOptions(tags = "", features = "src/test/resources/features/LoginPage.feature", glue = "com.example.testng.definitions",
                 plugin = {"com.aventstack.extentreports.cucumber.adapter.ExtentCucumberAdapter:"})
 
public class CucumberRunnerTests extends AbstractTestNGCucumberTests {
 
}

Step 10: Create the testng.xml for the project

Right-click on the project and select TestNG -> convert to TestNG.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="Suite">
  <test name="ExtentReport4 for Cucumber">
  
  <classes>
  <class name = "com.example.testng.runner.CucumberRunnerTests"/>
  </classes>
  </test> <!-- Test -->
</suite> <!-- Suite -->

Step 11: Execute the code

Right Click on the Runner class and select Run As -> TestNG Test.

Below is the screenshot of the Console. As expected, 5 tests, out of 7 are passed and 2 failed.

Step 12: View ExtentReport

Refresh the project and will see a new folder – Report. The ExtentReport will be present in that folder with the name Spark.html.

Right-click and select Open with Web Browser.

The report also has a summary section that displays the summary of the execution. The summary includes the overview of the pass/fail using a pictogram, start time, end time, and pass/fail details of features as shown in the image below.

Click on the Dashboard icon present on the left side of the report. To view the details about the steps, click on the scenarios. Clicking on the scenario will expand, showing off the details of the steps of each scenario.

The icon present at the end of the failed scenario is highlighted, click on that icon. It is the screenshot of the failed test.

Logger Report

This is the Dashboard Report.

Congratulations on making it through this tutorial and hope you found it useful! Happy Learning!! Cheers!!

ExtentReports Version 4 with Selenium and TestNG

Last Updated On

HOME

In this tutorial, I will describe How to generate an ExtentReport in Selenium with the TestNG maven project.

Table of Contents

  1. What is ExtentReports?
  2. Project Structure
  3. Implementation Steps
    1. Add the dependencies to the POM.xml
    2. Create ExtentManager Class
    3. Create ExtentListeners class
    4. Create the BaseTests class
    5. Create the LoginPage class
    6. Create the LoginTests class
    7. Create TestNG.xml
    8. Execute the tests from testng.xml
    9. Test Execution Result
    10. Extent Report Generation

What is ExtentReports?

ExtentReports is a logger-style reporting library for automated tests. ExtentReports is a library that can be used to build a customized detailed report. It can be integrated with TestNG, JUnit, etc. This report can be built in JAVA, .NET and it provides a detailed summary of each test case and each test step too in a graphical manner. Extent reports produce HTML-based documents that offer several advantages like pie charts, graphs, screenshots addition, and test summary.  ExtentReports 4 is built on an open-Core.

Project Structure

Implementation Steps

1. Add the dependencies to the POM.xml

<properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <selenium.version>4.3.0</selenium.version>
        <testng.version>7.4.0</testng.version>
        <extentreports.version>4.0.0</extentreports.version>
        <webdrivermanager.version>5.2.1</webdrivermanager.version>
        <maven-surefire-plugin-version>3.0.0-M5</maven-surefire-plugin-version>
    </properties>

	<dependencies>

		<!-- Extent Report -->
		<dependency>
			<groupId>com.aventstack</groupId>
			<artifactId>extentreports</artifactId>
			<version>${extentreports.version}</version>
		</dependency>

		<!-- TestNG -->
		<dependency>
			<groupId>org.testng</groupId>
			<artifactId>testng</artifactId>
			<version>${testng.version}</version>
			<scope>test</scope>
		</dependency>

		<!-- Apache Common -->
		<dependency>
			<groupId>org.apache.directory.studio</groupId>
			<artifactId>org.apache.commons.io</artifactId>
			<version>2.4</version>
		</dependency>

		<!-- Selenium -->
		<dependency>
			<groupId>org.seleniumhq.selenium</groupId>
			<artifactId>selenium-java</artifactId>
			<version>${selenium.version}</version>
		</dependency>

		<!-- Web Driver Manager -->
		<dependency>
			<groupId>io.github.bonigarcia</groupId>
			<artifactId>webdrivermanager</artifactId>
			<version>${webdrivermanager.version}</version>
		</dependency>

	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<source>11</source> <!--For JAVA 8 use 1.8-->
					<target>11</target> <!--For JAVA 8 use 1.8-->
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-surefire-plugin</artifactId>
				<version>${maven-surefire-plugin-version}</version>
				<configuration>
					<suiteXmlFiles>
						<suiteXmlFile>testng.xml</suiteXmlFile>
					</suiteXmlFiles>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

2. Create ExtentManager Class

In this class, we created a createInstance() method. Also, you need to set your ExtentReports report HTML file location.

import java.io.File;
import java.io.IOException;
import java.util.Date;
import org.apache.commons.io.FileUtils;
import org.openqa.selenium.TakesScreenshot;
import com.aventstack.extentreports.ExtentReports;
import com.aventstack.extentreports.reporter.ExtentHtmlReporter;
import com.aventstack.extentreports.reporter.configuration.Theme;
import com.example.testcases.BaseTests;
import org.openqa.selenium.OutputType;

public class ExtentManager extends BaseTests{

	private static ExtentReports extent;
	public static String screenshotName;
		
    public static ExtentReports createInstance(String fileName) {
        
    	ExtentHtmlReporter htmlReporter = new ExtentHtmlReporter(fileName);
            
        htmlReporter.config().setTheme(Theme.DARK);
        htmlReporter.config().setDocumentTitle(fileName);
        htmlReporter.config().setEncoding("utf-8");
        htmlReporter.config().setReportName(fileName);
        
        extent = new ExtentReports();
        extent.attachReporter(htmlReporter);
        extent.setSystemInfo("Release No", "22");
        extent.setSystemInfo("Environment", "QA");
        extent.setSystemInfo("Build no", "B-12673");
              
        return extent;
    }

	
	public static void captureScreenshot() {
		
		TakesScreenshot screenshot = (TakesScreenshot)driver;
	  	  
        // Call method to capture screenshot
        File src = screenshot.getScreenshotAs(OutputType.FILE);

        try
        {
        	Date d = new Date();
    		screenshotName = d.toString().replace(":", "_").replace(" ", "_") + ".jpg";  
            FileUtils.copyFile(src,new File(System.getProperty("user.dir") + "\\reports\\" + screenshotName));
            System.out.println("Successfully captured a screenshot");
       } catch (IOException e) {
           System.out.println("Exception while taking screenshot " + e.getMessage());
      }
	}
}

The ExtentHtmlReporter is used for creating an HTML file, and it accepts a file path as a parameter.

ExtentHtmlReporter htmlReporter = new ExtentHtmlReporter(fileName);

The file path represents the path in which our extent report would be generated. This is defined in ExtentListeners class.

	static Date d = new Date();
	static String fileName = "ExtentReport_" + d.toString().replace(":", "_").replace(" ", "_") + ".html";

	private static ExtentReports extent = ExtentManager.createInstance(System.getProperty("user.dir")+"\\reports\\"+fileName);

ExtentHtmlReporter is also used to customize the extent reports. It allows many configurations to be made through the config() method. Some of the configurations that can be made are described below.

htmlReporter.config().setDocumentTitle(fileName);
htmlReporter.config().setEncoding("utf-8");
htmlReporter.config().setReportName(fileName);

We have two themes – STANDARD and DARK for customizing the look and feel of our extent reports.

htmlReporter.config().setTheme(Theme.DARK);

STANDARD Look

   htmlReporter.config().setTheme(Theme.STANDARD);

captureScreenshot() is a method in ExtentTest class that attaches the captured screenshot in the Extent Report. It takes the image path where the screenshot has been captured as the parameter and attaches the screenshot to the Extent Report in Selenium.

public static void captureScreenshot() {
		
		TakesScreenshot screenshot = (TakesScreenshot)driver;
	  	  
        // Call method to capture screenshot
        File src = screenshot.getScreenshotAs(OutputType.FILE);

        try
        {
        	Date d = new Date();
    		screenshotName = d.toString().replace(":", "_").replace(" ", "_") + ".jpg";  
            FileUtils.copyFile(src,new File(System.getProperty("user.dir") + "\\reports\\" + screenshotName));
            System.out.println("Successfully captured a screenshot");
       } catch (IOException e) {
           System.out.println("Exception while taking screenshot " + e.getMessage());
      }
	}

3. Create ExtentListeners class

This class contains the action done by extent report on each step. In our tests, we implement ITestListener and use its methods. TestNG provides the @Listeners annotation, which listens to every event that occurs in a Selenium code. TestNG Listeners are activated either before the test or after the test case. It is an interface that modifies the TestNG behavior. If any event matches an event for which we want the listener to listen then it executes the code, which ultimately results in modifying the default behavior of TestNG. To know more about ITestListener, please refer to this tutorial.

import java.util.Arrays;
import java.util.Date;
import org.testng.ITestContext;
import org.testng.ITestListener;
import org.testng.ITestResult;
import com.aventstack.extentreports.ExtentReports;
import com.aventstack.extentreports.ExtentTest;
import com.aventstack.extentreports.MediaEntityBuilder;
import com.aventstack.extentreports.Status;
import com.aventstack.extentreports.markuputils.ExtentColor;
import com.aventstack.extentreports.markuputils.Markup;
import com.aventstack.extentreports.markuputils.MarkupHelper;

public class ExtentListeners implements ITestListener {
	
	static Date d = new Date();
	static String fileName = "ExtentReport_" + d.toString().replace(":", "_").replace(" ", "_") + ".html";

	private static ExtentReports extent = ExtentManager.createInstance(System.getProperty("user.dir")+"\\reports\\"+fileName);
	
	public static ThreadLocal<ExtentTest> testReport = new ThreadLocal<ExtentTest>();
	

	public void onTestStart(ITestResult result) {
	
		ExtentTest test = extent.createTest(result.getTestClass().getName()+"     @TestCase : "+result.getMethod().getMethodName());
        testReport.set(test);
        
	}

	public void onTestSuccess(ITestResult result) {
		
		String methodName=result.getMethod().getMethodName();
		String logText="<b>"+"TEST CASE:- "+ methodName.toUpperCase()+ " - PASSED"+"</b>";		
		Markup markup = MarkupHelper.createLabel(logText, ExtentColor.GREEN);
		testReport.get().pass(markup);
		
	}

	public void onTestFailure(ITestResult result) {
		
		String excepionMessage = Arrays.toString(result.getThrowable().getStackTrace());
		testReport.get().fail("<details>" + "<summary>" + "<b>" + "<font color=" + "red>" + "Exception Occured:Click to see"
				+ "</font>" + "</b >" + "</summary>" +excepionMessage.replaceAll(",", "<br>")+"</details>"+" \n");
		
		try {

			ExtentManager.captureScreenshot();
			testReport.get().fail("<b>" + "<font color=" + "red>" + "Screenshot of failure" + "</font>" + "</b>",
					MediaEntityBuilder.createScreenCaptureFromPath(ExtentManager.screenshotName)
							.build());
		} catch (Exception e) {

		}
		
		String failureLogg="TEST CASE FAILED";
		Markup markup = MarkupHelper.createLabel(failureLogg, ExtentColor.RED);
		testReport.get().log(Status.FAIL, markup);

	}

	public void onTestSkipped(ITestResult result) {
		
		String methodName=result.getMethod().getMethodName();
		String logText="<b>"+"TEST CASE:- "+ methodName.toUpperCase()+ " - SKIPPED"+"</b>";		
		Markup markup = MarkupHelper.createLabel(logText, ExtentColor.ORANGE);
		testReport.get().skip(markup);

	}

	public void onTestFailedButWithinSuccessPercentage(ITestResult result) {

	}

	public void onStart(ITestContext context) {
	}

	public void onFinish(ITestContext context) {

		if (extent != null) {

			extent.flush();
		}
	}
}

I have defined actions for onTestStart(), onTestSuccess(), onTestFailure(), onTestSkipped() and onFinish() methods.

The ITestListener is an interface that has unimplemented methods by default and we can add lines of code within each method. So whenever a specific event occurs, the code written within that method will be executed. 

onTestFailure() is a method in which this listener will be invoked whenever the test fails. Within this method, we shall add our code to capture screenshots whenever the test case fails on execution. The screenshot of the failed test case is also embedded in the report.

onTestSuccess() is a method that is invoked once the test execution is complete and the test has been passed. We shall add the log included in the Extent Report to mark the test case as passed within this method

String methodName=result.getMethod().getMethodName();
String logText="<b>"+"TEST CASE:- "+ methodName.toUpperCase()+ " PASSED"+"</b>";		
Markup markup = MarkupHelper.createLabel(logText, ExtentColor.GREEN);
testReport.get().pass(markup);

onTestSkipped() is a method that is invoked if the test execution is skipped. This is the same as the onTestSuccess() method.

public void onTestSkipped(ITestResult result) {
		
		String methodName=result.getMethod().getMethodName();
		String logText="<b>"+"TEST CASE:- "+ methodName.toUpperCase()+ " - SKIPPED"+"</b>";		
		Markup markup = MarkupHelper.createLabel(logText, ExtentColor.ORANGE);
		testReport.get().skip(markup);

	}

4. Create the BaseTests class

This class contains the methods to initialize the browser and exit the browser after every test.

import java.time.Duration;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import io.github.bonigarcia.wdm.WebDriverManager;

public class BaseTests {

	public static WebDriver driver;
	public WebDriverWait wait;
	  
	@BeforeTest
    public void setup() throws Exception {
          
       driver = WebDriverManager.firefoxdriver().create();
       driver.get("https://opensource-demo.orangehrmlive.com/");
       wait = new WebDriverWait(driver, Duration.ofSeconds(10));
       driver.manage().window().maximize();
    }
	  
	@AfterTest
	 public  void closeBrowser() {
	    	
	   driver.close();    	
			  
	  }

5. Create the LoginPage class

This class contains the locator of all the web elements and methods needed for the testing of the page.

package com.example.testcases;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;

public class LoginPage extends BaseTests{

    WebDriver driver;

    @FindBy(name = "txtUsername")
    WebElement userName;

    @FindBy(name = "txtPassword")
    WebElement password;

    @FindBy(id = "logInPanelHeading")
    WebElement titleText;

    @FindBy(id = "btnLogin")
    WebElement login;
    
    @FindBy(id="spanMessage")
    WebElement errorMessage;
    
    @FindBy(id="forgotPasswordLink")
    WebElement forgetPasswordLink;
    
    @FindBy(xpath="//*[@id='social-icons']/a[1]/img")
    WebElement linkedInIcon;

    public LoginPage(WebDriver driver) {
          this.driver = driver;

          // This initElements method will create all WebElements
          PageFactory.initElements(driver, this);
    }

    // Set user name in textbox
    public void setUserName(String strUserName) {
          userName.sendKeys(strUserName);
    }

    // Set password in password textbox
    public void setPassword(String strPassword) {
          password.sendKeys(strPassword);
    }

    // Click on login button
    public void clickLogin() {
          login.click();
    }

    // Get the title of Login Page
    public String getLoginTitle() {
          return titleText.getText();
    }
    
    // Get the text of forgotPasswordLink
    public String getforgotPasswordLinkText() {
          return forgetPasswordLink.getText();
    }
    
    // Get the errorMessage
    public String getErrorMessage() {
          return errorMessage.getText();
    }
    
    // Verify linkedInIcon is enabled
    public Boolean isEnabledLinkedIn() {
          return linkedInIcon.isEnabled();
    }

    public void login(String strUserName, String strPasword) {

          // Fill user name
          this.setUserName(strUserName);

          // Fill password
          this.setPassword(strPasword);

          // Click Login button
          this.clickLogin();
    }
}

6. Create the LoginTests class

This class contains all the tests. As we are using, I have assigned priority to all the tests to run the tests in a specified order.

import static org.testng.Assert.assertTrue;
import org.testng.Assert;
import org.testng.SkipException;
import org.testng.annotations.Test;

public class LoginTests extends BaseTests{

    LoginPage objLogin;

    @Test(priority = 0)
    public void verifyLoginPageTitle() {

          // Create Login Page object
          objLogin = new LoginPage(driver);

          // Verify login page text
          String loginPageTitle = objLogin.getLoginTitle();
          Assert.assertTrue(loginPageTitle.contains("LOGIN Panel"));
    }
    
    @Test(priority = 1)
    public void verifyforgetPasswordLink() {

    String expectedText= objLogin.getforgotPasswordLinkText();
    Assert.assertTrue(expectedText.contains("Forgot your password?"));
    
    }
      
    @Test(priority = 2)
    public void HomeTest() {

          // login to application
          objLogin.login("Admin1", "admin1234");

         String expectedError = objLogin.getErrorMessage();

          // Verify home page
          Assert.assertTrue(expectedError.contains("Username cannot be empty"));
    }

    @Test(priority = 3)
    public void verifyLinkedIn() {

        System.out.println("Actual linkedIn Text :" + objLogin.isEnabledLinkedIn());
        assertTrue(objLogin.isEnabledLinkedIn());
        
        System.out.println("Im in skip exception");
		throw new SkipException("Skipping this exception");
    }
       
}

In this example, there are 4 tests, and out of 4, 2 should pass, 1 should fail and 1 should skip.

7. Create TestNG.xml

In the TestNG.xml file, we shall add our classes and also the listener class.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="Extent Report Demo">

<listeners>
     <listener class-name ="com.example.extentlisteners.ExtentListeners"/>
  </listeners>
  
  <test name="Login Tests">
    <classes>
      <class name="com.example.testcases.LoginTests"/>
    </classes>
  </test> <!-- Test -->
</suite> <!-- Suite -->

8. Execute the tests from testng.xml

Right-click on testng.xml and select Run As -> TestNG Suite.

9 Test Execution Result

The test execution result can be seen in the console.

10. Extent Report Generation

Refresh the project and will see a folder with the name of the reports present in the project.

Right-click the report and open it in your choice of browser.

Upon opening the Extent Report, you can see the summary of the tests executed.

This is the view of the dashboard in the Extent Report. This page provides a complete view of the total number of tests executed, passed tests, failed tests, the total time taken for executing the tests, and also the classification of the tests based on the category.

Extent reports produce simple and visually appealing reports. Furthermore, the HTML-based report is simple to share with other stakeholders. Extent Reports provide greater detail, allowing testers to be more effective when it comes to quickly debugging software.

Congratulations!! We are able to generate an ExtentReport. Happy Learning!!