BDD library for the pytest runner

http://img.shields.io/pypi/v/pytest-bdd-ng.svg https://codecov.io/gh/elchupanebrej/pytest-bdd-ng/branch/default/graph/badge.svg Documentation Status https://badgen.net/badge/stand%20with/UKRAINE/?color=0057B8&labelColor=FFD700

pytest-bdd-ng combine descriptive clarity of Gherkin language with power and fullness of pytest infrastructure. It enables unifying unit and functional tests, reduces the burden of continuous integration server configuration and allows the reuse of test setups.

Pytest fixtures written for unit tests can be reused for setup and actions mentioned in feature steps with dependency injection. This allows a true BDD just-enough specification of the requirements without obligatory maintaining any context object containing the side effects of Gherkin imperative declarations.

Why NG ?

The current pytest plugin for cucumber is pytest-bdd , a popular project with 1.2k stars and used in 3k public repos and maintained by the pytest community. The upstream open-cucumber project does not have an official python release, so the current cucumber specs include features not available in pytest-bdd . This project is an effort to bridge the gap and also make it easier for pytest users to access new cucumber features.

Feature

original

NG

Description

Official parser support

-

+

All features of Feature files are supported from the “box” (localisation, Rules, Examples, Data tables, etc.)

Steps definitions via Cucumber expressions

-

+

Easy migration between implementations

Reporting using Messages

-

+

Possible to use all collection of Cucumber community tools for reporting

Pickles internal protocol

-

+

Allows to implement parsers based on other file types/principles

Heuristic step matching

-/+

+

Steps ease of use / amount of needed boilerplate code

Step execution context. Step types and variants of definition

-/+

+

Dispatching steps by kind. Steps injecting multiple fixtures. Default injecting behaviors. Steps could be used on import automatically. It’s possible to define default values for step variables.

Automatic collection of Feature files

-

+

No boilerplate code / No mix between steps definition and feature files

Load of Feature files by HTTP

-

+

Allows to integrate the library into external Feature storages

Stability and bugfixes

+

+/-

Supported python/pytest versions

+/-

+

NG supports wider and elder pytest&python version. Tested also for PyPy

Active community

+

-/+

Install pytest-bdd-ng

pip install pytest-bdd-ng

Packages for parsing Markdown defined features

npm install @cucumber/gherkin

Packages for reporting

npm install @cucumber/html-formatter

TLDR;

Feature: Simplest example
  Scenario:
    # https://cucumber.io/docs/gherkin/reference/
    Given File "Passing.feature" with content:
      """gherkin
      Feature: Passing feature
        Scenario: Passing scenario
          * Passing step
      """
    # https://docs.pytest.org/en/stable/reference/fixtures.html#conftest-py-sharing-fixtures-across-multiple-files
    And File "conftest.py" with content:
      """python
      from pytest_bdd import step

      @step('Passing step')
      def _():
        ...
      """
    # https://docs.pytest.org/en/stable/how-to/usage.html
    When run pytest
    Then pytest outcome must contain tests with statuses:
      |passed|
      |     1|

Project layout

pytest-bdd-ng automatically collects *.feature files from pytest tests directory. Important to remember, that feature files are used by other team members as live documentation, so it’s not a very good idea to mix documentation and test code.

The more features and scenarios you have, the more important becomes the question about their organization. So the recommended way is to organize your feature files in the folders by semantic groups:

features
├──frontend
│  └──auth
│     └──login.feature
└──backend
   └──auth
      └──login.feature

And tests for these features would be organized in the following manner:

tests
└──conftest.py
└──functional
│     └──__init__.py
│     └──conftest.py
│     │     └── "User step library used by descendant tests"
│     │
│     │         from steps.auth.given import *
│     │         from steps.auth.when import *
│     │         from steps.auth.then import *
│     │
│     │         from steps.order.given import *
│     │         from steps.order.when import *
│     │         from steps.order.then import *
│     │
│     │         from steps.browser.given import *
│     │         from steps.browser.when import *
│     │         from steps.browser.then import *
│     │
│     └──frontend_auth.feature -> ../../features/frontend/auth.feature
│     └──backend_auth.feature -> ../../features/backend/auth.feature
...

The step definitions would then be organized like this:

steps
└──auth
│     └── given.py
│     │      └── """User auth step definitions"""
│     │          from pytest import fixture
│     │          from pytest_bdd import given, when, then, step
│     │
│     │          @fixture
│     │          def credentials():
│     │             return 'test_login', 'test_very_secure_pass'
│     │
│     │          @given('User login into application')
│     │          def user_login(credentials):
│     │             ...
│     └── when.py
│     └── then.py
└──order
│     └── given.py
│     └── when.py
│     └── then.py
└──browser
│     └── ...
...

To make links between feature files at features directory and test directory there are few options (for more information please examine the project’s tests):

  1. Symlinks

  2. .desktop files

  3. .webloc files

  4. .url files

Note

Link files also could be used to load features by http://

How to Contribute

The project is now open to contributions. Please open an issue for more details.

Features

Note

Features below are part of end-to-end test suite; You always could find most specific use cases of pytest-bdd-ng by investigation of its regression test suite https://github.com/elchupanebrej/pytest-bdd-ng/tree/default/tests

Tutorial

Launch

Feature: Simple project tests that use pytest-bdd-ng could be run via pytest

Project per se: https://github.com/elchupanebrej/pytest-bdd-ng/tree/default/docs/tutorial
Scenario: Catalog example with simplest steps
  • Given Copy path from “docs/tutorial” to test path “tutorial”

  • When run pytest

    cli_args

    –rootdir=tutorial

    tutorial/tests

  • Then pytest outcome must contain tests with statuses:

    passed

    1

Step definition

Pytest fixtures substitution

Feature: Step definition could use pytest fixtures as step parameters

Test setup is implemented within the Given section. Even though these steps are executed imperatively to apply possible side-effects, pytest-bdd-ng is trying to benefit of the PyTest fixtures which is based on the dependency injection and makes the setup more declarative style.

In pytest-bdd-ng you just declare an argument of the step function that it depends on and the PyTest will make sure to provide it.

Scenario:
  • Given File “conftest.py” with content:

    from pytest import fixture
    from pytest_bdd import given, when, then
    
    @fixture
    def pocket():
      yield [{"cherry": "delicious"}]
    
    @given("I have an old pickle", param_defaults={"age": "old"}, target_fixture='pickle_age', params_fixtures_mapping=False)
    def i_have_cucumber(pocket):
        pocket.append({"age": "old", "cucumber": "pickle"})
    
    @when("I check pocket I found cucumber there")
    def i_check_pocket_for_cucumber(pocket):
      assert any(filter(lambda item: "cucumber" in item.keys(), pocket))
    
    @then("I lost everything")
    def i_check_pocket_for_cucumber(pocket):
      while pocket:
        pocket.pop()
    
  • Given File “Cucumber.feature” with content:

    Feature:
      Scenario:
        Given I have an old pickle
        When I check pocket I found cucumber there
        Then I lost everything
    
  • Given File “test_freshness.py” with content:

    from pytest_bdd import scenario
    
    @scenario("Cucumber.feature")
    def test_passing_feature(pocket):
      assert not pocket
    
  • When run pytest

    cli_args

    –disable-feature-autoload

  • Then pytest outcome must contain tests with statuses:

    passed

    1

Target fixtures specification

Feature: Step definition could override or setup new fixture

Dependency injection is not a panacea if you have complex structure of your test setup data. Sometimes there’s a need such a given step which would imperatively change the fixture only for certain test (scenario), while for other tests it will stay untouched. To allow this, special parameter target_fixture exists in the decorator:

Scenario: Single fixture injection
  • Given File “conftest.py” with content:

    from pytest_bdd import given
    
    @given("I have an old pickle", param_defaults={"age": "old"}, target_fixture='pickle_age', params_fixtures_mapping=False)
    def i_have_cucumber(age):
        yield age
    
  • Given File “Freshness.feature” with content:

    Feature:
      Scenario:
        Given I have an old pickle
    
  • Given File “test_freshness.py” with content:

    from pytest_bdd import scenario
    
    @scenario("Freshness.feature")
    def test_passing_feature(pickle_age, request):
      assert pickle_age == 'old'
      assert request.getfixturevalue('pickle_age') == pickle_age
    
  • When run pytest

    cli_args

    –disable-feature-autoload

  • Then pytest outcome must contain tests with statuses:

    passed

    1

Scenario: Multiple fixtures injection
  • Given File “conftest.py” with content:

    from pytest_bdd import given
    
    @given("I have an old pickle", target_fixtures=['pickle_age', 'cucumber_kind'])
    def i_have_cucumber():
        yield ['old', 'pickle']
    
  • Given File “Freshness.feature” with content:

    Feature:
      Scenario:
        Given I have an old pickle
    
  • Given File “test_freshness.py” with content:

    from pytest_bdd import scenario
    
    @scenario("Freshness.feature")
    def test_passing_feature(request, pickle_age, cucumber_kind):
      assert pickle_age == 'old'
      assert cucumber_kind == 'pickle'
      assert request.getfixturevalue('pickle_age') == pickle_age
      assert request.getfixturevalue('cucumber_kind') == cucumber_kind
    
  • When run pytest

    cli_args

    –disable-feature-autoload

  • Then pytest outcome must contain tests with statuses:

    passed

    1

Parameters

There is possibility to pass argument converters which may be useful if you need to postprocess step arguments after the parser.

Background:

  • Given File “Example.feature” with content:

    Feature:
      Scenario:
        Given I have a cucumber
    

Scenario: for non-anonymous groups

  • Given File “conftest.py” with content:

    from enum import Enum
    from pytest_bdd import given
    from re import compile as parse
    
    class Item(Enum):
      CUCUMBER = 'cucumber'
    
    @given(parse(r"I have a (?P<item>\w+)"), converters=dict(item=Item))
    def i_have_item(item):
        assert item == Item.CUCUMBER
    
  • When run pytest

  • Then pytest outcome must contain tests with statuses:

    passed

    1

Rule: for anonymous groups

Step definitions parameters could not have a name, so
we have to name them before conversion

Scenario:

  • Given File “conftest.py” with content:

    from enum import Enum
    from pytest_bdd import given
    from re import compile as parse
    
    class Item(Enum):
      CUCUMBER = 'cucumber'
    
    @given(
      parse(r"I have a (\w+)"),
      anonymous_group_names=('item',),
      converters=dict(item=Item)
    )
    def i_have_item(item):
        assert item == Item.CUCUMBER
    
  • When run pytest

  • Then pytest outcome must contain tests with statuses:

    passed

    1

Scenario:

  • Given File “conftest.py” with content:

    from enum import Enum
    from pytest_bdd import given
    from functools import partial
    from cucumber_expressions.expression import CucumberExpression
    from cucumber_expressions.parameter_type_registry import ParameterTypeRegistry
    
    parse = partial(
      CucumberExpression,
      parameter_type_registry = ParameterTypeRegistry()
    )
    
    class Item(Enum):
      CUCUMBER = 'cucumber'
    
    @given(
      parse(r"I have a {word}"),
      anonymous_group_names=('item',),
      converters=dict(item=Item)
    )
    def i_have_item(item):
        assert item == Item.CUCUMBER
    
  • When run pytest

  • Then pytest outcome must contain tests with statuses:

    passed

    1

Scenario:

  • Given File “Example.feature” with content:

    Feature:
      Scenario:
        Given I have a cucumber
        Given I have a rotten cucumber
        Given I have a fresh cucumber
        Given I have a pickle
    
  • Given File “conftest.py” with content:

    from enum import Enum
    from re import compile as parse
    from pytest import fixture
    from pytest_bdd import given
    
    class Freshness(Enum):
      FRESH = 'fresh'
      ROTTEN = 'rotten'
      SALTED = 'salted'
    
    @fixture
    def oracle_freshness():
      return [Freshness.FRESH, Freshness.ROTTEN, Freshness.FRESH, Freshness.SALTED]
    
    @given("I have a pickle", param_defaults=dict(freshness=Freshness.SALTED))
    @given(
      parse(r"I have a ((?P<freshness>\w+)\s)?cucumber"),
      converters=dict(freshness=Freshness),
      param_defaults=dict(freshness=Freshness.FRESH)
    )
    def i_have_cucumber(freshness, oracle_freshness):
        assert freshness == oracle_freshness.pop(0)
    
  • When run pytest

  • Then pytest outcome must contain tests with statuses:

    passed

    1

Step arguments are injected into step context and could be used as normal fixtures with the names equal to the names of the arguments by default.

Step’s argument are accessible as a fixture in other step function just by mentioning it as an argument

If the name of the step argument clashes with existing fixture, it will be overridden by step’s argument value. Value for some fixture deeply inside of the fixture tree could be set/override in a ad-hoc way by just choosing the proper name for the step argument.

Scenario: Step parameters are injected as fixtures by default

  • Given File “conftest.py” with content:

    from re import compile as parse
    from pytest_bdd import given, then
    
    @given("I have a pickle", param_defaults=dict(freshness='salted'))
    @given(
      parse(r"I have a ((?P<freshness>\w+)\s)?cucumber"),
      param_defaults=dict(freshness='fresh')
    )
    def i_have_cucumber(freshness):
        ...
    
    @then("Taste of cucumber is salt")
    def i_check_salted_cucumber(freshness):
        assert freshness=='salted'
    
  • Given File “Freshness.feature” with content:

    Feature:
      Scenario:
        Given I have a salted cucumber
        Then Taste of cucumber is salt
    
  • Given File “test_freshness.py” with content:

    from enum import Enum
    from pytest import fixture
    from pytest_bdd import scenario
    class Freshness(Enum):
      FRESH = 'fresh'
      ROTTEN = 'rotten'
      SALTED = 'salted'
    
    @fixture
    def oracle_freshness():
      return Freshness.SALTED
    
    @scenario("Freshness.feature")
    def test_passing_feature(request, oracle_freshness):
      assert Freshness(request.getfixturevalue('freshness'))==oracle_freshness
    
    @scenario("Freshness.feature")
    def test_another_passing_feature(freshness, oracle_freshness):
      assert Freshness(freshness)==oracle_freshness
    
  • When run pytest

    cli_args

    –disable-feature-autoload

  • Then pytest outcome must contain tests with statuses:

    passed

    2

Scenario: Step parameters injection as fixtures could be disabled

  • Given File “conftest.py” with content:

    from re import compile as parse
    from pytest_bdd import given, then
    
    @given(
      "I have a pickle",
      param_defaults=dict(freshness='salted'),
      params_fixtures_mapping={...:None},
      target_fixtures=['cuke_taste']
    )
    @given(
      parse(r"I have a ((?P<freshness>\w+)\s)?cucumber"),
      param_defaults=dict(freshness='fresh'),
      params_fixtures_mapping=False,
      target_fixture='cuke_taste'
    )
    def i_have_cucumber(freshness):
        assert freshness is not None
        yield freshness
    
    @then("Taste of cucumber is salt")
    def i_check_salted_cucumber(cuke_taste):
        assert cuke_taste=='salted'
    
  • Given File “Freshness.feature” with content:

    Feature:
      Scenario:
        Given I have a pickle
        Then Taste of cucumber is salt
    
  • Given File “test_freshness.py” with content:

    import pytest
    from pytest_bdd import scenario
    from pytest_bdd.compatibility.pytest import FixtureLookupError
    @scenario("Freshness.feature")
    def test_passing_feature(request, cuke_taste):
      assert cuke_taste == 'salted'
      with pytest.raises(FixtureLookupError):
        request.getfixturevalue('freshness')
    
  • When run pytest

    cli_args

    –disable-feature-autoload

  • Then pytest outcome must contain tests with statuses:

    passed

    1

Scenario: Step parameters renaming on injection as fixtures

  • Given File “conftest.py” with content:

    from re import compile as parse
    from pytest_bdd import given, then
    
    @given(
      "I have a pickle",
      param_defaults=dict(freshness='salted'),
      params_fixtures_mapping={"freshness":"cuke_taste"}
    )
    @given(
      parse(r"I have a ((?P<freshness>\w+)\s)?cucumber"),
      param_defaults=dict(freshness='fresh'),
      params_fixtures_mapping={"freshness":"cuke_taste"}
    )
    def i_have_cucumber(cuke_taste, freshness):
        assert cuke_taste is not None
        assert freshness == cuke_taste
        yield cuke_taste
    
    @then("Taste of cucumber is salt")
    def i_check_salted_cucumber(cuke_taste):
        assert cuke_taste=='salted'
    
  • Given File “Freshness.feature” with content:

    Feature:
      Scenario:
        Given I have a pickle
        Then Taste of cucumber is salt
    
  • Given File “test_freshness.py” with content:

    import pytest
    from pytest_bdd import scenario
    from pytest_bdd.compatibility.pytest import FixtureLookupError
    
    @scenario("Freshness.feature")
    def test_passing_feature(request, cuke_taste):
      assert cuke_taste == 'salted'
      with pytest.raises(FixtureLookupError):
        request.getfixturevalue('freshness')
    
  • When run pytest

    cli_args

    –disable-feature-autoload

  • Then pytest outcome must contain tests with statuses:

    passed

    1

Scenario: Only allowed step parameters injection as fixtures

  • Given File “conftest.py” with content:

    from pytest_bdd import given
    
    @given(
      "I have an old pickle",
      param_defaults=dict(freshness='salted', age='old'),
      params_fixtures_mapping={"freshness"}
    )
    def i_have_cucumber(age, freshness):
        assert age == 'old'
        assert freshness == 'salted'
    
  • Given File “Freshness.feature” with content:

    Feature:
      Scenario:
        Given I have an old pickle
    
  • Given File “test_freshness.py” with content:

    import pytest
    from pytest_bdd import scenario
    from pytest_bdd.compatibility.pytest import FixtureLookupError
    
    @scenario("Freshness.feature")
    def test_passing_feature(request, freshness):
      assert freshness == 'salted'
      with pytest.raises(FixtureLookupError):
        request.getfixturevalue('age')
    
  • When run pytest

    cli_args

    –disable-feature-autoload

  • Then pytest outcome must contain tests with statuses:

    passed

    1

Scenario:

  • Given File “Example.feature” with content:

    Feature:
      Scenario:
        Given there are 10 cucumbers
    
  • Given File “conftest.py” with content:

    import re
    from pytest_bdd import given, parsers
    
    class Parser(parsers.StepParser):
        def __init__(self, name, *args,**kwargs):
            self.name = name
            self.regex = re.compile(
              re.sub("%(.+)%", r"(?P<\1>.+)", name),
              *args,
              **kwargs
            )
    
        def parse_arguments(self, request, name, **kwargs):
            __doc__ = "Parse step arguments"
            return self.regex.match(name).groupdict()
    
        @property
        def arguments(self):
            return [*self.regex.groupindex.keys()]
    
        def is_matching(self, request ,name):
            __doc__ = "Match given name with the step name."
            return bool(self.regex.match(name))
    
        def __str__(self):
          return self.name
    
    @given(
      Parser("there are %start% cucumbers"),
      target_fixture="start_cucumbers",
      converters=dict(start=int)
    )
    def start_cucumbers(start):
        assert start == 10
    
  • When run pytest

  • Then pytest outcome must contain tests with statuses:

    passed

    1

Step parameters often enable the reuse of steps, which can reduce the amount of code required. This methodology allows for the same step to be used multiple times within a single scenario, but with different arguments. There are an multiple step parameter parsers available for your use.

Rule: Step definitions parameters parsing

Background:

  • Given File “Parametrized.feature” with content:

    Feature: StepHandler arguments
      Scenario: Every step takes a parameter with the same name
        Given I have a wallet
        Given I have 6 Euro
        When I lose 3 Euro
        And I pay 2 Euro
        Then I should have 1 Euro
        # In my dream...
        And I should have 999999 Euro
    

Example: Heuristic parser guesses a type and builds particular parser to be applied

Tries to select right parser between string, cucumber_expression, cfparse and re.
Any object that supports `__str__` interface and does not support parser interface
will be wrapped with this parser
  • Given File “conftest.py” with content:

    import pytest
    from pytest_bdd import given, when, then
    
    @pytest.fixture
    def values():
        return [6, 3, 2, 1, 999999]
    
    # string parser
    @given("I have a wallet", param_defaults={'wallet': 'wallet'})
    def i_have_wallet(wallet):
        assert wallet == 'wallet'
    
    # cucumber expressions parser
    @given("I have {int} Euro",
      anonymous_group_names=('euro',),
      converters=dict(euro=int)
    )
    def i_have(euro, values):
        assert euro == values.pop(0)
    
    # parse parser
    @when(
      "I pay {} Euro",
      anonymous_group_names=('euro',),
      converters=dict(euro=int)
    )
    def i_pay(euro, values):
        assert euro == values.pop(0)
    
    # cfparse parser
    @when("I lose {euro:d} Euro", converters=dict(euro=int))
    def i_lose(euro, values):
        assert euro == values.pop(0)
    
    # regular expression parser
    @then(
      r"I should have (\d+) Euro",
      anonymous_group_names=('euro',),
      converters=dict(euro=int)
    )
    def i_should_have(euro, values):
        assert euro == values.pop(0)
    
  • When run pytest

  • Then pytest outcome must contain tests with statuses:

    passed

    1

Example: by “parse”

http://pypi.python.org/pypi/parse

Provides a simple parser that replaces regular expressions for
step parameters with a readable syntax like ``{param:Type}``.
The syntax is inspired by the Python builtin ``string.format()``
function.
Step parameters must use the named fields syntax of pypi_parse_
in step definitions. The named fields are extracted,
optionally type converted and then used as step function arguments.
Supports type conversions by using type converters passed via `extra_types`
  • Given File “conftest.py” with content:

    import pytest
    from pytest_bdd import given, when, then
    from parse import Parser as parse
    
    @pytest.fixture
    def values():
        return [6, 3, 2, 1, 999999]
    
    @given(parse("I have a wallet"), param_defaults={'wallet': 'wallet'})
    def i_have_wallet(wallet):
        assert wallet == 'wallet'
    
    @given(parse("I have {euro:g} Euro"))
    def i_have(euro, values):
        assert euro == values.pop(0)
    
    @when(parse("I pay {euro:d} Euro"))
    def i_pay(euro, values):
        assert euro == values.pop(0)
    
    @when(
      parse("I lose {} Euro"),
      anonymous_group_names=('euro',),
      converters=dict(euro=int)
    )
    def i_pay(euro, values):
        assert euro == values.pop(0)
    
    @then(
      parse(r"I should have {:d} Euro"),
      anonymous_group_names=('euro',),
      converters=dict(euro=int)
    )
    def i_should_have(euro, values):
        assert euro == values.pop(0)
    
  • When run pytest

  • Then pytest outcome must contain tests with statuses:

    passed

    1

Example: by “cfparse”

http://pypi.python.org/pypi/parse_type

Provides an extended parser with "Cardinality Field" (CF) support.
Automatically creates missing type converters for related cardinality
as long as a type converter for cardinality=1 is provided.
Supports parse expressions like:
``{values:Type+}`` (cardinality=1..N, many)
``{values:Type*}`` (cardinality=0..N, many0)
``{value:Type?}``  (cardinality=0..1, optional)
Supports type conversions (as above).
  • Given File “conftest.py” with content:

    import pytest
    from pytest_bdd import given, when, then
    from parse_type.cfparse import Parser as parse
    
    @pytest.fixture
    def values():
        return [6, 3, 2, 1, 999999]
    
    @given(parse("I have a wallet"), param_defaults={'wallet': 'wallet'})
    def i_have_wallet(wallet):
        assert wallet == 'wallet'
    
    @given(parse("I have {euro:Number} Euro", extra_types=dict(Number=int)))
    def i_have(euro, values):
        assert euro == values.pop(0)
    
    @when(parse("I pay {euro:d} Euro"))
    def i_pay(euro, values):
        assert euro == values.pop(0)
    
    @when(
      parse("I lose {} Euro"),
      anonymous_group_names=('euro',),
      converters=dict(euro=int)
    )
    def i_pay(euro, values):
        assert euro == values.pop(0)
    
    @then(
      parse(r"I should have {:d} Euro"),
      anonymous_group_names=('euro',),
      converters=dict(euro=int)
    )
    def i_should_have(euro, values):
        assert euro == values.pop(0)
    
  • When run pytest

  • Then pytest outcome must contain tests with statuses:

    passed

    1

Example: by “cucumber-expressions”

https://github.com/cucumber/cucumber-expressions
Cucumber Expressions is an alternative to Regular Expressions
with a more intuitive syntax.
  • And File “conftest.py” with content:

    from functools import partial
    import pytest
    from pytest_bdd import given, when, then
    from cucumber_expressions.parameter_type_registry import ParameterTypeRegistry
    from cucumber_expressions.expression import CucumberExpression
    
    parse = partial(
      CucumberExpression,
      parameter_type_registry = ParameterTypeRegistry()
    )
    
    @pytest.fixture
    def values():
        return [6, 3, 2, 1, 999999]
    
    @given(parse("I have a wallet"), param_defaults={'wallet': 'wallet'})
    def i_have_wallet(wallet):
        assert wallet == 'wallet'
    
    @given(
      parse("I have {int} Euro"),
      anonymous_group_names=('euro',),
      converters=dict(euro=int)
    )
    def i_have(euro, values):
        assert euro == values.pop(0)
    
    @when(
      parse("I pay {} Euro"),
      anonymous_group_names=('euro',),
      converters=dict(euro=int)
    )
    def i_pay(euro, values, request):
        assert euro == values.pop(0)
    
    @when(
      parse(r"I lose {int} Dollar/Euro(s)"),
      anonymous_group_names=('euro',),
      converters=dict(euro=int)
    )
    def i_lose(euro, values):
        assert euro == values.pop(0)
    
    @then(
      parse("I should have {int} Euro"),
      anonymous_group_names=('euro',),
      converters=dict(euro=int)
    )
    def i_should_have(euro, values):
        assert euro == values.pop(0)
    
  • When run pytest

  • Then pytest outcome must contain tests with statuses:

    passed

    1

Example: by “cucumber-regular-expressions”

https://github.com/cucumber/cucumber-expressions

Cucumber Expressions is an alternative
to Regular Expressions with a more intuitive syntax.
  • And File “conftest.py” with content:

    import pytest
    from pytest_bdd import given, when, then
    from functools import partial
    
    from cucumber_expressions.parameter_type_registry import ParameterTypeRegistry
    from cucumber_expressions.regular_expression import (
      RegularExpression as CucumberRegularExpression
    )
    
    parse = partial(
      CucumberRegularExpression,
      parameter_type_registry = ParameterTypeRegistry()
    )
    
    @pytest.fixture
    def values():
        return [6, 3, 2, 1, 999999]
    
    @given(parse("I have a wallet"), param_defaults={'wallet': 'wallet'})
    def i_have_wallet(wallet):
        assert wallet == 'wallet'
    
    @given(
      parse(r"I have (\d+) Euro"),
      anonymous_group_names=('euro',),
      converters=dict(euro=int)
    )
    def i_have(euro, values):
        assert euro == values.pop(0)
    
    @when(
      parse("I pay (.*) Euro"),
      anonymous_group_names=('euro',),
      converters=dict(euro=int)
    )
    def i_pay(euro, values, request):
        assert euro == values.pop(0)
    
    @when(
      parse(r"I lose (.+) Euro"),
      anonymous_group_names=('euro',),
      converters=dict(euro=int)
    )
    def i_lose(euro, values):
        assert euro == values.pop(0)
    
    @then(
      parse(r"I should have (\d+) Euro"),
      anonymous_group_names=('euro',),
      converters=dict(euro=int)
    )
    def i_should_have(euro, values):
        assert euro == values.pop(0)
    
  • When run pytest

  • Then pytest outcome must contain tests with statuses:

    passed

    1

Example: by “regular-expressions”

This uses full regular expressions to parse the clause text. You will
need to use named groups "(?P<name>...)" to define the variables pulled
from the text and passed to your "step()" function.
Type conversion can only be done via "converters" step decorator
argument (see example in according feature).
  • Given File “conftest.py” with content:

    import pytest
    from pytest_bdd import given, when, then
    from re import compile as parse
    
    @pytest.fixture
    def values():
        return [6, 3, 2, 1, 999999]
    
    @given(parse("I have a wallet"), param_defaults={'wallet': 'wallet'})
    def i_have_wallet(wallet):
        assert wallet == 'wallet'
    
    @given(parse(r"I have (?P<euro>\d+) Euro"), converters=dict(euro=int))
    def i_have(euro, values):
        assert euro == values.pop(0)
    
    @when(
      parse(r"I pay (\d+) Euro"),
      anonymous_group_names=('euro',),
      converters=dict(euro=int)
    )
    def i_pay(euro, values):
        assert euro == values.pop(0)
    
    @when(parse(r"I lose (.+) Euro"),
      anonymous_group_names=('euro',),
      converters=dict(euro=int)
    )
    def i_lose(euro, values):
        assert euro == values.pop(0)
    
    @then(parse(r"I should have (?P<euro>\d+) Euro"), converters=dict(euro=int))
    def i_should_have(euro, values):
        assert euro == values.pop(0)
    
  • When run pytest

  • Then pytest outcome must contain tests with statuses:

    passed

    1

Step

Data table

Feature: Steps could have docstrings

Scenario:
  • Given File “Steps.feature” with content:

    Feature:
      Scenario:
        Given I check step datatable
          |first|second|
          |    a|     b|
    
  • And File “conftest.py” with content:

    from pytest_bdd import given
    from messages import Step
    
    def get_datatable_row_values(row):
      return list(map(lambda cell: cell.value, row.cells))
    
    @given('I check step datatable')
    def _(step: Step):
      title_row, *data_rows = step.data_table.rows
      assert get_datatable_row_values(title_row) == ["first", "second"]
      assert get_datatable_row_values(data_rows[0]) == ["a", "b"]
    
  • When run pytest

  • Then pytest outcome must contain tests with statuses:

    passed

    failed

    1

    0

Doc string

Feature: Steps could have docstrings

Scenario:
  • Given File “Steps.feature” with content:

    Feature:
      Scenario:
        Given I check step docstring
          ```
          Step docstring
          ```
    
  • And File “conftest.py” with content:

    from pytest_bdd import given
    
    @given('I check step docstring')
    def _(step):
      assert step.doc_string.content == "Step docstring"
    
  • When run pytest

  • Then pytest outcome must contain tests with statuses:

    passed

    failed

    1

    0

Step definition bounding

Feature: Gherkin steps bounding to steps definitions

Scenario: Steps are executed by corresponding step keyword decorator
  • Given File “steps.feature” with content:

    Feature: Steps are executed by corresponding step keyword decorator
    
      Scenario:
          Step execution definitions are pytest fixtures by their nature
          and are stored at pytest "conftest.py" files (or any other place
          where pytest fixtures could be placed)
    
          * Step is executed by plain step decorator
          Given Step is executed by given step decorator
          When Step is executed by when step decorator
          Then Step is executed by then step decorator
    
          Then there are passed steps by kind:
            |step|given|when|then|
            |   1|    1|   1|   1|
    
  • And File “conftest.py” with content:

    from pytest_bdd import given, when, then, step
    from pytest import fixture
    
    # pytest fixtures could be used from step definitions, so some
    # test preconditions could be stored on the pytest level
    @fixture
    def step_counter():
      yield {'step': 0, 'given': 0,'when': 0,'then': 0,}
    
    # Step with any kind of keyword could be bounded to step decorated with "step" definition
    @step('Step is executed by plain step decorator')
    def plain_step(step_counter):
      step_counter['step'] += 1
    
    # Step with "Given" keyword could be bounded to step decorated with "given" definition
    @given('Step is executed by given step decorator')
    def given_step(step_counter):
      step_counter['given'] += 1
    
    # Same as "given"
    @when('Step is executed by when step decorator')
    def when_step(step_counter):
      step_counter['when'] += 1
    
    # Same as "given"
    @then('Step is executed by then step decorator')
    def then_step(step_counter):
      step_counter['then'] += 1
    
    @then('there are passed steps by kind:')
    def check_step_counter(step, step_counter):
      # Step datatables data could be accessed in the next manner
      step_data_table = step.data_table
      oracle_results_header = [cell.value for cell in step_data_table.rows[0].cells]
      oracle_results_values = [int(cell.value) for cell in step_data_table.rows[1].cells]
      oracle_result = dict(zip(oracle_results_header, oracle_results_values))
    
      assert oracle_result == step_counter
    
  • When run pytest

  • Then pytest outcome must contain tests with statuses:

    passed

    1

Scenario: Steps could be executed by aliased step keyword decorator
Could be useful to declare the same fixtures or steps with
different names for better readability. In order to use the same step
function with multiple step names simply decorate it multiple times.
  • Given File “steps.feature” with content:

    Feature: Steps could be executed by aliased step keyword decorator
      Scenario:
          Given Step counter
    
          * Step is executed by aliased step decorator
          Given Step is executed by aliased step decorator
          When Step is executed by aliased step decorator
          Then Step is executed by aliased step decorator
    
          Then there are "4" passed aliased steps
    
  • And File “conftest.py” with content:

    from pytest_bdd import given, when, then, step
    
    @given('Step counter', target_fixture='step_counter')
    def step_counter():
      yield {'steps_count': 0}
    
    @step('Step is executed by aliased step decorator')
    @given('Step is executed by aliased step decorator')
    @when('Step is executed by aliased step decorator')
    @then('Step is executed by aliased step decorator')
    def aliased_step(step_counter):
      step_counter['steps_count'] += 1
    
    @then(
      'there are "{int}" passed aliased steps',
      anonymous_group_names=('oracle_steps',),
    )
    def then_step(step_counter, oracle_steps):
      assert step_counter['steps_count'] == oracle_steps
    
  • When run pytest

  • Then pytest outcome must contain tests with statuses:

    passed

    1

Rule: Steps could be executed by liberal step keyword decorator
Step definition decorator could be "liberal"
- so it could be bound to any kind of keyword

Background:

  • Given File “steps.feature” with content:

    Feature: Steps could be executed by liberal step keyword decorator
      Scenario:
        Given Step counter
    
        * Step is executed by liberal step decorator
        Given Step is executed by liberal step decorator
        When Step is executed by liberal step decorator
        Then Step is executed by liberal step decorator
    
        * Step is executed by liberal given decorator
        Given Step is executed by liberal given decorator
        When Step is executed by liberal given decorator
        Then Step is executed by liberal given decorator
    
        * Step is executed by liberal when decorator
        Given Step is executed by liberal when decorator
        When Step is executed by liberal when decorator
        Then Step is executed by liberal when decorator
    
        * Step is executed by liberal then decorator
        Given Step is executed by liberal then decorator
        When Step is executed by liberal then decorator
        Then Step is executed by liberal then decorator
    
        Then there are "16" passed liberal steps
    

Scenario: Same step is used with different keywords

  • Given File “conftest.py” with content:

    from pytest_bdd import given, when, then, step
    
    @given('Step counter', target_fixture='step_counter')
    def step_counter():
      yield {'steps_count': 0}
    
    @step('Step is executed by liberal step decorator', liberal=True)
    @given('Step is executed by liberal given decorator', liberal=True)
    @when('Step is executed by liberal when decorator', liberal=True)
    @then('Step is executed by liberal then decorator', liberal=True)
    def liberal_step(step_counter):
      step_counter['steps_count'] += 1
    
    @then(
      'there are "{int}" passed liberal steps',
      anonymous_group_names=('oracle_steps',),
    )
    def then_step(step_counter, oracle_steps):
      assert step_counter['steps_count'] == oracle_steps
    
  • When run pytest

  • Then pytest outcome must contain tests with statuses:

    passed

    1

Scenario: Keyworded steps could be treated as liberal by pytest command line option

  • Given File “conftest.py” with content:

    from pytest_bdd import given, when, then, step
    
    @given('Step counter', target_fixture='step_counter')
    def step_counter():
      yield {'steps_count': 0}
    
    @step('Step is executed by liberal step decorator')
    @given('Step is executed by liberal given decorator')
    @when('Step is executed by liberal when decorator')
    @then('Step is executed by liberal then decorator')
    def liberal_step(step_counter):
      step_counter['steps_count'] += 1
    
    @then(
      'there are "{int}" passed liberal steps',
      anonymous_group_names=('oracle_steps',),
    )
    def then_step(step_counter, oracle_steps):
      assert step_counter['steps_count'] == oracle_steps
    
  • When run pytest

    cli_args

    –liberal-steps

  • Then pytest outcome must contain tests with statuses:

    passed

    failed

    1

    0

Scenario

Description

Feature: Descriptions

Free-form descriptions can be placed underneath Feature, Example/Scenario, Background, Scenario Outline and Rule. You can write anything you like, as long as no line starts with a keyword. Descriptions can be in the form of Markdown - formatters including the official HTML formatter support this.

Scenario:
  • Given File “Description.feature” with content:

    Feature:
      Scenario:
        My Scenario description
    
        Given I check scenario description
    
  • And File “conftest.py” with content:

    from pytest_bdd import given
    
    @given('I check scenario description')
    def step(scenario):
      assert "My Scenario description" in scenario.description
    
  • When run pytest

  • Then pytest outcome must contain tests with statuses:

    passed

    failed

    1

    0

Tag

Feature: Scenarios could be tagged

Background:
  • Given File “steps.feature” with content:

    Feature: Steps are executed by corresponding step keyword decorator
      @passed
      Scenario: Passed
        Given I produce passed test
    
      @failed
      Scenario: Failed
        Given I produce failed test
    
      @both
      Rule:
        Scenario: Passed
          Given I produce passed test
    
        Scenario: Failed
          Given I produce failed test
    
  • Given File “pytest.ini” with content:

    [pytest]
    markers =
      passed
      failed
      both
    
  • And File “conftest.py” with content:

    from pytest_bdd.compatibility.pytest import fail
    from pytest_bdd import given
    
    @given('I produce passed test')
    def passing_step():
      ...
    
    @given('I produce failed test')
    def failing_step():
      fail('Enforce fail')
    
Scenario:
  • When run pytest

    cli_args

    -m

    passed

  • Then pytest outcome must contain tests with statuses:

    passed

    failed

    1

    0

Scenario:
  • When run pytest

    cli_args

    -m

    failed

  • Then pytest outcome must contain tests with statuses:

    passed

    failed

    0

    1

Scenario:
  • When run pytest

    cli_args

    -m

    passed or failed

  • Then pytest outcome must contain tests with statuses:

    passed

    failed

    1

    1

Scenario:
  • When run pytest

    cli_args

    -m

    not both

  • Then pytest outcome must contain tests with statuses:

    passed

    failed

    1

    1

Scenario:
  • When run pytest

    cli_args

    -m

    both

  • Then pytest outcome must contain tests with statuses:

    passed

    failed

    1

    1

Scenario:
  • When run pytest

  • Then pytest outcome must contain tests with statuses:

    passed

    failed

    2

    2

Outline

Rule:

Background:

  • Given File “steps.feature” with content:

    Feature: Steps are executed by corresponding step keyword decorator
    
      Scenario Outline:
          Given I produce <outcome> test
    
          @passed
          Examples:
          |outcome|
          |passed |
    
          @failed
          Examples:
          |outcome|
          |failed |
    
          @both
          Examples:
          |outcome|
          |passed |
          |failed |
    
  • Given File “pytest.ini” with content:

    [pytest]
    markers =
      passed
      failed
      both
    
  • And File “conftest.py” with content:

    from pytest_bdd.compatibility.pytest import fail
    from pytest_bdd import given
    
    @given('I produce passed test')
    def passing_step():
      ...
    
    @given('I produce failed test')
    def failing_step():
      fail('Enforce fail')
    

Scenario:

  • When run pytest

    cli_args

    -m

    passed

  • Then pytest outcome must contain tests with statuses:

    passed

    failed

    1

    0

Scenario:

  • When run pytest

    cli_args

    -m

    failed

  • Then pytest outcome must contain tests with statuses:

    passed

    failed

    0

    1

Scenario:

  • When run pytest

    cli_args

    -m

    passed or failed

  • Then pytest outcome must contain tests with statuses:

    passed

    failed

    1

    1

Scenario:

  • When run pytest

    cli_args

    -m

    not both

  • Then pytest outcome must contain tests with statuses:

    passed

    failed

    1

    1

Scenario:

  • When run pytest

    cli_args

    -m

    both

  • Then pytest outcome must contain tests with statuses:

    passed

    failed

    1

    1

Scenario:

  • When run pytest

  • Then pytest outcome must contain tests with statuses:

    passed

    failed

    2

    2

Rule: Mixing tags on feature & examples level

Background:

  • Given File “steps.feature” with content:

    @feature_tag
    Feature: Steps are executed by corresponding step keyword decorator
      Scenario Outline:
          Given I produce <outcome> test
    
          Examples:
          |outcome|
          |passed |
    
          @examples_tag
          Examples:
          |outcome|
          |failed |
    
  • Given File “pytest.ini” with content:

    [pytest]
    markers =
      feature_tag
      examples_tag
    
  • And File “conftest.py” with content:

    from pytest_bdd.compatibility.pytest import fail
    from pytest_bdd import given
    
    @given('I produce passed test')
    def passing_step():
      ...
    
    @given('I produce failed test')
    def failing_step():
      fail('Enforce fail')
    

Example:

  • When run pytest

    cli_args

    -m

    feature_tag

  • Then pytest outcome must contain tests with statuses:

    passed

    failed

    1

    1

Example:

  • When run pytest

    cli_args

    -m

    examples_tag

  • Then pytest outcome must contain tests with statuses:

    passed

    failed

    0

    1

Example:

  • When run pytest

    cli_args

    -m

    not feature_tag

  • Then pytest outcome must contain tests with statuses:

    passed

    failed

    0

    0

Example:

  • When run pytest

    cli_args

    -m

    not examples_tag

  • Then pytest outcome must contain tests with statuses:

    passed

    failed

    1

    0

Example:

  • When run pytest

    cli_args

    -m

    feature_tag

    –collect-only

  • Then pytest outcome must match lines:

    collected 2 items

Example:

  • When run pytest

    cli_args

    -m

    examples_tag

    –collect-only

  • Then pytest outcome must match lines:

    collected 2 items / 1 deselected / 1 selected

Example:

  • When run pytest

    cli_args

    -m

    not feature_tag

    –collect-only

  • Then pytest outcome must match lines:

    collected 2 items / 2 deselected*

Example:

  • When run pytest

    cli_args

    -m

    not examples_tag

    –collect-only

  • Then pytest outcome must match lines:

    collected 2 items / 1 deselected / 1 selected

Report

Gathering

Feature:

Background:
  • Given File “Passing.feature” with content:

    Feature: Passing feature
      Scenario: Passing scenario
        Given Passing step
    
  • And File “conftest.py” with content:

    from pytest_bdd import step
    
    @step('Passing step')
    def _():
      ...
    
Scenario: NDJson(JSONL) could be produced on the feature run
Output file could be fed into other @cucumber tools for more verbose report
[Messages](https://github.com/cucumber/messages)
  • When run pytest

    cli_args

    –messages-ndjson

    out.ndjson

    subprocess

    true

  • Then File “out.ndjson” has “15” lines

  • Then Report “out.ndjson” parsable into messages

Scenario: HTML report could be produced on the feature run
Dummy reporter based on [@cucumber/html-formatter](https://github.com/cucumber/html-formatter)
  • Given Install npm packages

    packages

    @cucumber/html-formatter

  • When run pytest

    cli_args

    –cucumber-html

    out.html

    subprocess

    true

  • Then File “out.html” is not empty

Feature

Description

Feature: Descriptions

Free-form descriptions can be placed underneath Feature, Example/Scenario, Background, Scenario Outline and Rule. You can write anything you like, as long as no line starts with a keyword. Descriptions can be in the form of Markdown - formatters including the official HTML formatter support this.

Scenario:
  • Given File “Description.feature” with content:

    Feature:
      My Feature description
      Scenario:
        Given I check feature description
    
  • And File “conftest.py” with content:

    from pytest_bdd import given
    
    @given('I check feature description')
    def step(feature):
      assert feature.description == "My Feature description"
    
  • When run pytest

  • Then pytest outcome must contain tests with statuses:

    passed

    failed

    1

    0

Localization

Scenarios tags could be localized

pytest-bdd-ng supports all localizations which Gherkin does: https://cucumber.io/docs/gherkin/languages/

Scenario:
  • Given File “Localized.feature” with content:

    #language: pt
    #encoding: UTF-8
    Funcionalidade: Login no Programa
        Cenário: O usuário ainda não é cadastrado
            Dado que o usuário esteja na tela de login
            Quando ele clicar no botão de Criar Conta
            Então ele deve ser levado para a tela de criação de conta
    
  • And File “conftest.py” with content:

    from pytest_bdd import  given, when, then
    
    @given("que o usuário esteja na tela de login")
    def tela_login():
        assert True
    
    @when("ele clicar no botão de Criar Conta")
    def evento_criar_conta():
        assert True
    
    @then("ele deve ser levado para a tela de criação de conta")
    def tela_criacao_conta():
        assert True
    
  • When run pytest

  • Then pytest outcome must contain tests with statuses:

    passed

    failed

    1

    0

Tag conversion

Feature: Scenarios tags could be converted via hooks

Scenario:
  • Given File “Passed.feature” with content:

    Feature:
      @todo
      Scenario: Failed
        Given I produce failed test
    
      Scenario: Passed
        Given I produce passed test
    
  • And File “conftest.py” with content:

    import pytest
    from pytest_bdd import given
    from pytest_bdd.compatibility.pytest import fail
    
    def pytest_bdd_convert_tag_to_marks(feature, scenario, tag):
      if tag == 'todo':
         marker = pytest.mark.skip(reason="Not implemented yet")
         return [marker]
    
    @given('I produce passed test')
    def passing_step():
      ...
    
    @given('I produce failed test')
    def failing_step():
      fail('Enforce fail')
    
  • When run pytest

  • Then pytest outcome must contain tests with statuses:

    passed

    failed

    skipped

    1

    0

    1

Tag

Feature: Features could be tagged

For picking up tests to run we can use tests selection <http://pytest.org/latest/usage.html#specifying-tests-selecting-tests>_ technique. The problem is that you have to know how your tests are organized, knowing only the feature files organization is not enough. cucumber tags <https://github.com/cucumber/cucumber/wiki/Tags>_ introduces standard way of categorizing your features and scenarios

Rule:

Background:

  • Given File “Passed.feature” with content:

    @passed
    Feature: Steps are executed by corresponding step keyword decorator
      Scenario: Passed
        Given I produce passed test
    
  • Given File “Failed.feature” with content:

    @failed
    Feature: Steps are executed by corresponding step keyword decorator
      Scenario: Failed
        Given I produce failed test
    
  • Given File “Both.feature” with content:

    @both
    Feature: Steps are executed by corresponding step keyword decorator
      Scenario: Passed
        Given I produce passed test
    
      Scenario: Failed
        Given I produce failed test
    
  • Given File “pytest.ini” with content:

    [pytest]
    markers =
      passed
      failed
      both
    
  • And File “conftest.py” with content:

    from pytest_bdd.compatibility.pytest import fail
    from pytest_bdd import given
    
    @given('I produce passed test')
    def passing_step():
      ...
    
    @given('I produce failed test')
    def failing_step():
      fail('Enforce fail')
    

Scenario:

  • When run pytest

    cli_args

    -m

    passed

  • Then pytest outcome must contain tests with statuses:

    passed

    failed

    1

    0

Scenario:

  • When run pytest

    cli_args

    -m

    failed

  • Then pytest outcome must contain tests with statuses:

    passed

    failed

    0

    1

Scenario:

  • When run pytest

    cli_args

    -m

    passed or failed

  • Then pytest outcome must contain tests with statuses:

    passed

    failed

    1

    1

Scenario:

  • When run pytest

    cli_args

    -m

    not both

  • Then pytest outcome must contain tests with statuses:

    passed

    failed

    1

    1

Scenario:

  • When run pytest

    cli_args

    -m

    both

  • Then pytest outcome must contain tests with statuses:

    passed

    failed

    1

    1

Scenario:

  • When run pytest

  • Then pytest outcome must contain tests with statuses:

    passed

    failed

    2

    2

Load

By default gherkin features are autoloaded and treated as usual pytest tests if are placed in the tests hierarchy proposed by pytest. This behavior could be disabled

Rule: Feature autoload

Background:

  • Given File “Passing.feature” with content:

    Feature: Passing feature
      Scenario: Passing scenario
        * Passing step
    
  • Given File “Another.passing.feature.md” with content:

    # Feature: Passing feature
    ## Scenario: Passing scenario
    * Given Passing step
    
  • Given Install npm packages

    packages

    @cucumber/gherkin

  • Given File “conftest.py” with content:

    from pytest_bdd import step
    
    @step('Passing step')
    
    def _():
      ...
    

Scenario: Feature is loaded by default

  • When run pytest

  • Then pytest outcome must contain tests with statuses:

    passed

    2

Scenario: Feature autoload could be disabled via command line

  • When run pytest

    cli_args

    –disable-feature-autoload

  • Then pytest outcome must contain tests with statuses:

    passed

    0

Scenario: Feature autoload could be disabled via pytest.ini

  • Given Set pytest.ini content to:

    [pytest]
    disable_feature_autoload=true
    
  • When run pytest

  • Then pytest outcome must contain tests with statuses:

    passed

    0

Background:

  • Given File “Passing.feature” in the temporary path with content:

    Feature: Passing feature
      Scenario: Passing scenario
        Given Passing step
    
  • And File “conftest.py” with content:

    from pytest_bdd import step
    
    @step('Passing step')
    def _():
      ...
    

Scenario: “scenario” function is used as decorator

  • And File “test_scenario_load.py” with fixture templated content:

    from pytest_bdd import scenario
    from pathlib import Path
    
    @scenario(Path(r"{tmp_path}") / "Passing.feature")
    def test_passing_feature():
      # It is however encouraged to try as much as possible to have your logic only inside the Given, When, Then steps.
      ...
    

Scenario: “scenarios” function is used as decorator

  • And File “test_scenario_load.py” with fixture templated content:

    from pytest_bdd import scenarios
    from pathlib import Path
    
    @scenarios(Path(r"{tmp_path}") / "Passing.feature", return_test_decorator=True)
    def test_passing_feature():
      # It is however encouraged to try as much as possible to have your logic only inside the Given, When, Then steps.
      ...
    
  • When run pytest

  • Then pytest outcome must contain tests with statuses:

    passed

    1

Scenario: “scenario” function is used to register feature as test

  • And File “test_scenario_load.py” with fixture templated content:

    from pytest_bdd import scenario
    from pathlib import Path
    
    test_passing_feature = scenario(Path(r"{tmp_path}") / "Passing.feature", return_test_decorator=False)
    
  • When run pytest

  • Then pytest outcome must contain tests with statuses:

    passed

    1

Scenario: “scenarios” function is used to register feature as test

  • And File “test_scenario_load.py” with fixture templated content:

    from pytest_bdd import scenarios
    from pathlib import Path
    
    test_passing_feature = scenarios(Path(r"{tmp_path}") / "Passing.feature")
    
  • When run pytest

  • Then pytest outcome must contain tests with statuses:

    passed

    1

By default, pytest-bdd-ng will use current module’s path as base path for finding feature files, but this behaviour can be changed in the pytest configuration file (i.e. pytest.ini, tox.ini or setup.cfg) by declaring the new base path in the bdd_features_base_dir key. The path is interpreted as relative to the pytest root directory. You can also override features base path on a per-scenario basis, in order to override the path for specific tests.

Background:

  • Given File “Passing.feature” in the temporary path with content:

    Feature: Passing feature
      Scenario: Passing scenario
        Given Passing step
      Scenario: Failing scenario
        Given Failing step
    
  • And File “conftest.py” with content:

    from pytest_bdd import step
    from pytest_bdd.compatibility.pytest import fail
    
    @step('Passing step')
    def _():
      ...
    
    @step('Failing step')
    def _():
      fail('Intentional')
    
  • And File “test_feature.py” with content:

    from pytest_bdd import scenarios
    
    test = scenarios('Passing.feature')
    

Scenario:

  • Given File “pytest.ini” with fixture templated content:

    [pytest]
    bdd_features_base_dir={tmp_path}
    
  • When run pytest

  • Then pytest outcome must contain tests with statuses:

    passed

    failed

    1

    1

By default, pytest-bdd-ng will use current module’s path as base path for finding feature files, but this behaviour can be changed in the pytest configuration file (i.e. pytest.ini, tox.ini or setup.cfg) by declaring the new base path in the bdd_features_base_dir key. The path is interpreted as relative to the pytest root directory. You can also override features base path on a per-scenario basis, in order to override the path for specific tests.

Background:

  • Given Localserver endpoint “/features/Passing.feature” responding content:

    Feature: Passing feature
      Scenario: Passing scenario
        Given Passing step
      Scenario: Failing scenario
        Given Failing step
    
  • And File “conftest.py” with content:

    from pytest_bdd import step
    from pytest_bdd.compatibility.pytest import fail
    
    @step('Passing step')
    def _():
      ...
    
    @step('Failing step')
    def _():
      fail('Intentional')
    
  • And File “test_feature.py” with content:

    from pytest_bdd import scenarios,FeaturePathType
    
    test = scenarios('Passing.feature', features_path_type=FeaturePathType.URL)
    

Scenario:

  • Given File “pytest.ini” with fixture templated content:

    [pytest]
    bdd_features_base_url=http://localhost:{httpserver_port}/features
    
  • When run pytest

  • Then pytest outcome must contain tests with statuses:

    passed

    failed

    1

    1

Advanced Features

Hooks

Note

Important difference from pytest-bdd

pytest-bdd-ng exposes several pytest hooks which might be helpful building useful reporting, visualization, etc on top of it:

  • pytest_bdd_before_scenario(request, feature, scenario) - Called before scenario is executed

  • pytest_bdd_run_scenario(request, feature, scenario) - Execution scenario protocol

  • pytest_bdd_after_scenario(request, feature, scenario) - Called after scenario is executed (even if one of steps has failed)

  • pytest_bdd_before_step(request, feature, scenario, step, step_func) - Called before step function is executed and it’s arguments evaluated

  • pytest_bdd_run_step(request, feature, scenario, step, previous_step) - Execution step protocol

  • pytest_bdd_before_step_call(request, feature, scenario, step, step_func, step_func_args) - Called before step function is executed with evaluated arguments

  • pytest_bdd_after_step(request, feature, scenario, step, step_func, step_func_args) - Called after step function is successfully executed

  • pytest_bdd_step_error(request, feature, scenario, step, step_func, step_func_args, exception) - Called when step function failed to execute

  • pytest_bdd_step_func_lookup_error(request, feature, scenario, step, exception) - Called when step lookup failed

  • pytest_bdd_match_step_definition_to_step(request, feature, scenario, step, previous_step) - Called to match step to step definition

  • pytest_bdd_get_step_caller(request, feature, scenario, step, step_func, step_func_args, step_definition) - Called to get step caller. For example could be used to make steps async

  • pytest_bdd_get_step_dispatcher(request, feature, scenario) - Provide alternative approach to execute scenario steps

Default steps

Here is the list of steps that are implemented inside of the pytest-bdd:

given
  • trace - enters the pdb debugger via pytest.set_trace()

when
  • trace - enters the pdb debugger via pytest.set_trace()

then
  • trace - enters the pdb debugger via pytest.set_trace()

Fixtures

pytest-bdd-ng exposes several plugin fixtures to give more testing flexibility

  • bdd_example - The current scenario outline parametrization.

  • attach - Fixture to allow attach files to Gherkin report

  • parameter_type_registry - Contains registry of user-defined types used in Cucumber expressions

  • step_registry - Contains registry of all user-defined steps

  • step_matcher- Contains matcher to help find step definition for selected step of scenario

  • steps_left - Current scenario steps left to execute; Allow inject steps to execute:

from collections import deque

from pytest_bdd.model import UserStep
from pytest_bdd import when

@when('I inject step "{keyword}" "{step_text}')
def inject_step(steps_left: deque, keyword, step_text, scenario):
    steps_left.appendleft(UserStep(text=step_text, keyword=keyword, scenario=scenario))

StructBDD

Gherkin itself isn’t a perfect tool to describe complex Data Driven Scenarios with alternative paths to execute test. For example it doesn’t support next things:

  • Few backgrounds per scenario

  • Alternative flows for scenario to setup same state

  • Alternative flows to describe same behavior defined by different steps

  • Usage of parameters inside Backgrounds

  • Joining of parameter tables, so full Cartesian product of parameters has to be listed in Examples

  • Example tables on different scenario levels

For such scenarios StructBDD DSL was developed. It independent on underlying data format, but supports most common formats for DSL development: YAML, Hocon, TOML, JSON5, HJSON out the box.

Steps could be defined as usual, and scenarios have different options. Let see.

steps.bdd.yaml

Name: Steps are executed one by one
Description: |
    Steps are executed one by one. Given and When sections
    are not mandatory in some cases.
Steps:
    - Step:
        Name: Executed step by step
        Description: Scenario description
        Steps:
            - I have a foo fixture with value "foo"
            - And: there is a list
            - When: I append 1 to the list
            - Step:
                Action: I append 2 to the list
                Type: And
            - Alternative:
                - Step:
                    Steps:
                        - And: I append 3 to the list
                        - Then: foo should have value "foo"
                        - But: the list should be [1, 2, 3]
                - Step:
                    Steps:
                        - And: I append 4 to the list
                        - Then: foo should have value "foo"
                        - But: the list should be [1, 2, 4]

Alternative steps produce separate test launches for every of flows. If alternative steps are defined on different levels - there would be Cartesian product of tests for every alternative step.

Scenario could be imported as usual, but with specified parser:

from textwrap import dedent
from pytest_bdd import given, when, then, scenario
from pytest_bdd.parser import StructBDDParser
from functools import partial

kind = StructBDDParser.KIND.YAML

@scenario(f"steps.bdd.{kind}", "Executed step by step", parser=partial(StructBDDParser, kind=kind)
def test_steps(feature):
    pass

Another option is to inject built scenario directly:

from pytest_bdd.struct_bdd.model import Step, Table

test_cukes = Step(
    name="Examples are substituted",
    steps=[
        Step(type='Given', action='I have <have> cucumbers'),
        Step(type='And', action='I eat <eat> cucumbers'),
        Step(type='Then', action='I have <left> cucumbers')
    ],
    examples=[
        Table(
            parameters=['have', 'eat', 'left'],
            values=[
                ['12', 5, 7.0],
                ["8.0", 3.0, "5"]
            ]
        )
    ]
)

There is also an option to build Step from dict(and use your own file format/preprocessor)

from pytest_bdd.struct_bdd.model import Step

cukes = Step.parse_obj(
        dict(
            Name="Examples are substituted",
            Steps=[
                dict(Given='I have <have> cucumbers'),
                dict(And='I eat <eat> cucumbers'),
                dict(Then='I have <left> cucumbers')
            ],
            Examples=[
                dict(
                    Table=dict(
                        Parameters=['have', 'eat', 'left'],
                        Values=[
                            ['12', 5, 7.0],
                            ["8.0", 3.0, "5"]
                        ]
                    )
                )
            ]
        )
    )

@cukes
def test(feature:Feature, scenario):
    assert feature.name == "Examples are substituted"

Example tables could be joined:

Tags:
  - TopTag
Name: StepName
Action: "Do first <HeaderA>, <HeaderB>, <HeaderC>"
Examples:
  - Join:
    - Table:
        Tags:
          - ExampleTagA
        Parameters:
          [ HeaderA, HeaderB ]
        Values:
          - [ A1, B1]
          - [ A2, B2]
    - Table:
        Tags:
          - ExampleTagB
        Parameters:
          [ HeaderB, HeaderC ]
        Values:
          - [ B1, C1 ]
          - [ B2, C2 ]
          - [ B3, C3 ]
Steps: []

Install StructBDD:

pip install pytest-bdd-ng[struct_bdd]

Reporting

It’s important to have nice reporting out of your bdd tests. Cucumber introduced some kind of standard for json format which can be used for, for example, by this Jenkins plugin.

To have an output in json format:

pytest --cucumberjson=<path to json report>

This will output an expanded (meaning scenario outlines will be expanded to several scenarios) cucumber format.

To enable gherkin-formatted output on terminal, use

pytest -vv --gherkin-terminal-reporter

Allure reporting is also in place https://docs.qameta.io/allure and based on allure-pytest https://pypi.org/project/allure-pytest/ plugin. Usage is same.

To install plugin

pip install pytest-bdd-ng[allure]

Test code generation helpers

For newcomers it’s sometimes hard to write all needed test code without being frustrated. To simplify their life, simple code generator was implemented. It allows to create fully functional but of course empty tests and step definitions for given a feature file. It’s done as a separate console script provided by pytest-bdd package:

pytest --generate --feature <feature file name> .. <feature file nameN>

It will print the generated code to the standard output so you can easily redirect it to the file:

pytest --generate --feature features/some.feature > tests/functional/test_some.py

Advanced code generation

For more experienced users, there’s smart code generation/suggestion feature. It will only generate the test code which is not yet there, checking existing tests and step definitions the same way it’s done during the test execution. The code suggestion tool is called via passing additional pytest arguments:

pytest --generate-missing --feature features tests/functional

The output will be like:

============================= test session starts ==============================
platform linux2 -- Python 2.7.6 -- py-1.4.24 -- pytest-2.6.2
plugins: xdist, pep8, cov, cache, bdd, bdd, bdd
collected 2 items

Scenario is not bound to any test: "Code is generated for scenarios which are not bound to any tests" in feature "Missing code generation" in /tmp/pytest-552/testdir/test_generate_missing0/tests/generation.feature
--------------------------------------------------------------------------------

Step is not defined: "I have a custom bar" in scenario: "Code is generated for scenario steps which are not yet defined(implemented)" in feature "Missing code generation" in /tmp/pytest-552/testdir/test_generate_missing0/tests/generation.feature
--------------------------------------------------------------------------------
Please place the code above to the test file(s):

@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."""

As as side effect, the tool will validate the files for format errors, also some of the logic bugs, for example the ordering of the types of the steps.

Known limitations

  • Unable fully collect messages report if tests were launched using xdist plugin

Authors

Oleg Pidsadnyi

original idea, initial implementation and further improvements

Anatoly Bubenkov

key implementation idea and realization, many new features and improvements

These people have contributed to pytest-bdd, in alphabetical order:

MIT License

Copyright (C) 2013-2024 Oleg Pidsadnyi, Anatoly Bubenkov and others

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Changelog

Planned

  • Refactor internal parser API: split loader and parser APIs

  • Check using Path globs on the feature loading via scenario/scenarios

  • API doc

  • Add struct_bdd autoload

  • Move tox.ini, pytest.ini into pyproject.toml

  • Review report generation to be conform with official tools

  • Add tests about linked files and features autoload (feature autoload must not be disabled on linked files)

  • Rework extended_step_context method usage

  • Remove tests targeting Feature parsing

  • Continue support of *.md files

  • Support of messages:

    • Pending:

      • parse_error

      • undefined_parameter_type

  • Add mode to execute scenarios with missing/failing steps

  • Remove

    • Hide traceback for pytest code “__tracebackhide__ = True”

  • Generate documentation via https://github.com/jolly-good-toolbelt/sphinx_gherkindoc instead of direct use

    • Move sphinx-gherkindoc to official parser

      • Documentation is ugly when contains injected code

  • Rework generation code to include new features directly

    • Generate code into dir structure aligned with proposed project layout

  • Test messages against

  • Switch testdir to pytester after pytest<6.2 get EOL (python 3.8 and 3.9 get EOL)

  • Use uv/ruff

  • Contribute to messages repository with python model

  • Add support of native legacy cucumber-json

2.3.1

  • Fixup documentation generation

2.3.0

  • Add mobile readthedocs site support theme

  • Convert e2e test features definitions to markdown

  • Implement support of Markdown using js based parser

  • Update versions:

  • Drop python 3.8

  • Add python 3.13

  • Drop pytest<5.2

  • Added dummy html reporter

  • Fixed pytest.ini non-working option “disable_feature_autoload”

  • Improved fixture injection by adding seamless fixtures on plugin/module collection

2.2.0

  • Move documentation to Gherkin itself

  • Fixed pytest.ini option “disable_feature_autoload”

  • Improved fixture injection by adding seamless fixtures on plugin/module collection

2.1.4

2.1.0

  • Add validation for legacy cucumber.json output

  • Migrated to Pydantic 2

  • Add tests for PyPy

  • Using tox4

  • Added support of conditional hooks https://cucumber.io/docs/cucumber/api/?lang=java#conditional-hooks

  • Support of messages:

    • Done:

      • meta

      • gherkin_document

      • pickle

      • source

      • step_definition

      • test_case

      • test_case_finished

      • test_case_started

      • test_run_finished

      • test_run_started

      • test_step_finished

      • test_step_started

      • parameter_type

      • attachment

      • hook

2.0.0

  • Reviewed StructBdd step collection; no more as_test / as_test_decorator Step methods are needed directly used

  • Drop python 3.7

  • Move StructBDD model to Pydantic

  • Remove ast module usage by StructBDD

  • Test filters in scenario/scenarios to filter out not needed scenarios

  • .url, .desktop and .webloc files are collected from test directories, so scenario/scenarios usages is not necessary

  • Load features/scenarios by url

  • Features are autoloaded by default; Feature autoload could be disabled by --disable-feature-autoload cli option

  • Relative feature paths are counted from pytest rootpath

  • No more injection of tests into module space; Tests have to be registered directly

  • Separate generation scripts were moved to pytest environment

  • scenario no more override collected scenarios; They have to be registered independently. Scenarios could be filtered out if needed.

  • Added support of messages

  • Added support of cucumber expressions https://github.com/cucumber/cucumber-expressions#readme

  • It possible to name anonymous groups during step parsing

  • Remove legacy feature parser (and surplus features of it)

  • Remove outdated migration script

1.2.3

  • Features could be autoloaded by –feature-autoload cli option

  • Remove possibility to manually register imported steps; They are registered automatically

1.2.2

  • Add possibility to register imported steps

1.2.0

1.1.2

  • Fixups

1.1.1

  • Added hook to alter scenario steps execution protocol

1.1.0

  • Added allure plugin extension for allure-pytest

  • Added StructBDD DSL

1.0.0

  • Default step parameter parser is switched to cfparse. String step name is compiled to cfparse

  • Step functions could get compiled instances of parse, cfparse and re.compile directly

  • Drop pytest 4

  • Drop python 3.6

  • Added tags support for Examples sections for original parser

  • Added joining by parameters between examples sections on different levels (and with fixtures) for original feature parser

  • Step could override multiple fixtures using target_fixtures parameter

  • Default step parameters injection as fixtures behavior could be changed by params_fixtures_mapping step parameter

  • Step definitions can have “yield” statements again (4.0 release broke it). They will be executed as normal fixtures: code after the yield is executed during teardown of the test. (youtux)

  • Show pass/fail status per step in Gherkin terminal reporter

  • Step definitions could be used independently from keyword by step decorator

  • pytest_bdd_apply_tag was removed; pytest_bdd_convert_tag_to_marks was added instead

  • Feature parser switched to official one

  • Changes scenario and scenarios function/decorator feature registration order. Both could be used as decorators

  • Move scenario execution & step matching to hooks

  • Added possibility to operate steps stack via fixture

  • Other

Pre pytest-bdd-ng era

5.0.0

This release introduces breaking changes, please refer to the Migration from 4.x.x.

  • Rewrite the logic to parse Examples for Scenario Outlines. Now the substitution of the examples is done during the parsing of Gherkin feature files. You won’t need to define the steps twice like @given("there are <start> cucumbers") and @given(parsers.parse("there are {start} cucumbers")). The latter will be enough.

  • Removed example_converters from scenario(...) signature. You should now use just the converters parameter for given, when, then.

  • Removed --cucumberjson-expanded and --cucumber-json-expanded options. Now the JSON report is always expanded.

  • Removed --gherkin-terminal-reporter-expanded option. Now the terminal report is always expanded.

4.1.0

  • when and then steps now can provide a target_fixture, just like given does. Discussion at https://github.com/pytest-dev/pytest-bdd/issues/402.

  • Drop compatibility for python 2 and officially support only python >= 3.6.

  • Fix error when using –cucumber-json-expanded in combination with example_converters (marcbrossaissogeti).

  • Fix –generate-missing not correctly recognizing steps with parsers

4.0.2

  • Fix a bug that prevents using comments in the Examples: section. (youtux)

4.0.1

  • Fixed performance regression introduced in 4.0.0 where collection time of tests would take way longer than before. (youtux)

4.0.0

This release introduces breaking changes, please refer to the Migration from 3.x.x.

  • Strict Gherkin option is removed (@scenario() does not accept the strict_gherkin parameter). (olegpidsadnyi)

  • @scenario() does not accept the undocumented parameter caller_module anymore. (youtux)

  • Given step is no longer a fixture. The scope parameter is also removed. (olegpidsadnyi)

  • Fixture parameter is removed from the given step declaration. (olegpidsadnyi)

  • pytest_bdd_step_validation_error hook is removed. (olegpidsadnyi)

  • Fix an error with pytest-pylint plugin #374. (toracle)

  • Fix pytest-xdist 2.0 compatibility #369. (olegpidsadnyi)

  • Fix compatibility with pytest 6 --import-mode=importlib option. (youtux)

3.4.0

  • Parse multiline steps according to the gherkin specification #365.

3.3.0

  • Drop support for pytest < 4.3.

  • Fix a Python 4.0 bug.

  • Fix pytest --generate-missing functionality being broken.

  • Fix problematic missing step definition from strings containing quotes.

  • Implement parsing escaped pipe characters in outline parameters (Mark90) #337.

  • Disable the strict Gherkin validation in the steps generation (v-buriak) #356.

3.2.1

  • Fix regression introduced in 3.2.0 where pytest-bdd would break in presence of test items that are not functions.

3.2.0

  • Fix Python 3.8 support

  • Remove code that rewrites code. This should help with the maintenance of this project and make debugging easier.

3.1.1

  • Allow unicode string in @given() step names when using python2. This makes the transition of projects from python 2 to 3 easier.

3.1.0

  • Drop support for pytest < 3.3.2.

  • Step definitions generated by $ pytest-bdd generate will now raise NotImplementedError by default.

  • @given(...) no longer accepts regex objects. It was deprecated long ago.

  • Improve project testing by treating warnings as exceptions.

  • pytest_bdd_step_validation_error will now always receive step_func_args as defined in the signature.

3.0.2

  • Add compatibility with pytest 4.2 (sliwinski-milosz) #288.

3.0.1

  • Minimal supported version of pytest is now 2.9.0 as lower versions do not support bool type ini options (sliwinski-milosz) #260

  • Fix RemovedInPytest4Warning warnings (sliwinski-milosz) #261.

3.0.0

  • Fixtures pytestbdd_feature_base_dir and pytestbdd_strict_gherkin have been removed. Check the Migration of your tests from versions 2.x.x for more information (sliwinski-milosz) #255

  • Fix step definitions not being found when using parsers or converters after a change in pytest (youtux) #257

2.21.0

  • Gherkin terminal reporter expanded format (pauk-slon)

2.20.0

  • Added support for But steps (olegpidsadnyi)

  • Fixed compatibility with pytest 3.3.2 (olegpidsadnyi)

  • MInimal required version of pytest is now 2.8.1 since it doesn’t support earlier versions (olegpidsadnyi)

2.19.0

  • Added –cucumber-json-expanded option for explicit selection of expanded format (mjholtkamp)

  • Step names are filled in when –cucumber-json-expanded is used (mjholtkamp)

2.18.2

  • Fix check for out section steps definitions for no strict gherkin feature

2.18.1

  • Relay fixture results to recursive call of ‘get_features’ (coddingtonbear)

2.18.0

  • Add gherkin terminal reporter (spinus + thedrow)

2.17.2

  • Fix scenario lines containing an @ being parsed as a tag. (The-Compiler)

2.17.1

  • Add support for pytest 3.0

2.17.0

  • Fix FixtureDef signature for newer pytest versions (The-Compiler)

  • Better error explanation for the steps defined outside of scenarios (olegpidsadnyi)

  • Add a pytest_bdd_apply_tag hook to customize handling of tags (The-Compiler)

  • Allow spaces in tag names. This can be useful when using the pytest_bdd_apply_tag hook with tags like @xfail: Some reason.

2.16.1

  • Cleaned up hooks of the plugin (olegpidsadnyi)

  • Fixed report serialization (olegpidsadnyi)

2.16.0

  • Fixed deprecation warnings with pytest 2.8 (The-Compiler)

  • Fixed deprecation warnings with Python 3.5 (The-Compiler)

2.15.0

  • Add examples data in the scenario report (bubenkoff)

2.14.5

  • Properly parse feature description (bubenkoff)

2.14.3

  • Avoid potentially random collection order for xdist compartibility (bubenkoff)

2.14.1

  • Pass additional arguments to parsers (bubenkoff)

2.14.0

  • Add validation check which prevents having multiple features in a single feature file (bubenkoff)

2.13.1

  • Allow mixing feature example table with scenario example table (bubenkoff, olegpidsadnyi)

2.13.0

  • Feature example table (bubenkoff, sureshvv)

2.12.2

  • Make it possible to relax strict Gherkin scenario validation (bubenkoff)

2.11.3

  • Fix minimal six version (bubenkoff, dustinfarris)

2.11.1

  • Mention step type on step definition not found errors and in code generation (bubenkoff, lrowe)

2.11.0

  • Prefix step definition fixture names to avoid name collisions (bubenkoff, lrowe)

2.10.0

  • Make feature and scenario tags to be fully compartible with pytest markers (bubenkoff, kevinastone)

2.9.1

  • Fixed FeatureError string representation to correctly support python3 (bubenkoff, lrowe)

2.9.0

  • Added possibility to inject fixtures from given keywords (bubenkoff)

2.8.0

  • Added hook before the step is executed with evaluated parameters (olegpidsadnyi)

2.7.2

  • Correct base feature path lookup for python3 (bubenkoff)

2.7.1

  • Allow to pass scope for given steps (bubenkoff, sureshvv)

2.7.0

  • Implemented scenarios shortcut to automatically bind scenarios to tests (bubenkoff)

2.6.2

  • Parse comments only in the beginning of words (santagada)

2.6.1

  • Correctly handle pytest-bdd command called without the subcommand under python3 (bubenkoff, spinus)

  • Pluggable parsers for step definitions (bubenkoff, spinus)

2.5.3

  • Add after scenario hook, document both before and after scenario hooks (bubenkoff)

2.5.2

  • Fix code generation steps ordering (bubenkoff)

2.5.1

  • Fix error report serialization (olegpidsadnyi)

2.5.0

  • Fix multiline steps in the Background section (bubenkoff, arpe)

  • Code cleanup (olegpidsadnyi)

2.4.5

  • Fix unicode issue with scenario name (bubenkoff, aohontsev)

2.4.3

  • Fix unicode regex argumented steps issue (bubenkoff, aohontsev)

  • Fix steps timings in the json reporting (bubenkoff)

2.4.2

  • Recursion is fixed for the –generate-missing and the –feature parameters (bubenkoff)

2.4.1

  • Better reporting of a not found scenario (bubenkoff)

  • Simple test code generation implemented (bubenkoff)

  • Correct timing values for cucumber json reporting (bubenkoff)

  • Validation/generation helpers (bubenkoff)

2.4.0

  • Background support added (bubenkoff)

  • Fixed double collection of the conftest files if scenario decorator is used (ropez, bubenkoff)

2.3.3

  • Added timings to the cucumber json report (bubenkoff)

2.3.2

  • Fixed incorrect error message using e.argname instead of step.name (hvdklauw)

2.3.1

  • Implemented cucumber tags support (bubenkoff)

  • Implemented cucumber json formatter (bubenkoff, albertjan)

  • Added ‘trace’ keyword (bubenkoff)

2.1.2

  • Latest pytest compartibility fixes (bubenkoff)

2.1.1

  • Bugfixes (bubenkoff)

2.1.0

  • Implemented multiline steps (bubenkoff)

2.0.1

  • Allow more than one parameter per step (bubenkoff)

  • Allow empty example values (bubenkoff)

2.0.0

  • Pure pytest parametrization for scenario outlines (bubenkoff)

  • Argumented steps now support converters (transformations) (bubenkoff)

  • scenario supports only decorator form (bubenkoff)

  • Code generation refactoring and cleanup (bubenkoff)

1.0.0

  • Implemented scenario outlines (bubenkoff)

0.6.11

  • Fixed step arguments conflict with the fixtures having the same name (olegpidsadnyi)

0.6.9

  • Implemented support of Gherkin “Feature:” (olegpidsadnyi)

0.6.8

  • Implemented several hooks to allow reporting/error handling (bubenkoff)

0.6.6

  • Fixes to unnecessary mentioning of pytest-bdd package files in py.test log with -v (bubenkoff)

0.6.5

  • Compatibility with recent pytest (bubenkoff)

0.6.4

  • More unicode fixes (amakhnach)

0.6.3

  • Added unicode support for feature files. Removed buggy module replacement for scenario. (amakhnach)

0.6.2

  • Removed unnecessary mention of pytest-bdd package files in py.test log with -v (bubenkoff)

0.6.1

  • Step arguments in whens when there are no given arguments used. (amakhnach, bubenkoff)

0.6.0

  • Added step arguments support. (curzona, olegpidsadnyi, bubenkoff)

  • Added checking of the step type order. (markon, olegpidsadnyi)

0.5.2

  • Added extra info into output when FeatureError exception raises. (amakhnach)

0.5.0

  • Added parametrization to scenarios

  • Coveralls.io integration

  • Test coverage improvement/fixes

  • Correct wrapping of step functions to preserve function docstring

0.4.7

  • Fixed Python 3.3 support

0.4.6

  • Fixed a bug when py.test –fixtures showed incorrect filenames for the steps.

0.4.5

  • Fixed a bug with the reuse of the fixture by given steps being evaluated multiple times.

0.4.3

  • Update the license file and PYPI related documentation.