pytest-dev / pytest-bdd

BDD library for the pytest runner
https://pytest-bdd.readthedocs.io/en/latest/
MIT License
1.31k stars 221 forks source link

Parsed step aliases no longer working on 6.0.0 #528

Closed kimsappi closed 2 years ago

kimsappi commented 2 years ago

Having multiple step aliases with parsing is no longer supported as of pytest-bdd 6.0.0. The error is reproducible by varying the order of the decorators (@given(...) in the following example): only the top-most decorator ever works.

Example:

Input:

$ cat x.feature 
Feature: x

    Scenario: x
        Given I have a "parameterised" step

        Then success

    Scenario: y
        Given I have a differently "parameterised" step

        Then success

$ cat test_x.py 
from pytest_bdd import scenario, given, then, parsers

@scenario('x.feature', 'x')
def test_x():
    pass

@scenario('x.feature', 'y')
def test_y():
    pass

@given(parsers.parse('I have a "{param}" step'))
@given(parsers.parse('I have a differently "{param}" step'))
def parameterised(param):
    pass

@then('success')
def success():
    assert True

Expected output (pytest-bdd==5.0.0):

$ py.test
===================================== test session starts =====================================
platform linux -- Python 3.9.2, pytest-7.1.2, pluggy-1.0.0
rootdir: /home/xxx/pytestbdd
plugins: bdd-5.0.0
collected 2 items                                                                             

test_x.py ..                                                                            [100%]

====================================== 2 passed in 0.04s ======================================

Real output (pytest-bdd==6.0.0):

$ py.test
===================================== test session starts =====================================
platform linux -- Python 3.9.2, pytest-7.1.2, pluggy-1.0.0
rootdir: /home/xxx/pytestbdd
plugins: bdd-6.0.0
collected 2 items                                                                             

test_x.py .F                                                                            [100%]

========================================== FAILURES ===========================================
___________________________________________ test_y ____________________________________________

request = <FixtureRequest for <Function test_y>>, _pytest_bdd_example = {}

    @pytest.mark.usefixtures(*func_args)
    def scenario_wrapper(request: FixtureRequest, _pytest_bdd_example: dict[str, str]) -> Any:
        scenario = templated_scenario.render(_pytest_bdd_example)
>       _execute_scenario(feature, scenario, request)

venv/lib/python3.9/site-packages/pytest_bdd/scenario.py:184: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
venv/lib/python3.9/site-packages/pytest_bdd/scenario.py:154: in _execute_scenario
    _execute_step_function(request, scenario, step, step_func)
venv/lib/python3.9/site-packages/pytest_bdd/scenario.py:112: in _execute_step_function
    for arg, value in parser.parse_arguments(step.name).items():
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <pytest_bdd.parsers.parse object at 0x7f7290892460>
name = 'I have a differently "parameterised" step'

    def parse_arguments(self, name: str) -> dict[str, Any]:
        """Get step arguments.

        :return: `dict` of step arguments
        """
>       return cast(Dict[str, Any], self.parser.parse(name).named)
E       AttributeError: 'NoneType' object has no attribute 'named'

venv/lib/python3.9/site-packages/pytest_bdd/parsers.py:68: AttributeError
=================================== short test summary info ===================================
FAILED test_x.py::test_y - AttributeError: 'NoneType' object has no attribute 'named'
================================= 1 failed, 1 passed in 0.10s =================================

Environment

$ pip freeze
attrs==21.4.0
glob2==0.7
iniconfig==1.1.1
Mako==1.2.1
MarkupSafe==2.1.1
packaging==21.3
parse==1.19.0
parse-type==0.6.0
pluggy==1.0.0
py==1.11.0
pyparsing==3.0.9
pytest==7.1.2
pytest-bdd==6.0.0
six==1.16.0
tomli==2.0.1

$ diff <(pip freeze) 5.0.0
13c13
< pytest-bdd==6.0.0
---
> pytest-bdd==5.0.0

$ python3 --version
Python 3.9.2
dcendents commented 2 years ago

I also saw the same error but in my case I have both a @given and a @when step on the same function in that order (@given declared first and @when second). I believe both steps are registered but only the first parser is ever used.

e.g.: In my test the @when step gets executed but the string is parsed using the statement from the @given parser which does not match and then returns None here:

    def parse(self, string, evaluate_result=True):
        """Match my format to the string exactly.

        Return a Result or Match instance or None if there's no match.
        """
        m = self._match_re.match(string)
        if m is None:
            return None

So my test fails with the following error:

    def parse_arguments(self, name: str) -> dict[str, Any]:
        """Get step arguments.

        :return: `dict` of step arguments
        """
>       return cast(Dict[str, Any], self.parser.parse(name).named)
E       AttributeError: 'NoneType' object has no attribute 'named'
youtux commented 2 years ago

I made a fix in #530, can you try it out before I make a release to check if it fixes it for your project?

You can install it using:

pip install git+https://github.com/pytest-dev/pytest-bdd.git@15e1a8aa201384e9cd7d026af0200f5942b2cadd#egg=pytest-bdd
dcendents commented 2 years ago

@youtux yes it is working for me (my project has 1267 tests)

I only had to convert some scenarios where I used vertical example tables which is not supported anymore, otherwise upgrading from 5.0.0 to this fixed version works.

kimsappi commented 2 years ago

Thank you for the swift work. Tested it against some of our projects, and they work with this version.

youtux commented 2 years ago

Thanks for testing, I'll probably release 6.0.1 tomorrow.

youtux commented 2 years ago

Version 6.0.1 is out.

tiborsimon commented 2 years ago

It seems like you only created a tag here in Github, but the latest release is still 6.0.0. On Pypi it was updated, and the latest version is 6.0.1.