### Install Pytest BDD Source: https://context7.com/pytest-dev/pytest-bdd/llms.txt Use pip to install the framework. ```bash pip install pytest-bdd ``` -------------------------------- ### Scenario Outlines with Examples Source: https://context7.com/pytest-dev/pytest-bdd/llms.txt Demonstrates parameterizing scenarios using `Scenario Outline` and `Examples` tables. Variables are defined with angle brackets ``. ```python from pytest_bdd import scenarios, given, when, then, parsers scenarios("outline.feature") @given(parsers.parse("there are {start:d} cucumbers"), target_fixture="cucumbers") def given_cucumbers(start): return {"count": start} @when(parsers.parse("I eat {eat:d} cucumbers")) def eat_cucumbers(cucumbers, eat): cucumbers["count"] -= eat @then(parsers.parse("I should have {left:d} cucumbers")) def should_have_cucumbers(cucumbers, left): assert cucumbers["count"] == left ``` ```gherkin # outline.feature Feature: Scenario outlines Scenario Outline: Eating cucumbers Given there are cucumbers When I eat cucumbers Then I should have cucumbers @positive Examples: Valid cases | start | eat | left | | 12 | 5 | 7 | | 20 | 5 | 15 | @negative Examples: Edge cases | start | eat | left | | 5 | 5 | 0 | | 10 | 0 | 10 | ``` ```bash # Run only positive examples pytest -m "positive" # Run only negative examples pytest -m "negative" ``` -------------------------------- ### Python Step Definitions for Multiple Example Tables Source: https://github.com/pytest-dev/pytest-bdd/blob/master/docs/index.md Python step definitions for a scenario outline featuring multiple example tables. It uses `parsers.parse` for parameter extraction and manages state through fixtures. ```python from pytest_bdd import scenarios, given, when, then, parsers scenarios("scenario_outline.feature") @given(parsers.parse("there are {start:d} cucumbers"), target_fixture="cucumbers") def given_cucumbers(start): return {"start": start, "eat": 0} @when(parsers.parse("I eat {eat:d} cucumbers")) def eat_cucumbers(cucumbers, eat): cucumbers["eat"] += eat @then(parsers.parse("I should have {left:d} cucumbers")) def should_have_left_cucumbers(cucumbers, left): assert cucumbers["start"] - cucumbers["eat"] == left ``` -------------------------------- ### Implement Background Steps in Python Source: https://context7.com/pytest-dev/pytest-bdd/llms.txt Define setup steps that execute before each scenario in a feature file. ```python from pytest_bdd import scenarios, given, when, then scenarios("multi_user.feature") @given('a global administrator named "Greg"', target_fixture="admin") def global_admin(): return {"name": "Greg", "role": "admin"} @given('a blog named "Greg\'s anti-tax rants"') def blog_gregs(admin, blogs): blogs.append({"name": "Greg's anti-tax rants", "owner": admin["name"]}) @given('a customer named "Wilson"', target_fixture="customer") def customer_wilson(): return {"name": "Wilson", "role": "customer"} @given('a blog named "Expensive Therapy" owned by "Wilson"') def blog_therapy(customer, blogs): blogs.append({"name": "Expensive Therapy", "owner": customer["name"]}) @given("I am logged in as Wilson") def logged_as_wilson(customer, session): session["user"] = customer @when('I try to post to "Expensive Therapy"') def post_to_blog(session, blogs): blog = next(b for b in blogs if b["name"] == "Expensive Therapy") return {"success": session["user"]["name"] == blog["owner"]} @then('I should see "Your article was published."') def see_published(post_result): assert post_result["success"] ``` -------------------------------- ### Define Preconditions with @given Source: https://context7.com/pytest-dev/pytest-bdd/llms.txt Use @given to define setup steps, optionally using target_fixture to pass data to other steps. ```python from pytest_bdd import given, when, then import pytest # Basic given step @given("I am on the login page") def on_login_page(browser): browser.visit('/login') # Given step that creates a fixture @given("I have a user account", target_fixture="user") def user_account(): return {"username": "john_doe", "email": "john@example.com", "balance": 100} # Given step with multiple aliases @given("I have a wallet") @given("there is a wallet") def wallet(): return {"eur": 0, "usd": 0, "gbp": 0} # Given step that depends on fixtures @given("the user has admin privileges") def admin_user(user): user["role"] = "admin" return user # Reusing an existing pytest fixture @pytest.fixture def database(): db = create_test_database() yield db db.cleanup() @given("I have a database connection") def database_connection(database): """Reuse the pytest fixture in the given step.""" assert database.is_connected ``` -------------------------------- ### Scenario Outlines with Multiple Example Tables Source: https://github.com/pytest-dev/pytest-bdd/blob/master/docs/index.md This Gherkin feature file demonstrates using multiple 'Examples' tables within a single scenario outline. Each table can be tagged for selective execution. ```gherkin # content of scenario_outline.feature Feature: Scenario outlines with multiple examples tables Scenario Outline: Outlined with multiple example tables Given there are cucumbers When I eat cucumbers Then I should have cucumbers @positive Examples: Positive results | start | eat | left | | 12 | 5 | 7 | | 5 | 4 | 1 | @negative Examples: Impossible negative results | start | eat | left | | 3 | 9 | -6 | | 1 | 4 | -3 | ``` -------------------------------- ### Define scenarios with aliases Source: https://github.com/pytest-dev/pytest-bdd/blob/master/docs/index.md Example of using different step names within a feature file. ```gherkin Feature: Resource owner Scenario: I'm the author Given I'm an author And I have an article Scenario: I'm the admin Given I'm the admin And there's an article ``` -------------------------------- ### Configure Step Argument Parsers Source: https://github.com/pytest-dev/pytest-bdd/blob/master/README.rst Examples of using cfparse and re parsers to handle step arguments with type conversion. ```python from pytest_bdd import parsers @given( parsers.cfparse("there are {start:Number} cucumbers", extra_types={"Number": int}), target_fixture="cucumbers", ) def given_cucumbers(start): return {"start": start, "eat": 0} ``` ```python from pytest_bdd import parsers @given( parsers.re(r"there are (?P\d+) cucumbers"), converters={"start": int}, target_fixture="cucumbers", ) def given_cucumbers(start): return {"start": start, "eat": 0} ``` -------------------------------- ### Basic Scenario with Parsed Step Arguments Source: https://github.com/pytest-dev/pytest-bdd/blob/master/docs/index.md A complete example demonstrating the use of parsers.parse with integer type conversion for given, when, and then steps. Includes scenario loading and step implementations. ```gherkin Feature: Step arguments Scenario: Arguments for given, when, then Given there are 5 cucumbers When I eat 3 cucumbers And I eat 2 cucumbers Then I should have 0 cucumbers ``` ```python from pytest_bdd import scenarios, given, when, then, parsers scenarios("arguments.feature") @given(parsers.parse("there are {start:d} cucumbers"), target_fixture="cucumbers") def given_cucumbers(start): return {"start": start, "eat": 0} @when(parsers.parse("I eat {eat:d} cucumbers")) def eat_cucumbers(cucumbers, eat): cucumbers["eat"] += eat @then(parsers.parse("I should have {left:d} cucumbers")) def should_have_left_cucumbers(cucumbers, left): assert cucumbers["start"] - cucumbers["eat"] == left ``` -------------------------------- ### Article publishing scenario with fixture Source: https://github.com/pytest-dev/pytest-bdd/blob/master/README.rst A Gherkin scenario for publishing an article, using a fixture for setup. ```gherkin Feature: News website Scenario: Publishing an article Given I have a beautiful article And my article is published ``` -------------------------------- ### Gherkin Scenario Outline with Examples Source: https://github.com/pytest-dev/pytest-bdd/blob/master/README.rst Defines a scenario outline in Gherkin with a single set of examples for parameterization. ```gherkin # content of scenario_outlines.feature Feature: Scenario outlines Scenario Outline: Outlined given, when, then Given there are cucumbers When I eat cucumbers Then I should have cucumbers Examples: | start | eat | left | | 12 | 5 | 7 | ``` -------------------------------- ### Implementing a Custom Step Parser Source: https://github.com/pytest-dev/pytest-bdd/blob/master/docs/index.md Provides an example of creating a custom step parser by extending parsers.StepParser. This custom parser uses regular expressions with a custom placeholder syntax. ```python import re from pytest_bdd import given, parsers class MyParser(parsers.StepParser): """Custom parser.""" def __init__(self, name, **kwargs): """Compile regex.""" super().__init__(name) self.regex = re.compile(re.sub("%(.+)%", "(?P<\1>.+)", self.name), **kwargs) def parse_arguments(self, name): """Get step arguments. :return: `dict` of step arguments """ return self.regex.match(name).groupdict() def is_matching(self, name): """Match given name with the step name.""" return bool(self.regex.match(name)) @given(parsers.parse("there are %start% cucumbers"), target_fixture="cucumbers") def given_cucumbers(start): return {"start": start, "eat": 0} ``` -------------------------------- ### Update example converters to step-level converters Source: https://github.com/pytest-dev/pytest-bdd/blob/master/README.rst Replaces the deprecated example_converters parameter in scenario decorators with step-level converters using parsers. ```python # Old code: @given("there are cucumbers") def given_cucumbers(start): return {"start": start} @scenario("outline.feature", "Outlined", example_converters={"start": float}) def test_outline(): pass # New code: @given(parsers.parse("there are {start} cucumbers"), converters={"start": float}) def given_cucumbers(start): return {"start": start} @scenario("outline.feature", "Outlined") def test_outline(): pass ``` -------------------------------- ### Define Gherkin scenarios Source: https://github.com/pytest-dev/pytest-bdd/blob/master/README.rst Example feature file defining scenarios for wallet currency validation. ```gherkin # contents of wallet.feature Feature: A feature Scenario: Wallet EUR amount stays constant Given I have 10 EUR in my wallet And I have a wallet Then I should have 10 EUR in my wallet Scenario: Second wallet JPY amount stays constant Given I have 100 JPY in my second wallet And I have a second wallet Then I should have 100 JPY in my second wallet ``` -------------------------------- ### Gherkin Docstring Example Source: https://github.com/pytest-dev/pytest-bdd/blob/master/README.rst Illustrates the structure of a Gherkin docstring with multiple lines and varying indentation. ```gherkin """ This is a sample docstring. It spans multiple lines. """ ``` ```gherkin """ This is a docstring on two lines """ ``` ```gherkin """ This is a docstring """ ``` ```gherkin """ This is a docstring """ ``` -------------------------------- ### Wallet Feature File Example Source: https://github.com/pytest-dev/pytest-bdd/blob/master/docs/index.md Defines Gherkin scenarios for testing wallet amounts. This feature file should be placed in a location discoverable by pytest. ```gherkin Feature: A feature Scenario: Wallet EUR amount stays constant Given I have 10 EUR in my wallet And I have a wallet Then I should have 10 EUR in my wallet Scenario: Second wallet JPY amount stays constant Given I have 100 JPY in my second wallet And I have a second wallet Then I should have 100 JPY in my second wallet ``` -------------------------------- ### Generated Test and Step Definition Example Source: https://github.com/pytest-dev/pytest-bdd/blob/master/README.rst This output shows an example of code generated by pytest-bdd for missing scenarios and steps. It includes the necessary decorators and function stubs. ```python @scenario('tests/generation.feature', 'Code is generated for scenarios which are not bound to any tests') def test_Code_is_generated_for_scenarios_which_are_not_bound_to_any_tests(): """Code is generated for scenarios which are not bound to any tests.""" @given("I have a custom bar") def I_have_a_custom_bar(): """I have a custom bar.""" ``` -------------------------------- ### Define Gherkin Rules and Examples Source: https://github.com/pytest-dev/pytest-bdd/blob/master/docs/index.md Use Rules to group related scenarios and apply tags to all contained examples. ```gherkin Feature: Rules and examples @feature_tag Rule: A rule for valid cases @rule_tag Example: Valid case 1 Given I have a valid input When I process the input Then the result should be successful Rule: A rule for invalid cases Example: Invalid case Given I have an invalid input When I process the input Then the result should be an error ``` -------------------------------- ### Handling Empty Example Cells with Custom Converters Source: https://github.com/pytest-dev/pytest-bdd/blob/master/docs/index.md This Gherkin feature file demonstrates handling empty cells in example tables by using a custom converter with `parsers.re` to interpret empty cells as `None`. ```gherkin # content of empty_example_cells.feature Feature: Handling empty example cells Scenario Outline: Using converters for empty cells Given I am starting lunch Then there are cucumbers Examples: | start | | | ``` -------------------------------- ### Define Backgrounds in Gherkin Source: https://context7.com/pytest-dev/pytest-bdd/llms.txt Use the Background keyword to share common setup steps across scenarios. ```gherkin # multi_user.feature Feature: Multiple site support Background: Given a global administrator named "Greg" And a blog named "Greg's anti-tax rants" And a customer named "Wilson" And a blog named "Expensive Therapy" owned by "Wilson" Scenario: Wilson posts to his own blog Given I am logged in as Wilson When I try to post to "Expensive Therapy" Then I should see "Your article was published." Scenario: Wilson cannot post to Greg's blog Given I am logged in as Wilson When I try to post to "Greg's anti-tax rants" Then I should see "Access denied." ``` -------------------------------- ### Automatically Bind Scenarios from Feature Files in Python Source: https://github.com/pytest-dev/pytest-bdd/blob/master/README.rst Use the `scenarios` helper to automatically bind all scenarios found in specified feature file paths. This simplifies test setup when dealing with many scenarios. ```python from pytest_bdd import scenarios # assume 'features' subfolder is in this file's directory scenarios('features') ``` -------------------------------- ### Define a Given step with a target fixture Source: https://github.com/pytest-dev/pytest-bdd/blob/master/docs/index.md Use the `@given` decorator with `target_fixture` to define a setup step that provides a PyTest fixture. This fixture can then be injected into other steps. ```python from pytest_bdd import given class Article: def __init__(self, is_beautiful=False): self.is_beautiful = is_beautiful @given("I have a beautiful article", target_fixture="article") def article(): return Article(is_beautiful=True) ``` -------------------------------- ### Filtering Scenarios with Tags Source: https://github.com/pytest-dev/pytest-bdd/blob/master/docs/index.md Example bash command to filter and run scenarios based on tags. This command will only execute scenarios associated with the '@positive' tag. ```bash pytest -k "positive" ``` -------------------------------- ### Define a Gherkin Background Source: https://github.com/pytest-dev/pytest-bdd/blob/master/docs/index.md Use the `Background` keyword in Gherkin to define common setup steps that will be executed before each scenario in the feature file. Only `Given` steps are allowed in `Background`. ```gherkin Feature: Multiple site support Background: Given a global administrator named "Greg" And a blog named "Greg's anti-tax rants" And a customer named "Wilson" And a blog named "Expensive Therapy" owned by "Wilson" Scenario: Wilson posts to his own blog Given I am logged in as Wilson When I try to post to "Expensive Therapy" Then I should see "Your article was published." Scenario: Greg posts to a client's blog Given I am logged in as Greg When I try to post to "Expensive Therapy" Then I should see "Your article was published." ``` -------------------------------- ### Use When and Then Steps to Provide Fixtures in Python Source: https://github.com/pytest-dev/pytest-bdd/blob/master/README.rst Utilize `@when` and `@then` decorators with `target_fixture` to provide fixtures, commonly used for asserting outcomes of operations like HTTP requests. This example demonstrates asserting a successful HTTP deletion request. ```python # content of test_blog.py from pytest_bdd import scenarios, given, when, then from my_app.models import Article scenarios("blog.feature") @given("there is an article", target_fixture="article") def there_is_an_article(): return Article() @when("I request the deletion of the article", target_fixture="request_result") def there_should_be_a_new_article(article, http_client): return http_client.delete(f"/articles/{article.uid}") @then("the request should be successful") def article_is_published(request_result): assert request_result.status_code == 200 ``` -------------------------------- ### Customizing Tag Conversion with pytest_bdd_apply_tag Hook Source: https://github.com/pytest-dev/pytest-bdd/blob/master/README.rst Provides a Python example of implementing the pytest_bdd_apply_tag hook to customize how Gherkin tags are converted into pytest markers. ```python def pytest_bdd_apply_tag(tag, function): # implement tag conversion logic here return True ``` -------------------------------- ### Define a scenario with conftest steps Source: https://github.com/pytest-dev/pytest-bdd/blob/master/docs/index.md Demonstrates a scenario that collects step definitions from a parent conftest.py file. ```python # content of test_common.py @scenario("common_steps.feature", "All steps are declared in the conftest") def test_conftest(): pass ``` -------------------------------- ### Provide fixtures from steps Source: https://github.com/pytest-dev/pytest-bdd/blob/master/docs/index.md Steps like when and then can provide fixtures, useful for asserting outcomes of operations like HTTP requests. ```python # content of test_blog.py from pytest_bdd import scenarios, given, when, then from my_app.models import Article scenarios("blog.feature") @given("there is an article", target_fixture="article") def there_is_an_article(): return Article() @when("I request the deletion of the article", target_fixture="request_result") def there_should_be_a_new_article(article, http_client): return http_client.delete(f"/articles/{article.uid}") @then("the request should be successful") def article_is_published(request_result): assert request_result.status_code == 200 ``` ```gherkin # content of blog.feature Feature: Blog Scenario: Deleting the article Given there is an article When I request the deletion of the article Then the request should be successful ``` -------------------------------- ### Using cfparse for Step Arguments Source: https://github.com/pytest-dev/pytest-bdd/blob/master/docs/index.md Demonstrates how to use the cfparse parser for step arguments with type conversion. Requires importing parsers and specifying extra_types. ```python from pytest_bdd import parsers @given( parsers.cfparse("there are {start:Number} cucumbers", extra_types={"Number": int}), target_fixture="cucumbers", ) def given_cucumbers(start): return {"start": start, "eat": 0} ``` -------------------------------- ### Generated step definition output Source: https://context7.com/pytest-dev/pytest-bdd/llms.txt Example of the boilerplate code generated by the pytest-bdd CLI. ```python # Example generated output from: pytest-bdd generate features/login.feature from pytest_bdd import scenarios, given, when, then scenarios('features/login.feature') @given('a registered user') def a_registered_user(): """a registered user.""" raise NotImplementedError @when('they enter valid credentials') def they_enter_valid_credentials(): """they enter valid credentials.""" raise NotImplementedError @then('they should be logged in') def they_should_be_logged_in(): """they should be logged in.""" raise NotImplementedError ``` -------------------------------- ### Generate dynamic step definitions in Python Source: https://github.com/pytest-dev/pytest-bdd/blob/master/README.rst Creates reusable step definitions for wallet fixtures by iterating over dataclass fields. ```python # contents of wallet_steps.py import re from dataclasses import fields from pytest_bdd import given, then, parsers def generate_wallet_steps(model_name="wallet", stacklevel=1): stacklevel += 1 human_name = model_name.replace("_", " ") # "second_wallet" -> "second wallet" @given(f"I have a {human_name}", target_fixture=model_name, stacklevel=stacklevel) def _(request): return request.getfixturevalue(model_name) # Generate steps for currency fields: for field in fields(Wallet): match = re.fullmatch(r"amount_(?P[a-z]{3})", field.name) if not match: continue currency = match["currency"] @given( parsers.parse(f"I have {{value:d}} {currency.upper()} in my {human_name}"), target_fixture=f"{model_name}__amount_{currency}", stacklevel=stacklevel, ) def _(value: int) -> int: return value @then( parsers.parse(f"I should have {{value:d}} {currency.upper()} in my {human_name}"), stacklevel=stacklevel, ) def _(request, value: int, _currency=currency, _model_name=model_name) -> None: wallet = request.getfixturevalue(_model_name) assert getattr(wallet, f"amount_{_currency}") == value # Inject the steps into the current module generate_wallet_steps("wallet") generate_wallet_steps("second_wallet") ``` -------------------------------- ### Define BDD steps and scenarios Source: https://github.com/pytest-dev/pytest-bdd/blob/master/docs/index.md Demonstrates using parsers for step arguments and declaring a scenario from a feature file. ```python @given(parsers.parse("there are {start} cucumbers"), converters={"start": float}) def given_cucumbers(start): return {"start": start} @scenario("outline.feature", "Outlined") def test_outline(): pass ``` -------------------------------- ### Auto-bind Scenarios with scenarios() Source: https://context7.com/pytest-dev/pytest-bdd/llms.txt Automatically discover and bind all scenarios from specified directories or files. ```python # test_all_features.py from pytest_bdd import scenarios, given, when, then # Automatically bind all scenarios from the 'features' directory scenarios('features') # Or specify multiple paths (files and/or directories) scenarios('features', 'other_features/login.feature', 'integration_features') # Step definitions are shared across all auto-bound scenarios @given("I have a user account") def user_account(): return create_user(username="testuser", password="secret123") @when("I log in with valid credentials") def login_valid(user_account, browser): browser.fill('username', user_account.username) browser.fill('password', 'secret123') browser.find_by_css('button[type=submit]').click() @then("I should see the dashboard") def see_dashboard(browser): assert browser.is_element_present_by_css('.dashboard') ``` -------------------------------- ### Build and Upload Release Source: https://github.com/pytest-dev/pytest-bdd/blob/master/CONTRIBUTING.md Commands to build distribution packages and upload them to PyPI using twine. ```shell python -m pip install --upgrade build twine # cleanup the ./dist folder rm -rf ./dist # Build the distributions python -m build # Upload them twine upload dist/* ``` -------------------------------- ### Using re Parser for Step Arguments Source: https://github.com/pytest-dev/pytest-bdd/blob/master/docs/index.md Shows how to use the re parser with named groups for step arguments and type conversion via the converters argument. Requires importing parsers. ```python from pytest_bdd import parsers @given( parsers.re(r"there are (?P\d+) cucumbers"), converters={"start": int}, target_fixture="cucumbers", ) def given_cucumbers(start): return {"start": start, "eat": 0} ``` -------------------------------- ### Implement scenario outlines Source: https://github.com/pytest-dev/pytest-bdd/blob/master/docs/index.md Parametrize scenarios using angular brackets in Gherkin and parsers in Python to handle multiple test cases. ```gherkin # content of scenario_outlines.feature Feature: Scenario outlines Scenario Outline: Outlined given, when, then Given there are cucumbers When I eat cucumbers Then I should have cucumbers Examples: | start | eat | left | | 12 | 5 | 7 | ``` ```python from pytest_bdd import scenarios, given, when, then, parsers scenarios("scenario_outlines.feature") @given(parsers.parse("there are {start:d} cucumbers"), target_fixture="cucumbers") def given_cucumbers(start): return {"start": start, "eat": 0} @when(parsers.parse("I eat {eat:d} cucumbers")) def eat_cucumbers(cucumbers, eat): cucumbers["eat"] += eat @then(parsers.parse("I should have {left:d} cucumbers")) def should_have_left_cucumbers(cucumbers, left): assert cucumbers["start"] - cucumbers["eat"] == left ``` -------------------------------- ### Generate step definitions via CLI Source: https://context7.com/pytest-dev/pytest-bdd/llms.txt Automate the creation of step definition files from feature files. ```bash # Generate test code for a feature file pytest-bdd generate features/login.feature > tests/test_login.py # Generate code for multiple features pytest-bdd generate features/*.feature > tests/test_features.py # Check for missing step definitions during test run pytest --generate-missing --feature features tests/ ``` -------------------------------- ### Use Gherkin Tags for Test Filtering Source: https://github.com/pytest-dev/pytest-bdd/blob/master/docs/index.md Example of Gherkin syntax using tags for categorizing features and scenarios. These tags are converted to pytest markers. ```gherkin @login @backend Feature: Login @successful Scenario: Successful login ``` -------------------------------- ### Override feature base directory in scenarios Source: https://github.com/pytest-dev/pytest-bdd/blob/master/docs/index.md Shows how to use the default base directory and how to override it per-scenario using the features_base_dir parameter. ```python from pytest_bdd import scenario @scenario("foo.feature", "Foo feature in features/foo.feature") def test_foo(): pass @scenario( "foo.feature", "Foo feature in tests/local-features/foo.feature", features_base_dir="./local-features/", ) def test_foo_local(): pass ``` -------------------------------- ### Implement Custom Tag Application Hook Source: https://github.com/pytest-dev/pytest-bdd/blob/master/docs/index.md Implement the `pytest_bdd_apply_tag` hook to customize how Gherkin tags are converted into pytest markers. This example skips tests tagged with 'todo'. ```python def pytest_bdd_apply_tag(tag, function): if tag == 'todo': marker = pytest.mark.skip(reason="Not implemented yet") marker(function) return True else: # Fall back to the default behavior of pytest-bdd return None ``` -------------------------------- ### Execute scenarios in test file Source: https://github.com/pytest-dev/pytest-bdd/blob/master/README.rst Integrates factory fixtures and step definitions to run Gherkin scenarios. ```python # contents of test_wallet.py from pytest_factoryboy import scenarios from wallet_factory import * # import the registered fixtures "wallet" and "second_wallet" from wallet_steps import * # import all the step definitions into this test file scenarios("wallet.feature") ``` -------------------------------- ### Inject fixtures into steps Source: https://github.com/pytest-dev/pytest-bdd/blob/master/docs/index.md Shows standard dependency injection for steps by declaring required fixtures as function arguments. ```python @given("there's an article") def there_is_an_article(article): pass ``` -------------------------------- ### Filter Tests Using Pytest Markers Source: https://github.com/pytest-dev/pytest-bdd/blob/master/docs/index.md Command-line example demonstrating how to use pytest markers to select tests based on Gherkin tags. The '@' symbol is automatically stripped. ```bash pytest -m "backend and login and successful" ``` -------------------------------- ### scenarios() - Auto-bind All Scenarios from Feature Files Source: https://context7.com/pytest-dev/pytest-bdd/llms.txt The `scenarios()` function automatically discovers and binds all scenarios from specified feature files or directories. ```APIDOC ## scenarios() - Auto-bind All Scenarios from Feature Files ### Description The `scenarios()` function automatically discovers and binds all scenarios from specified feature files or directories, eliminating the need for individual `@scenario` decorators. ### Example Usage ```python # test_all_features.py from pytest_bdd import scenarios, given, when, then # Automatically bind all scenarios from the 'features' directory scenarios('features') # Or specify multiple paths (files and/or directories) scenarios('features', 'other_features/login.feature', 'integration_features') # Step definitions are shared across all auto-bound scenarios @given("I have a user account") def user_account(): return create_user(username="testuser", password="secret123") @when("I log in with valid credentials") def login_valid(user_account, browser): browser.fill('username', user_account.username) browser.fill('password', 'secret123') browser.find_by_css('button[type=submit]').click() @then("I should see the dashboard") def see_dashboard(browser): assert browser.is_element_present_by_css('.dashboard') ``` ``` -------------------------------- ### Running Scenarios with Tags Source: https://context7.com/pytest-dev/pytest-bdd/llms.txt Demonstrates how to use pytest's `-m` option to run scenarios based on specific tags defined in feature files. ```APIDOC ## Run scenarios with specific tags ### Description Pytest allows you to filter tests based on markers (tags). You can use the `-m` command-line option to execute only those scenarios that have the specified tags. ### Examples **Run all scenarios tagged with `smoke`:** ```bash pytest -m "smoke" ``` **Run scenarios tagged with `critical` but not `slow`:** ```bash pytest -m "critical and not slow" ``` **Run scenarios tagged with `authentication` but not `todo`:** ```bash pytest -m "authentication and not todo" ``` ``` -------------------------------- ### Simplify scenario definitions with functools.partial Source: https://github.com/pytest-dev/pytest-bdd/blob/master/docs/index.md Uses partial to fix the feature file path, reducing repetition when defining multiple scenarios. ```python # content of test_publish_article.py from functools import partial import pytest_bdd scenario = partial(pytest_bdd.scenario, "/path/to/publish_article.feature") @scenario("Publishing the article") def test_publish(): pass @scenario("Publishing the article as unprivileged user") def test_publish_unprivileged(): pass ``` -------------------------------- ### Use Parameters in Docstrings and Datatables Source: https://github.com/pytest-dev/pytest-bdd/blob/master/docs/index.md This snippet shows how to use parameters within docstrings and datatables in a pytest-bdd feature file. The parameters are defined in the 'Examples' section and can be referenced within the docstring and datatable. ```gherkin Feature: Docstring and Datatable with example parameters Scenario Outline: Using parameters in docstrings and datatables Given the following configuration: """ username: password: """ When the user logs in Then the response should contain: | field | value | | username | | | logged_in | true | Examples: | username | password | | user1 | pass123 | | user2 | 123secure | ``` -------------------------------- ### Article publishing scenario Source: https://github.com/pytest-dev/pytest-bdd/blob/master/README.rst A Gherkin scenario demonstrating the use of the 'article' fixture across steps. ```gherkin Feature: The power of PyTest Scenario: Symbolic name across steps Given I have a beautiful article When I publish this article ``` -------------------------------- ### Combine PyTest fixtures with pytest-bdd steps Source: https://github.com/pytest-dev/pytest-bdd/blob/master/docs/index.md Integrate existing PyTest fixtures directly into your pytest-bdd tests. This allows you to reuse setup logic and apply side effects to fixtures within your BDD steps. ```python import pytest from pytest_bdd import given class Article: def __init__(self, is_beautiful=False): self.is_beautiful = is_beautiful def publish(self): print("Article published") @pytest.fixture def article(): return Article(is_beautiful=True) @given("I have a beautiful article") def i_have_a_beautiful_article(article): pass @given("my article is published") def published_article(article): article.publish() return article ``` -------------------------------- ### Map Feature Files to Test Files Source: https://github.com/pytest-dev/pytest-bdd/blob/master/docs/index.md Structure your test files separately from feature files. This example shows how to map feature files to Python test files using pytest-bdd's scenario decorator. ```python tests │ └──functional │ └──test_auth.py │ └ """Authentication tests.""" from pytest_bdd import scenario @scenario('frontend/auth/login.feature') def test_logging_in_frontend(): pass @scenario('backend/auth/login.feature') def test_logging_in_backend(): pass ``` -------------------------------- ### Python Step Definition for Empty Cell Conversion Source: https://github.com/pytest-dev/pytest-bdd/blob/master/docs/index.md Python step definitions that include a custom converter function (`empty_to_none`) to handle empty cells in example tables. The converter returns `None` for empty strings, which is then asserted in the step. ```python from pytest_bdd import then, parsers # Define a converter that returns None for empty strings def empty_to_none(value): return None if value.strip() == "" else value @given("I am starting lunch") def _(): pass @then( parsers.re("there are (?P.*?) cucumbers"), converters={"start": empty_to_none} ) def _(start): # Example assertion to demonstrate the conversion assert start is None ``` -------------------------------- ### Gherkin Scenario Outline with Docstring and Datatable Parameters Source: https://github.com/pytest-dev/pytest-bdd/blob/master/README.rst Demonstrates using parameters within Gherkin docstrings and datatables in a scenario outline. ```gherkin # content of docstring_and_datatable_with_params.feature Feature: Docstring and Datatable with example parameters Scenario Outline: Using parameters in docstrings and datatables Given the following configuration: """ username: password: """ When the user logs in Then the response should contain: | field | value | | username | | | logged_in | true | Examples: | username | password | | user1 | pass123 | | user2 | 123secure | ``` -------------------------------- ### Implement Docstring Step Definitions Source: https://github.com/pytest-dev/pytest-bdd/blob/master/docs/index.md Access multiline docstrings in step definitions using the docstring argument. ```gherkin Feature: Docstring Scenario: Step with docstrings Given some steps will have docstrings Then a step has a docstring """ This is a docstring on two lines """ And a step provides a docstring with lower indentation """ This is a docstring """ And this step has no docstring And this step has a greater indentation """ This is a docstring """ And this step has no docstring ``` ```python from pytest_bdd import given, then @given("some steps will have docstrings") def _(): pass @then("a step has a docstring") def _(docstring): assert docstring == "This is a docstring\non two lines" @then("a step provides a docstring with lower indentation") def _(docstring): assert docstring == "This is a docstring" @then("this step has a greater indentation") def _(docstring): assert docstring == "This is a docstring" @then("this step has no docstring") def _(): pass ``` -------------------------------- ### Configuration (pytest.ini) Source: https://context7.com/pytest-dev/pytest-bdd/llms.txt Explains how to configure pytest-bdd behavior using the `pytest.ini` file, such as setting the base directory for feature files and registering custom markers. ```APIDOC ## Configuration (pytest.ini) Configure pytest-bdd behavior via pytest configuration files. ### Description Centralize your pytest-bdd settings in `pytest.ini` for consistent test execution. This includes specifying the location of your feature files and defining custom markers for tags. ### `pytest.ini` Example ```ini # pytest.ini [pytest] # Set the base directory for feature files (relative to pytest rootdir) bdd_features_base_dir = features/ # Register custom markers for tags markers = smoke: Quick smoke tests slow: Slow running tests integration: Integration tests wip: Work in progress ``` ### Usage with `scenario` decorator ```python # tests/test_example.py from pytest_bdd import scenario # With bdd_features_base_dir set, paths are relative to features/ @scenario("login.feature", "Successful login") # Resolves to features/login.feature def test_login(): pass ``` ``` -------------------------------- ### Configure feature file base directory Source: https://github.com/pytest-dev/pytest-bdd/blob/master/README.rst Set the base directory for feature files in configuration files or override it per-scenario. ```ini [pytest] bdd_features_base_dir = features/ ``` ```python from pytest_bdd import scenario @scenario("foo.feature", "Foo feature in features/foo.feature") def test_foo(): pass @scenario( "foo.feature", "Foo feature in tests/local-features/foo.feature", features_base_dir="./local-features/", ) def test_foo_local(): pass ``` -------------------------------- ### Define common steps in conftest.py Source: https://github.com/pytest-dev/pytest-bdd/blob/master/README.rst Steps defined in a parent conftest.py are automatically available to child test files. ```gherkin # content of common_steps.feature Scenario: All steps are declared in the conftest Given I have a bar Then bar should have value "bar" ``` ```python # content of conftest.py from pytest_bdd import given, then @given("I have a bar", target_fixture="bar") def bar(): return "bar" @then('bar should have value "bar"') def bar_is_bar(bar): assert bar == "bar" ``` ```python # content of test_common.py @scenario("common_steps.feature", "All steps are declared in the conftest") def test_conftest(): pass ``` -------------------------------- ### Define data models for programmatic step generation Source: https://github.com/pytest-dev/pytest-bdd/blob/master/README.rst Define dataclasses and factories to support automated fixture and step generation. ```python # contents of wallet.py from dataclasses import dataclass @dataclass class Wallet: verified: bool amount_eur: int amount_usd: int amount_gbp: int amount_jpy: int ``` ```python # contents of wallet_factory.py from wallet import Wallet import factory from pytest_factoryboy import register class WalletFactory(factory.Factory): class Meta: model = Wallet verified = False amount_eur = 0 amount_usd = 0 amount_gbp = 0 amount_jpy = 0 register(Wallet) # creates the "wallet" fixture register(Wallet, "second_wallet") # creates the "second_wallet" fixture ``` -------------------------------- ### Optional Parameter with cfparse Source: https://context7.com/pytest-dev/pytest-bdd/llms.txt Demonstrates using an optional parameter with `parsers.cfparse`. The parameter `color` can be `None` if not provided in the step. ```python from pytest_bdd import when, parsers @when( parsers.cfparse( "I set color to {color:Color?}", extra_types={"Color": str} ) ) def set_color(color): # color can be None if not provided return color or "default" ``` -------------------------------- ### Migrate step definitions from 4.x.x Source: https://github.com/pytest-dev/pytest-bdd/blob/master/docs/index.md Update step definitions to use parsers instead of direct template syntax. ```python # Old step definition: @given("there are cucumbers") def given_cucumbers(start): pass # New step definition: @given(parsers.parse("there are {start} cucumbers")) def given_cucumbers(start): pass ``` ```python # Old code: @given("there are cucumbers") def given_cucumbers(start): return {"start": start} @scenario("outline.feature", "Outlined", example_converters={"start": float}) def test_outline(): pass ``` -------------------------------- ### Generate Wallet Steps Dynamically Source: https://github.com/pytest-dev/pytest-bdd/blob/master/docs/index.md Dynamically generates pytest-bdd Given and Then steps for wallet amounts based on model name and currency fields. Ensure the Wallet model is defined elsewhere. ```python import re from dataclasses import fields from pytest_bdd import given, then, parsers def generate_wallet_steps(model_name="wallet", stacklevel=1): stacklevel += 1 human_name = model_name.replace("_", " ") # "second_wallet" -> "second wallet" @given(f"I have a {human_name}", target_fixture=model_name, stacklevel=stacklevel) def _(request): return request.getfixturevalue(model_name) # Generate steps for currency fields: for field in fields(Wallet): match = re.fullmatch(r"amount_(?P[a-z]{3})", field.name) if not match: continue currency = match["currency"] @given( parsers.parse(f"I have {{value:d}} {currency.upper()} in my {human_name}"), target_fixture=f"{model_name}__amount_{currency}", stacklevel=stacklevel, ) def _(value: int) -> int: return value @then( parsers.parse(f"I should have {{value:d}} {currency.upper()} in my {human_name}"), stacklevel=stacklevel, ) def _(request, value: int, _currency=currency, _model_name=model_name) -> None: wallet = request.getfixturevalue(_model_name) assert getattr(wallet, f"amount_{_currency}") == value # Inject the steps into the current module generate_wallet_steps("wallet") generate_wallet_steps("second_wallet") ``` -------------------------------- ### Organize Feature Files in Folders Source: https://github.com/pytest-dev/pytest-bdd/blob/master/docs/index.md Organize your feature files into semantic groups using folders. This structure helps in managing larger projects. ```text features │ ├──frontend │ │ │ └──auth │ │ │ └──login.feature └──backend │ └──auth │ └──login.feature ``` -------------------------------- ### Declare common steps in conftest.py Source: https://github.com/pytest-dev/pytest-bdd/blob/master/docs/index.md Define reusable steps in your `conftest.py` file to make them available across multiple feature files. This promotes code reuse and centralizes step definitions. ```python # content of conftest.py from pytest_bdd import given, then @given("I have a bar", target_fixture="bar") def bar(): return "bar" @then('bar should have value "bar"') def bar_is_bar(bar): assert bar == "bar" ``` -------------------------------- ### Define Action Steps with @when Source: https://context7.com/pytest-dev/pytest-bdd/llms.txt Use @when to define system operations. The target_fixture parameter allows passing results to subsequent steps. ```python from pytest_bdd import given, when, then @given("there is an article", target_fixture="article") def article(): return {"title": "Test Article", "content": "Content", "published": False} @when("I publish the article") def publish_article(article): article["published"] = True article["published_at"] = datetime.now() # When step that captures a result @when("I request the deletion of the article", target_fixture="response") def delete_article(article, http_client): return http_client.delete(f"/api/articles/{article['id']}") # When step with browser interaction @when("I submit the contact form") def submit_form(browser): browser.fill('name', 'John Doe') browser.fill('email', 'john@example.com') browser.fill('message', 'Hello!') browser.find_by_css('form').first.submit() # When step capturing API response @when("I create a new product", target_fixture="create_response") def create_product(api_client, product_data): return api_client.post('/api/products', json=product_data) ``` -------------------------------- ### @given - Define Precondition Steps Source: https://context7.com/pytest-dev/pytest-bdd/llms.txt The `@given` decorator defines precondition steps that set up the initial state. Use `target_fixture` to make the return value available as a fixture to subsequent steps. ```APIDOC ## @given - Define Precondition Steps ### Description The `@given` decorator defines precondition steps that set up the initial state. Use `target_fixture` to make the return value available as a fixture to subsequent steps. ### Example Usage ```python from pytest_bdd import given, when, then import pytest # Basic given step @given("I am on the login page") def on_login_page(browser): browser.visit('/login') # Given step that creates a fixture @given("I have a user account", target_fixture="user") def user_account(): return {"username": "john_doe", "email": "john@example.com", "balance": 100} # Given step with multiple aliases @given("I have a wallet") @given("there is a wallet") def wallet(): return {"eur": 0, "usd": 0, "gbp": 0} # Given step that depends on fixtures @given("the user has admin privileges") def admin_user(user): user["role"] = "admin" return user # Reusing an existing pytest fixture @pytest.fixture def database(): db = create_test_database() yield db db.cleanup() @given("I have a database connection") def database_connection(database): """Reuse the pytest fixture in the given step.""" assert database.is_connected ``` ```