Gradle Project with Cucumber, Selenium and JUnit4

HOME

The previous tutorial explained the Integration of Cucumber with Selenium and JUnit4 in a Maven Project. This tutorial explains the test automation framework based on Gradle, Cucumber, Selenium, and JUnit4.

Pre Requisite:

  1. Java 8 or above installed
  2. Eclipse or IntelliJ IDE installed
  3. Gradle Installed
  4. Environment variables JAVA_HOME and GRADLE_HOME are correctly configured

In this tutorial, I’ll create a BDD Framework for the testing of web applications using Cucumber, and Selenium WebDriver with JUnit4. This framework consists of:-

  1. Cucumber Java- 7.6.0
  2. Cucumber JUnit– 7.6.0
  3. Java 11
  4. JUnit4 – 4.13.2
  5. Gradle – 7.5.1
  6. Selenium – 4.3.0

Project Structure

Steps to set up Cucumber Test Automation Framework with Selenium and TestNG

  1. Download and Install Java on the system
  2. Download and setup Eclipse IDE on the system
  3. Install and setup Gradle
  4. Install Cucumber Eclipse Plugin (For Eclipse IDE)
  5. Create a new Gradle Project
  6. Add SeleniumJUnit4, and Cucumber dependencies to the build.gradle
  7. Create a feature file under src/test/resources
  8. Create the classes for locators, actions, and utilities in src/main/java
  9. Create the Step Definition class or Glue Code in src/test/java
  10. Create a Hook class to contain the initialization and closing of the browser in src/test/java
  11. Create a JUnit4 Cucumber Runner class in src/test/java
  12. Run the tests from Command Line
  13. Cucumber Report Generation

Implementation Steps

Step 1- Download and Install Java

Cucumber and Selenium need Java to be installed on the system to run the tests. Click here to know How to install Java.

Step 2 – Download and setup Eclipse IDE on the system

The Eclipse IDE (integrated development environment) provides strong support for Java developers. Click here to know How to install Eclipse.

Step 3 – Setup Maven

To build a test framework, we need to add a number of dependencies to the project. Click here to know How to install Maven.

Step 4 – Install Cucumber Eclipse Plugin

The cucumber plugin is an Eclipse plugin that allows eclipse to understand the Gherkin syntax. When we are working with cucumber we will write the feature files that contain Feature, Scenario, Given, When, Then, And, But, Tags, Scenario Outline, and Examples. By default, eclipse doesn’t understand these keywords so it doesn’t show any syntax highlighter. Cucumber Eclipse Plugin highlights the keywords present in Feature File. Refer to this tutorial to get more detail – How to setup Cucumber with Eclipse.

Step 5 – Create a new Gradle Project

Below are the steps to create the Gradle project from the command line.

If you want to create the Gradle project from Eclipse IDE, click here to know How to create a Gradle Java project. Below is the structure of the Gradle project.

Step 6 – Add Selenium, JUnit4, and Cucumber dependencies to the build.gradle

Add below mentioned Selenium, JUnit4, and Cucumber dependencies to the project.

/*
 * This file was generated by the Gradle 'init' task.
 *
 */

plugins {
    // Apply the application plugin to add support for building a CLI application in Java.
    id 'application'
}

repositories {
    // Use Maven Central for resolving dependencies.
    mavenCentral()
}

java {
    sourceCompatibility = 11
    targetCompatibility = 11
}

dependencies {

    testImplementation 'io.cucumber:cucumber-java:7.6.0'
    testImplementation 'io.cucumber:cucumber-junit:7.6.0'  
    
    // Use JUnit test framework.
    testImplementation 'junit:junit:4.13.2'

    // This dependency is used by the application.
    implementation 'com.google.guava:guava:30.1.1-jre'
    implementation 'org.seleniumhq.selenium:selenium-java:4.4.0'
    implementation 'io.github.bonigarcia:webdrivermanager:5.3.0'
}

application {
    // Define the main class for the application.
    mainClass = 'com.example.App'
}

configurations {
    cucumberRuntime {
        extendsFrom testImplementation
    }
}

task cucumber() {
    dependsOn assemble, testClasses
    doLast {
        
        javaexec {      
            main = "io.cucumber.core.cli.Main"
            classpath = configurations.cucumberRuntime + sourceSets.main.output + sourceSets.test.output
               
            args = ['--plugin', 'pretty', 
            '--glue', 'com.example.definitions', 'src/test/resources'
            ]
        }

    }
}

I have added WebDriverManager dependency to the POM.xml to download the driver binaries automatically. To know more about this, please refer to this tutorial – How to manage driver executables using WebDriverManager.

Step 7 – Create a feature file in the src/test/resources directory

Create a folder with name features. Now, create the feature file in this folder. The feature file should be saved with the extension .feature. This feature file contains the test scenarios created to test the application. The Test Scenarios are written in Gherkins language in the format of Given, When, Then, And, But.

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 open
    
   @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               |
  | abc123     | xyz$$     | Invalid credentials               |
  | $$$$$$     | %%%%%     | Invalid credentials               |
  
   @MissingUsername @FailedTest
   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 8 – Create the classes for locators, actions, and utilities in src/main/java

Below is the sample code of the LoginPageLocators.

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

public class LoginPageLocators {

	@FindBy(name = "username")
    public WebElement userName;
  
    @FindBy(name = "password")
    public WebElement password;
  
    @FindBy(xpath = "//*[@id='app']/div[1]/div/div[1]/div/div[2]/div[2]/form/div[3]/button")
    public WebElement login;
  
    @FindBy(xpath = "//*[@id='app']/div[1]/div/div[1]/div/div[2]/div[2]/div/div[1]/div[1]/p")
    public  WebElement errorMessage;
     
    @FindBy(xpath = "//*[@id='app']/div[1]/div/div[1]/div/div[2]/div[2]/form/div[1]/div/span")
    public WebElement missingUsernameErrorMessage;
     
}

Below is the sample code for the 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;
 
}

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

import org.openqa.selenium.support.PageFactory;
import com.example.locators.LoginPageLocators;
import com.example.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 error message when invalid credentials are provided
    public String getErrorMessage() {
        return loginPageLocators.errorMessage.getText();
    }
    
   // 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.locators.HomePageLocators;
import com.example.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();
    }

}

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. 

import java.time.Duration;
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(Duration.ofSeconds(TIMEOUT));
	        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 9 – Create the Step Definition class or Glue Code in src/test/java

Now, we need to create the Step Definition of the Feature File

LoginPageDefinitions.java

import org.junit.Assert;
import com.example.actions.HomePageActions;
import com.example.actions.LoginPageActions;
import com.example.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) {
 
        objLogin.login(userName, passWord);     
    }
 
    @Then("User should be able to login sucessfully and new page open")
    public void verifyLogin() {
 
        Assert.assertTrue(objHomePage.getHomePageText().contains("Employee Information"));
    }
    
    @Then("User should be able to see error message {string}")
    public void verifyErrorMessageForInvalidCredentials(String expectedErrorMessage) {
 
        Assert.assertEquals(expectedErrorMessage,objLogin.getErrorMessage());
    }
     
    @Then("User should be able to see error message for empty username as {string}")
    public void verifyErrorMessageForEmptyUsername(String expectedErrorMessage) {
    	 
        Assert.assertEquals(expectedErrorMessage,objLogin.getMissingUsernameText());
 
    }   
}

Step 10 – Create a Hook class to contain the initialization and closing of the browser in src/test/java

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

public class BaseClass {

	@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 11 – Create a JUnit Cucumber Runner class in src/test/java

Cucumber needs a TestRunner class to run the feature files. It is suggested to create a folder with the name of the runner in the src/test/java directory and create the Cucumber TestRunner class in this folder. Below is the code of the Cucumber TestRunner class.

import org.junit.runner.RunWith;
import io.cucumber.junit.Cucumber;
import io.cucumber.junit.CucumberOptions;

@RunWith(Cucumber.class)
@CucumberOptions(tags = "", features = {"src/test/resources/features/LoginPage.feature"}, glue = {"com.example.definitions"})
   
public class CucumberRunnerTests {
   
}

Step 12 – Run the tests from Command Line

Run the below command in the command prompt to run the tests and to get the test execution report.

gradle cucumber

The output of the above program is

Step 13 – Cucumber Report Generation

To get Cucumber Test Reports, add cucumber.properties under src/test/resources and add the below instruction in the file

cucumber.publish.enabled=true

Below is the image of the Cucumber Report generated using the Cucumber Service.

In the above example, as we can see, one of the tests has failed. So, when a test fails, we have written the code to take a screenshot of the failed step. The highlighted box above shows the image of the failed test. You can click on that to see the screenshot.

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

Gradle Project with Cucumber, Selenium and TestNG

HOME

The previous tutorial explained the Integration of Cucumber with Selenium and TestNG in a Maven Project. This tutorial explains the test automation framework based on Gradle, Cucumber, Selenium, and TestNG.

Pre Requisite:

  1. Java 8 or above installed
  2. Eclipse or IntelliJ IDE installed
  3. Gradle Installed
  4. Environment variables JAVA_HOME and GRADLE_HOME are correctly configured

In this tutorial, I’ll create a BDD Framework for the testing of web applications using Cucumber, and Selenium WebDriver with TestNG. This framework consists of:-

  1. Cucumber Java- 7.6.0
  2. Cucumber TestNG – 7.6.0
  3. Java 11
  4. TestNG – 7.6.0
  5. Gradle – 7.5.1
  6. Selenium – 4.3.0

Project Structure

Steps to set up Cucumber Test Automation Framework with Selenium and TestNG

  1. Download and Install Java on the system
  2. Download and setup Eclipse IDE on the system
  3. Install and setup Gradle
  4. Install Cucumber Eclipse Plugin (For Eclipse IDE)
  5. Create a new Gradle Project
  6. Add SeleniumTestNG, and Cucumber dependencies to the build.gradle
  7. Create a feature file under src/test/resources
  8. Create the classes for locators, actions and utilities in src/main/java
  9. Create the Step Definition class or Glue Code in src/test/java
  10. Create a Hook class to contain the initialization and closing of browser in src/test/java
  11. Create a TestNG Cucumber Runner class in src/test/java
  12. Run the tests from Command Line
  13. Cucumber Report Generation

Implementation Steps

Step 1- Download and Install Java

Cucumber and Selenium need Java to be installed on the system to run the tests. Click here to know How to install Java.

Step 2 – Download and setup Eclipse IDE on the system

The Eclipse IDE (integrated development environment) provides strong support for Java developers. Click here to know How to install Eclipse.

Step 3 – Setup Maven

To build a test framework, we need to add a number of dependencies to the project. Click here to know How to install Maven.

Step 4 – Install Cucumber Eclipse Plugin

The cucumber plugin is an Eclipse plugin that allows eclipse to understand the Gherkin syntax. When we are working with cucumber we will write the feature files that contain Feature, Scenario, Given, When, Then, And, But, Tags, Scenario Outline, and Examples. By default, eclipse doesn’t understand these keywords so it doesn’t show any syntax highlighter. Cucumber Eclipse Plugin highlights the keywords present in Feature File. Refer to this tutorial to get more detail – How to setup Cucumber with Eclipse.

Step 5 – Create a new Gradle Project

Below are the steps to create the Gradle project from the command line.

If you want to create the Gradle project from Eclipse IDE, click here to know How to create a Gradle Java project. Below is the structure of the Gradle project.

Step 6 – Add Selenium, TestNG, and Cucumber dependencies to the build.gradle

Add below mentioned Selenium, TestNG, and Cucumber dependencies to the project.

I have added WebDriverManager dependency to the POM.xml to download the driver binaries automatically. To know more about this, please refer to this tutorial – How to manage driver executables using WebDriverManager.

/*
 * This file was generated by the Gradle 'init' task.
 *
 * This generated file contains a sample Java application project to get you started.
 */

plugins {
    // Apply the application plugin to add support for building a CLI application in Java.
    id 'application'
    id 'io.qameta.allure' version '2.11.0'
}

repositories {
    // Use Maven Central for resolving dependencies.
    mavenCentral()
}

java {
    sourceCompatibility = 11
    targetCompatibility = 11
}

dependencies {
   
    // Use TestNG framework, also requires calling test.useTestNG() below
    testImplementation 'io.cucumber:cucumber-java:7.6.0'
    testImplementation 'io.cucumber:cucumber-testng:7.6.0'
            
     //TestNG  
      testImplementation 'org.testng:testng:7.6.0'
      
     //Others  
     implementation 'com.google.guava:guava:31.0.1-jre'
     implementation 'org.seleniumhq.selenium:selenium-java:4.4.0'
     implementation 'io.github.bonigarcia:webdrivermanager:5.3.0'
}

application {
    // Define the main class for the application.
    mainClass = 'com.example.App'
}

tasks.named('test') {
    // Use TestNG for unit tests.
    useTestNG()
}

configurations {
    cucumberRuntime {
        extendsFrom testImplementation
    }
}

task cucumber() {
    dependsOn assemble, testClasses
    doLast {
        javaexec {
         
            main = "io.cucumber.core.cli.Main"
            classpath = configurations.cucumberRuntime + sourceSets.main.output + sourceSets.test.output
            args = ['--plugin', 'pretty', 
            '--glue', 'com.example.definitions', 'src/test/resources']
        }
    }
}

Step 7 – Create a feature file in the src/test/resources directory

Create a folder with name features. Now, create the feature file in this folder. The feature file should be saved with the extension .feature. This feature file contains the test scenarios created to test the application. The Test Scenarios are written in Gherkins language in the format of Given, When, Then, And, But.

Below is an example of the Feature File.

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 open
    
   @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               |
  | abc123     | xyz$$     | Invalid credentials               |
  
 
   @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 "Empty Username"

Step 8 – Create the classes for locators, actions and utilities in src/main/java

Below is the sample code of the LoginPageLocators.

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

public class LoginPageLocators {

	@FindBy(name = "username")
    public WebElement userName;
  
    @FindBy(name = "password")
    public WebElement password;
  
    @FindBy(xpath = "//*[@id='app']/div[1]/div/div[1]/div/div[2]/div[2]/form/div[3]/button")
    public WebElement login;
  
    @FindBy(xpath = "//*[@id='app']/div[1]/div/div[1]/div/div[2]/div[2]/div/div[1]/div[1]/p")
    public  WebElement errorMessage;
     
    @FindBy(xpath = "//*[@id='app']/div[1]/div/div[1]/div/div[2]/div[2]/form/div[1]/div/span")
    public WebElement missingUsernameErrorMessage;
       
}

Below is the sample code for the 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;
 
}

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

import org.openqa.selenium.support.PageFactory;
import com.example.locators.LoginPageLocators;
import com.example.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) {
 
    	loginPageLocators.userName.sendKeys(strUserName);
    	loginPageLocators.password.sendKeys(strPassword);
    	loginPageLocators.login.click();
 
    }
    
    // Get the error message when invalid credentials are provided
    public String getErrorMessage() {
        return loginPageLocators.errorMessage.getText();
    }
    
 
   // 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.locators.HomePageLocators;
import com.example.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();
    }

}

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. 

import java.time.Duration;
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(Duration.ofSeconds(TIMEOUT));
	        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 9 – Create the Step Definition class or Glue Code in src/test/java

Now, we need to create the Step Definition of the Feature File – LoginPageDefinitions.java.

import org.testng.Assert;
import org.testng.SkipException;
import com.example.actions.HomePageActions;
import com.example.actions.LoginPageActions;
import com.example.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) {
 
        objLogin.login(userName, passWord);
        
    }
 
    @Then("User should be able to login sucessfully and new page open")
    public void verifyLogin() {
 
        Assert.assertTrue(objHomePage.getHomePageText().contains("Employee Information"));
 
    }
    
    @Then("User should be able to see error message {string}")
    public void verifyErrorMessageForInvalidCredentials(String expectedErrorMessage) {
 
        Assert.assertEquals(objLogin.getErrorMessage(),expectedErrorMessage);
 
    }
  
    
    @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 10 – Create a Hook class to contain the initialization and closing of the browser in src/test/java

Below is the example of the Hook class where we initialize the browser as well as close the browser at the end of the execution.

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

public class BaseClass {

	@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 11 – Create a TestNG Cucumber Runner class in src/test/java

Cucumber needs a TestRunner class to run the feature files. It is suggested to create a folder with the name of the runner in the src/test/java directory and create the Cucumber TestRunner class in this folder. Below is the code of the Cucumber TestRunner class.

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

Step 12 – Run the tests from Command Line

Run the below command in the command prompt to run the tests and to get the test execution report.

gradle cucumber

The output of the above program is

Step 13 – Cucumber Report Generation

To get Cucumber Test Reports, add cucumber.properties under src/test/resources and add the below instruction in the file.

cucumber.publish.enabled=true

Below is the image of the Cucumber Report generated using the Cucumber Service.

In the above example, as we can see, one of the tests has failed. So, when a test fails, we have written the code to take a screenshot of the failed step. The highlighted box above shows the image of the failed test. You can click on that to see the screenshot.

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

Gradle – Allure Report for Selenium and TestNG

HOME

The previous tutorial explained the generation of Allure Report for Selenium and TestNG in a Maven Project. This tutorial explains the generation of Allure Report for Selenium and TestNG in a Gradle project.

Pre-Requisite:

  1. Java 8 or higher installed
  2. Gradle installed
  3. Eclipse or IntelliJ installed

This framework consists of:

  1. Selenium – 4.3
  2. Java 11
  3. TestNG – 7.6.1
  4. Gradle – 7.5.1
  5. Allure Maven Plugin – 2.11.0
  6. Allure TestNG – 2.19.1

Project Structure

To create a Gradle project from the command line, please refer to this tutorial – How to create a Java Gradle project using Command Line.

Implementation Steps

  1. Add Selenium, TestNG, and Allure-TestNG dependencies in build.gradle
  2. Create Pages and Test Code for the pages
  3. Create testng.xml
  4. Execute the Tests
  5. Generate Allure Report

Step 1 – Add Selenium, TestNG, and Allure-TestNG dependencies in the build.gradle

plugins {
    // Apply the application plugin to add support for building a CLI application in Java.
    id 'application'
    id 'io.qameta.allure' version '2.11.0'
}

repositories {
    // Use Maven Central for resolving dependencies.
    mavenCentral()
}

java {
    sourceCompatibility = 11
    targetCompatibility = 11
}

dependencies {

    
    // Use TestNG framework, also requires calling test.useTestNG() below
    testImplementation 'org.testng:testng:7.6.1'

    // This dependency is used by the application.
    implementation 'com.google.guava:guava:31.0.1-jre'
    implementation 'org.seleniumhq.selenium:selenium-java:4.4.0'
    implementation 'io.github.bonigarcia:webdrivermanager:5.3.0'
    implementation 'io.qameta.allure:allure-testng:2.19.0'
}

application {
    // Define the main class for the application.
    mainClass = 'com.example.App'
}

tasks.named('test') {
    // Use TestNG for unit tests.
    useTestNG() {
    
    useDefaultListeners = true
    suites "./testng.xml"
 
   }
   
    testLogging {
        events "PASSED", "FAILED", "SKIPPED"
        exceptionFormat = 'full'
    }
}

Step 2 – Create Pages and Test Code for the pages

Below is the sample project which uses Selenium and TestNG which is used to generate an Allure Report.

We have used the PageFactory model to build the tests. I have created a package named pages and created the page classes in that folder. Page class contains the locators of each web element present on that particular page along with the methods of performing actions using these web elements.

This is the BaseClass that contains the PageFactory.initElements. The initElements is a static method of PageFactory class that is used to initialize all the web elements located by @FindBy annotation. 

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

public class BasePage {	

	  public WebDriver driver;

	  public BasePage(WebDriver driver) {
		  this.driver = driver;
		  PageFactory.initElements(driver,this);
	}

}

Below is the code for LoginPage and HomePage

LoginPage

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

public class LoginPage extends BasePage{
	
	 public LoginPage(WebDriver driver) {
		 super(driver);
		
    }
	
	@FindBy(name = "username")
    public WebElement userName;
 
    @FindBy(name = "password")
    public WebElement password;
    
    @FindBy(xpath = "//*[@id='app']/div[1]/div/div[1]/div/div[2]/div[2]/form/div[1]/div/span")
    public WebElement missingUsernameErrorMessage;
    
    @FindBy(xpath = "//*[@id='app']/div[1]/div/div[1]/div/div[2]/div[2]/form/div[1]/div/span")
    public WebElement missingPasswordErrorMessage;
 
    @FindBy(xpath = "//*[@id='app']/div[1]/div/div[1]/div/div[2]/div[2]/form/div[3]/button")
    public WebElement login;
 
    @FindBy(xpath = "//*[@id='app']/div[1]/div/div[1]/div/div[2]/div[2]/div/div[1]/div[1]/p")
    public  WebElement errorMessage;
          
   // Get the error message when password is blank
    public String getMissingPasswordText() {
        return missingPasswordErrorMessage.getText();
    }
      
    // Get the Error Message
    public String getErrorMessage() {
        return errorMessage.getText();
    }
    
    public void login(String strUserName, String strPassword) {
 
    	userName.sendKeys(strUserName);
    	password.sendKeys(strPassword);
    	login.click();
    }

}

HomePage

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

public class HomePage extends BasePage {

	public HomePage(WebDriver driver) {
		super(driver);

	}

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

	  // Get the User name from Home Page
	    public String getHomePageText() {
	       return homePageUserName.getText();
   }

}

Here, we have BaseTests Class also which contains the common methods needed by other test pages.

import java.time.Duration;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import io.github.bonigarcia.wdm.WebDriverManager;
import io.qameta.allure.Step;

public class BaseTests {
	
	public WebDriver driver;
	public final static int TIMEOUT = 30;
    
	@BeforeMethod
    @Step("Start the application")
    public void setup() {
    	WebDriverManager.chromedriver().setup();
	    driver = new ChromeDriver();
	    driver.manage().window().maximize();
	    driver.get("https://opensource-demo.orangehrmlive.com/");	    
	    driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(TIMEOUT));

    }
 
    @Step("Stop the application")
    @AfterMethod
    public void tearDown() {
        driver.quit();
    }
    
}

LoginTests

import org.testng.Assert;
import org.testng.annotations.Test;
import io.qameta.allure.Description;
import io.qameta.allure.Severity;
import io.qameta.allure.SeverityLevel;

public class LoginTests extends BaseTests{
	 
	@Severity(SeverityLevel.NORMAL)
    @Test(description = "This test validates error message when credentials are incorrect", priority = 0)
	@Description("Test Description : Login Test with invalid credentials")
    public void invalidCredentials() {
   
	    LoginPage objLoginPage = new LoginPage(driver);
    	objLoginPage.login("Admin", "admin123$$");
    	 
    	// Verify Error Message
    	 Assert.assertEquals(objLoginPage.getErrorMessage(),"Invalid credentials");
    
    }
    
	@Severity(SeverityLevel.BLOCKER)
    @Test(description = "This test validates login to the application", priority = 1)
	@Description("Test Description : Login Test with valid credentials")
    public void gotoHomePage() {
   
	    LoginPage objLoginPage = new LoginPage(driver);
    	objLoginPage.login("Admin", "admin123");
    	 
    	HomePage objHomePage = new HomePage(driver);
    	
    	// Verify Home Page
    	 Assert.assertEquals(objHomePage.getHomePageText(),"Employee Information");
    
    }
    
	@Severity(SeverityLevel.NORMAL)
    @Test(description = "This test will fail", priority = 2)
	@Description("Test Description : Login Test with missing username")
    public void missingUsername() {
   
	    LoginPage objLoginPage = new LoginPage(driver);
    	objLoginPage.login("", "admin123");
    	    	
    	// Verify Error Message
   	        	 Assert.assertEquals(objLoginPage.getMissingUsernameText(),"Invalid credentials");
    
    }
	
	@Severity(SeverityLevel.NORMAL)
    @Test(description = "This test will skip", priority = 3, enabled = false)
	@Description("Test Description : Login Test with missing password")
    public void missingPassword() {
   
	    LoginPage objLoginPage = new LoginPage(driver);
    	objLoginPage.login("admin", "");
    	    	
    	// Verify Error Message
   	     Assert.assertEquals(objLoginPage.getErrorMessage(),"Invalid credentials");
    
    }
     
}

Step 3 – Create testng.xml

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="Gradle - Selenium with TestNG Tests">
    <classes>
      <class name="com.example.LoginTests"/>
    </classes>
  </test> <!-- Test -->
</suite> <!-- Suite -->

Step 4 – Execute the Tests

Note:- As you can see my project has two parts – the app and GradleSeleniumTestNG.

Go to the app project and run the tests, using the below command

gradle clean test

The output of the test execution is

Step 5 – Generate the Allure Report

Once the test execution is finished, a folder named allure-results will be generated in the build folder.

To generate Allure Report, use the below command

allure serve build/allure-results

This will generate the beautiful Allure Test Report as shown below.

Allure Report Dashboard

The overview page hosts several default widgets representing the basic characteristics of your project and test environment.

  1. Statistics – overall report statistics.
  2. Launches – if this report represents several test launches, statistics per launch will be shown here.
  3. Behaviors – information on results aggregated according to stories and features.
  4. Executors – information on test executors that were used to run the tests.
  5. History Trend – if tests accumulated some historical data, its trend will be calculated and shown on the graph.
  6. Environment – information on the test environment.

Categories in Allure Report

The categories tab gives you a way to create custom defects classifications to apply for test results. There are two categories of defects – Product Defects (failed tests) and Test Defects (broken tests).

Suites in Allure Report

On the Suites tab a standard structural representation of executed tests, grouped by suites and classes can be found.

Here, I have used TestNG, so to skip the tests have used enabled. But, in the Allure Report, it is marked as unknown (pink color).

Graphs in Allure Report

Graphs allow you to see different statistics collected from the test data: status breakdown or severity and duration diagrams.

Timeline in Allure Report

The timeline tab visualizes retrospective test execution, allure adaptors collect precise timings of tests, and here on this tab, they are arranged accordingly to their sequential or parallel timing structure.

Behaviors of Allure Report

This tab groups test results according to Epic, Feature, Story, Test Severity, Test Description, Test Steps, and so on.

Packages in Allure Report

The packages tab represents a tree-like layout of test results, grouped by different packages.

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

PDF ExtentReport for Cucumber and TestNG

HOME

In this tutorial, we will generate PDF reports using an Extent Adapter.

Step 1: Follow this article to add POM.xml, create and configure the sample project with the extent property file, and add the plugin to the Test Runner class.

Step 2: To attach the screenshot to your extent report, refer to How to add Screenshot to Cucumber ExtentReports.

Step 3: Add additional PDF-related properties in extent.properties.

extent.reporter.spark.start=true
extent.reporter.spark.out=Reports/Spark.html

#PDF Report
extent.reporter.pdf.start=true
extent.reporter.pdf.out=PdfReport/ExtentPdf.pdf

#FolderName
basefolder.name=ExtentReports/SparkReport_
basefolder.datetimepattern=d_MMM_YY HH_mm_ss

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

Execute the test code. The PDF report will be generated as shown below:

The report contains six sections – dashboard, summary, tags, features, scenarios, and detailed sections.

1. Dashboard

This section is a single-page dashboard that summarizes the test run. This contains the report title, duration, and status of breakups.

2. Summary section

This section provides an overview of the test run in terms of a feature breakdown, comprising duration, scenario count, and step count. The scenarios and steps are divided into status counts. The feature name has a link that navigates to further details in the detailed step section. This link is only present if the detailed section is enabled.

3. Tag section

This section provides an overview of the test run in terms of a tag breakdown, comprising feature count and scenario count.

4. Feature section

This section describes the feature details with a stacked bar chart and a table of the scenario status and duration. This section display can be controlled by a configuration setting, enabled by default. The feature name has a link that navigates to further details in the detailed step section. This link is only present if the detailed section is enabled.

5. Scenario section

This section describes the scenario details with a stacked bar chart and a table of the step status and duration. This section display can be controlled by a configuration setting, enabled by default. The feature and scenario names have a link that navigates to further details in the detailed step section. This link is only present if the detailed section is enabled. 


6. Detailed section

This section describes the details of individual steps and hooks, along with status and duration. This section display can be controlled by a configuration setting, enabled by default.

This section also contains screenshots of the failed images.

Customized PDF Report

The report settings can be used to toggle on and off optional report sections, and change the report title, text color for various data, background color, and other options.

The settings are saved in a YAML file called pdf-config.yaml, which is located in the project’s src/test/resources folder. If the file is missing or no settings are specified, the default values are used. To change the default values, create a pdf-config.yaml file in the project’s src/test/resources folder that contains only the new values for the settings.

sample YAML configuration file is shown below:

passColor: 05a167
failColor: ff00ff
skipColor: a89132
displayFeature: true
displayScenario: true
displayDetailed: true
displayAttached: false
displayExpanded: true

dashboardConfig:
   title: Cucumber PDF Report
   dataBackgroundColor: 4F0CC8
   titleColor: FF0000
   dateColor: 969696
   timeColor: 000000
   dial:
      featureRanges: 60 95
      scenarioRanges: 70 90
      stepRanges: 75 85
      badColor: f768a1
      averageColor: 93ffff
      goodColor: 32ecab
      
summaryConfig:
   totalColor: FF0000
   durationColor: FF0000
   
tagConfig:
   totalColor: FF0000
   
featureConfig:
   totalColor: FF0000
   durationColor: FF0000
   
scenarioConfig:
   totalColor: FF0000
   durationColor: FF0000
   
detailedFeatureConfig:
   featureNameColor: FF0000
   startEndTimeColor: 000000
   tagColor: 404040
   dataHeaderColor: FFFFFF
   dataBackgroundColor: 404040
   totalColor: 0000FF
   durationColor: FFFFFF
   durationBackgroundColor: 404040   
   
detailedScenarioConfig:
   featureNameColor: 404040
   scenarioNameColor: FF0000
   startEndTimeColor: 000000
   tagColor: 404040
   dataHeaderColor: FFFFFF
   dataBackgroundColor: 404040
   totalColor: 0000FF
   durationColor: FFFFFF
   durationBackgroundColor: 404040
   stepChartBarColor: 7f32a8
   
detailedStepHookConfig:
   stepTextColor: 0000FF
   stepBackgroundColor: FFFFFF
   hookTextColor: 00FF00
   hookBackgroundColor: FFFFFF
   durationColor: FF0000
   errorMsgColor: 000000
   logMsgColor: 000000

Execute the test code. Now the PDF Report will be generated as shown below:

This method of configuring report settings using a yaml properties file can be used both for the Maven plugin report generation and the ExtentReport style.

The passedfailed, and skipped colors can be set with the passColor, failColor and skipColor properties. These take in the colors in hex values (without the leading ‘#’) and are valid throughout the report.

The features, scenarios, and detailed sections can be displayed by setting the displayFeaturedisplayScenario and displayDetailed properties to true. The default value for these settings is true.

Screenshots are displayed as thumbnails and can be opened in the available native application. This is the default behaviour, in which the screenshot file is embedded in the PDF file. This can be toggled by the displayAttached setting. When the setting is set to false, only the thumbnail is displayed. These can also be displayed in zoomed images in a separate section by setting the displayExpanded to true and also displayAttached to false.

To know more about various settings in PDF Report, refer to this tutorial.

ExtentReports Version 5 for Cucumber 6 and JUnit4

HOME

The previous tutorial explained the steps to generate ExtentReports for Cucumber with TestNG. We can generate ExtentReports for Cucumber with JUnit4 also. This tutorial explains the steps that need to be followed to generate an ExtentReports Version5.

Pre-Requisite:

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

Project Structure

Step 1 – Add Maven dependencies to the POM

Add ExtentReport dependency

<dependency>
    <groupId>com.aventstack</groupId>
    <artifactId>extentreports</artifactId>
    <version>5.0.9</version>
</dependency>

Add tech grasshopper maven dependency for Cucumber

<dependency>
    <groupId>tech.grasshopper</groupId>
    <artifactId>extentreports-cucumber6-adapter</artifactId>
    <version>2.13.0</version>
</dependency>

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

 <properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<selenium.version>4.3.0</selenium.version>
		<cucumber.version>6.11.0</cucumber.version>
		<extentreports.cucumber6.adapter.version>2.13.0</extentreports.cucumber6.adapter.version>
		<extentreports.version>5.0.9</extentreports.version>
		<junit.version>4.13.2</junit.version>
		<apache.common.version>2.4</apache.common.version>
		<webdrivermanager.version>5.2.1</webdrivermanager.version>
		<maven.compiler.plugin.version>3.10.1</maven.compiler.plugin.version>
		<maven.surefire.plugin.version>3.0.0-M7</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-junit</artifactId>
           <version>${cucumber.version}</version>
           <scope>test</scope>
       </dependency>

		<!-- https://mvnrepository.com/artifact/tech.grasshopper/extentreports-cucumber6-adapter -->
		<dependency>
			<groupId>tech.grasshopper</groupId>
			<artifactId>extentreports-cucumber6-adapter</artifactId>
			<version>${extentreports.cucumber6.adapter.version}</version>
		</dependency>

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

		<!-- Junit -->
		<dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.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>
		
		<!-- 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>
         <version>${maven.compiler.plugin.version}</version>
         <configuration>
             <source>${maven.compiler.source.version}</source> <!--For JAVA 8 use 1.8-->
			 <target>${maven.compiler.target.version}</target> <!--For JAVA 8 use 1.8-->
            <encoding>UTF-8</encoding>          
         </configuration>
       </plugin>                
       </plugins>
   </build>

</project>

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

Below is a sample feature file

Feature: Login to HRM Application 
 
  @ValidCredentials
   Scenario: Login with valid credentials
     
    Given User is on HRMLogin page "https://opensource-demo.orangehrmlive.com/"
    When User enters username as "Admin" and password as "admin123"
    Then User should be able to login successfully and new page open
    
   @InvalidCredentials
   Scenario Outline: Login with invalid credentials
     
    Given User is on HRMLogin page "https://opensource-demo.orangehrmlive.com/"
    When User enters username as "<username>" and password as "<password>"
    Then User should be able to see error message "<errorMessage>"
    
  Examples:
  | username   | password  | errorMessage                      |
  |            | abc       | Username cannot be empty          |
  | admin      |           | Password cannot be empty          |
  |            |           | Username cannot be empty          |
  | admin      | Admin123  | Invalid credentials               |
  
   
  @ForgetPassword  
   Scenario: Verify Forget Password Functionality
   
    Given User is on HRMLogin page "https://opensource-demo.orangehrmlive.com/"
    When User clicks on Forgot your password link
    Then User should be able to navigate to new page of title "Forgot Your Password?"

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

We need to create the extent.properties file at 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.spark.start=true
extent.reporter.spark.out=Reports/Spark.html

#FolderName
basefolder.name=ExtentReports/SparkReport_
basefolder.datetimepattern=d_MMM_YY HH_mm_ss

#Screenshot
screenshot.dir=/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.time.Duration;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.WebDriverWait;
import io.github.bonigarcia.wdm.WebDriverManager;

public class HelperClass {
	
	private static HelperClass helperClass;
	
	private static WebDriver driver;
	private static WebDriverWait wait;
    public final static int TIMEOUT = 10;
		
	 private HelperClass() {
		 
			WebDriverManager.chromedriver().setup();
	    	driver = new ChromeDriver();
	        wait = new WebDriverWait(driver, Duration.ofSeconds(TIMEOUT));
	        driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(TIMEOUT));
	        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 3 locator classes – LoginPageLocators, HomePageLocators, and ForgetPasswordPageLocators.

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='forgotPasswordLink']/a")
    public  WebElement forgotPasswordLink;
  
}

HomePageLocators

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

public class HomePageLocators {

	@FindBy(id = "welcome")
	public  WebElement homePageUserName;
 
}

ForgetPasswordPageLocators

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

public class ForgetPasswordPageLocators {
	
	@FindBy(xpath = "//*[@id='content']/div[1]/div[2]/h1")
    public WebElement forgotPasswordPageHeading;
	
}

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 3 action classes – LoginPageActions, HomePageActions, and ForgetPasswordPageActions.

LoginPageActions

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

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

public class LoginPageActions {

	LoginPageLocators loginPageLocators = null; 
	
    public LoginPageActions() {

    	this.loginPageLocators = new LoginPageLocators();
		PageFactory.initElements(HelperClass.getDriver(),loginPageLocators);
	}

	// Set user name in textbox
    public void setUserName(String strUserName) {
    	loginPageLocators.userName.sendKeys(strUserName);
    }
 
    // Set password in password textbox
    public void setPassword(String strPassword) {
    	loginPageLocators.password.sendKeys(strPassword);
    }
 
    // Click on login button
    public void clickLogin() {
    	loginPageLocators.login.click();
    }
 
    // Get the title of Login Page
    public String getLoginTitle() {
        return loginPageLocators.titleText.getText();
    }
       
    // Get the title of Login Page
    public String getErrorMessage() {
        return loginPageLocators.errorMessage.getText();
    }
    
    // Click on forgotYourPassword Link
    public void clickOnForgotPasswordLink() {
    	loginPageLocators.forgotPasswordLink.click();
    }
 
    public void login(String strUserName, String strPassword) {
 
        // Fill user name
        this.setUserName(strUserName);
 
        // Fill password
        this.setPassword(strPassword);
 
        // Click Login button
        this.clickLogin();
 
    }
}

HomePageActions

import org.openqa.selenium.support.PageFactory;
import com.example.junit.locators.HomePageLocators;
import com.example.junit.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();
    }

}

ForgetPasswordPageActions

import org.openqa.selenium.support.PageFactory;
import com.example.junit.locators.ForgetPasswordPageLocators;
import com.example.junit.utils.HelperClass;

public class ForgetPasswordPageActions {
	
	ForgetPasswordPageLocators forgetPasswordPageLocators = null;
	
	public ForgetPasswordPageActions() {
		this.forgetPasswordPageLocators = new ForgetPasswordPageLocators();
		
		PageFactory.initElements(HelperClass.getDriver(), forgetPasswordPageLocators);
	}

	public String getHeading() {
				return forgetPasswordPageLocators.forgotPasswordPageHeading.getText();
		
	}
}

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

Create the corresponding Step Definition file of the feature file.

LoginPageDefinitions

import org.junit.Assert;
import com.example.junit.actions.ForgetPasswordPageActions;
import com.example.junit.actions.HomePageActions;
import com.example.junit.actions.LoginPageActions;
import com.example.junit.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();
    ForgetPasswordPageActions objForgotPasswordPage = new ForgetPasswordPageActions();
		
    @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
        
    }
    
    @When("User clicks on Forgot your password link")
    public void clickOnForgotPasswordLink() {
    	
    	objLogin.clickOnForgotPasswordLink();
    	
    }
   
    @Then("User should be able to login successfully and new page open")
    public void verifyLogin() {
 
        // Verify home page
        Assert.assertTrue(objHomePage.getHomePageText().contains("Welcome"));
 
    }
    
    @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 navigate to new page of title {string}")
    public void verifyForgotPasswordPage(String heading) {
    	
    	Assert.assertEquals(objForgotPasswordPage.getHeading(),heading);
    	
    }
     
}

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

Create the hook class that contains the Before and After hook. @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.junit.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. It is an important component of the configuration. It also ensures that the cucumber runner class recognizes and launches the extent report adapter for the cucumber. Please add the following text as a plugin to the CucumberOptions as described below.

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.junit.Cucumber;
import org.junit.runner.RunWith;
import io.cucumber.junit.CucumberOptions;

@RunWith(Cucumber.class)
@CucumberOptions(tags = "", features = "src/test/resources/features/LoginPage.feature", glue = "com.example.junit.definitions",
plugin = {"com.aventstack.extentreports.cucumber.adapter.ExtentCucumberAdapter:"})

public class CucumberRunnerTests {

}

Step 10: Execute the code

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

Below is the screenshot of the Console. 

Step 11: 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 on Spark.html 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 first 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. As we can see that a screenshot is attached to the failed tests here.

Congratulation!! We are able to create an Extent Report for Cucumber and JUnit4. Happy Learning!!!

ExtentReports Version 5 for Cucumber 6 and TestNG

HOME

What is ExtentReport?

ExtentReport is a logger-style reporting library for automated tests. ExtentReports uses the logging style to add information about test sessions, such as the creation of tests, adding screenshots, assigning tags, and adding events or series of steps to sequentially indicate the flow of test steps.  ExtentReports 5 is built on an open-Core. That means, both community and professional editions use the same, full-featured API with the exception of a few reporters.

Extent Report 4 onwards, there are 2 editions of Extent Report – Core and Professional.

Below is the screenshot that shows which reporters are available in Professional or Community Editions. You can also visit this page

This tutorial explains the use of Extent Report Core Edition.

Prerequisite:

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

Project Structure

Step 1 – Add Maven dependencies to the POM

Add ExtentReport dependency

<dependency>
    <groupId>com.aventstack</groupId>
    <artifactId>extentreports</artifactId>
    <version>5.0.9</version>
</dependency>

Add tech grasshopper maven dependency for Cucumber

<dependency>
    <groupId>tech.grasshopper</groupId>
    <artifactId>extentreports-cucumber6-adapter</artifactId>
    <version>2.13.0</version>
</dependency>

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

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<cucumber.version>6.11.0</cucumber.version>
		<extentreports.cucumber6.adapter.version>2.13.0</extentreports.cucumber6.adapter.version>
		<extentreports.version>5.0.9</extentreports.version>
		<selenium.version>4.3.0</selenium.version>
		<webdrivermanager.version>5.2.1</webdrivermanager.version>
		<testng.version>7.4.0</testng.version>		
		<maven.compiler.plugin.version>3.10.1</maven.compiler.plugin.version>
		<maven.surefire.plugin.version>3.0.0-M7</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-cucumber6-adapter</artifactId>
			<version>${extentreports.cucumber6.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>2.4</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> <!--For JAVA 8 use 1.8-->
					<target>${maven.compiler.target.version}</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>

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

Below is a sample feature file. I have also added a failed scenario in @FaceBookLink.

Feature: Login to HRM Application 
 
   @ValidCredentials
   Scenario: Login with valid credentials
     
    Given User is on HRMLogin page "https://opensource-demo.orangehrmlive.com/"
    When User enters username as "Admin" and password as "admin123"
    Then User should be able to login sucessfully and new page open
    
   @InvalidCredentials
   Scenario Outline: Login with invalid credentials
     
    Given User is on HRMLogin page "https://opensource-demo.orangehrmlive.com/"
    When User enters username as "<username>" and password as "<password>"
    Then User should be able to see error message "<errorMessage>"
    
  Examples:
  | username   | password    | errorMessage                           |
  |                   | abc              | Username cannot be empty     |
  | admin        |                    | Password cannot be empty          |
  |                   |                    | Username cannot be empty          |
  | Admin       | admin12$$ | Invalid credentials               |
  | admin$$    | admin123   | Invalid credentials               |
  
    
  @FaceBookLink
  Scenario: Verify FaceBook Icon on Login Page
     
    Given User is on HRMLogin page "https://opensource-demo.orangehrmlive.com/"
    Then User should be able to see FaceBook Icon
    
  @LinkedInLink
  Scenario: Verify LinkedIn Icon on Login Page
     
    Given User is on HRMLogin page "https://opensource-demo.orangehrmlive.com/"
    Then User should be able to see LinkedIn Icon  

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

We need to create the extent.properties file at 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.spark.start=true
extent.reporter.spark.out=Reports/Spark.html

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, within 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.time.Duration;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.WebDriverWait;
import io.github.bonigarcia.wdm.WebDriverManager;

public class HelperClass {
	
	private static HelperClass helperClass;
	
	private static WebDriver driver;
	private static WebDriverWait wait;
    public final static int TIMEOUT = 10;
	
	 private HelperClass() {
		 
			WebDriverManager.chromedriver().setup();
	    	driver = new ChromeDriver();
	        wait = new WebDriverWait(driver, Duration.ofSeconds(TIMEOUT));
        driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(TIMEOUT));
	        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(id = "welcome")
	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 LoginPageLocators class so that we should be able to access all the PageFactory elements. Secondly, create a public constructor of LoginPageActions class

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

public class LoginPageActions {

	LoginPageLocators loginPageLocators = null; 
	
    public LoginPageActions() {

    	this.loginPageLocators = new LoginPageLocators();

		PageFactory.initElements(HelperClass.getDriver(),loginPageLocators);
	}

  
	// Set user name in textbox
    public void setUserName(String strUserName) {
    	loginPageLocators.userName.sendKeys(strUserName);
    }
 
    // Set password in password textbox
    public void setPassword(String strPassword) {
    	loginPageLocators.password.sendKeys(strPassword);
    }
 
    // Click on login button
    public void clickLogin() {
    	loginPageLocators.login.click();
    }
 
    // Get the title of Login Page
    public String getLoginTitle() {
        return loginPageLocators.titleText.getText();
    }
    
    
    // Get the title of Login Page
    public String getErrorMessage() {
        return loginPageLocators.errorMessage.getText();
    }
    
    // LinkedIn Icon is displayed
    public Boolean getLinkedInIcon() {
   
        return loginPageLocators.linkedInIcon.isDisplayed();
    }
    
    // FaceBook Icon is displayed
    public Boolean getFaceBookIcon() {
   
        return loginPageLocators.faceBookIcon.isDisplayed();
    }
 
    public void login(String strUserName, String strPassword) {
 
        // Fill user name
        this.setUserName(strUserName);
 
        // Fill password
        this.setPassword(strPassword);
 
        // Click Login button
        this.clickLogin();
 
    }
}

HomePageActions

import org.openqa.selenium.support.PageFactory;
import com.example.locators.HomePageLocators;
import com.example.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.actions.HomePageActions;
import com.example.actions.LoginPageActions;
import com.example.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 open")
    public void verifyLogin() {
 
        // Verify home page
        Assert.assertTrue(objHomePage.getHomePageText().contains("Welcome"));
 
    }
    
    @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 LinkedIn Icon")
    public void verifyLinkedInIcon( ) {
    	
    	Assert.assertTrue(objLogin.getLinkedInIcon());
    }
    
    @Then("User should be able to see FaceBook Icon")
    public void verifyFaceBookIcon( ) {
    	
    	Assert.assertTrue(objLogin.getFaceBookIcon());
    }
      
}

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

Create the hook class that contains the Before and After hook. @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 com.example.utils.HelperClass;
import io.cucumber.java.After;
import io.cucumber.java.Before;

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

       HelperClass.setUpDriver();
    }
	
	@After
	public static void tearDown() {
		
		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. It is an important component of the configuration. It also ensures that the cucumber runner class recognizes and launches the extent report adapter for cucumber. Please add the following text as a plugin to the CucumberOptions as described below.

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.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="ExtentReport5 for Cucumber">
  
  <classes>
  <class name = "com.example.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 Console. As expected, 7 tests, out of 8 are passed and 1 is failed.

Step 12: View the 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 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 first 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.

Step 13: How to customize the report folder name

We learned how to generate an ExtentReport in Cucumber Junit in the previous section. The problem with the previous approach is that it will continue to override the previous report once the new report is created. Typically, we must keep a backup of all the reports generated by previous tests. To accomplish this, we must save each report with a unique report name or folder name.

It’s simple to create reports with different folder names using the Extent reporter plugin adapter. Two settings must be added to our extent. basefolder.name and basefolder.datetimepattern are properties files. The values assigned to these will be combined to form a folder name. As a result, a report will be generated within that. The basefolder.datetimepattern value must be in a valid date-time format.

Let us update the extent.properties file.

extent.reporter.spark.start=true
extent.reporter.spark.out=Spark.html

#FolderName
basefolder.name=Reports/SparkReport
basefolder.datetimepattern=d-MMM-YY HH-mm-ss

The value for basefolder.name in the preceding snippet is “Report/SparkReport.” It means that the folder will be named SparkReport, and that it will create a Report folder within the project directory. You can specify the location of your folder. In the following setting, we’ve used a date and time stamp to create unique folder names by concatenating them with the report name.

So, when we run the report, it will generate at the location shown in the image below:

Congratulation!! We are able to create an Extent Report for Cucumber. Happy Learning!!!

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!!

How to Retry failed tests in TestNG – IRetryAnalyzer

HOME

TestNG is a well thought Test Framework. It provides a lot of different features which makes the life of a tester a little easy. It happens sometimes that a test execution fails, but the failure is not a product bug, but there can be different reasons for the failure such as the environment is down, third party web service is down, or the browser becomes unresponsive. Imagine a scenario where we need to run a test suite consisting of 100 tests and a few tests failed as a result of a known intermittent environment issue. We know that these tests can pass if rerun a couple of times. So, in this case, the retry functionality of TestNG comes to the rescue. This is one of the best and most frequently used functionality.

In this tutorial let us study how we can implement retry on failed tests in TestNG. In order to achieve this, we have to first understand the org.testng.IRetryAnalyzer interface.

To start with, please add the below dependencies to the Maven Project.

<dependencies>
  
      <dependency>
          <groupId>org.seleniumhq.selenium</groupId>
          <artifactId>selenium-java</artifactId>
          <version>3.141.59</version>
      </dependency>
      
      <dependency>
          <groupId>io.github.bonigarcia</groupId>
          <artifactId>webdrivermanager</artifactId>
          <version>5.1.0</version>
       </dependency>

      <dependency>
           <groupId>org.testng</groupId>
           <artifactId>testng</artifactId>
           <version>7.5</version>
           <scope>test</scope>
      </dependency>

  </dependencies>

IRetryAnalyzer – It is an interface to implement to be able to have a chance to retry a failed test. The definition of this interface is

public interface IRetryAnalyzer {

  /**
   * Returns true if the test method has to be retried, false otherwise.
   *
   * @param result The result of the test method that just ran.
   * @return true if the test method has to be retried, false otherwise.
   */
  boolean retry(ITestResult result);
}

This method implementation returns true if you want to re-execute your failed test and false if you don’t want to re-execute your test.

When you bind a retry analyzer to a test, TestNG automatically invokes the retry analyzer to determine if TestNG can retry a test case again in an attempt to see if the test that just fails now passes. Here is how you use a retry analyzer:

  1. Bind this implementation to the @Test annotation for e.g., @Test(retryAnalyzer = Retry.class)
  2. Build an implementation of the interface org.testng.IRetryAnalyzer

1. Add IRetryAnalyzer to the @Test Annotation

First of all, you need to create a class that implements the IRetryAnalyzer like the below example:

import org.testng.IRetryAnalyzer;
import org.testng.ITestResult;

public class Retry implements IRetryAnalyzer {
	
	int retryCount = 0;
	int maxRetryCount = 2;

	public boolean retry(ITestResult result) {
 
	if(!result.isSuccess()) {                         //Check if test is failed
		
		if(retryCount<maxRetryCount) {                //Check if the maximum number of test execution is reached
			System.out.println("Retrying Test : Re-running " + result.getName() +
 " for " + (retryCount+1) + " time(s)."); //Print the number of Retry attempts
			
			retryCount++;                             //Increase the maxRetryCount by 1
			
			result.setStatus(ITestResult.FAILURE);    //Mark test as failed
         return true;                                 //Rerun the failed test
		} else {
			result.setStatus(ITestResult.FAILURE);    //TestNG marks last run as failed, if last run is max retry
		} 
	  }else {
			result.setStatus(ITestResult.SUCCESS);    //TestNG parks test as passed when the test test passes
			
	  }
	
return false;
	}
}

This example shows that failed test case will run 3 times till it passes. In case it fails the third time, test execution will stop and TestNG will mark this case as failed. We can change the number of tries by changing the value of maxRetryCount.

Using retryAnalyzer attribute in the @Test annotation

The next step is to associate your test cases with IRetryAnalyzer. In order to do this, you need to use the method below.

@Test(retryAnalyzer = Retry.class)
public void verifyLoginPage() {
}

Let us see the complete implementation with the help of the below example.

import java.util.concurrent.TimeUnit;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.testng.Assert;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import io.github.bonigarcia.wdm.WebDriverManager;

public class RetryFailedTests {
	
	WebDriver driver;
	 
    @BeforeTest
    public void setUp() {
    	 
    	WebDriverManager.chromedriver().setup();
    	 
        ChromeOptions chromeOptions = new ChromeOptions();
  
        driver = new ChromeDriver(chromeOptions);
        driver.get("https://opensource-demo.orangehrmlive.com/");
 
        driver.manage().window().maximize();
        driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);
    }
 
    @Test(retryAnalyzer = Retry.class)
    public void verifyLoginPage() {
 
        String expectedTitle = driver.findElement(By.xpath("//*[@id='logInPanelHeading']")).getText();
 
        System.out.println("Title :" + expectedTitle);
        Assert.assertTrue(expectedTitle.equalsIgnoreCase("LOGIN Panel !!"));
    }
 
    @Test(retryAnalyzer = Retry.class)
    public void verifyHomePage() {
 
        System.out.println("Username Entered");
        driver.findElement(By.name("txtUsername")).sendKeys("Admin");
 
        System.out.println("Password Entered");
        driver.findElement(By.name("txtPassword")).sendKeys("admin123");
 
        driver.findElement(By.id("btnLogin")).submit();
 
        String newPageText = driver.findElement(By.id("welcome")).getText();
        System.out.println("newPageText :" + newPageText);
        Assert.assertTrue(newPageText.contains("Welcome"));
    }
 
    @AfterTest
    public void teardown() {
 
        driver.quit();
    }
 
}

In the above example, test – verifyLoginPage() will be retried a maximum of 3 times, if the test fails. To run the tests, Right-click on the class and select Run As ->TestNG Suite.

The output of the above program is

2. Implement Interface ITestAnnotationTransformer to retry failed tests

In this case, you would need to implement ITestAnnotationTransformer interface. The implementation of this interface is

public interface IAnnotationTransformer extends ITestNGListener {

  /**
   * This method will be invoked by TestNG to give you a chance to modify a TestNG annotation read
   * from your test classes. You can change the values you need by calling any of the setters on the
   * ITest interface.
   *
   * <p>Note that only one of the three parameters testClass, testConstructor and testMethod will be
   * non-null.
   *
   * @param annotation The annotation that was read from your test class.
   * @param testClass If the annotation was found on a class, this parameter represents this class
   *     (null otherwise).
   * @param testConstructor If the annotation was found on a constructor, this parameter represents
   *     this constructor (null otherwise).
   * @param testMethod If the annotation was found on a method, this parameter represents this
   *     method (null otherwise).
   */
  default void transform(
      ITestAnnotation annotation, Class testClass, Constructor testConstructor, Method testMethod) {
    // not implemented
  }

The transform method is called for every test during the test run. We can use this listener for our retry analyzer as shown below:

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import org.testng.IAnnotationTransformer;
import org.testng.annotations.ITestAnnotation;

public class RetryListener implements IAnnotationTransformer{

	public void transform(ITestAnnotation arg0, Class arg1, Constructor arg2,Method arg3) {
		
			arg0.setRetryAnalyzer(Retry.class);
		}

	}

Now let us create a class that contains all the tests.

import java.util.concurrent.TimeUnit;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.testng.Assert;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import io.github.bonigarcia.wdm.WebDriverManager;

public class RetryTests {
	
	WebDriver driver;
	 
    @BeforeTest
    public void setUp() {
    	 
    	 WebDriverManager.chromedriver().setup();
    	 
         ChromeOptions chromeOptions = new ChromeOptions();
  
         driver = new ChromeDriver(chromeOptions);
         driver.get("https://opensource-demo.orangehrmlive.com/");
 
         driver.manage().window().maximize();
         driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);
    }
 
    @Test(description = "This test validates title of login functionality")
    public void verifyLoginPage() {
 
        String expectedTitle = driver.findElement(By.xpath("//*[@id='logInPanelHeading']")).getText();
 
        System.out.println("Title :" + expectedTitle);
        Assert.assertTrue(expectedTitle.equalsIgnoreCase("LOGIN Panel !!"));
    }
 
    @Test(description = "This test validates  successful login to Home page")
    public void verifyHomePage() {
 
        System.out.println("Username Entered");
        driver.findElement(By.name("txtUsername")).sendKeys("Admin");
 
        System.out.println("Password Entered");
        driver.findElement(By.name("txtPassword")).sendKeys("admin123");
 
        driver.findElement(By.id("btnLogin")).submit();
 
        String newPageText = driver.findElement(By.id("welcome")).getText();
        System.out.println("newPageText :" + newPageText);
        Assert.assertTrue(newPageText.contains("Welcome"));
    }
 
    @AfterTest
    public void teardown() {
 
        driver.quit();
    }
 
}

Once we have the implementation of IAnnotationTransformer, we just need to add it as a listener in the testng.xml. Like this:

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

<listeners>
<listener class-name="com.example.retrydemo.RetryListener"></listener>

</listeners>

  <test name="Test">
    <classes>
      <class name="com.example.retrydemo.RetryTests"/>
    </classes>
  </test> <!-- Test -->
</suite> <!-- Suite -->

Now let us run the tests. Right-click on testng.xml and select Run As -> TestNG Suite.

The output of the above program is

This is pretty much it on this topic. Congratulations on making it through this tutorial and hope you found it useful! Happy Learning!! Cheers!!

Integration Testing of Springboot with Cucumber and TestNG

HOME

In this tutorial, I am going to build an automation framework to test the Springboot application with Cucumber, Rest Assured, and TestNG.

What is Springboot?

Spring Boot is an open-source micro-framework maintained by a company called Pivotal. It provides Java developers with a platform to get started with an auto-configurable production-grade Spring application. With it, developers can get started quickly without losing time on preparing and configuring their Spring application.

What is Cucumber?

Cucumber is a software tool that supports behavior-driven development (BDD). Cucumber can be defined as a testing framework, driven by plain English. It serves as documentation, automated tests, and development aid – all in one.

This framework consists of:

  1. Springboot – 2.5.2
  2. Cucumber – 7.3.4
  3. Java 11
  4. TestNG – 7.3.4
  5. Maven – 3.8.1
  6. RestAssured – 5.1.1

Steps to setup Cucumber Test Automation Framework for API Testing using Rest-Assured

  1. Add SpringbootTest, Rest-AssuredJUnit, and Cucumber dependencies to the project
  2. Create a source folder src/test/resources and create a feature file under src/test/resources
  3. Create the Step Definition class or Glue Code for the Test Scenario under the src/test/java directory
  4. Create a Cucumber Runner class under the src/test/java directory
  5. Run the tests from Cucumber Test Runner
  6. Run the tests from Command Line
  7. Run the tests from TestNG
  8. Generation of TestNG Reports
  9. Cucumber Report Generation

Below is the structure of a SpringBoot application project

We need the below files to create a SpringBoot Application.

SpringBootRestServiceApplication.java

The Spring Boot Application class is generated with Spring Initializer. This class acts as the launching point for the application.

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringBootRestServiceApplication {

    public static void main(String[] args) {

        SpringApplication.run(SpringBootRestServiceApplication.class, args);
    }
}

Student.java

This is JPA Entity for Student class

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

@Entity
public class Student {
    @Id
    @GeneratedValue
    private Long id;

    @NotNull
    @Size(min = 4, message = "Name should have atleast 4 characters")
    private String name;

    @NotBlank(message = "passportNumber is mandatory")
    private String passportNumber;

    public Student() {
        super();
    }

    public Student(Long id, String name, String passportNumber) {
        super();
        this.id = id;
        this.name = name;
        this.passportNumber = passportNumber;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassportNumber() {
        return passportNumber;
    }

    public void setPassportNumber(String passportNumber) {
        this.passportNumber = passportNumber;
    }
}

StudentRepository.java 

This is JPA Repository for Student. This is created using Spring Data JpaRepository.

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface StudentRepository extends JpaRepository<Student, Long>{

}

StudentController.java

Spring Rest Controller exposes all services on the student resource.

import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;
import java.net.URI;
import java.util.List;
import java.util.Optional;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.hateoas.EntityModel;
import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

@RestController
public class StudentController {

    @Autowired
    private StudentRepository studentRepository;

    @GetMapping("/students")
    public List<Student> retrieveAllStudents() {
        return studentRepository.findAll();
    }

    @GetMapping("/students/{id}")
    public EntityModel<Student> retrieveStudent(@PathVariable long id) {
        Optional<Student> student = studentRepository.findById(id);

        if (!student.isPresent())
            throw new StudentNotFoundException("id-" + id);

        EntityModel<Student> resource = EntityModel.of(student.get());

        WebMvcLinkBuilder linkTo = linkTo(methodOn(this.getClass()).retrieveAllStudents());

        resource.add(linkTo.withRel("all-students"));

        return resource;
    }

    @PostMapping("/students")
    public ResponseEntity<Object> createStudent(@Valid @RequestBody Student student) {
        Student savedStudent = studentRepository.save(student);

        URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}")
                .buildAndExpand(savedStudent.getId()).toUri();

        return ResponseEntity.created(location).build();

    }
}

application.properties

Spring Boot automatically loads the application.properties whenever it starts up. You can de-reference values from the property file in the java code through the environment.

spring.jpa.defer-datasource-initialization=true

data.sql 

Data is loaded from data.sql into the Student table. Spring Boot would execute this script after the tables are created from the entities.

insert into student values(10001,'Annie', 'E1234567');
insert into student values(20001,'John', 'A1234568');
insert into student values(30001,'David','C1232268');
insert into student values(40001,'Amy','D213458');

Test Automation Framework Implementation

Step 1 – Add SpringbootTest, Cucumber, Rest-Assured, and TestNG dependencies to the project (Maven project)

 <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <rest-assured.version>5.1.1</rest-assured.version>
        <cucumber.version>7.3.4</cucumber.version>
    </properties>

<dependencies>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>io.rest-assured</groupId>
            <artifactId>rest-assured</artifactId>
            <version>${rest-assured.version}</version>
            <scope>test</scope>
        </dependency>

        <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>

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

</dependencies>

Step 2 – Create a source folder src/test/resources and create a feature file under src/test/resources

By default, the Maven project has an src/test/java directory only. Create a new Source Folder under src/test with the name of resources. Create a folder name as Features within the src/test/resources directory.

Create a feature file to test the Springboot application. Below is a sample feature file.

Feature: Verify springboot application using Cucumber and TestNG

  @ReceiveUserDetails
  Scenario Outline: Send a valid Request to get user details
    Given I send a request to the URL "/students" to get user details
    Then The response will return status 200 
    And The response contains id <studentID> and names "<studentNames>" and passport_no "<studentPassportNo>"

    Examples:
      |studentID    |studentNames  |studentPassportNo|
      |10001        |Annie         |E1234567         |
      |20001        |John          |A1234568         |
      |30001        |David         |C1232268         |
      |40001        |Amy           |D213458          |
      
   
  @CreateUser
  Scenario: Send a valid Request to create a user 
    Given I send a request to the URL "/students" to create a user with name "Annie" and passportNo "E1234567"
    Then The response will return status 201
    And Resend the request to the URL "/students" and the response returned contains name "Annie" and passport_no "E1234567"

Step 3 – Create the Step Definition class or Glue Code for the Test Scenario under src/test/java

The corresponding step definition file of the above feature file is shown below.

import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasItem;
import org.json.JSONObject;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.server.LocalServerPort;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import io.cucumber.spring.CucumberContextConfiguration;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import io.restassured.response.ValidatableResponse;
import io.restassured.specification.RequestSpecification;

@CucumberContextConfiguration
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class SpringbootDefinitions {

	private final static String BASE_URI = "http://localhost";

	@LocalServerPort
	private int port;

	private ValidatableResponse validatableResponse, validatableResponse1;

	private void configureRestAssured() {
		RestAssured.baseURI = BASE_URI;
		RestAssured.port = port;
	}

	protected RequestSpecification requestSpecification() {
		configureRestAssured();
		return given();
	}

	@Given("I send a request to the URL {string} to get user details")
	public void getStudentDetails(String endpoint) throws Throwable {
		validatableResponse = requestSpecification().contentType(ContentType.JSON).when().get(endpoint).then();
		System.out.println("RESPONSE :" + validatableResponse.extract().asString());
	}

	@Given("I send a request to the URL {string} to create a user with name {string} and passportNo {string}")
	public void createStudent(String endpoint, String studentName, String studentPassportNumber) throws Throwable {

		JSONObject student = new JSONObject();
		student.put("name", studentName);
		student.put("passportNumber", studentPassportNumber);

		validatableResponse = requestSpecification().contentType(ContentType.JSON).body(student.toString()).when()
				.post(endpoint).then();
		System.out.println("RESPONSE :" + validatableResponse.extract().asString());
	}

	@Then("The response will return status {int}")
	public void verifyStatusCodeResponse(int status) {
		validatableResponse.assertThat().statusCode(equalTo(status));

	}

	@Then("The response contains id {int} and names {string} and passport_no {string}")
	public void verifyResponse(int id, String studentName, String passportNo) {
		validatableResponse.assertThat().body("id", hasItem(id)).body(containsString(studentName))
				.body(containsString(passportNo));

	}

	@Then("Resend the request to the URL {string} and the response returned contains name {string} and passport_no {string}")
	public void verifyNewStudent(String endpoint, String studentName, String passportNo) {

		validatableResponse1 = requestSpecification().contentType(ContentType.JSON).when().get(endpoint).then();
		System.out.println("RESPONSE :" + validatableResponse1.extract().asString());
		validatableResponse1.assertThat().body(containsString(studentName)).body(containsString(passportNo));

	}
}

To make Cucumber aware of your test configuration you can annotate a configuration class on your glue path with @CucumberContextConfiguration and with one of the following annotations: @ContextConfiguration, @ContextHierarchy, or @BootstrapWith.It is imported from:

import io.cucumber.spring.CucumberContextConfiguration;

As we are using SpringBoot, we are annotating the configuration class with @SpringBootTest. It is imported from:

import org.springframework.boot.test.context.SpringBootTest;

By default, @SpringBootTest does not start the webEnvironment to refine further how your tests run. It has several options: MOCK(default), RANDOM_PORT, DEFINED_PORT, NONE.

RANDOM_PORT loads a WebServerApplicationContext and provides a real web environment. The embedded server is started and listens on a random port. LocalServerPort is imported from the package:

import org.springframework.boot.web.server.LocalServerPort;

Step 4 – Create a Cucumber TestNG Runner class under src/test/java

A runner will help us to run the feature file and acts as an interlink between the feature file and StepDefinition Class. The TestRunner should be created within the directory src/test/java.

import io.cucumber.testng.AbstractTestNGCucumberTests;
import io.cucumber.testng.CucumberOptions;

@CucumberOptions(features = {"src/test/resources/Features"}, glue = {"com.example.demo.definitions"})
public class CucumberRunnerTests extends AbstractTestNGCucumberTests {

}

The @CucumberOptions annotation is responsible for pointing to the right feature package, configuring the plugin for a better reporting of tests in the console output, and specifying the package where extra glue classes may be found. We use it to load configuration and classes that are shared between tests.

Step 5 – Run the tests from Cucumber Test Runner

You can execute the test script by right-clicking on TestRunner class -> Run As TestNG in Eclipse.

In case you are using IntelliJ, select Run CucumberRunnerTests.

SpringBootTest creates an application context containing all the objects we need for the Integration Testing It, starts the embedded server, creates a web environment, and then enables methods to do Integration testing.

Step 6 – Run the tests from Command Line

Use the below command to run the tests through the command line.

mvn clean test

Step 7 – Run the tests from TestNG

Create a testng.xml in the project as shown below:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name = "Suite1">
  <test name = "SpringBoot Cucumber TestNG Demo">
    <classes>
          <class name = "com.example.demo.runner.CucumberRunnerTests"/>
     </classes>  
   </test>
</suite>

Step 8 – Generation of TestNG Reports

TestNG generates various types of reports under the test-output folder like emailable-report.html, index.html, testng-results.xml.

We are interested in the “emailable-report.html” report. Open “emailable-report.html”, as this is an HTML report, and open it with the browser. The below image shows emailable-report.html.

TestNG also produce “index.html” report, and it resides under test-output folder. The below image shows index.html report.

Step 9 – Cucumber Report Generation

Add cucumber.properties under src/test/resources and add the below instruction in the file.

cucumber.publish.enabled=true

The link to the Cucumber Report is present in the execution status.

Below is the image of the Cucumber Report generated using the Cucumber Service.

Complete Source Code:
Refer to GitHub for the source code.

Congratulations!! We are able to build a test framework to test the SpringBoot application using Cucumber, Rest Assured, and TestNG.

Step Definition in Cucumber

HOME

The previous tutorial explained the Feature File in Cucumber. This tutorial explains the step definition of the Cucumber.

To start with, please add the below dependencies to the POM.xml, in the case of the Maven project.

<dependencies>
  
   <dependency>
      <groupId>io.cucumber</groupId>
      <artifactId>cucumber-java</artifactId>
      <version>7.18.1</version>
   </dependency>

   <dependency>
      <groupId>io.cucumber</groupId>
      <artifactId>cucumber-junit</artifactId>
      <version>7.18.1</version>
      <scope>test</scope>
    </dependency>
       
   <dependency>
       <groupId>junit</groupId>
       <artifactId>junit</artifactId>
       <version>4.13.2</version>
       <scope>test</scope>
   </dependency>
    
</dependencies>

For the Gradle project, add the below dependencies to build.gradle

implementation 'io.cucumber:cucumber-java:718.1'
testImplementation 'io.cucumber:cucumber-junit:7.18.1'
testImplementation 'junit:junit:4.13.2'

What is a Step Definition?

A Step Definition is a Java method with an expression that links it to one or more Gherkin steps. When Cucumber executes a Gherkin step in a scenario, it will look for a matching step definition to execute.

Cucumber finds the Step Definition file with the help of the Glue code in Cucumber Options.

By storing state in instance variables, a step definition can transfer state to a subsequent step definition.

Step definitions are not associated with a specific feature file or scenario. The name of a step definition’s file, class, or package has no bearing on which Gherkin steps it will match. The formulation of the step definition is the only thing that matters, which means the step definition should only match Gherkin’s steps.

Imagine, we want to test a web application. One of the first steps is Login to the website and then check the various functionalities on the website. We can create a Gherkin step like “I login to the website” and the corresponding step definition of this Gherkin Step. This Gherkin step can be used in multiple feature files, and we don’t need to create the step definition of this Gherkin step for each feature file.

In the previous tutorial, we have seen that when the Feature file is executed without the Step Definition file, the runner shows the missing steps with the snippet in the console.

When a Cucumber encounters a Gherkin step without a matching step definition, it will print a step definition snippet with a matching Cucumber Expression. You can use this as a starting point for new step definitions.

It is very easy to implement all the steps, all you need to do is copy the complete text marked in the above box and paste it into the MyHolidayDefinitions class.

@Given, @When, and @Then are imported from packages:-

import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;

Feature File

Feature: Book flight ticket 

@BookOneWayFlight
Scenario: Book Flight for one way trip

Given I live in Dublin with 2 adults and 2 kids
And I want to book one way flight ticket from Dublin to London on 22 Jan 2020
When I search online
Then TripAdvisor should provide me options of flights on 22 Jan 2020
And Cost of my flight should not be more than 50 Euro per person
And Tickets should be refundable

Let me create the step definition for the above Feature file

import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;

public class MyHolidayDefinitions {

	@Given("I live in Dublin with {int} adults and {int} kids")
	public void liveInDublin(Integer int1, Integer int2) {

		System.out.println("I live in Dublin with 2 adults and 2 kids");
	}

	@Given("I want to book one way flight ticket from Dublin to London on {int} Jan {int}")
	public void bookFlightTicket(Integer int1, Integer int2) {

		System.out.println("I want to book one way flight ticket from Dublin to London on 22 Jan 2020");
	}

	@When("I search online")
	public void searchOnline() {

		System.out.println("I search online");
	}

	@Then("TripAdvisor should provide me options of flights on {int} Jan {int}")
	public void tripAdvisor(Integer int1, Integer int2) {

		System.out.println("TripAdvisor should provide me options of flights on 22 Jan 2020");
	}

	@Then("Cost of my flight should not be more than {int} Euro per person")
	public void costOfFlightLimit(Integer int1) {

		System.out.println("Cost of my flight should not be more than 50 Euro per person");
	}

	@Then("Tickets should be refundable")
	public void refundableTickets() {

		System.out.println("Tickets should be refundable");
	}

}

To run the scenarios present in the Feature File, we need TestRunner class. To learn more about the TestRunner class, please refer to this tutorial – Cucumber Tutorial – JUnit Test Runner Class

import org.junit.runner.RunWith;
import io.cucumber.junit.Cucumber;
import io.cucumber.junit.CucumberOptions;

@RunWith(Cucumber.class)
@CucumberOptions(features = "src/test/resources/Features/MyHoliday.feature", 
tags = "@BookOneWayFlight", glue = "com.cucumber.MyCucumberProject.definitions")

public class CucumberRunnerTest {

}

The output of the above program is

Congratulations. We have created the setup definition for the feature file successfully and are able to run it.

Refer to the next tutorials to know the integration of Cucumber with Selenium – Integration of Cucumber with Selenium and JUnit and JUnit4 and Integration of Cucumber with Selenium and TestNG

Happy Learning!!