Wednesday 6 July 2022

Integration of Cucumber with Selenium and TestNG, Maven | end to end example using 7.1 dependencies

Hi, In this post, we'll learn about how to integrate Cucumber with Selenium, TestNG and Maven.

This is as part of a project that we wanted to migrate the UI automation from Junit frame work to TestNG and we also wanted to upgrade to the latest versions of the dependencies. 

For the demo, we will use the HRM website. 
https://opensource-demo.orangehrmlive.com/index.php/auth/login
Username : Admin, Password: admin123 

Let's take the following use case of scenario's. 

Test Cases/Scenarios: 

Verify log-in page with valid credentials 
Display the size of quick launch elements Dashboard page/tab
Directory tab navigation from Dashboard page/tab
Verify Search button is displayed or not in Dictionary page/tab

In this framework, we will see the below technical implementations of Cucumber, Selenium and TestNG

Cucumber features: 

  • How to implement  data driven approach using DataTable object in Login scenario ? 
  • How to use Background keyword in feature files to to login for each scenario except for Login?
    (keep the tests independent of each other)
  • How to give Tags(single and multiple tags) in CucumberOptions in RunCucumberTest  class? 
  • How to use Cucumber Before and  After Hooks for each scenario ?
  • Online cucumber report 

Selenium features: 

  • Implicit wait with Duration of times 
  • Explicit wait with Duration of times
  • findElements , add elments to List<WebElement>, display the list elements, size of the list


TestNG and Maven features:

  • Assertions used in this demo are from TestNG framework. 
  • How to configure testng.xml file in pom.xml in the maven-surefire-plugin
  • How to run the cucumber scenarios from command line ? 

Let's begin! 

The first and foremost thing, I've come across is to create the proper maven project structure.
i.e., the usage of src/test/java and src/test/resources 
We can keep our source code in src/main/java and src/main/resources but in order to run the scenarios from maven command line, this cucumber-java-skeleton is recommended to keep the code in 
src/test/java and src/test/resources - at least for the RunnerTest classes so the maven sure fire plug-in identifies the code from test

Having walk-through above all,  here we start with what versions this writing consists of
  • cucumber-java 7.1.0
  • cucumber-testng 7.1.0
  • selenium 4.3.0
  • testng 7.1.0
  • maven-surefire-plugin 3.0.0-M7
  • maven-compiler-plugin 3.10.1
  • Maven installed in Windows is 3.8.6

Project Structure: 

Source code : GitHub or download this zip 

Watch, the no voice walk-through video tutorial on YouTube



Now, the steps: 

  • Download and install Cucumber plug-in in Eclipse from Market Place
  • Download and install TestNG plug-in in Eclipse from Market Place
  • Create a new Maven project (say : CucucumberTestNGSeleniumMavenCommandLine)
  • Add cucumber-java, cucumber-testng, testng and selenium dependencies in the pom.xml and etc. 
  • Add Maven compiler plugin, maven-surefire-plugin in pom.xml 
  • Create feature files(Gherkhin script) in src/test/resources folder
  • Write java, selenium, glue(step definitions), cucumber testng runner class in src/test/java folder
    • BasePage class for driver
    • Hooks class for cucumber Before and After hooks. 
    • Step Definition Or Glue Code for the feature files
    • Cucumber & TestNG runner class, RunCucumberTest.java
  •  Create testng.xml for the project and configure it in pom.xml
  •  Run Tests from TestNG Tests
  •  Run Tests from testng.xml
  •  Run Tests from command line
  •  Test results analysis from Cucumber report
  •  Test results analysis from TestNG report

Step 1: Download and install Cucumber plug-in in Eclipse from Market Place
Install the cucumber plug in from the market place. 

Step 2: Download and install TestNG plug-in in Eclipse from Market Place
Install the cucumber plug in from the market place. 

Step 3: Download and install TestNG plug-in in Eclipse from Market Place

Refer to the above image : Project Structure (It should be  a maven project)

Step 4: Add cucumber-java, cucumber-testng, testng and selenium dependencies in the pom.xml and etc.  and 

Step 5: Add Maven compiler plugin, maven-surefire-plugin in pom.xml 

Add the dependencies as shown in below pom.xml  
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>Cucumber7-TestNG7-Selenium4-OrnageHRM-POC2</groupId>
	<artifactId>Cucumber7-TestNG7-Selenium4-OrnageHRM-POC2</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	</properties>
	<dependencies>
		<!-- https://mvnrepository.com/artifact/io.cucumber/cucumber-java -->
		<dependency>
			<groupId>io.cucumber</groupId>
			<artifactId>cucumber-java</artifactId>
			<version>7.1.0</version>

		</dependency>
		<!-- https://mvnrepository.com/artifact/io.cucumber/cucumber-java -->
		<!-- https://mvnrepository.com/artifact/io.cucumber/cucumber-testng -->
		<dependency>
			<groupId>io.cucumber</groupId>
			<artifactId>cucumber-testng</artifactId>
			<version>7.1.0</version>
		</dependency>

		<!-- https://mvnrepository.com/artifact/org.seleniumhq.selenium/selenium-java -->
		<dependency>
			<groupId>org.seleniumhq.selenium</groupId>
			<artifactId>selenium-java</artifactId>
			<version>4.3.0</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.testng/testng -->
		<dependency>
			<groupId>org.testng</groupId>
			<artifactId>testng</artifactId>
			<version>7.1.0</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.apache.maven.plugins/maven-compiler-plugin -->

	</dependencies>
	<build>
		<pluginManagement>
			<plugins>
				<plugin>
					<groupId>org.apache.maven.plugins</groupId>
					<artifactId>maven-surefire-plugin</artifactId>
					<version>3.0.0-M7</version>
					<configuration>
						<suiteXmlFiles>
							<suiteXmlFile>testng.xml</suiteXmlFile>
						</suiteXmlFiles>
					</configuration>
				</plugin>
				<plugin>
					<groupId>org.apache.maven.plugins</groupId>
					<artifactId>maven-compiler-plugin</artifactId>
					<version>3.10.1</version>
					<configuration>
						<source>1.8</source>
						<target>1.8</target>
						<encoding>UTF-8</encoding>
					</configuration>
				</plugin>
			</plugins>
		</pluginManagement>
	</build>
</project>

Step 6: Create feature files(Gherkhin script) in src/test/resources folder

The following scenarios are verified in this demo. This demo is designed to login for each scenario so the login step is used as Background step in the Dashbaord.feature and Directory.feature. 

HRMLogin.feature
@HRMLogin
Feature: Login to HRM Application
  I want to use this template for HRM Login page

  @LoginValidCredentials
  Scenario: LoginValidCredentials
    Given User login to HRM application with UserName and Password
      | Admin | admin123 |
    Then User navigates to Dashboard page

  Dashboard.feature
@Dasbhoard
Feature: Dashboard page
  I want to use this template for my Dashboard Page

Background: 
  Given User login to HRM application with UserName and Password
      | Admin | admin123 |
  @DashboardTabCountOfQuickLaunhElements
  Scenario: DashboardTabCountOfQuickLaunhElements
    Then User finds the list of quick launch elements

  @DirectoryTabNavigationFromDashboardTab
  Scenario: DirectoryTabNavigationFromDashboardTab
    Then User clicks on Directory tab and verifies the navigation

Directory.feature
@Directory
Feature: Dashboard page
  I want to use this template for my Directory Page

  Background: 
    Given User login to HRM application with UserName and Password
      | Admin | admin123 |

  @DirectoryTabIsSearchButtonDisplayed
  Scenario: DirectoryTabIsSearchButtonDisplayed
    Then User is on Directory page
    Then Is Search button displayed

Step 7: Write java, selenium, glue(step definitions), cucumber testng runner class in src/test/java folder

BasePage class for driver
Define the web driver in this call and use it as super class for the step definition classes or in the Hooks class. 

Hooks class for cucumber Before and After hooks. 
In the Before Hook, get the login page.
Since it is a repeated activity for each scenario, we keep it in this Hook which means that the code that we write in Before Hook is executed/called before the execution of each scenario.
We can do this in Background band in the feature files as well. 

Use Before Hook and Background wisely. 

Step Definition Or Glue Code for the feature files
Write the glude code or step definitions code for each scenario in the HRMLoginPage.java, Dashboard.java and Directory.java files. 
iWe can rename the method names to avoid lengthy names that are generated through Feature run. 

Cucumber & TestNG runner class, RunCucumberTest.java
Runner class should end with Test because maven doesn't identify the cucumber testng integration while executing from command line. 
We can take any name for the runner class but should end with Test, in this case it is : RunCucumberTest.java
RunCucumberTest class should extends AbstractTestNGCucumberTests . 
The latest version of the cucumber testng accept tags with and or not 
For example, below statement executes two scenaro @LoginValidCredentials and @DirectoryTabIsSearchButtonDisplayed

tags="@LoginValidCredentials and not @DashboardTabCountOfQuickLaunhElements 
and not @DirectoryTabNavigationFromDashboardTab 
or @DirectoryTabIsSearchButtonDisplayed",

BasePage.java
package com.sadakar.common;
import org.openqa.selenium.WebDriver;
public class BasePage {
	
	public static WebDriver driver;

}

Hooks.java
package com.sadakar.common;
import org.openqa.selenium.chrome.ChromeDriver;
import io.cucumber.java.After;
import io.cucumber.java.Before;

public class Hooks extends BasePage {

	@Before
	public static void setupDriver() throws InterruptedException {

		System.setProperty("webdriver.chrome.driver", "D:\\chromedriver.exe");
		driver = new ChromeDriver();
		driver.manage().window().maximize();
		driver.get("https://opensource-demo.orangehrmlive.com/index.php/auth/login");
	}

	@After
	public static void quitDriver() throws Exception {
		driver.quit();
	}

}

HRMLoginPage.java 
package com.sadakar.stepdefinitions;

import java.time.Duration;
import java.util.List;

import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.testng.Assert;

import com.sadakar.common.BasePage;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then; 
public class HRMLoginPage extends BasePage {

	
	@Given("User login to HRM application with UserName and Password")
	public void loginToHRMApp(io.cucumber.datatable.DataTable dataTable) {

		List<List<String>> cells = dataTable.cells();
		driver.findElement(By.xpath("//*[@id=\"txtUsername\"]")).sendKeys(cells.get(0).get(0));
		driver.findElement(By.xpath("//*[@id=\"txtPassword\"]")).sendKeys(cells.get(0).get(1));
		driver.findElement(By.id("btnLogin")).submit();
		driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(5));
	}
	@Then("User navigates to Dashboard page")
	public void navigateToDashboardTab() {
	   
		WebDriverWait wait = new WebDriverWait(driver,Duration.ofSeconds(10));
		wait.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("//*[@id=\"menu_dashboard_index\"]")));
		
		WebElement dashboardLabel = driver.findElement(By.xpath("//*[@id=\"content\"]/div/div[1]/h1"));
		Assert.assertTrue(dashboardLabel.isDisplayed());	
	}
}

DashboardPage.java
 
package com.sadakar.stepdefinitions;

import java.time.Duration;
import java.util.List;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import com.sadakar.common.BasePage;

import io.cucumber.java.en.Then; 
public class DashboardPage extends BasePage {

	@Then("User finds the list of quick launch elements")
	public void listOfQuickLaunchElementsOnDashboardPage() {

		// Adding table data of a row to WebElement List
		List<WebElement> actualListOfQuickLaunchElements = driver
				.findElements(By.xpath("//*[@id=\"dashboard-quick-launch-panel-menu_holder\"]/table/tbody/tr/td"));

		// Display the table data of row from the WebElementList
		for (WebElement ele : actualListOfQuickLaunchElements) {
			System.out.println(ele.getText());
		}

		// Display the size of WebElement List
		System.out.println("Size of Quick launch elements : " + actualListOfQuickLaunchElements.size());

	}

	@Then("User clicks on Directory tab and verifies the navigation")
	public void navigateToDirectoryTabFromDashbaordTab() {

		driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(5));
		driver.findElement(By.xpath("//*[@id=\"menu_directory_viewDirectory\"]")).click();
	}

}

DirectoryPage.java
package com.sadakar.stepdefinitions;

import java.time.Duration;

import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.testng.Assert;

import com.sadakar.common.BasePage;

import io.cucumber.java.en.Then; 
public class DirectoryPage extends BasePage{
	
	@Then("User is on Directory page")
	public void directoryPage() {

		driver.findElement(By.xpath("//*[@id=\"menu_directory_viewDirectory\"]")).click();
		driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(5));
	}
	
	@Then("Is Search button displayed")
	public void isSearchButtonDisplayed() {
	
		WebElement searchButton = driver.findElement(By.xpath("//*[@id=\"searchBtn\"]"));
		Assert.assertTrue(searchButton.isDisplayed());
	}



}

RunCucumberTest.java
package com.sadakar.testng.runner;

import io.cucumber.testng.AbstractTestNGCucumberTests;
import io.cucumber.testng.CucumberOptions; 
@CucumberOptions(
		
		
		//tags="@LoginValidCredentials",
		//tags="@DashboardTabCountOfQuickLaunhElements",
		//tags="@DirectoryTabNavigationFromDashboardTab",
		//tags="@DirectoryTabIsSearchButtonDisplayed",
		tags="@LoginValidCredentials or @DashboardTabCountOfQuickLaunhElements or @DirectoryTabNavigationFromDashboardTab or @DirectoryTabIsSearchButtonDisplayed",
		
		//tags="@LoginValidCredentials and not @DashboardTabCountOfQuickLaunhElements and not @DirectoryTabNavigationFromDashboardTab and not @DirectoryTabIsSearchButtonDisplayed",
		
		features = "classpath:features", glue = {"com.sadakar.common", "com.sadakar.stepdefinitions",
				"com.sadakar.testng.runner"},

		plugin = { "pretty", "json:target/cucumber-reports/cucumber.json",	"html:target/cucumber-reports/cucumberreport.html" }, 
		
		monochrome = true)
public class RunCucumberTest extends AbstractTestNGCucumberTests {


}

Step 8: Create testng.xml for the project and configure it in pom.xml

Creating testng.xml is optional when we run the code from Eclipse TestNG
But, what if the code has to be deployed to a Automation server and in it the code has to be triggered. 
We use maven command line to trigger the tests. 
Create testng.xml file in the project root and give the Runner class name as shown in below. 
And, then in the pom.xml configure the testng.xml - go back to to pom.xml and look at the plug-in section. 
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="Suite">
  <test  name="Test">
    <classes>
      <class name="com.sadakar.testng.runner.RunCucumberTest"/>
    </classes>
  </test> 
</suite>

Step 9: Run Tests from TestNG Tests


[RemoteTestNG] detected TestNG version 7.0.1

@Dasbhoard @DashboardTabCountOfQuickLaunhElements
Scenario: DashboardTabCountOfQuickLaunhElements                  # features/Dashboard.feature:9
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
Starting ChromeDriver 103.0.5060.53 (a1711811edd74ff1cf2150f36ffa3b0dae40b17f-refs/branch-heads/5060@{#853}) on port 50670
Only local connections are allowed.
Please see https://chromedriver.chromium.org/security-considerations for suggestions on keeping ChromeDriver safe.
ChromeDriver was started successfully.
Jul 06, 2022 12:49:38 PM org.openqa.selenium.remote.ProtocolHandshake createSession
INFO: Detected upstream dialect: W3C
Jul 06, 2022 12:49:38 PM org.openqa.selenium.devtools.CdpVersionFinder findNearestMatch
INFO: Found exact CDP implementation for version 103
  Given User login to HRM application with UserName and Password # com.sadakar.stepdefinitions.HRMLoginPage.loginToHRMApp(io.cucumber.datatable.DataTable)
    | Admin | admin123 |
Assign Leave
Leave List
Timesheets
Apply Leave
My Leave
My Timesheet
Size of Quick launch elements : 6
  Then User finds the list of quick launch elements              # com.sadakar.stepdefinitions.DashboardPage.listOfQuickLaunchElementsOnDashboardPage()

@Dasbhoard @DirectoryTabNavigationFromDashboardTab
Scenario: DirectoryTabNavigationFromDashboardTab                 # features/Dashboard.feature:13
Starting ChromeDriver 103.0.5060.53 (a1711811edd74ff1cf2150f36ffa3b0dae40b17f-refs/branch-heads/5060@{#853}) on port 52607
Only local connections are allowed.
Please see https://chromedriver.chromium.org/security-considerations for suggestions on keeping ChromeDriver safe.
ChromeDriver was started successfully.
Jul 06, 2022 12:49:51 PM org.openqa.selenium.remote.ProtocolHandshake createSession
INFO: Detected upstream dialect: W3C
Jul 06, 2022 12:49:51 PM org.openqa.selenium.devtools.CdpVersionFinder findNearestMatch
INFO: Found exact CDP implementation for version 103
  Given User login to HRM application with UserName and Password # com.sadakar.stepdefinitions.HRMLoginPage.loginToHRMApp(io.cucumber.datatable.DataTable)
    | Admin | admin123 |
  Then User clicks on Directory tab and verifies the navigation  # com.sadakar.stepdefinitions.DashboardPage.navigateToDirectoryTabFromDashbaordTab()

@Directory @DirectoryTabIsSearchButtonDisplayed
Scenario: DirectoryTabIsSearchButtonDisplayed                    # features/Directory.feature:10
Starting ChromeDriver 103.0.5060.53 (a1711811edd74ff1cf2150f36ffa3b0dae40b17f-refs/branch-heads/5060@{#853}) on port 64336
Only local connections are allowed.
Please see https://chromedriver.chromium.org/security-considerations for suggestions on keeping ChromeDriver safe.
ChromeDriver was started successfully.
Jul 06, 2022 12:50:11 PM org.openqa.selenium.remote.ProtocolHandshake createSession
INFO: Detected upstream dialect: W3C
Jul 06, 2022 12:50:11 PM org.openqa.selenium.devtools.CdpVersionFinder findNearestMatch
INFO: Found exact CDP implementation for version 103
  Given User login to HRM application with UserName and Password # com.sadakar.stepdefinitions.HRMLoginPage.loginToHRMApp(io.cucumber.datatable.DataTable)
    | Admin | admin123 |
  Then User is on Directory page                                 # com.sadakar.stepdefinitions.DirectoryPage.directoryPage()
  Then Is Search button displayed                                # com.sadakar.stepdefinitions.DirectoryPage.isSearchButtonDisplayed()

@HRMLogin @LoginValidCredentials
Scenario: LoginValidCredentials                                  # features/HRMLogin.feature:6
Starting ChromeDriver 103.0.5060.53 (a1711811edd74ff1cf2150f36ffa3b0dae40b17f-refs/branch-heads/5060@{#853}) on port 56598
Only local connections are allowed.
Please see https://chromedriver.chromium.org/security-considerations for suggestions on keeping ChromeDriver safe.
ChromeDriver was started successfully.
Jul 06, 2022 12:50:32 PM org.openqa.selenium.remote.ProtocolHandshake createSession
INFO: Detected upstream dialect: W3C
Jul 06, 2022 12:50:32 PM org.openqa.selenium.devtools.CdpVersionFinder findNearestMatch
INFO: Found exact CDP implementation for version 103
  Given User login to HRM application with UserName and Password # com.sadakar.stepdefinitions.HRMLoginPage.loginToHRMApp(io.cucumber.datatable.DataTable)
    | Admin | admin123 |
  Then User navigates to Dashboard page                          # com.sadakar.stepdefinitions.HRMLoginPage.navigateToDashboardTab()
????????????????????????????????????????????????????????????????????????????
? View your Cucumber Report at:                                            ?
? https://reports.cucumber.io/reports/bebe12f4-ffa5-46a1-a8e2-11ea5d3ef259 ?
?                                                                          ?
? This report will self-destruct in 24h.                                   ?
? Keep reports forever: https://reports.cucumber.io/profile                ?
????????????????????????????????????????????????????????????????????????????PASSED: runScenario("DashboardTabCountOfQuickLaunhElements", "Optional[Dashboard page]")
        Runs Cucumber Scenarios
PASSED: runScenario("DirectoryTabNavigationFromDashboardTab", "Optional[Dashboard page]")
        Runs Cucumber Scenarios
PASSED: runScenario("DirectoryTabIsSearchButtonDisplayed", "Optional[Dashboard page]")
        Runs Cucumber Scenarios
PASSED: runScenario("LoginValidCredentials", "Optional[Login to HRM Application]")
        Runs Cucumber Scenarios

===============================================
    Default test
    Tests run: 4, Failures: 0, Skips: 0
===============================================


===============================================
Default suite
Total tests run: 4, Passes: 4, Failures: 0, Skips: 0
===============================================

Step 10:  Run Tests from testng.xml

Logs generated through running testng.xml are same as above in Eclipse.

Step 11: Run Tests from command line

On the command line navigate to the folder where the project is saved and then issue mvn test command. This command will run all the scenarios from the project. 

Below are few examples on how to run or negate specific scenarios from the command line. 

These are the tags used for 4 scenarios. 
//tags="@LoginValidCredentials",
//tags="@DashboardTabCountOfQuickLaunhElements",
//tags="@DirectoryTabNavigationFromDashboardTab",
//tags="@DirectoryTabIsSearchButtonDisplayed",

Below command by default runs all the scenarios, basically irrespective of what we specify in CucumberOptions in the runner class maven will override it and run all the scenarios.
mvn test 
Tests run: 4, Failures: 0, Errors: 0, Skipped: 0

 
To run all the scenarios by mentionting all the tags, we need to use or between scenarios.
mvn test -Dcucumber.filter.tags="@LoginValidCredentials or @DashboardTabCountOfQuickLaunhElements or @DirectoryTabNavigationFromDashboardTab or @DirectoryTabIsSearchButtonDisplayed"
Tests run: 4, Failures: 0, Errors: 0, Skipped: 0

 
This command runs only one particular scenario
mvn test -Dcucumber.filter.tags="@LoginValidCredentials"
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

This command runs two scenarios
mvn test -Dcucumber.filter.tags="@LoginValidCredentials or @DashboardTabCountOfQuickLaunhElements"
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0

If we use and between two scenarios, none of the scenarios will be executed.
mvn test -Dcucumber.filter.tags="@LoginValidCredentials and @DashboardTabCountOfQuickLaunhElements"
Tests run: 0, Failures: 0, Errors: 0, Skipped: 0

To negate a command i.e., to not run a particular scenario use and not
For instance, to run a scenario and to not a run scenario use below format. 
mvn test -Dcucumber.filter.tags="@LoginValidCredentials and not @DashboardTabCountOfQuickLaunhElements"
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0





Step 12: Test results analysis from Cucumber report



Step 13: Test results analysis from TestNG report
emailable-report.html



index.html

Cheers! We are done integrating cucumber with selenium, testng and maven. 

I hope you find this article is helped! Keep an eye on this space for future updates. 

No comments:

Post a Comment