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

Advertisement

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

How to create Gradle project with Selenium and TestNG

HOME

The previous tutorial explained How to create Java Gradle project in Eclipse. In this tutorial, I will explain how we can set up a Gradle project with Selenium and TestNG.

This framework consists of:

  • Java 8 or above
  • TestNG – 7.6.1
  • Gradle – 7.5.1 (Build Tool)
  • Selenium – 4.3.0

Steps to set up Gradle Java Project for Selenium and TestNG

  • Download and Install Java on the system
  • Download and setup Eclipse IDE on the system
  • Setup Gradle on System
  • Create a new Gradle Project
  • Add Selenium and TestNG dependencies to the Gradle project
  • Add Gradle Test Task to build.gradle
  • Create Test Code under src/test/java
  • Create testng.xml
  • Run the tests from TestNG
  • Run the tests from Command Line
  • TestNG and Gradle Report generation

Implementation Steps

Step 1- Download and Install Java

Selenium needs 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 Gradle

To build a test framework, we need to add several dependencies to the project. This can be achieved by any build tool. I have used Gradle Build Tool. Click here to know How to install Gradle.

Step 4 – Create a new Gradle Project

Below are the steps to create the Gradle project from 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 5 – Add Selenium and TestNG dependencies to the Gradle project

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

    // 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'
}

Step 6 – Add Gradle Test Task to build.gradle 

tasks.named('test') {
    // Use TestNG for unit tests.
    useTestNG() {
    useDefaultListeners = true
	outputDirectory = file("$projectDir/TestNG_Reports")
   }
 reports.html.setDestination(file("$projectDir/GradleReports"))
}

The complete gradle.build looks like something shown below.

*
 * 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()
}

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

    // 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'
}

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

tasks.named('test') {
    // Use TestNG for unit tests.
    useTestNG() {
    useDefaultListeners = true
	outputDirectory = file("$projectDir/TestNG_Reports")
   }
 reports.html.setDestination(file("$projectDir/GradleReports"))
}

Step 7 – Create Test Code under src/test/java

Let us write the code to test a web application. I have created 3 tests and out of 3, 1 test will fail intentionally.

import java.time.Duration;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import io.github.bonigarcia.wdm.WebDriverManager;

public class LoginTests {
	
	WebDriver driver;
	 
    @BeforeMethod
    public void setUp() {
    	
    	WebDriverManager.chromedriver().setup();
    	driver = new ChromeDriver();     
        driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
        driver.manage().window().maximize();
        driver.get("https://opensource-demo.orangehrmlive.com/");
 
    }
 
    @Test(description = "This test validates error message when credentials are incorrect", priority = 0)
    public void verifyIncorrectCredentials() {
 
    	driver.findElement(By.name("username")).sendKeys("Admin");
    	driver.findElement(By.name("password")).sendKeys("admin123$$");   	 
        driver.findElement(By.xpath("//*[@id='app']/div[1]/div/div[1]/div/div[2]/div[2]/form/div[3]/button")).submit();
        
        String actualErrorMessage = driver.findElement(By.xpath("//*[@id='app']/div[1]/div/div[1]/div/div[2]/div[2]/div/div[1]/div[1]/p")).getText();
        
    	// Verify Error Message
    	Assert.assertEquals(actualErrorMessage,"Invalid credentials");
    
    }
       
    @Test(description = "This test will fail", priority = 1)
    public void verifyBlankCredentials() {
 
    	driver.findElement(By.name("username")).sendKeys("");
    	driver.findElement(By.name("password")).sendKeys("admin123$$");   	 
        driver.findElement(By.xpath("//*[@id='app']/div[1]/div/div[1]/div/div[2]/div[2]/form/div[3]/button")).submit();
        
        String actualErrorMessage = driver.findElement(By.xpath("//*[@id='app']/div[1]/div/div[1]/div/div[2]/div[2]/div/div[1]/div[1]/p")).getText();
        
    	// Verify Error Message
    	Assert.assertEquals(actualErrorMessage,"Invalid credentials");
    
    }
       
    @Test(description = "This test validates  successful login to Home page", priority = 2)
    public void verifyLoginPage() {
    	
    	driver.findElement(By.name("username")).sendKeys("Admin");
    	driver.findElement(By.name("password")).sendKeys("admin123");   	 
        driver.findElement(By.xpath("//*[@id='app']/div[1]/div/div[1]/div/div[2]/div[2]/form/div[3]/button")).submit();
 
        String homePageHeading = driver.findElement(By.xpath("//*[@id='app']/div[1]/div[2]/div[2]/div/div[1]/div[1]/div[1]/h5")).getText();
                                                              
   	     //Verify new page - HomePage
        Assert.assertEquals(homePageHeading,"Employee Information");
    	
    }
    
    @AfterMethod
    public void teardown() {
 
        driver.quit();
    }
 
}

Step 8 – Create testng.xml

Right-click on the project and select TestNG and select 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="Selenium Tests with TestNG">
    <classes>
      <class name="com.example.LoginTests"/>
    </classes>
  </test> <!-- Test -->
</suite> <!-- Suite -->

Step 9 – Run the tests from TestNG

Right-Click on the testng.xml and select Run As TestNG Suite.

The output of the above tests in Eclipse Console is as shown below.

This also generates a folder with the name test-output that contains the TestNG reports like index.html, emailable-report.html.

Step 10 – Run the tests from Command Line

To run the tests from the command line, use the below-mentioned command.

gradle clean test

The output of the above program is

Step 11 – TestNG and Gradle Report generation

Once the test execution is finished, refresh the project. We will see 2 folders – GradleReports and TestNG_ Reports.

Gradle Reports

This folder contains index.html.

Right-click on index.html and select open with Web Browser. This report shows the summary of all the tests executed. As you can see that Failed tests are selected (highlighted in blue), so the name of the test failed along with the class name is displayed here.

TestNG Reports

Go to TestNG_Reports folder and right-click and open emailable-report.html.

Index.html

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

How to rerun failed tests in Cucumber

HOME

The previous tutorial explained the Integration of Cucumber with Selenium and TestNG. Sometimes, inconsistent test results are common as a result of an unstable environment such as network issue or Database down and soon. A few tests may fail for no apparent reason and then rerun successfully. We are sometimes required to run only failed test cases after bug fixes in order to validate fixes quickly. We will learn how to rerun failed test cases in the Cucumber with TestNG project in this post.

Cucumber provides a rerun plugin option in the Runner class that generates a file which contains the information about the failed tests.

The Cucumber Framework with Selenium and TestNG can be found here. Use this tutorial to setup the project.

Now, let us add rerun plugin to the Cucumber Runner class. Here, we are creating a failedrerun.txt file that contains the information about the failed test. This file will be created under target folder.

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

Create Second Runner Class

The next step is to run failed test scenarios present in the text file. We need to create a class same as our runner class which will contain the location of the file which we want to execute to rerun our failed scenarios. In the ‘features’ variable, you need mention the failedrerun.txt file and don’t forget that you must mention ‘@’ symbol before the file path.

import io.cucumber.testng.AbstractTestNGCucumberTests;
import io.cucumber.testng.CucumberOptions;
   
@CucumberOptions(tags = "", features = {"@target/failedrerun.txt"}, glue = {"com.example.definitions"},
                 plugin = {})
   
public class FailedRunnerTests extends AbstractTestNGCucumberTests {
   
}

Run the tests using the below-mentioned command

mvn clean test

After running the tests from the command line, first, all the tests will be executed. If any test fails, a failedrerun.txt file will be generated that contains the details about the failed tests.

In the below screenshot, we can see that a scenario starting at line 7 is failed.

Once the first round of execution ends, Cucumber Runner goes to the second runner and runs the failed tests that are mentioned in failedrerun.txt.

We can see that 2 separate reports are generated here.

The first Cucumber Report shows that out of 5 tests, 1 test is failed.

The second Cucumber Report shows that the one failed test is rerun again, and it again failed.

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

ExtentReports Version 5 for Cucumber 7 and TestNG

HOME

The previous tutorial explained the steps to generate ExtentReports Version for Cucumber6 with TestNG. This tutorial explains the steps needed to be followed to generate an ExtentReports Version5 for Cucumber 7.

New Features in ExtentReports Version 5

Report Attachments 

To add attachments, like screen images, two settings need to be added to the extent.properties. Firstly property, named screenshot.dir, is the directory where the attachments are stored. Secondly is screenshot.rel.path, which is the relative path from the report file to the screenshot directory.

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

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

Extent PDF Reporter

The PDF reporter summarizes the test run results in a dashboard and other sections with feature, scenario, and, step details. The PDF report needs to be enabled in the extent.properties file.

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

Ported HTML Reporter

The original HTML Extent Reporter was deprecated in 4.1.3 and removed in 5.0.0. The HTML report available in the adapter is based on the same code base and is similar in appearance. The major changes are in the Freemarker template code which has been modified to work with the Extent Reports version 5. The HTML report needs to be enabled in the extent.properties file.

#HTML Report
extent.reporter.html.start=true
extent.reporter.html.out=HtmlReport/ExtentHtml.html

Customized Report Folder Name

To enable the report folder name with date and\or time details, two settings need to be added to the extent.properties. These are basefolder.name and basefolder.datetimepattern. These will be merged to create the base folder name, inside which the reports will be generated.

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

Attach Image as Base64 String

This feature can be used to attach images to the Spark report by setting the src attribute of the img tag to a Base64 encoded string of the image. When this feature is used, no physical file is created. There is no need to modify any step definition code to use this. To enable this, use the below settings in extent.properties, which is false by default.

extent.reporter.spark.base64imagesrc=true

Environment or System Info Properties

 It is now possible to add environment or system info properties in the extent.properties or pass them in the maven command line. 

#System Info
systeminfo.os=windows
systeminfo.version=10

Generation of ExtentReport 5 in Cucumber7 with TestNG

Pre-Requisite:

  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-cucumber7-adapter</artifactId>
    <version>1.7.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>7.6.0</cucumber.version>
		<extentreports.cucumber7.adapter.version>1.7.0</extentreports.cucumber7.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.6.1</testng.version>
		<apache.common.version>2.4</apache.common.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-cucumber7-adapter</artifactId>
			<version>${extentreports.cucumber7.adapter.version}</version>
		</dependency>

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

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

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

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

	</dependencies>

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

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 in the src/test/resources folder for the grasshopper extent report adapter to recognize it. Using a property file for reporting is quite helpful if you want to define several different properties.

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

#HTML Report
extent.reporter.html.start=true
extent.reporter.html.out=HtmlReport/ExtentHtml.html

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

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

#Base64
extent.reporter.spark.base64imagesrc=true

#System Info
systeminfo.os=windows
systeminfo.version=10

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. 

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 = "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 = "//*[@href='https://www.linkedin.com/company/orangehrm/mycompany/']")
    public  WebElement linkedInIcon;
    
    @FindBy(xpath = "//*[@href='https://www.facebook.com/OrangeHRM/mycompany']") //Invalid Xpath
    public  WebElement faceBookIcon;
      
}

HomePageLocators

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

public class HomePageLocators {

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

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

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

LoginPageActions

In this class, the very first thing will do is to create the object of 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 error message 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("Employee Information"));
 
    }
    
    @Then("User should be able to see error message {string}")
    public void verifyErrorMessage(String expectedErrorMessage) {
 
        // Verify home page
        Assert.assertEquals(objLogin.getErrorMessage(),expectedErrorMessage);
 
    }
    
    @Then("User should be able to see 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.

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 Hooks {
		
	@Before
    public static void setUp() {
       HelperClass.setUpDriver();
    }

	@After
	public static void tearDown(Scenario scenario) {

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

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

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

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

The updated Cucumber Runner class looks like as shown below:

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 Cucumber7">
  
  <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 the Console. As expected, 4 tests, out of 5 are passed and 1 failed.

Step 12: View ExtentReport

Refresh the project and will see a new folder – SparkReport_ which further contains 4 folders – HtmlReport, PdfReport, Reports, and Screenshots.

The ExtentReport will be present in the Reports folder with the name Spark.html. PDF Report is present in PdfReport folder and HTML Report is present in HtmlReport folder. We can see that the Screenshots folder is empty because we have used base64imagesrc feature that results in no physical screenshots. The screenshots are embedded in the reports.

Right-click and open the ExtentHtml.html report 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.

ExtentHtml.html

The failed test has a screenshot embedded in it. Double-click on mase64image and it will open the screenshot in full screen.

Screenshot of failed Test Case

PDF Report

Spark Report

Right-click and open the Spark.html report with Web Browser.

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.

How to add Screenshot to Cucumber ExtentReports

HOME

The previous tutorial explained the generation of Extent Report 5 for Cucumber and TestNG. This tutorial explains how to add screenshots to the Extent Report.

Project Structure

To set up the above project, please refer to this tutorial – ExtentReports Version 5 for Cucumber 6 and TestNG.

We want to add screenshots of failed tests to the Extent Report Version 5.

Step 1 – Add Screenshot configuration in extent.properties

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/

In the above example, we have provided the name “ExtentReports/SparkReport_”. It means that a folder starts with the name “SparkReport_” under the “ExtentReports” folder. The date-time pattern we have provided in another format is the basis of a valid pattern. It will concatenate with the folder name to generate a unique folder for each execution.

As seen in the image above, the “Reports” and “Screenshots” folders get created inside the new folder of SparkReports_. If we look inside the folder, we can see that the report generates.

We can browse the screenshot folder to see all the screenshots taken during each step. Additionally, screenshots will be generated and named automatically.

Step 2 – Add a method to capture the screenshot

@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()); 
		}	

In the preceding example, the tearDown() method accepts a Scenario type object. The Scenario can be found within the io.cucumber. We used Selenium’s standard screenshot feature within the method. As an example, we’d like to read the file as a byte[] type. As a parameter, the attach method accepts byte[] type objects. Scenario.attach also includes a screenshot with each step of the scenario.

The updated Hooks class will be as shown below:

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 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();
	}
}

Let’s open the report and view the report. As you can see, besides the scenario, an attachment sign is available, which means something attaches to the scenario. As we have only one failed step, only one screenshot has been captured, as seen in the above image. 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.

Congratulations!! We are able to attach screenshots of the failed tests. 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.

Generation of ExtentReport 5 in Cucumber6 with TestNG

Pre-Requisite:

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

InvocationCount in TestNG

HOME

InnvocationCount is one of the feature available in TestNG. InvocationCount is used when we want to run the same test multiple times.  If we want to run single @Test 10 times at a single thread, then invocationCount can be used. To invoke a method multiple times, below syntax is used.

@Test(invocationCount = 3)

In this example, the @Test method will execute for 3 times each on a single thread.

In this tutorial, we will illustrate how to get the current invocation count.

Step 1 − Create a TestNG class, TestInvocationCount.

Step 2 − Write two @Test methods in the class TestInvocationCountas shown in the programming code section below. Add invocationCount=3 to method verifyLinkedIn and 2 to validLoginTest.

Step 3 − Create the testNG.xml as given below to run the TestNG classes.

Step 4 − Now, run the testNG.xml or directly testNG class in IDE or compile and run it using command line.

Step 5 − In the output, the user can see a total of 1 thread running sequentially for all invocations of @Test.

import static org.testng.Assert.assertTrue;
import java.util.concurrent.TimeUnit;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import io.github.bonigarcia.wdm.WebDriverManager;

public class TestInvocationCount {
	
	WebDriver driver;
	
	@BeforeMethod
    public void setup() throws Exception {
        
       driver = WebDriverManager.firefoxdriver().create();

       driver.get("https://opensource-demo.orangehrmlive.com/");
       driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
       driver.manage().window().maximize();
    }
	
	@Test(invocationCount = 3)
    public void verifyLinkedIn() {

		System.out.println("Test Case 1 with Thread Id - "+Thread.currentThread().getId());
    	
        driver.manage().window().maximize();
        Boolean linkedInIcon = driver.findElement(By.xpath("//*[@id='social-icons']/a[1]/img")).isEnabled();
        assertTrue(linkedInIcon);
    }

    
    @Test(invocationCount = 2)
    public void validLoginTest() throws InterruptedException {
    	
    	System.out.println("Test Case 2 with Thread Id - "+Thread.currentThread().getId());

        driver.findElement(By.name("txtUsername")).sendKeys("Admin");
        driver.findElement(By.name("txtPassword")).sendKeys("admin123");
        driver.findElement(By.id("btnLogin")).click();
        String expectedTitle = driver.findElement(By.xpath("//*[@id='content']/div/div[1]/h1")).getText();
        Assert.assertTrue(expectedTitle.contains("Dashboard"));
    }

	    @AfterMethod
	    public  void closeBrowser() {
	    	
	    	driver.quit();
	    			  
	    }
}

testng.xml

This is a configuration file that is used to organize and run the TestNG test cases. It is very handy when limited tests are needed to execute rather than the full suite.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="Suite">
  <test name="Invocation Test">
    <classes>
      <class name="com.example.listeners.TestInvocationCount"/>
    </classes>
  </test> <!-- Test -->
</suite> <!-- Suite -->

The output of the above program is

We can add threadPoolSize to the @Test.

threadPoolSize – It defines the size of the thread pool for any method. The method will be invoked from multiple threads as specified by invocationCount.

@Test(invocationCount = 3, threadPoolSize)

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

Selenium 4 Grid – Parallel Testing

HOME

The previous tutorial explained running the tests in Selenium4 Grid using the Standalone option. One of the major advantages of Selenium Grid is the ability to run tests parallelly on multiple browsers simultaneously. Parallel testing helps to reduce execution time and efforts and results in a faster time to delivery.

In this tutorial, we will run the same set of tests on Chrome, Firefox, and Edge browsers sequentially initially to confirm that we can perform Cross Browser testing also

It is very important to construct our tests thread-safe in order to run them in parallel without a problem

This example uses Selenium 4 with TestNG.

Implementation Steps

1. Download Selenium Grid 4

The latest version of Selenium 4 is 4.3.0 and the same can be downloaded from the official website of Selenium

2. Download various Browser driver exe

It is recommended to download the exe of various browsers in the same location where the Selenium WebDriver jar file is present. This is because Selenium 4 Alpha has the ability to automatically detect the WebDrivers present on the node machine. For this example, I have downloaded Chrome, Firefox, and edge drivers.

3. Start Selenium Server Jar

Open a command line terminal. Go to the location where these files are present

cd C:/Users/Vibha/Software/SeleniumGrid

Use the below command to run selenium-server standalone jar files.

java -jar selenium-server-4.3.0.jar standalone --port 4445

It’s optional to mention the port number at the end of the command. By default, it will use port 4444. It is good practice to mention the port at the end to avoid any conflict between the ports.

4. Open Selenium Console

The server is listening on http://172.30.96.1:4445/ui/index.html  which is the same as shown in the above image. This is a dynamic address, so make sure to get the address from the logs when Selenium Grid is started.

The Grid automatically identifies that the WebDrivers for Chrome and Firefox are present on the system.

5. Add dependencies to the project

In this example, we are using a Maven project, so are adding the dependencies to the POM.xml.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>Selenium4Parallel</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

    <dependencies>

        <!-- Selenium 4 Dependency -->
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-java</artifactId>
            <version>4.1.2</version>
        </dependency>

        <!-- Selenium WebDriver Manager -->
        <dependency>
            <groupId>io.github.bonigarcia</groupId>
            <artifactId>webdrivermanager</artifactId>
            <version>5.1.0</version>
        </dependency>

        <!-- TestNG Dependency -->
        <dependency>
            <groupId>org.testng</groupId>
            <artifactId>testng</artifactId>
            <version>7.4.0</version>
            <scope>test</scope>
        </dependency>

    </dependencies>

</project>

6. Create a Test Code

Creating an instance of the Remote WebDriver and passing the selenium endpoint and chrome options defined in it.

To run a Remote WebDriver client, we first need to connect to the RemoteWebDriver. We do this by pointing the URL to the address of the server running our tests. In order to customize our configuration, we set desired capabilities.

Below is an example of instantiating a remote WebDriver object pointing to our remote web server running our tests on Chrome, Firefox, and Edge. We have used @Parameters Annotation to pass the browser names to the tests.

import org.openqa.selenium.By;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.edge.EdgeOptions;
import org.openqa.selenium.firefox.FirefoxOptions;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Parameters;
import java.net.MalformedURLException;
import java.net.URL;
import java.time.Duration;

public class BaseTest {


    protected static ThreadLocal<RemoteWebDriver> driver = new ThreadLocal<RemoteWebDriver>();
    public static String remote_url = "http://localhost:4445/";
    public Capabilities capabilities;

    @Parameters({"browser"})
    @BeforeMethod
    public void setDriver(String browser) throws MalformedURLException {
    	
    	System.out.println("Test is running on "+browser);

        if(browser.equals("firefox")) {
            capabilities = new FirefoxOptions();
        } else if (browser.equals("chrome")) {
            capabilities = new ChromeOptions();
        } else if (browser.equals("edge")) {
        	capabilities = new EdgeOptions();
        }

        driver.set(new RemoteWebDriver(new URL(remote_url), capabilities));
        driver.get().get("https://opensource-demo.orangehrmlive.com/");
        driver.get().manage().window().maximize();
        driver.get().manage().timeouts().pageLoadTimeout(Duration.ofSeconds(10));
        WebDriverWait wait = new WebDriverWait(driver.get(), Duration.ofSeconds(10));
        wait.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("//*[@id='divUsername']/span")));
    }

    public WebDriver getDriver() {
        return driver.get();
    }

    @AfterMethod
    public  void closeBrowser() {
        driver.get().quit();
        driver.remove();
    }
}

Selenium4ParallelDemo

This class contains the various tests that need to be executed.

  • In BaseTest class, I created ThreadLocal <>() webdriver (ThreadLocalMap) for thread-safe test execution
  • I got the TestNG parameter (browser) with @Parameter annotation.
  • BaseTest returns browser Capabilities based on browser name.
  • In BaseTest class, the getDriver() method returns the created driver.
  • Selenium4ParallelDemo class extends TestBase class and comprises their test code.

import org.openqa.selenium.By;
import org.testng.annotations.Test;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;

public class Selenium4ParallelDemo extends BaseTest {

    @Test
    public void validCredentials()  {

        getDriver().findElement(By.xpath("//*[@id='txtUsername']")).sendKeys("Admin");
        getDriver().findElement(By.xpath("//*[@id='txtPassword']")).sendKeys("admin123");
        getDriver().findElement(By.xpath("//*[@id='btnLogin']")).click();
        String newPageText = getDriver().findElement(By.xpath("//*[@id='content']/div/div[1]/h1")).getText();
        System.out.println("newPageText :" + newPageText);
        assertEquals(newPageText,"Dashboard");

    }

    @Test
    public void invalidCredentials() {
    	
        getDriver().findElement(By.xpath("//*[@id='txtUsername']")).sendKeys("1234");
        getDriver().findElement(By.xpath("//*[@id='txtPassword']")).sendKeys("12342");
        getDriver().findElement(By.xpath("//*[@id='btnLogin']")).click();
        String actualErrorMessage = getDriver().findElement(By.xpath("//*[@id='spanMessage']")).getText();
        System.out.println("Actual ErrorMessage :" + actualErrorMessage);
        assertEquals(actualErrorMessage,"Invalid credentials");

    }

    @Test
    public void loginPageHeading() {

        String loginText = getDriver().findElement(By.xpath("//*[@id='logInPanelHeading']")).getText();
        System.out.println("Actual loginText :" + loginText);
        assertEquals(loginText,"LOGIN Panel");

    }

    @Test
    public void forgotPasswordPageHeading()  {

        getDriver().findElement(By.xpath("//*[@id='forgotPasswordLink']/a")).click();
        String forgetPasswordTitle= getDriver().findElement(By.xpath(" //*[@id='content']/div[1]/div[2]/h1")).getText();
        System.out.println("Actual Page Title of Forgot Password Page :" + forgetPasswordTitle);
        assertEquals(forgetPasswordTitle,"Forgot Your Password?");
    }

    @Test
    public void verifyLinkedIn() {

        Boolean linkedInIcon = getDriver().findElement(By.xpath("//*[@id='social-icons']/a[1]/img")).isEnabled();
        System.out.println("Actual linkedIn Text :" + linkedInIcon);
        assertTrue(linkedInIcon);

    }
}

7. Create a testng.xml

It is very easy to create testng.xml in the case of Eclipse. Right-click on the project, and select TestNG -> Convert to TestNG. It will create a basic testng.xml structure. We need to add the parameter name and value to it.

We are planning to run the test on 3 different browsers, so we are passing the name of the browser from this testng.xml using the parameter. This testng.xml will run the test sequentially. There is a minor change needs to be done in testng.xml that will run the tests parallelly.

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

<suite name="Suite">
  <test thread-count="5" name="Chrome Test">
    <parameter name ="browser" value="chrome"/>
    <classes>
      <class name="org.example.Selenium4ParallelDemo"/>
    </classes>
  </test> <!-- Test -->
  
   <test thread-count="5" name="Firefox Test">
    <parameter name ="browser" value="firefox"/>
    <classes>
      <class name="org.example.Selenium4ParallelDemo"/>
    </classes>
  </test> <!-- Test -->
  
   <test thread-count="5" name="Edge Test">
    <parameter name ="browser" value="edge"/>
    <classes>
      <class name="org.example.Selenium4ParallelDemo"/>
    </classes>
  </test> <!-- Test -->
</suite> <!-- Suite -->

8. Run the tests from testng.xml

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

9. Navigate to the sessions tab on the Selenium Grid UI upon running the command

It would reflect an active session.

10. Review the test execution result

There are 2 ways to see if the tests are getting executed or not. First, we can check in the command line. We can see the logs there as shown below.

The complete test execution result can be found in the console too.

The tests are TestNG ones, so we can also check the Result of Running Suite tab also.

11. TestNG Report Generation

TestNG generates the test reports in the test-output folder.

We are interested in 2 reports – index.html and emailable-report.html.

Index.html

The below image shows that the tests are run sequentially.

Emailable-Report.html

This report is the summary report. It contains the summary of all the tests executed like, the number of tests passed, skipped, retried, and failed, and the execution time taken by each test.

Parallel Testing

Update the testng.xml for parallel testing. Add parallel=”tests” in the XML.

Updated testng.xml

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

<suite name="Suite" parallel="tests">
  <test thread-count="5" name="Chrome Test">
    <parameter name ="browser" value="chrome"/>
    <classes>
      <class name="org.example.Selenium4ParallelDemo"/>
    </classes>
  </test> <!-- Test -->
  
   <test thread-count="5" name="Firefox Test">
    <parameter name ="browser" value="firefox"/>
    <classes>
      <class name="org.example.Selenium4ParallelDemo"/>
    </classes>
  </test> <!-- Test -->
  
   <test thread-count="5" name="Edge Test">
    <parameter name ="browser" value="edge"/>
    <classes>
      <class name="org.example.Selenium4ParallelDemo"/>
    </classes>
  </test> <!-- Test -->
</suite> <!-- Suite -->

The below image shows that the tests were running on multiple browsers at the same time.

The console also shows that tests were started on all three browsers at the same time.

Index.html shows that methods run in chronological order. It can be seen that the setDriver() method for all 3 browsers was the first one to be executed.

Best Practices:

  1. It is advisable to use nodes other than 4444 to run the tests. By using the different port numbers, we prevent the risk that the port is already in use on your system.
  2. It is suggested to use RemoteWebDriver objects, as it is used to set which node (or machine) our test will run against.

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