Skip to content

Commit 7124f1a

Browse files
authored
Merge branch 'master' into master
2 parents 7393c8f + 93e446e commit 7124f1a

17 files changed

+645
-80
lines changed

CHANGES.rst

Lines changed: 78 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,92 @@
11
Changelog
22
=========
33

4+
All notable changes to this project will be documented in this file.
5+
6+
The format is based on `Keep a Changelog <https://keepachangelog.com/en/1.1.0/>`_,
7+
and this project adheres to `Semantic Versioning <https://semver.org/spec/v2.0.0.html>`_.
8+
49
Unreleased
510
----------
6-
- Dropped support for python 3.8. Supported python versions: 3.9, 3.10, 3.11, 3.12, 3.13.
7-
- Text after the `#` character is no longer stripped from the Scenario and Feature name.
8-
- Gherkin keyword aliases can now be used and correctly reported in json and terminal output (see `Keywords <https://cucumber.io/docs/gherkin/reference/#keywords>` for permitted list).
9-
- Added localization support. The language of the feature file can be specified using the `# language: <language>` directive at the beginning of the file.
11+
12+
Added
13+
+++++
14+
15+
Changed
16+
+++++++
17+
18+
Deprecated
19+
++++++++++
20+
21+
Removed
22+
+++++++
23+
24+
Fixed
25+
+++++
26+
27+
Security
28+
++++++++
29+
30+
31+
[8.0.0] - 2024-11-14
32+
----------
33+
34+
Added
35+
+++++
36+
* Gherkin keyword aliases can now be used and correctly reported in json and terminal output (see `Keywords <https://cucumber.io/docs/gherkin/reference/#keywords>`_ for permitted list).
37+
* Added localization support. The language of the feature file can be specified using the `# language: <language>` directive at the beginning of the file.
38+
* Rule keyword can be used in feature files (see `Rule <https://cucumber.io/docs/gherkin/reference/#rule>`_)
39+
* Added support for multiple example tables
40+
* Added filtering by tags against example tables
41+
* Since the 7.x series:
42+
* Tags can now be on multiple lines (stacked)
43+
* Continuation of steps using asterisks (``*``) instead of ``And``/``But`` supported.
44+
* Added ``datatable`` argument for steps that contain a datatable (see `Data Tables <https://cucumber.io/docs/gherkin/reference/#data-tables>`_).
45+
* Added ``docstring`` argument for steps that contain a docstring (see `Doc Strings <https://cucumber.io/docs/gherkin/reference/#doc-strings>`_).
46+
47+
Changed
48+
+++++++
49+
* Changelog format updated to follow `Keep a Changelog <https://keepachangelog.com/en/1.1.0/>`_.
50+
* Text after the ``#`` character is no longer stripped from the Scenario and Feature name.
51+
* Since the 7.x series:
52+
53+
* Use the `gherkin-official <https://pypi.org/project/gherkin-official/>`_ parser, replacing the custom parsing logic. This will make pytest-bdd more compatible with the Gherkin specification.
54+
* Multiline steps must now always use triple-quotes for the additional lines.
55+
* All feature files must now use the keyword ``Feature:`` to be considered valid.
56+
* Tags can no longer have spaces (e.g. ``@tag one`` and ``@tag two`` are no longer valid).
57+
* Text after the ``#`` character is no longer stripped from the Step name.
58+
* Multiline strings no longer match name based on multiple lines - only on the actual step text on the step line.
59+
60+
Removed
61+
+++++++
62+
* Dropped support for python 3.8. Supported python versions: 3.9, 3.10, 3.11, 3.12, 3.13.
63+
* Since the 7.x series:
64+
65+
* Drop compatibility with pytest < 7.0.0.
66+
67+
Fixed
68+
+++++
69+
* Since the 7.x series:
70+
71+
* Updated documentation to clarify that ``--gherkin-terminal-reporter`` needs to be used with ``-v`` or ``-vv``.
1072

1173
8.0.0b2
1274
----------
13-
- Update documentation to clarify that `--gherkin-terminal-reporter` needs to be used with `-v` or `-vv`.
14-
- Drop compatibility with pytest < 7.0.0.
15-
- Continuation of steps using asterisks instead of And/But supported.
16-
- Added `datatable` argument for steps that contain a datatable (see `Data Tables <https://cucumber.io/docs/gherkin/reference/#data-tables>`).
17-
- Added `docstring` argument for steps that contain a docstring (see `Doc Strings <https://cucumber.io/docs/gherkin/reference/#doc-strings>`).
18-
- Multiline strings no longer match name based on multiple lines - only on the actual step text on the step line.
75+
* Updated documentation to clarify that ``--gherkin-terminal-reporter`` needs to be used with ``-v`` or ``-vv``.
76+
* Drop compatibility with pytest < 7.0.0.
77+
* Continuation of steps using asterisks instead of And/But supported.
78+
* Added ``datatable`` argument for steps that contain a datatable (see `Data Tables <https://cucumber.io/docs/gherkin/reference/#data-tables>`_).
79+
* Added ``docstring`` argument for steps that contain a docstring (see `Doc Strings <https://cucumber.io/docs/gherkin/reference/#doc-strings>`_).
80+
* Multiline strings no longer match name based on multiple lines - only on the actual step text on the step line.
1981

2082
8.0.0b1
2183
----------
22-
- Use `gherkin-official` parser to replace custom parsing logic. This will make pytest-bdd more compatible with the Gherkin specification.
23-
- Multiline steps must now always use triple-quotes for the additional lines.
24-
- All feature files must now use the keyword `Feature:` to be considered valid.
25-
- Tags can no longer have spaces (e.g. "@tag one" and "@tag two" are no longer valid).
26-
- Tags can now be on multiple lines (stacked)
27-
- Text after the `#` character is no longer stripped from the Step name.
84+
* Use the `gherkin-official <https://pypi.org/project/gherkin-official/>`_ parser, replacing the custom parsing logic. This will make pytest-bdd more compatible with the Gherkin specification.
85+
* Multiline steps must now always use triple-quotes for the additional lines.
86+
* All feature files must now use the keyword ``Feature:`` to be considered valid.
87+
* Tags can no longer have spaces (e.g. ``@tag one`` and ``@tag two`` are no longer valid).
88+
* Tags can now be on multiple lines (stacked)
89+
* Text after the ``#`` character is no longer stripped from the Step name.
2890

2991
7.3.0
3092
----------

README.rst

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,103 @@ Example:
513513
def should_have_left_cucumbers(cucumbers, left):
514514
assert cucumbers["start"] - cucumbers["eat"] == left
515515
516+
Rules
517+
-----
518+
519+
In Gherkin, `Rules` allow you to group related scenarios or examples under a shared context.
520+
This is useful when you want to define different conditions or behaviours
521+
for multiple examples that follow a similar structure.
522+
You can use either ``Scenario`` or ``Example`` to define individual cases, as they are aliases and function identically.
523+
524+
Additionally, **tags** applied to a rule will be automatically applied to all the **examples or scenarios**
525+
under that rule, making it easier to organize and filter tests during execution.
526+
527+
Example:
528+
529+
.. code-block:: gherkin
530+
531+
Feature: Rules and examples
532+
533+
@feature_tag
534+
Rule: A rule for valid cases
535+
536+
@rule_tag
537+
Example: Valid case 1
538+
Given I have a valid input
539+
When I process the input
540+
Then the result should be successful
541+
542+
Rule: A rule for invalid cases
543+
Example: Invalid case
544+
Given I have an invalid input
545+
When I process the input
546+
Then the result should be an error
547+
548+
549+
Scenario Outlines with Multiple Example Tables
550+
----------------------------------------------
551+
552+
In `pytest-bdd`, you can use multiple example tables in a scenario outline to test
553+
different sets of input data under various conditions.
554+
You can define separate `Examples` blocks, each with its own table of data,
555+
and optionally tag them to differentiate between positive, negative, or any other conditions.
556+
557+
Example:
558+
559+
.. code-block:: gherkin
560+
561+
# content of scenario_outline.feature
562+
563+
Feature: Scenario outlines with multiple examples tables
564+
Scenario Outline: Outlined with multiple example tables
565+
Given there are <start> cucumbers
566+
When I eat <eat> cucumbers
567+
Then I should have <left> cucumbers
568+
569+
@positive
570+
Examples: Positive results
571+
| start | eat | left |
572+
| 12 | 5 | 7 |
573+
| 5 | 4 | 1 |
574+
575+
@negative
576+
Examples: Impossible negative results
577+
| start | eat | left |
578+
| 3 | 9 | -6 |
579+
| 1 | 4 | -3 |
580+
581+
.. code-block:: python
582+
583+
from pytest_bdd import scenarios, given, when, then, parsers
584+
585+
586+
scenarios("scenario_outline.feature")
587+
588+
589+
@given(parsers.parse("there are {start:d} cucumbers"), target_fixture="cucumbers")
590+
def given_cucumbers(start):
591+
return {"start": start, "eat": 0}
592+
593+
594+
@when(parsers.parse("I eat {eat:d} cucumbers"))
595+
def eat_cucumbers(cucumbers, eat):
596+
cucumbers["eat"] += eat
597+
598+
599+
@then(parsers.parse("I should have {left:d} cucumbers"))
600+
def should_have_left_cucumbers(cucumbers, left):
601+
assert cucumbers["start"] - cucumbers["eat"] == left
602+
603+
604+
When you filter scenarios by a tag, only the examples associated with that tag will be executed.
605+
This allows you to run a specific subset of your test cases based on the tag.
606+
For example, in the following scenario outline, if you filter by the @positive tag,
607+
only the examples under the "Positive results" table will be executed, and the "Negative results" table will be ignored.
608+
609+
.. code-block:: bash
610+
611+
pytest -k "positive"
612+
516613
517614
Datatables
518615
----------

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "pytest-bdd"
3-
version = "8.0.0b2"
3+
version = "8.0.0"
44
description = "BDD for pytest"
55
authors = ["Oleg Pidsadnyi <[email protected]>", "Anatoly Bubenkov <[email protected]>"]
66
maintainers = ["Alessio Bogon <[email protected]>"]

src/pytest_bdd/generation.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
from .compat import getfixturedefs
1313
from .feature import get_features
14+
from .parser import Feature, ScenarioTemplate, Step
1415
from .scenario import inject_fixturedefs_for_step, make_python_docstring, make_python_name, make_string_literal
1516
from .steps import get_step_fixture_name
1617
from .types import STEP_TYPES
@@ -25,7 +26,6 @@
2526
from _pytest.main import Session
2627
from _pytest.python import Function
2728

28-
from .parser import Feature, ScenarioTemplate, Step
2929

3030
template_lookup = TemplateLookup(directories=[os.path.join(os.path.dirname(__file__), "templates")])
3131

@@ -88,8 +88,10 @@ def print_missing_code(scenarios: list[ScenarioTemplate], steps: list[Step]) ->
8888
for scenario in scenarios:
8989
tw.line()
9090
tw.line(
91-
'Scenario "{scenario.name}" is not bound to any test in the feature "{scenario.feature.name}"'
92-
" in the file {scenario.feature.filename}:{scenario.line_number}".format(scenario=scenario),
91+
(
92+
f'Scenario "{scenario.name}" is not bound to any test in the feature "{scenario.feature.name}" '
93+
f"in the file {scenario.feature.filename}:{scenario.line_number}"
94+
),
9395
red=True,
9496
)
9597

@@ -100,18 +102,16 @@ def print_missing_code(scenarios: list[ScenarioTemplate], steps: list[Step]) ->
100102
tw.line()
101103
if step.scenario is not None:
102104
tw.line(
103-
"""Step {step} is not defined in the scenario "{step.scenario.name}" in the feature"""
104-
""" "{step.scenario.feature.name}" in the file"""
105-
""" {step.scenario.feature.filename}:{step.line_number}""".format(step=step),
105+
(
106+
f'Step {step} is not defined in the scenario "{step.scenario.name}" '
107+
f'in the feature "{step.scenario.feature.name}" in the file '
108+
f"{step.scenario.feature.filename}:{step.line_number}"
109+
),
106110
red=True,
107111
)
108112
elif step.background is not None:
109-
tw.line(
110-
"""Step {step} is not defined in the background of the feature"""
111-
""" "{step.background.feature.name}" in the file"""
112-
""" {step.background.feature.filename}:{step.line_number}""".format(step=step),
113-
red=True,
114-
)
113+
message = f"Background step {step} is not defined."
114+
tw.line(message, red=True)
115115

116116
if step:
117117
tw.sep("-", red=True)

src/pytest_bdd/gherkin_parser.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ def from_dict(cls, data: dict[str, Any]) -> Self:
104104
@dataclass
105105
class ExamplesTable:
106106
location: Location
107+
tags: list[Tag]
107108
name: str | None = None
108109
table_header: Row | None = None
109110
table_body: list[Row] | None = field(default_factory=list)
@@ -115,6 +116,7 @@ def from_dict(cls, data: dict[str, Any]) -> Self:
115116
name=data.get("name"),
116117
table_header=Row.from_dict(data["tableHeader"]) if data.get("tableHeader") else None,
117118
table_body=[Row.from_dict(row) for row in data.get("tableBody", [])],
119+
tags=[Tag.from_dict(tag) for tag in data["tags"]],
118120
)
119121

120122

src/pytest_bdd/gherkin_terminal_reporter.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ def configure(config: Config) -> None:
4646
class GherkinTerminalReporter(TerminalReporter): # type: ignore
4747
def __init__(self, config: Config) -> None:
4848
super().__init__(config)
49+
self.current_rule = None
4950

5051
def pytest_runtest_logreport(self, report: TestReport) -> Any:
5152
rep = report
@@ -66,16 +67,27 @@ def pytest_runtest_logreport(self, report: TestReport) -> Any:
6667
word_markup = {"yellow": True}
6768
feature_markup = {"blue": True}
6869
scenario_markup = word_markup
70+
rule_markup = {"purple": True}
6971

7072
if self.verbosity <= 0 or not hasattr(report, "scenario"):
7173
return super().pytest_runtest_logreport(rep)
7274

75+
rule = report.scenario.get("rule")
76+
indent = " " if rule else ""
77+
7378
if self.verbosity == 1:
7479
self.ensure_newline()
7580
self._tw.write(f"{report.scenario['feature']['keyword']}: ", **feature_markup)
7681
self._tw.write(report.scenario["feature"]["name"], **feature_markup)
7782
self._tw.write("\n")
78-
self._tw.write(f" {report.scenario['keyword']}: ", **scenario_markup)
83+
84+
if rule and rule["name"] != self.current_rule:
85+
self._tw.write(f" {rule['keyword']}: ", **rule_markup)
86+
self._tw.write(rule["name"], **rule_markup)
87+
self._tw.write("\n")
88+
self.current_rule = rule["name"]
89+
90+
self._tw.write(f"{indent} {report.scenario['keyword']}: ", **scenario_markup)
7991
self._tw.write(report.scenario["name"], **scenario_markup)
8092
self._tw.write(" ")
8193
self._tw.write(word, **word_markup)
@@ -85,12 +97,19 @@ def pytest_runtest_logreport(self, report: TestReport) -> Any:
8597
self._tw.write(f"{report.scenario['feature']['keyword']}: ", **feature_markup)
8698
self._tw.write(report.scenario["feature"]["name"], **feature_markup)
8799
self._tw.write("\n")
88-
self._tw.write(f" {report.scenario['keyword']}: ", **scenario_markup)
100+
101+
if rule and rule["name"] != self.current_rule:
102+
self._tw.write(f" {rule['keyword']}: ", **rule_markup)
103+
self._tw.write(rule["name"], **rule_markup)
104+
self._tw.write("\n")
105+
self.current_rule = rule["name"]
106+
107+
self._tw.write(f"{indent} {report.scenario['keyword']}: ", **scenario_markup)
89108
self._tw.write(report.scenario["name"], **scenario_markup)
90109
self._tw.write("\n")
91110
for step in report.scenario["steps"]:
92-
self._tw.write(f" {step['keyword']} {step['name']}\n", **scenario_markup)
93-
self._tw.write(f" {word}", **word_markup)
111+
self._tw.write(f"{indent} {step['keyword']} {step['name']}\n", **scenario_markup)
112+
self._tw.write(f"{indent} {word}", **word_markup)
94113
self._tw.write("\n\n")
95114

96115
self.stats.setdefault(cat, []).append(rep)

0 commit comments

Comments
 (0)