Play with Cucumber

Play with Cucumber

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:

KeywordsWhyExample
FeatureThis keyword indicates the feature that we are going to test. Standard practice to have max 3-4 scenarios in a single feature fileFeature: Login
BackgroundThis keyword is used to perform any common task before executing any scenarioOpen the browser before each scenario (test case). Background: Open the browser before each test
ScenarioThis keyword is used to write down each test case.Scenario: User can log in with valid credentials
Scenario OutlineThis keyword is used to run the same scenario multiple times with different data using ExamplesScenario Outline: User can log in with valid <username> and <password>
ExamplesThis 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
GivenThis keyword is used to set up the preconditionsGiven the user is on the login page
WhenThis keyword is used to perform the action or conditionThen a user enters a valid username and pass
ThenThis keyword is used to validate the outcome or assert the expectationThen login is successful
AndThis keyword is used to contact the action togetherThen a user enters valid credentials AND hit the login button
ButThis keyword is used to negate somethingThen 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

  1. open the browser before executing any scenario or

  2. I want to tear down my browser after each scenario or

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