Gherkin tags - veepee-oss/gingerspec GitHub Wiki

The standard Cucumber Gherkin language has significant limitations that don't give enough freedom and comfort using BDD approach:

  • No Variables
  • No Conditional execution
  • No omitting elements during execution
  • No way to loop elements.

The mission of Gherkin tags is providing all these functionalities, editing less code, enabling easier maintainability, and doing the Features more readable. To enable this new functionality GingerSpec allows the use of the following tags in the Gherkin files:

@web tag

This tag indicates GingerSpec to bootstrap a new selenium web driver before each Scenario. Typically, you will use this tag anytime you want to execute a test that requires selenium (a test on a browser). In the background, GingerSpec will create a new instance of a Selenium web driver. This tag is also responsible for closing the session of the driver right after the scenario finishes.

You can use the tag like in the following example:

@web
Feature: Testing basic functionality of a web page

    Scenario: Fill the form and click the submit button
        Given I go to 'http://demoqa.com/text-box'
        And I type 'John' on the element with 'id:userName'
        And I type '[email protected]' on the element with 'id:userEmail'
        And I type '123 fake address' on the element with 'id:currentAddress'
        When I scroll down until the element with 'id:submit' is visible
        And I click on the element with 'id:submit'
        Then at least '1' elements exists with 'id:output'

You can get the instance of the Selenium WebDriver created by the @web tag and use it in your own steps. You can get this instance through the CommonG class like this:

public class CustomStepsDefinition extends BaseGSpec {

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

    @Given("^Fill the form and click the submit button$")
    public void myCustomSeleniumStep() {

        /* Get access to the selenium driver instance like this */
        WebDriver driver = this.commonspec.getDriver();

        /* Use regular selenium functions and APIs */
        driver.get("http://demoqa.com/text-box");
        driver.findElement(By.id("userName")).sendKeys("John");
        driver.findElement(By.id("userEmail")).sendKeys("[email protected]");

    }
}

In this case, the @web annotation is at Feature level, but it can also be at Scenario level. Take also a look at the information presented in Running Selenium tests

@mobile tag

This one is similar to the @web tag, but in this case GIngerSpec creates a new Appium driver (for testing on mobile devices). This tag is also responsible for closing the Appium session of the driver right after the scenario finishes.

@mobile
Feature: Running tests in mobile devices

  Scenario: Opening an closing the app
    Given I open the application
    And I wait '5' seconds
    Given I close the application

  Scenario: Changing orientation
    Given I rotate the device to 'landscape' mode
    And I wait '3' seconds
    Given I rotate the device to 'portrait' mode

You can also get the instance of the WebDriver for your use in your own steps in the same way as when using the @web tag

In this case, the @mobile annotation is at Feature level, but it can also be at Scenario level. Take also a look at the information presented in Testing mobile applications

@sql tag

Deprecated. Not necessary since GIngerSpec 2.2.13

This tag is typically used in features that contain steps related to SQL operations (accessing MySQL or PostgreSQL databases). This tag only executes at the end of the scenarios and its job is to close any remaining open connection to a database

Example

@sql
Feature: SQL Database Steps

  Scenario: Connect to mysql Database
    Given I connect with JDBC to database 'mysql' type 'mysql' on host '${MYSQL_HOST}' and port '3306' with user 'root' and password 'mysql'

The database connection is closed right after the Scenario finishes its execution.

@rest tag

Deprecated. Not necessary since GIngerSpec 2.2.13

The @rest tag bootstraps a new rest-client and is used in Features/Scenarios that interact with REST services. Internally, GingerSpec uses rest-assured for all its logic related to testing web services. This tag is also responsible for closing the rest-client at the end of the scenario.

@rest
Feature: Testing a RestFull API

    Scenario: A successful response with a valid body is returned
        Given I securely send requests to 'jsonplaceholder.typicode.com:443'
        When I send a 'GET' request to '/posts'
        Then the service response status must be '200'
        And I save element '$.[0].userId' in environment variable 'USER_ID'
        Then '${USER_ID}' matches '1'

You can get the instance of the RequestSpecification created by the @rest tag through the CommonG class like this:

public class CustomStepsDefinition extends BaseGSpec {

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

    @Given("^I verify the service response is successful$")
    public void myCustomSeleniumStep() {

        /* Get access to the RequestSpecification object */
        this.commonspec.getRestRequest()
                .given().contentType(ContentType.JSON)
                .when()
                .get("https://jsonplaceholder.typicode.com:443/posts")
                .then()
                .statusCode(200);

    }
}

@debug tag

Gingerspec makes use of Log4j for printing messages in the console, and this tag allows changing the Log4j logging level at runtime for a particular scenario. By default, the logging level used is WARN, meaning that only WARN, ERROR and FATAL messages will be printed in the console. However, you can specify the logging level for a particular scenario. This means that all internal Gingerspec messages that are above or equal to that level will be printed, giving you more insight into what is going on in the background and helping you detect errors or problems more easily.

@rest
Feature: Testing a RestFull API

    @debug
    Scenario: A successful response with a valid body is returned
        Given I securely send requests to 'jsonplaceholder.typicode.com:443'
        When I send a 'GET' request to '/posts'
        Then the service response status must be '200'
        And I save element '$.[0].userId' in environment variable 'USER_ID'
        Then '${USER_ID}' matches '1'

In the example above, Gingerspec will print all debug messages for that scenario. After the scenario completes, the logging level is switched back to WARN. You can use any logging level as a tag: @fatal, @error, @warn, @info, @debug, and @trace

If you want this to work when creating your own steps, you will have to use the logger instance from the CommonG class like in the following example:

public class CustomStepsDefinition extends BaseGSpec {

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

    @Given("^I have 5 cucumbers$")
    public void myCustomSeleniumStep() {
        
        /* Get access to the logger object */
        Logger logger = this.commonspec.getLogger();
        logger.debug("This is a debug message");

        /* Or just use it directly*/
        this.commonspec.getLogger().debug("This is a debug message");

    }
}

Remember you can also use the maven variable -DlogLevel=DEBUG to change the logging level when running your tests in the console

if statement

Only available since GingerSpec 2.2.6+

This allows the conditional execution of steps during runtime. All steps enclosed between if (statement) { and } will be executed only if the given statement returns true, otherwise, the steps will be skipped. The statement can be any javascript expression that can return a true/false output. You can even use variables created during the scenario execution.

Feature: Conditional execution

  Scenario: Using if block to control execution
    * if (1==1) {
    * I run 'echo "This should be executed"' locally
    * }
    * if (1==2) {
    * I run 'echo "This should NOT be executed"' locally
    * I run 'exit 1' locally
    * }

  Scenario: Using variables created during the scenario
    * I save 'GingerSpec' in variable 'NAME'
    * if ('${NAME}'.contains('Ginger')) {
    * I run 'echo "This should be executed"' locally
    * }
    * if ('${NAME}'.contains('foo')) {
    * I run 'echo "This should NOT be executed"' locally
    * I run 'exit 1' locally
    * }

Warning: use this functionality sparingly, or only in very concrete automation cases. We discourage the creation of tests that could return different results on different runs. Also, this functionality has not been tested on multi-threaded mode, so it may break when running tests in parallel

@ignore tag

The tag ignore can be used before the Feature title to skip the entire feature execution. To skip the particular Scenario also we use the ignore tag but is needed to add the complementary tag explaining this way the reason for the ignore.

Scenario notation Description
@ignore @tillfixed(JIRA_ISSURE-number) TBD functionality. When a bug is found, the QA creates JIRA issue, when the ticket is already solved by the developer, the ticket will be checked by our test account if is resolved the slack message will be generated "delete the tag, the bug ISSUE-number is resolved"
@ignore @notimplemented whenever there is no implementation of some used steps (might be a new step or a failing one)
@ignore @manual When the feature is executed manually and will be rewritten in a near future
@ignore @toocomplex for really complex weigh scenarios, will be removed eventually

You can check the usage of the @ignored tag in the following example:

@jira tag

This functionality is available from GingerSpec 2.2.4+

This tag allows for the conditional execution of scenarios based on the status of the linked entity in Jira. It is also capable of updating the status of the given entity in Jira depending on the result of the scenario

@jira[QMS-990]
Scenario: A new element is inserted via a POST call
    Given I send requests to '${REST_SERVER_HOST}:3000'
    When I send a 'POST' request to '/posts' based on 'schemas/mytestdata.json' as 'json'
    Then the service response status must be '201'
    And I save element '$.title' in environment variable 'TITLE'
    Then '${TITLE}' matches 'This is a test'

For this tag to work, you will have to create the file src/test/resources/jira.properties with the following properties:

jira.server.url=https://my.jira.server
jira.personal.access.token=abcdefg123456
jira.valid.runnable.statuses=DONE,DEPLOYED
jira.transition.if.fail=true
jira.transition.if.fail.status=TO REVIEW

Where:

  • jira.server.url: Base URL of the Jira server
  • jira.personal.access.token: Personal authentication token
  • jira.valid.runnable.statuses: Will run the scenario only if the linked Jira entity is in one of the given statuses (if not specified, defaults to 'DONE,DEPLOYED'. Case insensitive)
  • jira.transition.if.fail: Change linked Jira entity status if scenario fails (if not specified, defaults to 'true')
  • jira.transition.if.fail.status: If jira.transition.if.fail=true, the linked Jira entity will transition to this status (if not specified, defaults to 'TO REVIEW'. Case insensitive)

All variables can also be set with maven variables (i.e -Djira.personal.access.token=abcdefg123456) in case you would like to obfuscate them for privacy reasons

Within the same organization, many people will work with different Jira configurations, so, make sure to include the statuses that are valid for your specific boards and that make sense for your team's workflow since the ones used as default may not apply for your case

Check some examples here

@slack tag

Only available since GingerSpec 2.2.8+

This allows you to send a notification to a Slack channel (or group of slack channels) every time your scenario fails. You can specify only one channel like @slack[#mychannel] or a list of channels separated by commas like @slack[#channel1,#channel2,#channel3]. If the scenario fails, a message will be sent to those channels specifying the name and the location of the scenario that failed

@rest
Feature: Testing a RestFull API

    @slack[#mychannel]
    Scenario: A successful response with a valid body is returned
        Given I securely send requests to 'jsonplaceholder.typicode.com:443'
        When I send a 'GET' request to '/posts'
        Then the service response status must be '200'
        And I save element '$.[0].userId' in environment variable 'USER_ID'
        Then '${USER_ID}' matches '1'

For this tag to work, you will have to create the file src/test/resources/slack.properties with the following properties:

slack.token=abcdefg123456
slack.iconEmoji=:ko:
slack.username=Test report bot

Where:

  • slack.token: Authentication token bearing required scopes. Required.
  • slack.iconEmoji: Emoji to use as the icon for this message. Defaults to :ko: if not specified.
  • slack.username: Set your bot's user name. Defaults to 'Test report bot' if not specified.

Gingerspec also provides a step to directly send a slack message with any given text to a channel or group of channels. Take a look at the following example:

  Scenario: Sending message to a slack channel
    Given I save 'GingerSpec' in variable 'FRAMEWORK'
    Given I send a message to the slack channel '#qms-notifications' with text
    """
    :wave: Hello! You can send any type of text to a given slack channel.
    You can even send variables created during your steps
    Regards, ${FRAMEWORK} :slightly_smiling_face:
    """

This will send a message to the channel #qms-notifications that will look like this:

Default location of screenshots

Check also some examples here

Advance use case

You can directly make use of the SlackConnector class in your Java code to make your own reports and further configure the message that is sent to slack. Let's say that you want to send a slack notification when ALL the tests have been executed and that you want to show in this message a brief summary of the number of tests that passed, failed, or were ignored. To accomplish this you can create an @AfterSuite TestNG annotation in your runner class with the following code:

public class CucumberRunnerIT extends BaseGTest {

        SlackConnector slackConnector = new SlackConnector();

        @AfterSuite
        public void sendSlackNotification(ITestContext test) throws SlackApiException, IOException {
                int passed = test.getPassedTests().getAllResults().size();
                int failed = test.getFailedTests().getAllResults().size();
                int skipped = test.getSkippedTests().getAllResults().size();

                String result = "My custom report: \n"
                        + "-> Test passed: " + passed + "\n"
                        + "-> Test failed: " + failed + "\n"
                        + "-> Test skipped: " + skipped + "\n";

                this.slackConnector.sendMessageToChannels(Collections.singletonList("#qms-notifications"), result);
        }
}

That code will be executed after all tests in this suite have run (all feature files included in the CucumberOptions annotation). Take this just as a very simple example. You can further configure the message to your specific needs

All variables can also be set with maven variables (i.e -Dslack.token=abcdefg123456) in case you would like to obfuscate them for privacy reasons

Only channels are supported (#channel), you cannot send messages directly to a user (@username)

@include tag

Aspect that includes a scenario before the tagged one. Syntax:

@include(feature:<feature>,scenario:<scenario>)
@include(feature:<feature>,scenario:<scenario>,params:<params>)

Examples:

This will execute the scenario "Not_so_dummy_scenario" from the feature file sample.feature before executing "Dummy scenario"

@include(feature:sample.feature,scenario:Not_so_dummy_scenario)
Scenario: Dummy scenario
      And I wait '1' seconds

Similarly, this will execute the scenario "Not_so_dummy_scenario" from the feature file sample.feature but will inject the variables time1=1 and time2=2

@include(feature:sample.feature,scenario:Not_so_dummy_scenario,params:[time1:1,time2:2])
Scenario: Dummy scenario
    And I wait '1' seconds

To make use of the variables time1 and time2 in the scenario "Not_so_dummy_scenario":

Feature: Included Feature
 
    Scenario: Not_so_dummy_scenario
        And I wait '<time1>' seconds
        And I wait '<time2>' seconds

Don't abuse this feature!

You can check the usage of the @include tag in the following example

@runOnEnv tag

RunOnEnv tags permit us to create conditional flows within the feature files before the Scenario. It is represented by @runOnEnv(My_VAR) and @skipOnEnv(MY_VAR); @runOnEnv(MY_VAR) is permitted to run the scenario when MY_VAR (some maven environment variable)

exists (is similar to traditional IF ) and @skipOnEnv(MY_VAR) skips the scenario if MY_VAR doesn't exist (is similar to traditional IF NOT). Those kinds of tags are useful when you want to merge similar feature files with a little different that manifests itself in some different Scenario.

For example:

@runOnEnv(SECS)
Scenario: Dummy scenario
     And I wait '${SECS}' seconds

This scenario will ONLY be executed if the environment variable SECS is defined.

@skipOnEnv(SECS_2)
Scenario: Dummy scenario
    And I wait '${SECS}' seconds

This scenario will be omitted if the environment variable SECS_2 is defined.

Don't abuse this feature!

You can check the usage of the @skipOnEnv and @runOnEnv in the following examples

@loop tag

Allows looping over scenarios. Using this tag before a scenario will convert this scenario into a scenario outline, changing parameter defined "NAME" for every element in the environment variable list received.

@loop(LIST_PARAM,NAME)

Being LIST_PARAM: -DLIST_PARAM=elem1,elem2,elem3

For example:

    @loop(AGENT_LIST,VAR_NAME)
    Scenario: write <VAR_NAME> a file the final result of the scenario.
        Given I run 'echo <VAR_NAME> >> testOutput.txt' locally

or

    @loop(AGENT_LIST,VAR_NAME)
    Scenario: With scenarios outlines and datatables
        Given I create file 'testSOATtag<VAR_NAME.id>.json' based on 'schemas/simple<VAR_NAME>.json' as 'json' with:
            | $.a | REPLACE | ${file:UTF-8:src/test/resources/schemas/empty.json} | object |
     Given I save '${file:UTF-8:target/test-classes/testSOATtag<VAR_NAME.id>.json}' in variable 'VAR'
        Then I run '[ "${VAR}" = "{"a":{}}" ]' locally

or

    @runOnEnv(AGENT_LIST)
    @loop(AGENT_LIST,VAR_NAME)
    Scenario: With scenarios outlines and datatables
      Given I create file 'testSOATtag<VAR_NAME.id>B.json' based on 'schemas/simple<VAR_NAME>.json' as 'json' with:
        | $.a | REPLACE | ${file:UTF-8:src/test/resources/schemas/empty.json} | object |
     Given I save '${file:UTF-8:target/test-classes/testSOATtag<VAR_NAME.id>B.json}' in variable 'VAR'
      Then I run '[ "${VAR}" = "{"a":{}}" ]' locally

Don't abuse this feature!

You can check the usage of the @loop tag in the following examples

@background tag

AspectJ aspect included in loopTagAspect that allows conditional steps to be executed. Can be used inside regular scenarios or inside feature background.

    @background(VAR) // Beginning of conditional block of steps
        Given X
        When Y
        Then Z
    @/background // End of block

Example

    Background:
     Given I run '[ "SHOULD_RUN" = "SHOULD_RUN" ]' locally
    @background(WAIT_NO)
     Given I run '[ "SHOULD_RUN" = "FAIL_RUN" ]' locally
     Given I run '[ "SHOULD_RUN" = "FAIL_RUN" ]' locally
     Given I run '[ "SHOULD_RUN" = "FAIL_RUN" ]' locally
    @/background
     Given I run '[ "SHOULD_RUN" = "SHOULD_RUN" ]' locally

If the test above its executed WITH -DWAIT_NO=value then the background will be:

    Background:
     Given I run '[ "SHOULD_RUN" = "SHOULD_RUN" ]' locally
     Given I run '[ "SHOULD_RUN" = "FAIL_RUN" ]' locally
     Given I run '[ "SHOULD_RUN" = "FAIL_RUN" ]' locally
     Given I run '[ "SHOULD_RUN" = "FAIL_RUN" ]' locally
     Given I run '[ "SHOULD_RUN" = "SHOULD_RUN" ]' locally

On the other hand, if it is executed WITHOUT the environment variable, the background will be:

    Background:
     Given I run '[ "SHOULD_RUN" = "SHOULD_RUN" ]' locally
     Given I run '[ "SHOULD_RUN" = "SHOULD_RUN" ]' locally

The tag can even be used inside tables. For example, use -DAGE to also change the property 'age' on a JSON file

      When I send a 'POST' request to '/rest/myapiendpoint' based on 'schemas/myseedfile.json' as 'json' with:
        | $.name                        | UPDATE | John         |
        | $.lastname                    | UPDATE | Smith        |
    @background(AGE)
        | $.age                         | UPDATE | 20           |
    @/background

In conclusion, if the environment variable is defined the code below the tag would be included as part of the background, if not, it will be omitted.

More examples SOON in src/test/resources/features/ in GingerSpec library.

Don't abuse this feature!

You can check the usage of the @background tag in the following examples

@log tag

This aspect allows the user to print comments from the feature file via CLI when executing the tests. All comments starting with "#log" are considered, otherwise, it is considered a regular comment and will not be printed

Example:

    Scenario: Testing comments via CLI
      #log this is a log comment, SHOULD be printed
      Given I send requests to '${REST_SERVER_HOST}:3000'
      #log comments can make use of variable replacement: REST_SERVER_HOST -> ${REST_SERVER_HOST}
      When I send a 'GET' request to '/posts'
      #This is a regular comment, SHOULD NOT be printed
      Then the service response status must be '200'

You can check the usage of the @log tag in the following examples:

⚠️ **GitHub.com Fallback** ⚠️