Step definition¶
To match gherkin steps into python code, we used a pattern matcher.
The definition of the step is written using the syntax of the pattern matcher.
Turşu has two pattern matcher, a default pattern matcher which is documented here,
and a regular expression pattern matcher for even more flexibility, but less readable.
Default pattern matcher¶
The default pattern matcher is based on curly brace to discover variable, and it match a single world.
But if it is enclosed by "
, then it can be a sentence.
matcher |
Python decorator example |
Gherkin usage example |
---|---|---|
{username}
|
@given('a user {username}')
|
Given a user Alice
|
"{username}"
|
@then('the user sees the text "{expected}"')
|
the user sees the text "Welcome Alice"
|
Typing support¶
The default matcher does not enforce the type in the text of the decorator, it use the function signature.
Supported types are: bool
, date
, datetime
, float
, int
, str
, Enum
and Literal[...]
:
boolean¶
Python signature:
@given('Given the feature flag "{feature_flag}" is set to {ff_toggle}')
def toggle_feature_flag(feature_flag: str, ff_toggle: bool):
...
Gherkin example:
Given the feature flag "DARK_MODE" is set to true
Then the system status should be off
Note
True values are
true
,1
,yes
,on
False values are
false
,0
,no
,off
Those values are case insensitive.
Everything else will raise a value error.
date¶
Python signature:
@given("the user's subscription expires on {expires_on}")
def given_expires_on_subscription(expires_on: date):
...
Gherkin example:
Given the user's subscription expires on 2025-12-31
datetime¶
Python signature:
@given("the user's subscription expires at {expires_at}")
def given_expires_at_subscription(expires_at: date):
...
Gherkin example:
Given the user's subscription expires at 2025-12-31T08:00:00Z
float¶
Python signature:
@given("the account balance is {account_balance}")
def toggle_feature_flag(account_balance: float):
...
Gherkin example:
Given the account balance is 99.99
int¶
Python signature:
@then("the cart should contain {items_count} items")
def assert_cart_items_count(feature_flag: int):
...
Gherkin example:
Then the cart should contain 3 items
str¶
Python signature:
@given('Given the user role {role} protected by passphrase "{passphrase}"')
def given_role(role: str, passphrase: str):
...
Gherkin example:
Given the user role admin protected by passphrase "I am feeling lucky"
Enum¶
Python signature:
from enum import Enum
class OrderStatus(Enum):
PENDING = "Pending"
PROCESSING = "Processing"
CANCELLED = "Cancelled"
REFUNDED = "Refunded"
COMPLETED = "Completed"
@given("the order status is {order_status}")
def toggle_feature_flag(order_status: OrderSatus):
...
Gherkin example:
Given the order status is PROCESSING
Note
For enum, the key must be passed, not the value.
Literal¶
Python signature:
from typing import Literal
FeatureFlagName = Literal["dark_mode", "light_mode", "beta_feature"]
@given("the feature flag {feature} is set to {status}")
def set_feature_flag(feature: FeatureFlagName, status: bool):
...
Gherkin example:
Given the feature flag dark_mode is set to on
RegEx pattern matcher¶
This is less readable, but, may be usefull in certain situation,
Regular Expression can be used to match patterns.
First you need to import the RegEx class:
from tursu import RegEx
Afterwhat, the named capturing group syntax has to be used (?P<matched_name>regex_pattern)
:
matcher |
Python decorator example |
Gherkin usage example |
---|---|---|
r"(?P<username>[^\s]+)"
|
@given(
RegEx(r'a user (?P<username>[^\s]+)')
)
|
Given a user Alice
|
r'(?P<expected>[^\"]+)'
|
@then(
RegEx(r'the user sees the text "(?P<expected>[^\"]+)"')
)
|
the user sees the text "Welcome Alice"
|
Complex types from Gherkin DataTable and DocString¶
Regardless of the pattern matcher you use, a step definition can also include a complex type derived directly from Gherkin grammar.
To handle long text or datasets, Gherkin supports two features: doc strings and data tables.
These allow for multiline steps, and we’ll begin with doc strings for JSON support.
DocString¶
Multiline text are not feet for gherkin pattern matcher, so a doc string with the following syntax can be used to capture multiline from a scenario.
As text¶
@then("the page containts the following text:")
def create_user_from_json(page: Page, doc_string: string):
...
Then the user provides the following profile data:
"""
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus.
Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor.
Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper congue, euismod
non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum
diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in,
pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor.
"""
As json¶
@given("the user provides the following profile data:")
def create_user_from_json(doc_string: dict[str, Any]):
...
Given the user provides the following profile data:
"""json
{
"username": "johndoe",
"email": "johndoe@example.com",
"address": {
"street": "123 Main St",
"city": "Anytown",
"postal_code": "12345"
}
}
"""
Tip
When a media type is set to json
("""json
in the Gherkin doc string)
then Turşu will parse the json using the python standard library.
Otherwise, a plain string will be returned.
DataTable¶
Data table are usefull to get tabular data input. You may have multiple input with many row, or a reversed datatable can be used for a single structured input.
Tabular data¶
In a tabula data, the data table, is column based and can be seen filled out like:
Given users with the following informations:
| username | password |
| johndoe | secret123 |
| janedoe | password1 |
and in that case we can use the step definition bellow:
@given("users with the following informations:")
def fill_user_table(data_table: list[dict[str, str]]):
...
Note
data_table looks like
[
{"username": "johndoe", "password": "secret123"},
{"username": "janedoe", "password": "password1"},
]
The type list is very important to tell Turşu that the data table is list base, otherwise, Turşu data table parser will interpret it as a row based data table, also known as reversed data table.
Reversed DataTable¶
A reversed data table is column based. It is a key value per.
And it is ideal to fill a profile with many attributes, here we set two attibutes for brevity.
Given a user with the following informations:
| username | johndoe |
| password | secret123 |
@given("a user with the following informations:")
def fill_user_profile(data_table: dict[str, str]):
...
Note
data_table looks like
{"username": "johndoe", "password": "secret123"},
Because there is no list type annotated, then the Turşu parser will only used the two first column of the table.
Tip
If you set more attributes on the table then they will be ignored. Use them as comment if you want.
Using dataclass or pydantic objects.¶
Gherkin tabular data is nice and can be even nicer with advanced python types, or even faked data, so
Arbitrary type for doc string and data table¶
The step definition signature can be annotated with complex type, using libraries such as dataclasses or pydantic in order to improve the developper experience with type completion and increasing the readability.
from pydantic import BaseModel
class Address(BaseModel):
street: str
city: str
postal_code: str
class UserProfile(BaseModel):
username: str
email: str
address: Address
class User(BaseModel):
username: str
password: str
@given("the user provides the following profile data:")
def create_user_from_json(doc_string: UserProfile):
...
@given("the user provides the following informations:")
def fill_user_table(data_table: list[User]):
...
Given the user provides the following profile data:
"""json
{
"username": "johndoe",
"email": "johndoe@example.com",
"address": {
"street": "123 Main St",
"city": "Anytown",
"postal_code": "12345"
}
}
"""
And the user provides the following informations:
| username | password |
| johndoe | secret123 |
| janedoe | password1 |
Note
doc_string looks like
UserProfile(
username="johndoe",
email="johndoe@example.com",
address=Address(street="123 Main St", city="Anytown", postal_code="12345")
)
data_table looks like
[
User(username="johndoe", password="secret123"),
User(username="janedoe", password="password1"),
]
This will be the subject of the next chapter.