Skip to content

Commit bacf9fd

Browse files
committed
Pytest BDD implementation: Rule keyword: WIP
1 parent 9eec76a commit bacf9fd

File tree

3 files changed

+81
-5
lines changed

3 files changed

+81
-5
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Copyright 2025 EPAM Systems
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Rule keyword test module."""
16+
from pytest_bdd import given, scenarios, then
17+
18+
19+
scenarios("../features/rule_keyword.feature")
20+
21+
22+
@given("I have empty step")
23+
def empty_step():
24+
"""Empty step implementation."""
25+
pass
26+
27+
28+
@then("I have another empty step")
29+
def another_empty_step():
30+
"""Another empty step implementation."""
31+
pass
32+
33+
34+
@then("I have one more empty step")
35+
def one_more_empty_step():
36+
"""One more empty step implementation."""
37+
pass
38+
39+
40+
@then("I have one more else empty step")
41+
def one_more_else_empty_step():
42+
"""One more else empty step implementation."""
43+
pass

pytest_reportportal/service.py

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,12 @@
6767
make_python_name: Callable[[str], str] = lambda x: x
6868
PYTEST_BDD = False
6969

70+
try:
71+
# noinspection PyPackageRequirements
72+
from pytest_bdd.parser import Rule
73+
except ImportError:
74+
Rule = type("dummy", (), {}) # Old pytest-bdd versions do not have Rule
75+
7076
from reportportal_client import RP, create_client
7177
from reportportal_client.helpers import dict_to_payload, gen_attributes, get_launch_sys_attrs, get_package_version
7278

@@ -366,9 +372,18 @@ def _generate_names(self, test_tree: Dict[str, Any]) -> None:
366372
elif isinstance(item, Scenario):
367373
keyword = getattr(item, "keyword", "Scenario")
368374
test_tree["name"] = f"{keyword}: {item.name}"
375+
elif isinstance(item, Rule):
376+
keyword = getattr(item, "keyword", "Rule")
377+
test_tree["name"] = f"{keyword}: {item.name}"
369378
else:
370379
test_tree["name"] = item.name
371380

381+
if test_tree["type"] == LeafType.SUITE:
382+
item = test_tree["item"]
383+
if isinstance(item, Rule):
384+
keyword = getattr(item, "keyword", "Rule")
385+
test_tree["name"] = f"{keyword}: {item.name}"
386+
372387
for item, child_leaf in test_tree["children"].items():
373388
self._generate_names(child_leaf)
374389

@@ -395,7 +410,7 @@ def _merge_dirs(self, test_tree: Dict[str, Any]) -> None:
395410
self._merge_leaf_types(test_tree, {LeafType.DIR, LeafType.FILE}, self._config.rp_dir_path_separator)
396411

397412
def _merge_code_with_separator(self, test_tree: Dict[str, Any], separator: str) -> None:
398-
self._merge_leaf_types(test_tree, {LeafType.CODE, LeafType.FILE, LeafType.DIR}, separator)
413+
self._merge_leaf_types(test_tree, {LeafType.CODE, LeafType.FILE, LeafType.DIR, LeafType.SUITE}, separator)
399414

400415
def _merge_code(self, test_tree: Dict[str, Any]) -> None:
401416
self._merge_code_with_separator(test_tree, "::")
@@ -454,7 +469,7 @@ def _get_item_description(self, test_item: Any) -> Optional[str]:
454469
return trim_docstring(doc)
455470
if isinstance(test_item, DoctestItem):
456471
return test_item.reportinfo()[2]
457-
if isinstance(test_item, (Feature, Scenario, ScenarioTemplate)):
472+
if isinstance(test_item, (Feature, Scenario, ScenarioTemplate, Rule)):
458473
description = test_item.description
459474
if description:
460475
return description
@@ -472,7 +487,7 @@ def _lock(self, leaf: Dict[str, Any], func: Callable[[Dict[str, Any]], Any]) ->
472487
return func(leaf)
473488
return func(leaf)
474489

475-
def _process_bdd_attributes(self, scenario: Union[Feature, Scenario]) -> List[Dict[str, str]]:
490+
def _process_bdd_attributes(self, scenario: Union[Feature, Scenario, Rule]) -> List[Dict[str, str]]:
476491
attributes = []
477492
for tag in scenario.tags:
478493
key = None
@@ -485,7 +500,7 @@ def _process_bdd_attributes(self, scenario: Union[Feature, Scenario]) -> List[Di
485500
attributes.append(attribute)
486501
return attributes
487502

488-
def _build_start_suite_rq(self, leaf: Dict[str, Any]) -> Dict[str, Any]:
503+
def _get_suite_code_ref(self, leaf: Dict[str, Any]) -> str:
489504
item = leaf["item"]
490505
if leaf["type"] == LeafType.DIR:
491506
code_ref = str(item)
@@ -494,9 +509,16 @@ def _build_start_suite_rq(self, leaf: Dict[str, Any]) -> Dict[str, Any]:
494509
code_ref = str(item.rel_filename)
495510
else:
496511
code_ref = str(item.fspath)
512+
elif leaf["type"] == LeafType.SUITE:
513+
code_ref = self._get_suite_code_ref(leaf["parent"]) + f"/[{type(item).__name__}:{item.name}]"
497514
else:
498515
code_ref = str(item.fspath)
516+
return code_ref
517+
518+
def _build_start_suite_rq(self, leaf: Dict[str, Any]) -> Dict[str, Any]:
519+
code_ref = self._get_suite_code_ref(leaf)
499520
parent_item_id = self._lock(leaf["parent"], lambda p: p.get("item_id")) if "parent" in leaf else None
521+
item = leaf["item"]
500522
payload = {
501523
"name": self._truncate_item_name(leaf["name"]),
502524
"description": self._get_item_description(item),
@@ -505,7 +527,7 @@ def _build_start_suite_rq(self, leaf: Dict[str, Any]) -> Dict[str, Any]:
505527
"code_ref": code_ref,
506528
"parent_item_id": parent_item_id,
507529
}
508-
if isinstance(item, Feature):
530+
if isinstance(item, (Feature, Scenario, Rule)):
509531
payload["attributes"] = self._process_bdd_attributes(item)
510532
return payload
511533

@@ -1057,12 +1079,14 @@ def start_bdd_scenario(self, feature: Feature, scenario: Scenario) -> None:
10571079
else:
10581080
feature_leaf = self._create_leaf(LeafType.FILE, root_leaf, feature)
10591081
children_leafs[feature] = feature_leaf
1082+
children_leafs = feature_leaf["children"]
10601083
rule = getattr(scenario, "rule", None)
10611084
if rule:
10621085
if rule in children_leafs:
10631086
rule_leaf = children_leafs[rule]
10641087
else:
10651088
rule_leaf = self._create_leaf(LeafType.SUITE, feature_leaf, rule)
1089+
children_leafs[rule] = rule_leaf
10661090
else:
10671091
rule_leaf = feature_leaf
10681092
children_leafs = rule_leaf["children"]
@@ -1074,6 +1098,7 @@ def start_bdd_scenario(self, feature: Feature, scenario: Scenario) -> None:
10741098
if background not in children_leafs:
10751099
background_leaf = self._create_leaf(LeafType.NESTED, rule_leaf, background)
10761100
children_leafs[background] = background_leaf
1101+
10771102
self._remove_file_names(root_leaf)
10781103
self._generate_names(root_leaf)
10791104
if not self._config.rp_hierarchy_code:

tests/integration/test_bdd.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,3 +390,11 @@ def test_bdd_background_two_steps(mock_client_init):
390390
assert scenario_step_call[0][2] == "step"
391391
assert scenario_step_call[1]["parent_item_id"] == scenario_call[1]["name"] + "_1"
392392
assert scenario_step_call[1]["has_stats"] is False
393+
394+
395+
@mock.patch(REPORT_PORTAL_SERVICE)
396+
def test_bdd_rule(mock_client_init):
397+
mock_client = setup_mock(mock_client_init)
398+
setup_mock_for_logging(mock_client_init)
399+
result = utils.run_pytest_tests(tests=["examples/bdd/step_defs/test_rule_steps.py"])
400+
assert int(result) == 0, "Exit code should be 0 (no errors)"

0 commit comments

Comments
 (0)