Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 10 additions & 9 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,16 @@ Unreleased

This release introduces breaking changes in order to be more in line with the official gherkin specification.

- Cleanup of the documentation and tests related to parametrization (elchupanebrej)
- Removed feature level examples for the gherkin compatibility (olegpidsadnyi)
- Removed vertical examples for the gherkin compatibility (olegpidsadnyi)
- Step arguments are no longer fixtures (olegpidsadnyi)
- Drop support of python 3.6, pytest 4 (elchupanebrej)
- 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)
- Scenario outlines unused example parameter validation is removed (olegpidsadnyi)
- Add type decorations
- ``pytest_bdd.parsers.StepParser`` now is an Abstract Base Class. Subclasses must make sure to implement the abstract methods.
- Cleanup of the documentation and tests related to parametrization (elchupanebrej) https://github.com/pytest-dev/pytest-bdd/pull/469
- Removed feature level examples for the gherkin compatibility (olegpidsadnyi) https://github.com/pytest-dev/pytest-bdd/pull/490
- Removed vertical examples for the gherkin compatibility (olegpidsadnyi) https://github.com/pytest-dev/pytest-bdd/pull/492
- Step arguments are no longer fixtures (olegpidsadnyi) https://github.com/pytest-dev/pytest-bdd/pull/493
- Drop support of python 3.6, pytest 4 (elchupanebrej) https://github.com/pytest-dev/pytest-bdd/pull/495 https://github.com/pytest-dev/pytest-bdd/pull/504
- 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) https://github.com/pytest-dev/pytest-bdd/pull/503
- Scenario outlines unused example parameter validation is removed (olegpidsadnyi) https://github.com/pytest-dev/pytest-bdd/pull/499
- Add type annotations (youtux) https://github.com/pytest-dev/pytest-bdd/pull/505
- ``pytest_bdd.parsers.StepParser`` now is an Abstract Base Class. Subclasses must make sure to implement the abstract methods. (youtux) https://github.com/pytest-dev/pytest-bdd/pull/505
- Angular brackets in step definitions are only parsed in "Scenario Outline" (previously they were parsed also in normal "Scenario"s) (youtux) https://github.com/pytest-dev/pytest-bdd/pull/524.



Expand Down
42 changes: 28 additions & 14 deletions pytest_bdd/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,11 +153,17 @@ def parse_feature(basedir: str, filename: str, encoding: str = "utf-8") -> Featu

# Remove Feature, Given, When, Then, And
keyword, parsed_line = parse_line(clean_line)

if mode in [types.SCENARIO, types.SCENARIO_OUTLINE]:
tags = get_tags(prev_line)
feature.scenarios[parsed_line] = scenario = ScenarioTemplate(
feature=feature, name=parsed_line, line_number=line_number, tags=tags
scenario = ScenarioTemplate(
feature=feature,
name=parsed_line,
line_number=line_number,
tags=tags,
templated=mode == types.SCENARIO_OUTLINE,
)
feature.scenarios[parsed_line] = scenario
elif mode == types.BACKGROUND:
feature.background = Background(feature=feature, line_number=line_number)
elif mode == types.EXAMPLES:
Expand Down Expand Up @@ -210,7 +216,7 @@ class ScenarioTemplate:

Created when parsing the feature file, it will then be combined with the examples to create a Scenario."""

def __init__(self, feature: Feature, name: str, line_number: int, tags=None) -> None:
def __init__(self, feature: Feature, name: str, line_number: int, templated: bool, tags=None) -> None:
"""

:param str name: Scenario name.
Expand All @@ -223,6 +229,7 @@ def __init__(self, feature: Feature, name: str, line_number: int, tags=None) ->
self.examples = Examples()
self.line_number = line_number
self.tags = tags or set()
self.templated = templated

def add_step(self, step: Step) -> None:
"""Add step to the scenario.
Expand All @@ -238,31 +245,38 @@ def steps(self) -> list[Step]:
return (background.steps if background else []) + self._steps

def render(self, context: Mapping[str, Any]) -> Scenario:
steps = [
Step(
name=templated_step.render(context),
type=templated_step.type,
indent=templated_step.indent,
line_number=templated_step.line_number,
keyword=templated_step.keyword,
)
for templated_step in self.steps
]
background_steps = self.feature.background.steps if self.feature.background else []
if not self.templated:
scenario_steps = self._steps
else:
scenario_steps = [
Step(
name=step.render(context),
type=step.type,
indent=step.indent,
line_number=step.line_number,
keyword=step.keyword,
)
for step in self._steps
]
steps = background_steps + scenario_steps
return Scenario(feature=self.feature, name=self.name, line_number=self.line_number, steps=steps, tags=self.tags)


class Scenario:

"""Scenario."""

def __init__(self, feature: Feature, name: str, line_number: int, steps: list[Step], tags=None) -> None:
def __init__(self, feature: Feature, name: str, line_number: int, steps: list[Step] = None, tags=None) -> None:
"""Scenario constructor.

:param pytest_bdd.parser.Feature feature: Feature.
:param str name: Scenario name.
:param int line_number: Scenario line number.
:param set tags: Set of tags.
"""
if steps is None:
steps = []
self.feature = feature
self.name = name
self.steps = steps
Expand Down
8 changes: 4 additions & 4 deletions pytest_bdd/scenario.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,18 +74,18 @@ def _find_step_function(request: FixtureRequest, step: Step, scenario: Scenario)
try:
# Simple case where no parser is used for the step
return request.getfixturevalue(get_step_fixture_name(name, step.type))
except FixtureLookupError:
except FixtureLookupError as e:
try:
# Could not find a fixture with the same name, let's see if there is a parser involved
argumented_name = find_argumented_step_fixture_name(name, step.type, request._fixturemanager, request)
if argumented_name:
return request.getfixturevalue(argumented_name)
raise
except FixtureLookupError:
raise e
except FixtureLookupError as e2:
raise exceptions.StepDefinitionNotFoundError(
f"Step definition is not found: {step}. "
f'Line {step.line_number} in scenario "{scenario.name}" in the feature "{scenario.feature.filename}"'
)
) from e2


def _execute_step_function(request: FixtureRequest, scenario: Scenario, step: Step, step_func: Callable) -> None:
Expand Down
2 changes: 1 addition & 1 deletion tests/feature/test_cucumber_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def test_step_trace(testdir):
And a failing step

@scenario-outline-passing-tag
Scenario: Passing outline
Scenario Outline: Passing outline
Given type <type> and value <value>

Examples: example1
Expand Down
2 changes: 1 addition & 1 deletion tests/feature/test_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ def test_complex_types(testdir, pytestconfig):
"""
Feature: Report serialization containing parameters of complex types

Scenario: Complex
Scenario Outline: Complex
Given there is a coordinate <point>

Examples:
Expand Down
46 changes: 46 additions & 0 deletions tests/feature/test_scenario.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,49 @@ def bar():
)
result = testdir.runpytest_subprocess(*pytest_params)
result.assert_outcomes(passed=1)


def test_angular_brakets_are_not_parsed(testdir):
"""Test that angular brackets are not parsed for "Scenario"s.

(They should be parsed only when used in "Scenario Outline")

"""
testdir.makefile(
".feature",
simple="""
Feature: Simple feature
Scenario: Simple scenario
Given I have a <tag>
Then pass

Scenario Outline: Outlined scenario
Given I have a templated <foo>
Then pass

Examples:
| foo |
| bar |
""",
)
testdir.makepyfile(
"""
from pytest_bdd import scenarios, given, then, parsers

scenarios("simple.feature")

@given("I have a <tag>")
def bar():
return "tag"

@given(parsers.parse("I have a templated {foo}"))
def bar(foo):
return "foo"

@then("pass")
def bar():
pass
"""
)
result = testdir.runpytest()
result.assert_outcomes(passed=2)