From 487e952b6d8ea64a56b245d4c9d69e3a1bf4a228 Mon Sep 17 00:00:00 2001 From: Patrick Barna Date: Thu, 9 Feb 2023 10:59:06 -0700 Subject: [PATCH 1/7] Add scenario descriptions --- src/pytest_bdd/parser.py | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/src/pytest_bdd/parser.py b/src/pytest_bdd/parser.py index b010a4c4..392e0655 100644 --- a/src/pytest_bdd/parser.py +++ b/src/pytest_bdd/parser.py @@ -28,6 +28,8 @@ ("But ", None), ] +TYPES_WITH_DESCRIPTIONS = [types.FEATURE, types.SCENARIO, types.SCENARIO_OUTLINE] + if typing.TYPE_CHECKING: from typing import Any, Iterable, Mapping, Match, Sequence @@ -125,7 +127,8 @@ def parse_feature(basedir: str, filename: str, encoding: str = "utf-8") -> Featu multiline_step = False stripped_line = line.strip() clean_line = strip_comments(line) - if not clean_line and (not prev_mode or prev_mode not in types.FEATURE): + if not clean_line and (not prev_mode or prev_mode not in TYPES_WITH_DESCRIPTIONS): + # Blank lines are included in feature and scenario descriptions continue mode = get_step_type(clean_line) or mode @@ -142,7 +145,9 @@ def parse_feature(basedir: str, filename: str, encoding: str = "utf-8") -> Featu feature.line_number = line_number feature.tags = get_tags(prev_line) elif prev_mode == types.FEATURE: - description.append(clean_line) + # Do not include comments in descriptions + if not stripped_line.startswith("#"): + description.append(line) else: raise exceptions.FeatureError( "Multiple features are not allowed in a single feature file", @@ -157,6 +162,15 @@ def parse_feature(basedir: str, filename: str, encoding: str = "utf-8") -> Featu keyword, parsed_line = parse_line(clean_line) if mode in [types.SCENARIO, types.SCENARIO_OUTLINE]: + # Lines between the scenario declaration + # and the scenario's first step line + # are considered part of the scenario description. + if scenario and not keyword: + # Do not include comments in descriptions + if stripped_line.startswith("#"): + continue + scenario.add_description_line(line) + continue tags = get_tags(prev_line) scenario = ScenarioTemplate( feature=feature, @@ -215,6 +229,7 @@ class ScenarioTemplate: tags: set[str] = field(default_factory=set) examples: Examples | None = field(default_factory=lambda: Examples()) _steps: list[Step] = field(init=False, default_factory=list) + _description_lines: list[str] = field(init=False, default_factory=list) def add_step(self, step: Step) -> None: step.scenario = self @@ -241,7 +256,20 @@ def render(self, context: Mapping[str, Any]) -> Scenario: 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) + return Scenario(feature=self.feature, name=self.name, line_number=self.line_number, steps=steps, tags=self.tags, description=self._description_lines) + + def add_description_line(self, description_line): + """Add a description line to the scenario. + :param str description_line: + """ + self._description_lines.append(description_line) + + @property + def description(self): + """Get the scenario's description. + :return: The scenario description + """ + return u"\n".join(self._description_lines) @dataclass @@ -251,6 +279,7 @@ class Scenario: line_number: int steps: list[Step] tags: set[str] = field(default_factory=set) + description: list[str] = field(default_factory=list) @dataclass From 89b9fde551e290955ca8476d0f14bf3e130bccd2 Mon Sep 17 00:00:00 2001 From: Patrick Barna Date: Thu, 9 Feb 2023 13:41:47 -0700 Subject: [PATCH 2/7] Add test --- tests/feature/test_description.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/tests/feature/test_description.py b/tests/feature/test_description.py index 9678bff1..bda03bcf 100644 --- a/tests/feature/test_description.py +++ b/tests/feature/test_description.py @@ -19,6 +19,11 @@ def test_description(pytester): Some description goes here. Scenario: Description + + Also, the scenario can have a description. + + It goes here between the scenario name + and the first step. Given I have a bar """ ), @@ -39,7 +44,7 @@ def test_description(): def _(): return "bar" - def test_scenario_description(): + def test_feature_description(): assert test_description.__scenario__.feature.description == textwrap.dedent( \"\"\"\\ In order to achieve something @@ -49,9 +54,18 @@ def test_scenario_description(): Some description goes here.\"\"\" ) + + def test_scenario_description(): + assert test_description.__scenario__.description == textwrap.dedent( + \"\"\"\\ + Also, the scenario can have a description. + + It goes here between the scenario name + and the first step.\"\"\" + ) """ ) ) result = pytester.runpytest() - result.assert_outcomes(passed=2) + result.assert_outcomes(passed=3) From 8212c5a8a9061c5b1d91a8fed786af408078745d Mon Sep 17 00:00:00 2001 From: Patrick Barna Date: Thu, 9 Feb 2023 13:48:06 -0700 Subject: [PATCH 3/7] Remove unncessary string type --- src/pytest_bdd/parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pytest_bdd/parser.py b/src/pytest_bdd/parser.py index 392e0655..2b29e28c 100644 --- a/src/pytest_bdd/parser.py +++ b/src/pytest_bdd/parser.py @@ -269,7 +269,7 @@ def description(self): """Get the scenario's description. :return: The scenario description """ - return u"\n".join(self._description_lines) + return "\n".join(self._description_lines) @dataclass From ce87c51380af4273a46374cdb4c9d43e68397151 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 9 Feb 2023 21:08:27 +0000 Subject: [PATCH 4/7] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/pytest_bdd/parser.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/pytest_bdd/parser.py b/src/pytest_bdd/parser.py index 2b29e28c..c92f738a 100644 --- a/src/pytest_bdd/parser.py +++ b/src/pytest_bdd/parser.py @@ -256,7 +256,14 @@ def render(self, context: Mapping[str, Any]) -> Scenario: 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, description=self._description_lines) + return Scenario( + feature=self.feature, + name=self.name, + line_number=self.line_number, + steps=steps, + tags=self.tags, + description=self._description_lines, + ) def add_description_line(self, description_line): """Add a description line to the scenario. From eff9bfa5662bdf8ce5db4c353969d9f444538c01 Mon Sep 17 00:00:00 2001 From: Patrick Barna Date: Thu, 16 Feb 2023 12:16:33 -0700 Subject: [PATCH 5/7] Always use clean line for description --- src/pytest_bdd/parser.py | 4 ++-- tests/feature/test_description.py | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/pytest_bdd/parser.py b/src/pytest_bdd/parser.py index c92f738a..1bc697ff 100644 --- a/src/pytest_bdd/parser.py +++ b/src/pytest_bdd/parser.py @@ -147,7 +147,7 @@ def parse_feature(basedir: str, filename: str, encoding: str = "utf-8") -> Featu elif prev_mode == types.FEATURE: # Do not include comments in descriptions if not stripped_line.startswith("#"): - description.append(line) + description.append(clean_line) else: raise exceptions.FeatureError( "Multiple features are not allowed in a single feature file", @@ -169,7 +169,7 @@ def parse_feature(basedir: str, filename: str, encoding: str = "utf-8") -> Featu # Do not include comments in descriptions if stripped_line.startswith("#"): continue - scenario.add_description_line(line) + scenario.add_description_line(clean_line) continue tags = get_tags(prev_line) scenario = ScenarioTemplate( diff --git a/tests/feature/test_description.py b/tests/feature/test_description.py index bda03bcf..5d0dcb96 100644 --- a/tests/feature/test_description.py +++ b/tests/feature/test_description.py @@ -19,7 +19,6 @@ def test_description(pytester): Some description goes here. Scenario: Description - Also, the scenario can have a description. It goes here between the scenario name From 73b8b208eda8c8f92719ea4a6f46db3ab96b4c40 Mon Sep 17 00:00:00 2001 From: Patrick Barna Date: Mon, 27 Feb 2023 11:41:13 -0700 Subject: [PATCH 6/7] Update changelog --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 7d2b6c4d..4eb8037b 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,7 +4,7 @@ Changelog Unreleased ---------- - ⚠️ Backwards incompatible: - ``parsers.re`` now does a `fullmatch `_ instead of a partial match. This is to make it work just like the other parsers, since they don't ignore non-matching characters at the end of the string. `#539 `_ - +- Add support for Scenarios and Scenario Outlines to have descriptions. `#600 `_ 6.1.1 ----- From 84aa72a1d5fbed5ff7b2094ca14e4f1879ee77d6 Mon Sep 17 00:00:00 2001 From: Alessio Bogon <778703+youtux@users.noreply.github.com> Date: Mon, 27 Feb 2023 19:51:59 +0100 Subject: [PATCH 7/7] Simplify check --- src/pytest_bdd/parser.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/pytest_bdd/parser.py b/src/pytest_bdd/parser.py index 1bc697ff..aa7c4ec7 100644 --- a/src/pytest_bdd/parser.py +++ b/src/pytest_bdd/parser.py @@ -167,9 +167,8 @@ def parse_feature(basedir: str, filename: str, encoding: str = "utf-8") -> Featu # are considered part of the scenario description. if scenario and not keyword: # Do not include comments in descriptions - if stripped_line.startswith("#"): - continue - scenario.add_description_line(clean_line) + if not stripped_line.startswith("#"): + scenario.add_description_line(clean_line) continue tags = get_tags(prev_line) scenario = ScenarioTemplate(