Table of contents
- Why Cucumber?
- Cucumber POM dependency with Maven
- What Cucumber feature file do?
- What Cucumber step definition file do?
- How I can run a basic Cucumber Junit test?
- How can I filter my test using Tags?
- How can I control my execution flow using hooks?
- How can I fit data to my test using Doc String?
- How can I run the same test for different sets of data using a scenario outline?
- How can I fit data into my test using a Data table?
- How Can I fit data from the external source as an Excel sheet?
- How I can generate a report for the Cucumber test using TestNG?
- How do I run the failed test case only?
Why Cucumber?
To make my test script understandable to both tech and non-tech people. how cucumber makes it possible? This is possible because cucumber tests are written in plain text by following some Gerkhin keywords.
A basic example of a Cucumber test:
Feature: Login
I want to use login to the HRM protal with valid credentials.
@login
Scenario: Admin can login to the HRM portal with valid credentials
Given Admin is on the login page
When Admin enter valid "Admin" and "admin123"
Then Home page is displayed for the admin
Cucumber POM dependency with Maven
Considering the Maven project below dependency need to be added in the pom.XML file to work with Cucumber.
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java</artifactId>
<version>${cucumber-version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-junit</artifactId>
<version>${cucumber-version}</version>
<scope>test</scope>
</dependency>
What Cucumber feature file do?
The feature file is the starting point to write down the feature that we are going to test for our application. It contains scenarios that basically the test cases. Typically feature files have an extension .fetaure. Below is a snapshot of what they look like. For example Login. feature
An example of a feature file is already added earlier.
Keywords that are used to write the feature files:
Keywords | Why | Example |
Feature | This keyword indicates the feature that we are going to test. Standard practice to have max 3-4 scenarios in a single feature file | Feature: Login |
Background | This keyword is used to perform any common task before executing any scenario | Open the browser before each scenario (test case). Background: Open the browser before each test |
Scenario | This keyword is used to write down each test case. | Scenario: User can log in with valid credentials |
Scenario Outline | This keyword is used to run the same scenario multiple times with different data using Examples | Scenario Outline: User can log in with valid <username> and <password> |
Examples | This keyword is used to fit multiple data to the scenario outline to run the same test script multiple times. | Will be discussed later |
Gherkin keywords | ||
Given | This keyword is used to set up the preconditions | Given the user is on the login page |
When | This keyword is used to perform the action or condition | Then a user enters a valid username and pass |
Then | This keyword is used to validate the outcome or assert the expectation | Then login is successful |
And | This keyword is used to contact the action together | Then a user enters valid credentials AND hit the login button |
But | This keyword is used to negate something | Then login is successful but not showing the user's full name in the dashboard |
What Cucumber step definition file do?
This is the file one-to-one maps with the feature file, which is basically the action class performing an action related to each step written in the feature file.
Example of feature file:
Feature: Login
I want to use login to the HRM protal with valid credentials.
@login
Scenario: Admin can login to the HRM portal with valid credentials
Given Admin is on the login page
When Admin enter valid admin as "Admin" and the pass as "admin123"
Then Home page is displayed for the admin
the corresponding step definition file is below:
@Given("Admin is on the login page")
public void admin_is_on_the_login_page() {
Base.goToLoginPage(loginUrl);//loginUrl is the path to the login page
login = new LoginPage(Base.getDriver());
}
@When("Admin enter valid admin as {string} and the pass as {string}")
public void admin_enter_valid_admincredentials(String username, String pass) {
login.enterUsername(username);
login.enterPassword(pass);
login.clickLogin();
}
@Then("Home page is displayed for the admin")
public void home_page_is_displayed_for_the_admin() {
HomePage home = new HomePage(Base.getDriver());
Assert.isTrue(home.isHomePageIsDisplayed(), "Home page is not displayed");
}
How I can run a basic Cucumber Junit test?
To run a basic cucumber test as the Junit test, we need to create a runner class as below:
import org.junit.runner.RunWith;
import io.cucumber.junit.Cucumber;
import io.cucumber.junit.CucumberOptions;
@RunWith(Cucumber.class)
@CucumberOptions(
features = "src/test/resources/features", //path to the feature files
glue = {"steps" }, //path to the step definition files
monochrome = true, //to color the output to make it easy to understand also to avoid some unexpected garbage code.
plugin = { "pretty", "json:target/cucumber-report/cucumber.json",
"junit:target/cucumber-report/cucumber.xml", "html:target/cucumber-report/cucumber.html" }//generate diffrent reports
)
public class CucumberRunner {
}
from ide, if we run this file as a Junit test, then it will run the feature file and produce junit result. what the runner class does will be discussed in a separate section.
How can I filter my test using Tags?
We can filter our execution using tags. Suppose each time I want to the login feature as a regression suite. then we can simply add a @regression tag to our login scenario as below:
Feature: Login
I want to use login to the HRM protal with valid credentials.
@login @regression
Scenario: Admin can login to the HRM portal with valid credentials
Given Admin is on the login page
When Admin enter valid "Admin" and "admin123"
Then Home page is displayed for the admin
Then mention these tags to our runner class as below, then when we execute our runner class it will pick the login test case as our regression
import org.junit.runner.RunWith;
import io.cucumber.junit.Cucumber;
import io.cucumber.junit.CucumberOptions;
@RunWith(Cucumber.class)
@CucumberOptions(
features = "src/test/resources/features", //path to the feature files
glue = {"steps" }, //path to the step definition files
monochrome = true, //to color the output to make it easy to understand also to avoid some unexpected garbage code.
tags = "@regression", //tags to filter the execution
plugin = { "pretty", "json:target/cucumber-report/cucumber.json",
"junit:target/cucumber-report/cucumber.xml", "html:target/cucumber-report/cucumber.html" }//generate diffrent reports
)
public class CucumberRunner {
}
How can I control my execution flow using hooks?
Hooks are normally used to control some execution flow. Suppose I want to
open the browser before executing any scenario or
I want to tear down my browser after each scenario or
I want to capture the screenshot while a step failed then we can use hooks to achieve that.
Look into the below code base. We can achieve it by creating a Base class, cucumber will automatically pick this by looking into the below cucumber @Before(), @After(), @BeforeStep(), @AfterStep().
//Trigger this method once to Open the browser before stating the first step of a scenario
@Before()
public void openBrowser() throws InterruptedException {
driver = new ChromeDriver();
driver.manage().deleteAllCookies(); driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(40));
driver.manage().window().maximize();
}
public static WebDriver getDriver() {
return driver;
}
public static void goToLoginPage(String loginUrl) {
driver.get(loginUrl);
}
//Trigger this method once to close the browswer after the last step of a scenario
@After()
public void tearDownBrowser() {
driver.quit();
}
//Trigger this method each time after a step of a scenario.
@AfterStep()
public void takeScreenShot(Scenario scenario) {
// only for the failed scenario to capture
if(scenario.isFailed()) {
final byte[] ss = ((TakesScreenshot) driver).getScreenshotAs(OutputType.BYTES);
scenario.attach(ss, "image/png", "image");
}
// caputre for all steps
// final byte[] ss = ((TakesScreenshot)driver).getScreenshotAs(OutputType.BYTES);
// scenario.attach(ss, "image/png", "image");
}
How can I fit data to my test using Doc String?
Doc string is used to perform the data-driven test in cucumber. The actual need for the Doc String is to send the data as the formal that the user wants to send to the test script. For example, if say a user wants to fill up a form where he needs to send out a message as an email where the format of the message is important then we can use Doc string in cucumber to achieve that.
Consider this contact form as an example: http://autopract.com/selenium/form4/
we want to send out a message:
Hello HR,
This is a demo message from a testing academy in order to validate
either the doc string is working with the cucumber.
Thanks,
Tester Lean Academy
Then we can write a scenario using the doc string to keep the format as is. Doc string syntax is """ .
@doc
Scenario: Verify Contact form is working with doc input
Given User is at Contact page
When User enter "Tester" as first name
And User enter "leanacademy@test.com" as email
And Email as below
"""
Hello HR,
This is a demo message from a testing academy in order to validate
either the doc string is working with the cucumber.
Thanks,
Tester Lean Academy
"""
And user click on the send button
Then Message was submitted successfully
The corresponding step definition for this scenario is:
@Given("User is at Contact page")
public void user_is_at_contact_page() {
Base.goToLoginPage(contactpageurl);
contact = new Contact(Base.getDriver());
}
@When("User enter {string} as first name")
public void user_enter_as_first_name(String fname) {
Contact.enterFirstname(fname);
}
@When("User enter {string} as last name")
public void user_enter_as_last_name(String lname) {
Contact.enterLastname(lname);
}
//parametarized the email from the feature file.
@When("User enter {string} as email")
public void user_enter_as_email(String em) {
Contact.enterEmail(em);
}
// this tiem no parameter but the cucumber taking care it as a param
@When("Email as below")
public void email_as_below(String doc) {
Contact.enterMessage(doc);
}
@Then("Message was submitted successfully")
public void message_was_submitted_successfully() {
String status = contact.returnValidationErrorText();
Assert.isTrue(status.equalsIgnoreCase("Validation Problem"), "Failed");
}
How can I run the same test for different sets of data using a scenario outline?
Suppose I want to test the login feature for multiple user with valid credentials whether they are able to log in to the system or not. That means we are running the same login with a different set of data. Another feature says we want to fill up a form multiple times with a different set of data. We can easily do it by using the scenario outline.
Consider this contact form as an example: http://autopract.com/selenium/form4/. we are going to fill up this form with the below data from different users.
| user | email | message |
| Tester1 | Tester1@test.com | This is the message1 |
| Tester2 | Tester2@test.com | This is the message2 |
| Tester3 | Tester3@test.com | This is the message3 |
Then we can write a scenario outline using examples to fit data:
Feature: Send mass message with data table
Background: user should be at form page
Given User is at Contact page
@scenariooutline
Scenario Outline: Verify user can send multiple message
Given user has entered "<user>","<email>" and "<message>"
When user click on the send button
Then message is sent successfully
Examples:
| user | email | message |
| Tester1 | Tester1@test.com | This is the message1 |
| Tester2 | Tester2@test.com | This is the message2 |
| Tester3 | Tester3@test.com | This is the message3 |
Corresponding definition file for this scenario:
//parameterized
@Given("user has entered {string},{string} and {string}")
public void user_has_entered(String user, String mail, String message) {
contact = new Contact(Base.getDriver());
contact.enterFirstname(user);
contact.enterEmail(mail);
contact.enterMessage(message);
}
How can I fit data into my test using a Data table?
A data table is used to send data to the test script only for a single step. look at the below scenario, here the user has name, email and message data to send to the given step.
Feature: Send mass message with data table
Background: user should be at form page
Given User is at Contact page
@datatable
Scenario: Verify user can send multiple message
Given user has the below information to send
| Tester1 | Tester1@test.com | This is the message1 |
When user click on the send button
Then message is sent successfully
step definition for this scenario is
List<List<String>> userinfo;
@Given("user has the below information to send")
public void user_has_the_below_information_to_send(DataTable dataTable) {
userinfo = dataTable.asLists(); //convert datatable to list of string list.
contact = new Contact(Base.getDriver());
contact.enterFirstname(userinfo.get(0).get(0));
contact.enterEmail(userinfo.get(0).get(1));
contact.enterMessage(userinfo.get(0).get(0));
}
@When("user click on the send button")
public void user_click_on_the_send_button() {
contact.clickSendButton();
}
@Then("message is sent successfully")
public void message_is_sent_successfully() {
}
How Can I fit data from the external source as an Excel sheet?
Sometimes, we may need to get data from an external source like an excel sheet and then feed it to our test script. Cucumber allows us to do that.
We can prepare an excel utility to read data from the excel sheet then we can call the utility class from the cucumber step definition file to get the data from there.
Maven dependency regarding apache POI.
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.3</version>
<exclusions>
<exclusion>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
</exclusion>
</exclusions>
</dependency>
package utilities;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
public class Excel {
private static final String FILE_NAME = "C:\\Users\\ubiswas\\git\\cucumberpet\\Login.xlsx";
public static List<Map<String, String>> getTestData(String sheetname) {
List<Map<String, String>> testData = new ArrayList<>();
try {
Workbook workbook = WorkbookFactory.create(new FileInputStream(FILE_NAME));
Sheet sheet = workbook.getSheet(sheetname);
int roNums = sheet.getLastRowNum() - sheet.getFirstRowNum() + 1;
int colNums = sheet.getRow(0).getLastCellNum();
for (int i = 1; i < roNums; i++) {
Map<String, String> map = new HashMap<String, String>();
Row row = sheet.getRow(i);
for (int j = 0; j < colNums; j++) {
Cell cell = row.getCell(j);
String key = sheet.getRow(0).getCell(j).getStringCellValue();
String cellvalueString = cell.getStringCellValue();
map.put(key, cellvalueString);
}
testData.add(map);
}
} catch (Exception e) {
throw new RuntimeException("Failed to read test data from Excel sheet", e);
}
return testData;
}
}
package steps;
import java.util.List;
import java.util.Map;
import actions.Contact;
import io.cucumber.datatable.DataTable;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
import utilities.Excel;
public class ContactDataTableSteps {
List<List<String>> userinfo;
Contact contact;
@Given("user has the below information to send")
public void user_has_the_below_information_to_send() {
List<Map<String, String>>data = Excel.getTestData("loginInfo");
contact.enterFirstname(data.get(0).get("user"));
contact.enterEmail(data.get(0).get("email"));
contact.enterMessage(data.get(0).get("message"));
}
@When("user click on the send button")
public void user_click_on_the_send_button() {
contact.clickSendButton();
}
@Then("message is sent successfully")
public void message_is_sent_successfully() {
}
}
Feature: Send mass message with data table
Background: user should be at form page
Given User is at Contact page
@excel
Scenario: Verify user can send multiple message
Given user has the below information to send
When user click on the send button
Then message is sent successfully
How I can generate a report for the Cucumber test using TestNG?
Import TestNG maven dependency to the pom.XML file as below
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-testng</artifactId>
<version>7.11.1</version>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>7.7.1</version>
<scope>test</scope>
</dependency>
then convert your maven project to a TestNG project, which will get a testng.xml file.
Now create TestNg cucumber runner class as below to run it.
package testrunner;
import io.cucumber.testng.AbstractTestNGCucumberTests; //cucumber testng
import io.cucumber.testng.CucumberOptions;
@CucumberOptions(
features = "src/test/resources/features",
glue = { "steps", "testrunner" }
)
public class TestNgRunner extends AbstractTestNGCucumberTests {
}
How do I run the failed test case only?
We can achieve it by the below two way-
Run the failed XML file using the TestNG
Using the rerun plugin
Using the Failed XML file is pretty straightforward, simply right-click on the failed XML file and run it as testNG suite as we normally run testng-xml file.
Using the rerun plugin first open the Runner class add the below line to the plugin section
"rerun:target/failedrerun.txt"
package testrunner;
import org.junit.runner.RunWith;
import io.cucumber.junit.Cucumber;
import io.cucumber.junit.CucumberOptions;
@RunWith(Cucumber.class)
@CucumberOptions(publish = true, features = "src/test/resources/features", glue = {
"steps" }, monochrome = true, plugin = { "pretty", "json:target/cucumber-report/cucumber.json",
"junit:target/cucumber-report/cucumber.xml", "html:target/cucumber-report/cucumber.html",
"rerun:target/failedrerun.txt"
})
public class CucumberRunner {
}
Now run the test. when done refresh the project and check the target folder it will create a file named failedrerun.txt containing the info about the failed scenarios.
Now create another test runner class as below to run the failed features.
package testrunner;
import org.junit.runner.RunWith;
import io.cucumber.junit.Cucumber;
import io.cucumber.junit.CucumberOptions;
@RunWith(Cucumber.class)
@CucumberOptions(publish = true, features = "@target/failedrerun.txt", glue = {
"steps" }, monochrome = true, plugin = { "pretty", "json:target/cucumber-report/cucumber.json",
"junit:target/cucumber-report/cucumber.xml", "html:target/cucumber-report/cucumber.html",
"rerun:target/failedrerun.txt"
})
public class CucumberRunnerFailed {
}
If we run it then it will pick up only the failed features or scenarios.