Skip to content

Creating your own steps

José Fernández edited this page Jan 9, 2021 · 35 revisions

All steps included in Gingerspec were created with the idea of reusability and flexibility. The problem is, that in order to achieve this, the steps must be low level enough to be used for a wide range of cases. You can create your own steps with the level of abstraction that you prefer, however, I would like you to consider the following points before trying to apply BDD with GingerSpec:

  • GingerSpec was designed to work as a DSL for the creation of integration tests. There are several reasons to keep the legacy of Given-When-Then, like making the tests more readable, and its integration with current IDEs.

  • GingerSpec is, in itself, a super-simplified programming language that heavily borrows BDD syntax.

  • Cucumber, when used outside of its intended scope (collaboration at the beginning of the development process), becomes no more than a simple wrapper between integration tests.

  • You can create your own steps and just call GingerSpec methods behind the scenes using the GingerSpec API. This may make sense for UI testing, but in other contexts like REST API testing, where what is under test is a contract and not a behavior, this makes little sense. You might be better of just using something like REST-assured instead.

Creating your own steps

Creating your own step logic

You can create your own step definitions in Java code and then link these step definitions to specific Gherkin steps. There is already a very good tutorial in the official cucumber documentation about how to do this.

This process can be further simplified if you're using an IDE that has support for Gherkin syntax. If you have installed and configured Intellij IDEA, you can do this directly in the Feature file

creating a gherkin step definition

Check the official documentation of Intellij IDEA here where this process is explained in detail.

Selenium example

When you are creating your own steps for UI testing, you can even get an instance of the current selenium Webdriver GingerSpec is using. Take the following code as an example:

import com.privalia.qa.specs.BaseGSpec;
import com.privalia.qa.specs.CommonG;
import io.cucumber.java.en.Given;
import org.openqa.selenium.WebDriver;

public class StepDefinitions extends BaseGSpec {

    public StepDefinitions(CommonG spec) {
        this.commonspec = spec;
    }

    @Given("I open the page {string}")
    public void openUrl(String url) {
        WebDriver d = this.commonspec.getDriver();
        d.get(url);
    }
}

If you created your project using the instructions provided here, the resulting project will contain pre-made examples of this. You can even mix your own selenium code with the methods contained in GingerSpec, for example:

import com.privalia.qa.specs.BaseGSpec;
import com.privalia.qa.specs.CommonG;
import com.privalia.qa.specs.SeleniumGSpec;
import io.cucumber.java.en.Given;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;

public class StepDefinitions extends BaseGSpec {

    SeleniumGSpec seleniumGSpec;

    public StepDefinitions(CommonG spec) {
        this.commonspec = spec;

        /* Access all functions for working with selenium */
        seleniumGSpec = new SeleniumGSpec(this.commonspec);
    }

    @Given("I open the page {string}")
    public void openUrl(String url) {
        WebDriver d = this.commonspec.getDriver();
        d.get(url);

        /* Using GingerSpec methods */
        this.seleniumGSpec.seleniumTypeByLocator("Rick and Morty", "name", "q", 0);

        /* Using plain old Selenium */
        d.findElement(By.name("q")).sendKeys("Rick and Morty");

    }
}

Rest API example

GingerSpec also works with REST-assured, so, if you want to get an instance of the RequestSpecification:

@Given("lotto results return the expected response")
    public void lotto_resource_returns_200_with_expected_id_and_winners(String url) {
        RequestSpecification d = this.commonspec.getRestRequest();

        when().
                get("/lotto/{id}", 5).
                then().
                statusCode(200).
                body("lotto.lottoId", equalTo(5),
                        "lotto.winners.winnerId", hasItems(23, 54));
    }

Other technologies

GingerSpec has separated classes that contain the step definitions for each technology domain. You can get a list of all the step definitions of each one by directly checking the API


Merging low-level steps

This could certainly be the easiest way in the sense that you can just reuse the steps already contained in Gingerspec and group several of them to create a single and more high-level step

Consider the following scenario as an example. the scenario contains 4 low-level GingerSpec steps. It can be converted from this:

    Scenario: Verify that there are two main sections "Interactions" and "Widgets"
        Given My app is running in 'demoqa.com:80'
        And I browse to '/'
        When '2' elements exists with 'class:widget-title'
        And I wait '1' seconds

To this:

    Scenario: This is the same scenario as above, but in one line, using custom steps
        Given I verify the Interactions and Widgets sections are present

To accomplish this, you will have to first create the new step definition and just call the underlying java functions for the low-level steps with the correct parameters, like this:

    @Given("^I verify the Interactions and Widgets sections are present$")
    public void iVerifyTheInteractionsAndWidgetsSectionsArePresent() throws Throwable {
        seleniumGSpec.setupApp("demoqa.com:80");
        seleniumGSpec.seleniumBrowse(null,"/");
        seleniumGSpec.assertSeleniumNElementExists("at least", 2,"class","widget-title");
        utilsGSpec.idleWait(1);
    }

Remember that the java class where you create your new steps definitions must be included in the glue parameter in the runner class.

Since hunting down the underlying functions and parameters of all steps in a scenario can be a long and painstaking process, GingerSpec provides a feature that makes this process much easier. Run your tests with the -DSHOW_STACK_INFO and you will get information about the underlying java function and parameters that are being called for each gherkin step

Example:

mvn verify -Dit.test=eu.vptech.firstProject.tribe1.CucumberSeleniumIT -DSHOW_STACK_INFO

Output:

  Scenario: Verify that there are two main sections "Interactions" and "Widgets" # src/test/resources/features/tribe1/cucumber_selenium_test.feature:19
    Given My app is running in 'demoqa.com:80'
         # SeleniumGSpec.setupApp(String)
         # Argument 0: demoqa.com:80
    And I browse to '/'
         # SeleniumGSpec.seleniumBrowse(String,String)
         # Argument 0: null
         # Argument 1: /
    When '2' elements exists with 'class:widget-title'
         # SeleniumGSpec.assertSeleniumNElementExists(String,Integer,String,String)
         # Argument 0: null
         # Argument 1: 2
         # Argument 2: class
         # Argument 3: widget-title
    And I wait '1' seconds
         # UtilsGSpec.idleWait(Integer)
         # Argument 0: 1

Where:

Given My app is running in 'demoqa.com:80' -> Step being executed
         # SeleniumGSpec.setupApp(String)  -> Underlying Java method being executed and the type of each parameter 
         # Argument 0: demoqa.com:80       -> Value of first parameter

-DSHOW_STACK_INFO is only available in Gingerspec 2.0.3 and up