From b83eb7fe63aa5c9910d2a499f2cf40dab40d4c70 Mon Sep 17 00:00:00 2001 From: "reportportal.io" Date: Wed, 8 Apr 2026 13:25:22 +0000 Subject: [PATCH 1/9] Changelog update --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b63e098..4cb9c15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Changelog ## [Unreleased] + +## [5.6.5] ### Fixed - Issue [#418](https://github.com/reportportal/agent-python-pytest/issues/418) parametrize marker IDs, by @drcrazy From 7223f6eb8790d910c0a1a02ab97c6409b5227eed Mon Sep 17 00:00:00 2001 From: "reportportal.io" Date: Wed, 8 Apr 2026 13:25:22 +0000 Subject: [PATCH 2/9] Version update --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 95f948d..7dd91d5 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ from setuptools import setup -__version__ = "5.6.5" +__version__ = "5.6.6" def read_file(fname): From e89522b5d00ed973619771da185f242c73d5d8b9 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Fri, 10 Apr 2026 22:16:02 +0300 Subject: [PATCH 3/9] Dependency version update --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 0944a3d..c8b6e60 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -reportportal-client==5.7.2 +reportportal-client==5.7.4 From 89c29c3bcfc0dab6f5e2159a1b596500905d337a Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Fri, 10 Apr 2026 22:21:47 +0300 Subject: [PATCH 4/9] Remove redundant truncation (now done at the client level) --- CHANGELOG.md | 4 ++++ pytest_reportportal/service.py | 25 ++++--------------------- 2 files changed, 8 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4cb9c15..a1ea6af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Changelog ## [Unreleased] +### Changed +- Client version updated to [5.7.4](https://github.com/reportportal/client-Python/releases/tag/5.7.4), by @HardNorth +### Removed +- Redundant item name truncation (now handled on the Client level), by @HardNorth ## [5.6.5] ### Fixed diff --git a/pytest_reportportal/service.py b/pytest_reportportal/service.py index b93d96b..12efcc0 100644 --- a/pytest_reportportal/service.py +++ b/pytest_reportportal/service.py @@ -28,7 +28,7 @@ from _pytest.doctest import DoctestItem from py.path import local -from pytest import Class, Function, Item, Module, Package, PytestWarning, Session +from pytest import Class, Function, Item, Module, Package, Session from reportportal_client.aio import Task from reportportal_client.core.rp_issues import ExternalIssue, Issue from reportportal_client.helpers import markdown_helpers, timestamp @@ -80,8 +80,6 @@ LOGGER = logging.getLogger(__name__) KNOWN_LOG_LEVELS = ("TRACE", "DEBUG", "INFO", "WARN", "ERROR") -MAX_ITEM_NAME_LENGTH: int = 1024 -TRUNCATION_STR: str = "..." ROOT_DIR: str = str(os.path.abspath(curdir)) PYTEST_MARKS_IGNORE: set[str] = {"parametrize", "usefixtures", "filterwarnings"} NOT_ISSUE: Issue = Issue("NOT_ISSUE") @@ -497,21 +495,6 @@ def collect_tests(self, session: Session) -> None: self._merge_code(test_tree) self._build_item_paths(test_tree, []) - def _truncate_item_name(self, name: str) -> str: - """Get name of item. - - :param name: Test Item name - :return: truncated to maximum length name if needed - """ - if len(name) > MAX_ITEM_NAME_LENGTH: - name = name[: MAX_ITEM_NAME_LENGTH - len(TRUNCATION_STR)] + TRUNCATION_STR - LOGGER.warning( - PytestWarning( - f'Test leaf ID was truncated to "{name}" because of name size constrains on Report Portal' - ) - ) - return name - def _get_item_description(self, test_item: Any) -> Optional[str]: """Get description of item. @@ -580,7 +563,7 @@ def _build_start_suite_rq(self, leaf: dict[str, Any]) -> dict[str, Any]: parent_item_id = self._lock(leaf["parent"], lambda p: p.get("item_id")) if "parent" in leaf else None item = leaf["item"] payload = { - "name": self._truncate_item_name(leaf["name"]), + "name": leaf["name"], "description": self._get_item_description(item), "start_time": timestamp(), "item_type": "SUITE", @@ -882,7 +865,7 @@ def _process_metadata_item_finish(self, leaf: dict[str, Any]) -> None: def _build_start_step_rq(self, leaf: dict[str, Any]) -> dict[str, Any]: payload = { "attributes": leaf.get("attributes", None), - "name": self._truncate_item_name(leaf["name"]), + "name": leaf["name"], "description": leaf["description"], "start_time": timestamp(), "item_type": "STEP", @@ -1351,7 +1334,7 @@ def start_bdd_step(self, feature: Feature, scenario: Scenario, step: Step) -> No if feature.background: background_leaf = scenario_leaf["children"][feature.background] self._finish_bdd_step(background_leaf, "PASSED") - item_id = reporter.start_nested_step(self._truncate_item_name(f"{step.keyword} {step.name}"), timestamp()) + item_id = reporter.start_nested_step(f"{step.keyword} {step.name}", timestamp()) step_leaf["item_id"] = item_id step_leaf["exec"] = ExecStatus.IN_PROGRESS From fa5a44a0c86f7cdf6fb746a590172f3e8799dd48 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Fri, 10 Apr 2026 22:30:08 +0300 Subject: [PATCH 5/9] Microseconds precision for timestamps --- CHANGELOG.md | 2 ++ pytest_reportportal/service.py | 29 +++++++++++++++-------------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1ea6af..e61ddda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Changelog ## [Unreleased] +### Added +- Microseconds precision for timestamps, by @HardNorth ### Changed - Client version updated to [5.7.4](https://github.com/reportportal/client-Python/releases/tag/5.7.4), by @HardNorth ### Removed diff --git a/pytest_reportportal/service.py b/pytest_reportportal/service.py index 12efcc0..fceb5f4 100644 --- a/pytest_reportportal/service.py +++ b/pytest_reportportal/service.py @@ -20,6 +20,7 @@ import threading import traceback from collections import OrderedDict +from datetime import datetime, timezone from enum import Enum from functools import wraps from os import curdir @@ -31,7 +32,7 @@ from pytest import Class, Function, Item, Module, Package, Session from reportportal_client.aio import Task from reportportal_client.core.rp_issues import ExternalIssue, Issue -from reportportal_client.helpers import markdown_helpers, timestamp +from reportportal_client.helpers import markdown_helpers from .config import AgentConfig @@ -236,7 +237,7 @@ def _build_start_launch_rq(self) -> dict[str, Any]: start_rq = { "attributes": self._get_launch_attributes(attributes), "name": self._config.rp_launch, - "start_time": timestamp(), + "start_time": datetime.now(tz=timezone.utc), "description": self._config.rp_launch_description, "rerun": self._config.rp_rerun, "rerun_of": self._config.rp_rerun_of, @@ -565,7 +566,7 @@ def _build_start_suite_rq(self, leaf: dict[str, Any]) -> dict[str, Any]: payload = { "name": leaf["name"], "description": self._get_item_description(item), - "start_time": timestamp(), + "start_time": datetime.now(tz=timezone.utc), "item_type": "SUITE", "code_ref": code_ref, "parent_item_id": parent_item_id, @@ -867,7 +868,7 @@ def _build_start_step_rq(self, leaf: dict[str, Any]) -> dict[str, Any]: "attributes": leaf.get("attributes", None), "name": leaf["name"], "description": leaf["description"], - "start_time": timestamp(), + "start_time": datetime.now(tz=timezone.utc), "item_type": "STEP", "code_ref": leaf.get("code_ref", None), "parameters": leaf.get("parameters", None), @@ -946,7 +947,7 @@ def _build_finish_step_rq(self, leaf: dict[str, Any]) -> dict[str, Any]: issue = None payload = { "attributes": leaf.get("attributes", None), - "end_time": timestamp(), + "end_time": datetime.now(tz=timezone.utc), "status": status, "issue": issue, "item_id": leaf["item_id"], @@ -962,7 +963,7 @@ def _finish_suite(self, finish_rq: dict[str, Any]) -> None: self.rp.finish_test_item(**finish_rq) def _build_finish_suite_rq(self, leaf) -> dict[str, Any]: - payload = {"end_time": timestamp(), "item_id": leaf["item_id"]} + payload = {"end_time": datetime.now(tz=timezone.utc), "item_id": leaf["item_id"]} return payload def _proceed_suite_finish(self, leaf) -> None: @@ -1039,7 +1040,7 @@ def finish_suites(self) -> None: self._lock(leaf, lambda p: self._proceed_suite_finish(p)) def _build_finish_launch_rq(self) -> dict[str, Any]: - finish_rq = {"end_time": timestamp()} + finish_rq = {"end_time": datetime.now(tz=timezone.utc)} return finish_rq def _finish_launch(self, finish_rq) -> None: @@ -1057,7 +1058,7 @@ def _build_log( ) -> dict[str, Any]: sl_rq = { "item_id": item_id, - "time": timestamp(), + "time": datetime.now(tz=timezone.utc), "message": message, "level": log_level, } @@ -1106,7 +1107,7 @@ def report_fixture(self, name: str, error_msg: str) -> Generator[None, Any, None return reporter = self.rp.step_reporter - item_id = reporter.start_nested_step(name, timestamp()) + item_id = reporter.start_nested_step(name, datetime.now(tz=timezone.utc)) try: outcome = yield @@ -1123,11 +1124,11 @@ def report_fixture(self, name: str, error_msg: str) -> Generator[None, Any, None ) exception_log = self._build_log(item_id, traceback_str, log_level="ERROR") self.rp.log(**exception_log) - reporter.finish_nested_step(item_id, timestamp(), status) + reporter.finish_nested_step(item_id, datetime.now(tz=timezone.utc), status) except Exception as e: LOGGER.error("Failed to report fixture: %s", name) LOGGER.exception(e) - reporter.finish_nested_step(item_id, timestamp(), "FAILED") + reporter.finish_nested_step(item_id, datetime.now(tz=timezone.utc), "FAILED") def _get_python_name(self, scenario: Scenario) -> str: python_name = f"test_{make_python_name(self._get_scenario_template(scenario).name)}" @@ -1284,7 +1285,7 @@ def _finish_bdd_step(self, leaf: dict[str, Any], status: str) -> None: reporter = self.rp.step_reporter item_id = leaf["item_id"] - reporter.finish_nested_step(item_id, timestamp(), status) + reporter.finish_nested_step(item_id, datetime.now(tz=timezone.utc), status) leaf["exec"] = ExecStatus.FINISHED def _is_background_step(self, step: Step, feature: Feature) -> bool: @@ -1326,7 +1327,7 @@ def start_bdd_step(self, feature: Feature, scenario: Scenario, step: Step) -> No background_leaf = scenario_leaf["children"][feature.background] background_leaf["children"][step] = step_leaf if background_leaf["exec"] != ExecStatus.IN_PROGRESS: - item_id = reporter.start_nested_step(BACKGROUND_STEP_NAME, timestamp()) + item_id = reporter.start_nested_step(BACKGROUND_STEP_NAME, datetime.now(tz=timezone.utc)) background_leaf["item_id"] = item_id background_leaf["exec"] = ExecStatus.IN_PROGRESS else: @@ -1334,7 +1335,7 @@ def start_bdd_step(self, feature: Feature, scenario: Scenario, step: Step) -> No if feature.background: background_leaf = scenario_leaf["children"][feature.background] self._finish_bdd_step(background_leaf, "PASSED") - item_id = reporter.start_nested_step(f"{step.keyword} {step.name}", timestamp()) + item_id = reporter.start_nested_step(f"{step.keyword} {step.name}", datetime.now(tz=timezone.utc)) step_leaf["item_id"] = item_id step_leaf["exec"] = ExecStatus.IN_PROGRESS From 72c8ef579152229bc0234c10a28c3b918b32c8ef Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Fri, 10 Apr 2026 22:38:20 +0300 Subject: [PATCH 6/9] Remove redundant test --- tests/integration/test_bdd.py | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/tests/integration/test_bdd.py b/tests/integration/test_bdd.py index 6e26132..1b10c5b 100644 --- a/tests/integration/test_bdd.py +++ b/tests/integration/test_bdd.py @@ -1081,33 +1081,3 @@ def test_rp_tests_attributes_bdd_tags(mock_client_init): assert {"key": "test_key", "value": "test_value"} in attributes assert {"value": "ok"} in attributes assert {"key": "key", "value": "value"} in attributes - - -@mock.patch(REPORT_PORTAL_SERVICE) -def test_long_step(mock_client_init): - mock_client = setup_mock_for_logging(mock_client_init) - result = utils.run_pytest_tests(tests=["examples/bdd/step_defs/long_step_scenario_steps.py"]) - assert int(result) == 0, "Exit code should be 0 (no errors)" - - # Verify scenario is correctly identified - scenario_call = mock_client.start_test_item.call_args_list[0] - assert scenario_call[1]["name"] == "Feature: Test long step scenario - Scenario: The scenario" - assert scenario_call[1]["item_type"] == "STEP" - assert scenario_call[1]["code_ref"] == "features/long_step_scenario.feature/[SCENARIO:The scenario]" - - # Verify step is truncated but still identifiable - step_call = mock_client.start_test_item.call_args_list[1] - step_name = step_call[0][0] - assert step_name.startswith("Given A very long step.") - assert len(step_name) <= 1024, "Step name should be truncated to at most 1024 characters" - assert step_name.endswith("..."), "Truncated step should have ellipsis" - - # Verify step details - assert step_call[0][2] == "step" - assert step_call[1]["parent_item_id"] == scenario_call[1]["name"] + "_1" - assert step_call[1]["has_stats"] is False - - # Verify all steps pass - finish_calls = mock_client.finish_test_item.call_args_list - for call in finish_calls: - assert call[1]["status"] == "PASSED" From 13c1541a44971bce42a7b169a468e5c0091229b5 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Fri, 10 Apr 2026 23:12:20 +0300 Subject: [PATCH 7/9] Fix tests --- tests/integration/test_config_handling.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tests/integration/test_config_handling.py b/tests/integration/test_config_handling.py index df6bdf5..6437211 100644 --- a/tests/integration/test_config_handling.py +++ b/tests/integration/test_config_handling.py @@ -177,9 +177,10 @@ def test_rp_api_retries(mock_client_init): retries = 5 variables = dict(utils.DEFAULT_VARIABLES) variables.update({"rp_api_retries": str(retries)}.items()) + mock_client = mock_client_init.return_value with warnings.catch_warnings(record=True) as w: - result = utils.run_pytest_tests(["examples/test_rp_logging.py"], variables=variables) + result = utils.run_tests_with_client(mock_client, ["examples/test_rp_logging.py"], variables=variables) assert int(result) == 0, "Exit code should be 0 (no errors)" expect(mock_client_init.call_count == 1) @@ -210,7 +211,8 @@ def test_launch_uuid_print(mock_client_init): print_uuid = True variables = utils.DEFAULT_VARIABLES.copy() variables.update({"rp_launch_uuid_print": str(print_uuid)}.items()) - result = utils.run_pytest_tests(["examples/test_rp_logging.py"], variables=variables) + mock_client = mock_client_init.return_value + result = utils.run_tests_with_client(mock_client, ["examples/test_rp_logging.py"], variables=variables) assert int(result) == 0, "Exit code should be 0 (no errors)" expect(mock_client_init.call_count == 1) expect(mock_client_init.call_args_list[0][1]["launch_uuid_print"] == print_uuid) @@ -223,7 +225,8 @@ def test_launch_uuid_print_stderr(mock_client_init): print_uuid = True variables = utils.DEFAULT_VARIABLES.copy() variables.update({"rp_launch_uuid_print": str(print_uuid), "rp_launch_uuid_print_output": "stderr"}.items()) - result = utils.run_pytest_tests(["examples/test_rp_logging.py"], variables=variables) + mock_client = mock_client_init.return_value + result = utils.run_tests_with_client(mock_client, ["examples/test_rp_logging.py"], variables=variables) assert int(result) == 0, "Exit code should be 0 (no errors)" expect(mock_client_init.call_count == 1) expect(mock_client_init.call_args_list[0][1]["launch_uuid_print"] == print_uuid) @@ -244,7 +247,8 @@ def test_launch_uuid_print_invalid_output(mock_client_init): @mock.patch(REPORT_PORTAL_SERVICE) def test_no_launch_uuid_print(mock_client_init): variables = utils.DEFAULT_VARIABLES.copy() - result = utils.run_pytest_tests(["examples/test_rp_logging.py"], variables=variables) + mock_client = mock_client_init.return_value + result = utils.run_tests_with_client(mock_client, ["examples/test_rp_logging.py"], variables=variables) assert int(result) == 0, "Exit code should be 0 (no errors)" expect(mock_client_init.call_count == 1) expect(mock_client_init.call_args_list[0][1]["launch_uuid_print"] is False) @@ -264,7 +268,8 @@ def test_client_timeouts(mock_client_init, connect_value, read_value, expected_r if read_value: variables["rp_read_timeout"] = read_value - result = utils.run_pytest_tests(["examples/test_rp_logging.py"], variables=variables) + mock_client = mock_client_init.return_value + result = utils.run_tests_with_client(mock_client, ["examples/test_rp_logging.py"], variables=variables) assert int(result) == 0, "Exit code should be 0 (no errors)" assert mock_client_init.call_count == 1 From 0699ee450e912f7d27c3ce3466831a361473480b Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Fri, 10 Apr 2026 23:20:05 +0300 Subject: [PATCH 8/9] Remove redundant test --- tests/integration/test_max_name_length.py | 33 ----------------------- 1 file changed, 33 deletions(-) delete mode 100644 tests/integration/test_max_name_length.py diff --git a/tests/integration/test_max_name_length.py b/tests/integration/test_max_name_length.py deleted file mode 100644 index 51430db..0000000 --- a/tests/integration/test_max_name_length.py +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright 2024 EPAM Systems -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from unittest import mock - -from tests import REPORT_PORTAL_SERVICE -from tests.helpers import utils - - -@mock.patch(REPORT_PORTAL_SERVICE) -def test_custom_attribute_report(mock_client_init): - result = utils.run_pytest_tests(tests=["examples/test_max_item_name.py"], variables=utils.DEFAULT_VARIABLES) - assert int(result) == 0, "Exit code should be 0 (no errors)" - - mock_client = mock_client_init.return_value - start_count = mock_client.start_test_item.call_count - finish_count = mock_client.finish_test_item.call_count - assert start_count == finish_count == 1, 'Incorrect number of "start_test_item" or "finish_test_item" calls' - - call_args = mock_client.start_test_item.call_args_list - step_call_args = call_args[0][1] - assert len(step_call_args["name"]) == 1024, "Incorrect item name length" From 474fb8aaf5b8d6a805d55c91d13c1dde000a4282 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Fri, 10 Apr 2026 23:22:50 +0300 Subject: [PATCH 9/9] Fix test --- tests/integration/test_empty_run.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/integration/test_empty_run.py b/tests/integration/test_empty_run.py index 8101f32..e3bda68 100644 --- a/tests/integration/test_empty_run.py +++ b/tests/integration/test_empty_run.py @@ -13,6 +13,7 @@ """This module includes integration tests for the empty run.""" +from datetime import datetime, timezone from unittest import mock from tests import REPORT_PORTAL_SERVICE @@ -25,6 +26,7 @@ def test_empty_run(mock_client_init): :param mock_client_init: Pytest fixture """ + test_start_time = datetime.now(timezone.utc) result = utils.run_pytest_tests(tests=["examples/empty/"]) assert int(result) == 5, "Exit code should be 5 (no tests)" @@ -36,4 +38,4 @@ def test_empty_run(mock_client_init): finish_args = mock_client.finish_launch.call_args_list assert "status" not in finish_args[0][1], "Launch status should not be defined" launch_end_time = finish_args[0][1]["end_time"] - assert launch_end_time is not None and int(launch_end_time) > 0, "Launch end time is empty" + assert launch_end_time is not None and launch_end_time > test_start_time, "Launch end time is empty"