-
Notifications
You must be signed in to change notification settings - Fork 6
Creating your own steps
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.
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
Check the official documentation of Intellij IDEA here where this process is explained in detail.
When you are creating your own steps for UI testing, you can get the instance of the current selenium Driver GingerSpec is using. If you added the @web annotation in your Gherkin Feature or Scenario, GingerSpec will automatically create a new Selenium driver for you in an internal @Before cucumber hook, and you can have access to this driver instance via the getDriver() method in the CommonG class.. Check the following example to see how to do this
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 don't know what a cucumber hook is, or you want to create your own, you can check the official cucumber documentation.
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");
}
}
GingerSpec also works with REST-assured, so, you need to add the REST-assured dependency to your POM file in Maven if you already have the GingerSpec dependency. 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));
}
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
GingerSpec comes preloaded with AssertJ, a very popular assertions java library. So, whenever you create your own steps, it is recommended to use AssertJ to make assertions in the Java code, for example:
// basic assertions
Assertions.assertThat(frodo.getName()).isEqualTo("Frodo");
Assertions.assertThat(frodo).isNotEqualTo(sauron);
// chaining string specific assertions
Assertions.assertThat(frodo.getName()).startsWith("Fro")
.endsWith("do")
.isEqualToIgnoringCase("frodo");
// as() is used to describe the test and will be shown before the error message
Assertions.assertThat(frodo.getAge()).as("check %s's age", frodo.getName()).isEqualTo(33);
Check the official AssertJ documentation for more examples
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