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
- @mobile tag
- @sql tag
- @rest tag
- @debug tag
- if statement
- @ignore tag
- @jira tag
- @slack tag
- @include tag
- @runOnEnv tag
- @loop tag
- @background tag
- @log 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
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
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.
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);
}
}
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
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
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:
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
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:
Check also some examples here
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)
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 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
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
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
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: