Skip to content

Commit 986030a

Browse files
committed
Fixes #389
1 parent 2daf3aa commit 986030a

File tree

5 files changed

+118
-40
lines changed

5 files changed

+118
-40
lines changed

pytest_reportportal/config.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
import warnings
1717
from os import getenv
18-
from typing import Any, Optional, Tuple, Union
18+
from typing import Any, List, Optional, Tuple, Union
1919

2020
from _pytest.config import Config
2121
from reportportal_client import ClientType, OutputType
@@ -49,7 +49,8 @@ class AgentConfig:
4949
rp_bts_url: str
5050
rp_launch: str
5151
rp_launch_id: Optional[str]
52-
rp_launch_attributes: Optional[list]
52+
rp_launch_attributes: Optional[List[str]]
53+
rp_tests_attributes: Optional[List[str]]
5354
rp_launch_description: str
5455
rp_log_batch_size: int
5556
rp_log_batch_payload_size: int
@@ -96,6 +97,7 @@ def __init__(self, pytest_config: Config) -> None:
9697
self.rp_launch = self.find_option(pytest_config, "rp_launch")
9798
self.rp_launch_id = self.find_option(pytest_config, "rp_launch_id")
9899
self.rp_launch_attributes = self.find_option(pytest_config, "rp_launch_attributes")
100+
self.rp_tests_attributes = self.find_option(pytest_config, "rp_tests_attributes")
99101
self.rp_launch_description = self.find_option(pytest_config, "rp_launch_description")
100102
self.rp_log_batch_size = int(self.find_option(pytest_config, "rp_log_batch_size"))
101103
batch_payload_size = self.find_option(pytest_config, "rp_log_batch_payload_size")

pytest_reportportal/service.py

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,6 @@
9090
ISSUE_DESCRIPTION_ID_TEMPLATE: str = " {issue_id}"
9191
PYTHON_REPLACE_REGEX = re.compile(r"\W")
9292
ALPHA_REGEX = re.compile(r"^\d+_*")
93-
ATTRIBUTE_DELIMITER = ":"
9493
BACKGROUND_STEP_NAME = "Background"
9594

9695

@@ -505,11 +504,13 @@ def _lock(self, leaf: Dict[str, Any], func: Callable[[Dict[str, Any]], Any]) ->
505504
return func(leaf)
506505
return func(leaf)
507506

508-
def _process_bdd_attributes(self, scenario: Union[Feature, Scenario, Rule]) -> List[Dict[str, str]]:
507+
def _process_bdd_attributes(self, item: Union[Feature, Scenario, Rule]) -> List[Dict[str, str]]:
509508
tags = []
510-
tags.extend(scenario.tags)
511-
if isinstance(scenario, Scenario):
512-
template = self._get_scenario_template(scenario)
509+
tags.extend(item.tags)
510+
if isinstance(item, Scenario):
511+
test_attributes = self._config.rp_tests_attributes
512+
tags.extend(test_attributes if test_attributes else [])
513+
template = self._get_scenario_template(item)
513514
if template and template.templated:
514515
examples = []
515516
if isinstance(template.examples, list):
@@ -518,17 +519,7 @@ def _process_bdd_attributes(self, scenario: Union[Feature, Scenario, Rule]) -> L
518519
examples.append(template.examples)
519520
for example in examples:
520521
tags.extend(getattr(example, "tags", []))
521-
attributes = []
522-
for tag in tags:
523-
key = None
524-
value = tag
525-
if ATTRIBUTE_DELIMITER in tag:
526-
key, value = tag.split(ATTRIBUTE_DELIMITER, 1)
527-
attribute = {"value": value}
528-
if key:
529-
attribute["key"] = key
530-
attributes.append(attribute)
531-
return attributes
522+
return gen_attributes(tags)
532523

533524
def _get_suite_code_ref(self, leaf: Dict[str, Any]) -> str:
534525
item = leaf["item"]
@@ -768,7 +759,13 @@ def _process_attributes(self, item: Item) -> List[Dict[str, Any]]:
768759
:param item: Pytest.Item
769760
:return: a set of attributes
770761
"""
771-
attributes = set()
762+
test_attributes = self._config.rp_tests_attributes
763+
if test_attributes:
764+
attributes = {
765+
(attr.get("key", None), attr["value"]) for attr in gen_attributes(self._config.rp_tests_attributes)
766+
}
767+
else:
768+
attributes = set()
772769
for marker in item.iter_markers():
773770
if marker.name == "issue":
774771
if self._config.rp_issue_id_marks:

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
dill>=0.3.6
22
pytest>=4.6.10
3-
reportportal-client~=5.6.1
3+
reportportal-client~=5.6.2
44
aenum>=3.1.0

tests/integration/test_attributes.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515

1616
from unittest import mock
1717

18+
import pytest
19+
1820
from tests import REPORT_PORTAL_SERVICE
1921
from tests.helpers import utils
2022

@@ -124,3 +126,45 @@ def test_custom_runtime_attribute_report(mock_client_init):
124126
attribute_tuple_list = [(kv.get("key"), kv["value"]) for kv in actual_attributes]
125127

126128
assert set(attribute_tuple_list) == {("scope", "smoke"), (None, "runtime")}
129+
130+
131+
@pytest.mark.parametrize("rp_hierarchy_code", [True, False])
132+
@mock.patch(REPORT_PORTAL_SERVICE)
133+
def test_rp_tests_attributes(mock_client_init, rp_hierarchy_code):
134+
"""Verify configuration attributes are reported.
135+
136+
:param mock_client_init: Pytest fixture
137+
"""
138+
variables = {"rp_tests_attributes": "test_key:test_value", "rp_hierarchy_code": rp_hierarchy_code}
139+
variables.update(utils.DEFAULT_VARIABLES.items())
140+
result = utils.run_pytest_tests(tests=["examples/test_simple.py"], variables=variables)
141+
assert int(result) == 0, "Exit code should be 0 (no errors)"
142+
143+
mock_client = mock_client_init.return_value
144+
assert mock_client.start_test_item.call_count > 0, '"start_test_item" called incorrect number of times'
145+
146+
call_args = mock_client.start_test_item.call_args_list
147+
step_call_args = call_args[-1][1]
148+
assert step_call_args["attributes"] == [{"key": "test_key", "value": "test_value"}]
149+
150+
151+
@mock.patch(REPORT_PORTAL_SERVICE)
152+
def test_rp_tests_attributes_add(mock_client_init):
153+
"""Verify configuration attributes are reported along with custom attribute.
154+
155+
:param mock_client_init: Pytest fixture
156+
"""
157+
variables = {"markers": "scope: to which test scope a test relates", "rp_tests_attributes": "test_key:test_value"}
158+
variables.update(utils.DEFAULT_VARIABLES.items())
159+
result = utils.run_pytest_tests(tests=["examples/attributes/test_one_attribute.py"], variables=variables)
160+
assert int(result) == 0, "Exit code should be 0 (no errors)"
161+
162+
mock_client = mock_client_init.return_value
163+
assert mock_client.start_test_item.call_count > 0, '"start_test_item" called incorrect number of times'
164+
165+
call_args = mock_client.start_test_item.call_args_list
166+
step_call_args = call_args[-1][1]
167+
attributes = step_call_args["attributes"]
168+
assert len(attributes) == 2
169+
assert {"key": "scope", "value": "smoke"} in attributes
170+
assert {"key": "test_key", "value": "test_value"} in attributes

tests/integration/test_bdd.py

Lines changed: 55 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,7 @@ def setup_mock_for_logging(mock_client_init):
8282

8383
@mock.patch(REPORT_PORTAL_SERVICE)
8484
def test_basic(mock_client_init):
85-
mock_client = setup_mock(mock_client_init)
86-
setup_mock_for_logging(mock_client_init)
85+
mock_client = setup_mock_for_logging(mock_client_init)
8786
result = utils.run_pytest_tests(tests=["examples/bdd/step_defs/test_arguments.py"])
8887
assert int(result) == 0, "Exit code should be 0 (no errors)"
8988

@@ -124,8 +123,7 @@ def test_basic(mock_client_init):
124123

125124
@mock.patch(REPORT_PORTAL_SERVICE)
126125
def test_basic_with_feature_suite(mock_client_init):
127-
mock_client = setup_mock(mock_client_init)
128-
setup_mock_for_logging(mock_client_init)
126+
mock_client = setup_mock_for_logging(mock_client_init)
129127
variables = {"rp_hierarchy_code": True}
130128
variables.update(utils.DEFAULT_VARIABLES.items())
131129
result = utils.run_pytest_tests(tests=["examples/bdd/step_defs/test_arguments.py"], variables=variables)
@@ -189,8 +187,7 @@ def test_feature_descriptions(mock_client_init):
189187

190188
@mock.patch(REPORT_PORTAL_SERVICE)
191189
def test_failed_feature(mock_client_init):
192-
mock_client = setup_mock(mock_client_init)
193-
setup_mock_for_logging(mock_client_init)
190+
mock_client = setup_mock_for_logging(mock_client_init)
194191
result = utils.run_pytest_tests(tests=["examples/bdd/step_defs/test_failed_step.py"])
195192
assert int(result) == 1, "Exit code should be 1 (test error)"
196193

@@ -224,8 +221,7 @@ def test_failed_feature(mock_client_init):
224221

225222
@mock.patch(REPORT_PORTAL_SERVICE)
226223
def test_scenario_attributes(mock_client_init):
227-
mock_client = setup_mock(mock_client_init)
228-
setup_mock_for_logging(mock_client_init)
224+
mock_client = setup_mock_for_logging(mock_client_init)
229225

230226
test_file = "examples/bdd/step_defs/test_belly.py"
231227
result = utils.run_pytest_tests(tests=[test_file])
@@ -241,8 +237,7 @@ def test_scenario_attributes(mock_client_init):
241237

242238
@mock.patch(REPORT_PORTAL_SERVICE)
243239
def test_feature_attributes(mock_client_init):
244-
mock_client = setup_mock(mock_client_init)
245-
setup_mock_for_logging(mock_client_init)
240+
mock_client = setup_mock_for_logging(mock_client_init)
246241

247242
variables = {"rp_hierarchy_code": True}
248243
variables.update(utils.DEFAULT_VARIABLES.items())
@@ -268,8 +263,7 @@ def test_feature_attributes(mock_client_init):
268263

269264
@mock.patch(REPORT_PORTAL_SERVICE)
270265
def test_background_step(mock_client_init):
271-
mock_client = setup_mock(mock_client_init)
272-
setup_mock_for_logging(mock_client_init)
266+
mock_client = setup_mock_for_logging(mock_client_init)
273267

274268
test_file = "examples/bdd/step_defs/test_background.py"
275269
result = utils.run_pytest_tests(tests=[test_file])
@@ -332,8 +326,7 @@ def test_background_step(mock_client_init):
332326

333327
@mock.patch(REPORT_PORTAL_SERVICE)
334328
def test_background_two_steps(mock_client_init):
335-
mock_client = setup_mock(mock_client_init)
336-
setup_mock_for_logging(mock_client_init)
329+
mock_client = setup_mock_for_logging(mock_client_init)
337330

338331
test_file = "examples/bdd/step_defs/test_background_two_steps.py"
339332
result = utils.run_pytest_tests(tests=[test_file])
@@ -379,8 +372,7 @@ def test_background_two_steps(mock_client_init):
379372
@pytest.mark.skipif(pytest_bdd_version[0] < 8, reason="Only for pytest-bdd 8+")
380373
@mock.patch(REPORT_PORTAL_SERVICE)
381374
def test_rule(mock_client_init):
382-
mock_client = setup_mock(mock_client_init)
383-
setup_mock_for_logging(mock_client_init)
375+
mock_client = setup_mock_for_logging(mock_client_init)
384376
result = utils.run_pytest_tests(tests=["examples/bdd/step_defs/test_rule_steps.py"])
385377
assert int(result) == 0, "Exit code should be 0 (no errors)"
386378

@@ -474,8 +466,7 @@ def test_rule(mock_client_init):
474466
@pytest.mark.skipif(pytest_bdd_version[0] < 8, reason="Only for pytest-bdd 8+")
475467
@mock.patch(REPORT_PORTAL_SERVICE)
476468
def test_rule_hierarchy(mock_client_init):
477-
mock_client = setup_mock(mock_client_init)
478-
setup_mock_for_logging(mock_client_init)
469+
mock_client = setup_mock_for_logging(mock_client_init)
479470

480471
variables = {"rp_hierarchy_code": True}
481472
variables.update(utils.DEFAULT_VARIABLES.items())
@@ -899,8 +890,7 @@ def test_scenario_outline_dynamic_name(mock_client_init):
899890

900891
@mock.patch(REPORT_PORTAL_SERVICE)
901892
def test_scenario_outline_fail(mock_client_init):
902-
mock_client = setup_mock(mock_client_init)
903-
setup_mock_for_logging(mock_client_init)
893+
mock_client = setup_mock_for_logging(mock_client_init)
904894
result = utils.run_pytest_tests(tests=["examples/bdd/step_defs/scenario_outline_fail_steps.py"])
905895
assert int(result) == 1, "Exit code should be 1 (test error)"
906896

@@ -1094,3 +1084,48 @@ def test_custom_test_case_id(mock_client_init):
10941084
finish_calls = mock_client.finish_test_item.call_args_list
10951085
for call in finish_calls:
10961086
assert call[1]["status"] == "PASSED"
1087+
1088+
1089+
@mock.patch(REPORT_PORTAL_SERVICE)
1090+
def test_rp_tests_attributes_rule(mock_client_init):
1091+
mock_client = setup_mock(mock_client_init)
1092+
variables = {"rp_tests_attributes": "test_key:test_value"}
1093+
variables.update(utils.DEFAULT_VARIABLES.items())
1094+
result = utils.run_pytest_tests(
1095+
tests=["examples/bdd/step_defs/test_rule_description_steps.py"], variables=variables
1096+
)
1097+
assert int(result) == 0, "Exit code should be 0 (no errors)"
1098+
1099+
scenario_call = mock_client.start_test_item.call_args_list[0]
1100+
assert scenario_call[1]["attributes"] == [{"key": "test_key", "value": "test_value"}]
1101+
1102+
1103+
@pytest.mark.parametrize("rp_hierarchy_code, scenario_idx", [(True, 2), (False, 0)])
1104+
@mock.patch(REPORT_PORTAL_SERVICE)
1105+
def test_rp_tests_attributes_rule_hierarchy(mock_client_init, rp_hierarchy_code, scenario_idx):
1106+
mock_client = setup_mock(mock_client_init)
1107+
variables = {"rp_tests_attributes": "test_key:test_value", "rp_hierarchy_code": rp_hierarchy_code}
1108+
variables.update(utils.DEFAULT_VARIABLES.items())
1109+
result = utils.run_pytest_tests(
1110+
tests=["examples/bdd/step_defs/test_rule_description_steps.py"], variables=variables
1111+
)
1112+
assert int(result) == 0, "Exit code should be 0 (no errors)"
1113+
1114+
scenario_call = mock_client.start_test_item.call_args_list[scenario_idx]
1115+
assert scenario_call[1]["attributes"] == [{"key": "test_key", "value": "test_value"}]
1116+
1117+
1118+
@mock.patch(REPORT_PORTAL_SERVICE)
1119+
def test_rp_tests_attributes_bdd_tags(mock_client_init):
1120+
mock_client = setup_mock(mock_client_init)
1121+
variables = {"rp_tests_attributes": "test_key:test_value"}
1122+
variables.update(utils.DEFAULT_VARIABLES.items())
1123+
result = utils.run_pytest_tests(tests=["examples/bdd/step_defs/test_belly.py"], variables=variables)
1124+
assert int(result) == 0, "Exit code should be 0 (no errors)"
1125+
1126+
scenario_call = mock_client.start_test_item.call_args_list[0]
1127+
attributes = scenario_call[1]["attributes"]
1128+
assert len(attributes) == 3
1129+
assert {"key": "test_key", "value": "test_value"} in attributes
1130+
assert {"value": "ok"} in attributes
1131+
assert {"key": "key", "value": "value"} in attributes

0 commit comments

Comments
 (0)