From 08fad6c5527c5c3a79c0cd5f689569a796f1bdbe Mon Sep 17 00:00:00 2001 From: drcrazy Date: Tue, 7 Apr 2026 22:09:10 +0300 Subject: [PATCH 1/6] fix: detect pytest-bdd scenario module path on Windows (#418) Item.location[0] uses backslashes on Windows, so the suffix check for pytest_bdd/scenario.py never matched and RP received duplicate items. Normalize path separators before comparing the suffix. Closes #418 Made-with: Cursor --- pytest_reportportal/service.py | 16 +++++++++++++--- tests/unit/test_service.py | 19 +++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/pytest_reportportal/service.py b/pytest_reportportal/service.py index 685d8b5..39e42b2 100644 --- a/pytest_reportportal/service.py +++ b/pytest_reportportal/service.py @@ -93,6 +93,16 @@ BACKGROUND_STEP_NAME = "Background" +def _is_pytest_bdd_scenario_location(location_path: str) -> bool: + """ + Return True if the pytest collection path points at pytest-bdd's scenario module. + + ``Item.location[0]`` uses OS-native separators (backslashes on Windows), so a + plain suffix check with ``/`` is not portable. See #418. + """ + return location_path.replace("\\", "/").endswith("/pytest_bdd/scenario.py") + + def trim_docstring(docstring: str) -> str: """ Convert docstring. @@ -907,7 +917,7 @@ def start_pytest_item(self, test_item: Optional[Item] = None): if not self.__started(): self.start() - if PYTEST_BDD and test_item.location[0].endswith("/pytest_bdd/scenario.py"): + if PYTEST_BDD and _is_pytest_bdd_scenario_location(test_item.location[0]): self._bdd_item_by_name[test_item.name] = test_item return @@ -928,7 +938,7 @@ def process_results(self, test_item: Item, report): if report.longrepr: self.post_log(test_item, report.longreprtext, log_level="ERROR") - if PYTEST_BDD and test_item.location[0].endswith("/pytest_bdd/scenario.py"): + if PYTEST_BDD and _is_pytest_bdd_scenario_location(test_item.location[0]): return leaf = self._tree_path[test_item][-1] @@ -1011,7 +1021,7 @@ def finish_pytest_item(self, test_item: Optional[Item] = None) -> None: leaf = self._tree_path[test_item][-1] self._process_metadata_item_finish(leaf) - if PYTEST_BDD and test_item.location[0].endswith("/pytest_bdd/scenario.py"): + if PYTEST_BDD and _is_pytest_bdd_scenario_location(test_item.location[0]): del self._bdd_item_by_name[test_item.name] return diff --git a/tests/unit/test_service.py b/tests/unit/test_service.py index b914cde..69e646a 100644 --- a/tests/unit/test_service.py +++ b/tests/unit/test_service.py @@ -15,6 +15,25 @@ from delayed_assert import assert_expectations, expect +from pytest_reportportal.service import _is_pytest_bdd_scenario_location + + +def test_is_pytest_bdd_scenario_location_posix_path(): + """pytest-bdd scenario items use forward slashes in location on POSIX.""" + path = "/usr/lib/python3.12/site-packages/pytest_bdd/scenario.py" + assert _is_pytest_bdd_scenario_location(path) is True + + +def test_is_pytest_bdd_scenario_location_windows_path(): + """pytest-bdd scenario items use backslashes in location on Windows (#418).""" + path = r"C:\Python312\Lib\site-packages\pytest_bdd\scenario.py" + assert _is_pytest_bdd_scenario_location(path) is True + + +def test_is_pytest_bdd_scenario_location_regular_test_module(): + """Regular tests must not be treated as pytest-bdd scenario glue.""" + assert _is_pytest_bdd_scenario_location("/project/tests/test_foo.py") is False + def test_get_item_parameters(mocked_item, rp_service): """Test that parameters are returned in a way supported by the client.""" From ae4e75bd4769d4cc9d606007d49debcde998d4fc Mon Sep 17 00:00:00 2001 From: drcrazy Date: Tue, 7 Apr 2026 22:12:34 +0300 Subject: [PATCH 2/6] Fix issue 418: Detect pytest-bdd scenario module path on Windows --- ...t-bdd-scenario-module-path-on-Window.patch | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 patches/0001-fix-detect-pytest-bdd-scenario-module-path-on-Window.patch diff --git a/patches/0001-fix-detect-pytest-bdd-scenario-module-path-on-Window.patch b/patches/0001-fix-detect-pytest-bdd-scenario-module-path-on-Window.patch new file mode 100644 index 0000000..f9c2f53 --- /dev/null +++ b/patches/0001-fix-detect-pytest-bdd-scenario-module-path-on-Window.patch @@ -0,0 +1,99 @@ +From 08fad6c5527c5c3a79c0cd5f689569a796f1bdbe Mon Sep 17 00:00:00 2001 +From: drcrazy +Date: Tue, 7 Apr 2026 22:09:10 +0300 +Subject: [PATCH] fix: detect pytest-bdd scenario module path on Windows (#418) + +Item.location[0] uses backslashes on Windows, so the suffix check for +pytest_bdd/scenario.py never matched and RP received duplicate items. + +Normalize path separators before comparing the suffix. + +Closes #418 + +Made-with: Cursor +--- + pytest_reportportal/service.py | 16 +++++++++++++--- + tests/unit/test_service.py | 19 +++++++++++++++++++ + 2 files changed, 32 insertions(+), 3 deletions(-) + +diff --git a/pytest_reportportal/service.py b/pytest_reportportal/service.py +index 685d8b5..39e42b2 100644 +--- a/pytest_reportportal/service.py ++++ b/pytest_reportportal/service.py +@@ -93,6 +93,16 @@ ALPHA_REGEX = re.compile(r"^\d+_*") + BACKGROUND_STEP_NAME = "Background" + + ++def _is_pytest_bdd_scenario_location(location_path: str) -> bool: ++ """ ++ Return True if the pytest collection path points at pytest-bdd's scenario module. ++ ++ ``Item.location[0]`` uses OS-native separators (backslashes on Windows), so a ++ plain suffix check with ``/`` is not portable. See #418. ++ """ ++ return location_path.replace("\\", "/").endswith("/pytest_bdd/scenario.py") ++ ++ + def trim_docstring(docstring: str) -> str: + """ + Convert docstring. +@@ -907,7 +917,7 @@ class PyTestService: + if not self.__started(): + self.start() + +- if PYTEST_BDD and test_item.location[0].endswith("/pytest_bdd/scenario.py"): ++ if PYTEST_BDD and _is_pytest_bdd_scenario_location(test_item.location[0]): + self._bdd_item_by_name[test_item.name] = test_item + return + +@@ -928,7 +938,7 @@ class PyTestService: + if report.longrepr: + self.post_log(test_item, report.longreprtext, log_level="ERROR") + +- if PYTEST_BDD and test_item.location[0].endswith("/pytest_bdd/scenario.py"): ++ if PYTEST_BDD and _is_pytest_bdd_scenario_location(test_item.location[0]): + return + + leaf = self._tree_path[test_item][-1] +@@ -1011,7 +1021,7 @@ class PyTestService: + leaf = self._tree_path[test_item][-1] + self._process_metadata_item_finish(leaf) + +- if PYTEST_BDD and test_item.location[0].endswith("/pytest_bdd/scenario.py"): ++ if PYTEST_BDD and _is_pytest_bdd_scenario_location(test_item.location[0]): + del self._bdd_item_by_name[test_item.name] + return + +diff --git a/tests/unit/test_service.py b/tests/unit/test_service.py +index b914cde..69e646a 100644 +--- a/tests/unit/test_service.py ++++ b/tests/unit/test_service.py +@@ -15,6 +15,25 @@ + + from delayed_assert import assert_expectations, expect + ++from pytest_reportportal.service import _is_pytest_bdd_scenario_location ++ ++ ++def test_is_pytest_bdd_scenario_location_posix_path(): ++ """pytest-bdd scenario items use forward slashes in location on POSIX.""" ++ path = "/usr/lib/python3.12/site-packages/pytest_bdd/scenario.py" ++ assert _is_pytest_bdd_scenario_location(path) is True ++ ++ ++def test_is_pytest_bdd_scenario_location_windows_path(): ++ """pytest-bdd scenario items use backslashes in location on Windows (#418).""" ++ path = r"C:\Python312\Lib\site-packages\pytest_bdd\scenario.py" ++ assert _is_pytest_bdd_scenario_location(path) is True ++ ++ ++def test_is_pytest_bdd_scenario_location_regular_test_module(): ++ """Regular tests must not be treated as pytest-bdd scenario glue.""" ++ assert _is_pytest_bdd_scenario_location("/project/tests/test_foo.py") is False ++ + + def test_get_item_parameters(mocked_item, rp_service): + """Test that parameters are returned in a way supported by the client.""" +-- +2.53.0.windows.2 + From ca5caa71335b5a01f271afde4a9c2ad14645dab6 Mon Sep 17 00:00:00 2001 From: drcrazy Date: Tue, 7 Apr 2026 23:50:14 +0300 Subject: [PATCH 3/6] Use os.path.sep --- ...t-bdd-scenario-module-path-on-Window.patch | 99 ------------------- pytest_reportportal/service.py | 10 +- tests/unit/test_service.py | 14 +-- 3 files changed, 12 insertions(+), 111 deletions(-) delete mode 100644 patches/0001-fix-detect-pytest-bdd-scenario-module-path-on-Window.patch diff --git a/patches/0001-fix-detect-pytest-bdd-scenario-module-path-on-Window.patch b/patches/0001-fix-detect-pytest-bdd-scenario-module-path-on-Window.patch deleted file mode 100644 index f9c2f53..0000000 --- a/patches/0001-fix-detect-pytest-bdd-scenario-module-path-on-Window.patch +++ /dev/null @@ -1,99 +0,0 @@ -From 08fad6c5527c5c3a79c0cd5f689569a796f1bdbe Mon Sep 17 00:00:00 2001 -From: drcrazy -Date: Tue, 7 Apr 2026 22:09:10 +0300 -Subject: [PATCH] fix: detect pytest-bdd scenario module path on Windows (#418) - -Item.location[0] uses backslashes on Windows, so the suffix check for -pytest_bdd/scenario.py never matched and RP received duplicate items. - -Normalize path separators before comparing the suffix. - -Closes #418 - -Made-with: Cursor ---- - pytest_reportportal/service.py | 16 +++++++++++++--- - tests/unit/test_service.py | 19 +++++++++++++++++++ - 2 files changed, 32 insertions(+), 3 deletions(-) - -diff --git a/pytest_reportportal/service.py b/pytest_reportportal/service.py -index 685d8b5..39e42b2 100644 ---- a/pytest_reportportal/service.py -+++ b/pytest_reportportal/service.py -@@ -93,6 +93,16 @@ ALPHA_REGEX = re.compile(r"^\d+_*") - BACKGROUND_STEP_NAME = "Background" - - -+def _is_pytest_bdd_scenario_location(location_path: str) -> bool: -+ """ -+ Return True if the pytest collection path points at pytest-bdd's scenario module. -+ -+ ``Item.location[0]`` uses OS-native separators (backslashes on Windows), so a -+ plain suffix check with ``/`` is not portable. See #418. -+ """ -+ return location_path.replace("\\", "/").endswith("/pytest_bdd/scenario.py") -+ -+ - def trim_docstring(docstring: str) -> str: - """ - Convert docstring. -@@ -907,7 +917,7 @@ class PyTestService: - if not self.__started(): - self.start() - -- if PYTEST_BDD and test_item.location[0].endswith("/pytest_bdd/scenario.py"): -+ if PYTEST_BDD and _is_pytest_bdd_scenario_location(test_item.location[0]): - self._bdd_item_by_name[test_item.name] = test_item - return - -@@ -928,7 +938,7 @@ class PyTestService: - if report.longrepr: - self.post_log(test_item, report.longreprtext, log_level="ERROR") - -- if PYTEST_BDD and test_item.location[0].endswith("/pytest_bdd/scenario.py"): -+ if PYTEST_BDD and _is_pytest_bdd_scenario_location(test_item.location[0]): - return - - leaf = self._tree_path[test_item][-1] -@@ -1011,7 +1021,7 @@ class PyTestService: - leaf = self._tree_path[test_item][-1] - self._process_metadata_item_finish(leaf) - -- if PYTEST_BDD and test_item.location[0].endswith("/pytest_bdd/scenario.py"): -+ if PYTEST_BDD and _is_pytest_bdd_scenario_location(test_item.location[0]): - del self._bdd_item_by_name[test_item.name] - return - -diff --git a/tests/unit/test_service.py b/tests/unit/test_service.py -index b914cde..69e646a 100644 ---- a/tests/unit/test_service.py -+++ b/tests/unit/test_service.py -@@ -15,6 +15,25 @@ - - from delayed_assert import assert_expectations, expect - -+from pytest_reportportal.service import _is_pytest_bdd_scenario_location -+ -+ -+def test_is_pytest_bdd_scenario_location_posix_path(): -+ """pytest-bdd scenario items use forward slashes in location on POSIX.""" -+ path = "/usr/lib/python3.12/site-packages/pytest_bdd/scenario.py" -+ assert _is_pytest_bdd_scenario_location(path) is True -+ -+ -+def test_is_pytest_bdd_scenario_location_windows_path(): -+ """pytest-bdd scenario items use backslashes in location on Windows (#418).""" -+ path = r"C:\Python312\Lib\site-packages\pytest_bdd\scenario.py" -+ assert _is_pytest_bdd_scenario_location(path) is True -+ -+ -+def test_is_pytest_bdd_scenario_location_regular_test_module(): -+ """Regular tests must not be treated as pytest-bdd scenario glue.""" -+ assert _is_pytest_bdd_scenario_location("/project/tests/test_foo.py") is False -+ - - def test_get_item_parameters(mocked_item, rp_service): - """Test that parameters are returned in a way supported by the client.""" --- -2.53.0.windows.2 - diff --git a/pytest_reportportal/service.py b/pytest_reportportal/service.py index 39e42b2..2073f2a 100644 --- a/pytest_reportportal/service.py +++ b/pytest_reportportal/service.py @@ -93,14 +93,14 @@ BACKGROUND_STEP_NAME = "Background" -def _is_pytest_bdd_scenario_location(location_path: str) -> bool: +def _is_pytest_bdd_scenario(location_path: str) -> bool: """ Return True if the pytest collection path points at pytest-bdd's scenario module. ``Item.location[0]`` uses OS-native separators (backslashes on Windows), so a plain suffix check with ``/`` is not portable. See #418. """ - return location_path.replace("\\", "/").endswith("/pytest_bdd/scenario.py") + return location_path.replace(os.path.sep, "/").endswith("/pytest_bdd/scenario.py") def trim_docstring(docstring: str) -> str: @@ -917,7 +917,7 @@ def start_pytest_item(self, test_item: Optional[Item] = None): if not self.__started(): self.start() - if PYTEST_BDD and _is_pytest_bdd_scenario_location(test_item.location[0]): + if PYTEST_BDD and _is_pytest_bdd_scenario(test_item.location[0]): self._bdd_item_by_name[test_item.name] = test_item return @@ -938,7 +938,7 @@ def process_results(self, test_item: Item, report): if report.longrepr: self.post_log(test_item, report.longreprtext, log_level="ERROR") - if PYTEST_BDD and _is_pytest_bdd_scenario_location(test_item.location[0]): + if PYTEST_BDD and _is_pytest_bdd_scenario(test_item.location[0]): return leaf = self._tree_path[test_item][-1] @@ -1021,7 +1021,7 @@ def finish_pytest_item(self, test_item: Optional[Item] = None) -> None: leaf = self._tree_path[test_item][-1] self._process_metadata_item_finish(leaf) - if PYTEST_BDD and _is_pytest_bdd_scenario_location(test_item.location[0]): + if PYTEST_BDD and _is_pytest_bdd_scenario(test_item.location[0]): del self._bdd_item_by_name[test_item.name] return diff --git a/tests/unit/test_service.py b/tests/unit/test_service.py index 69e646a..9df96a6 100644 --- a/tests/unit/test_service.py +++ b/tests/unit/test_service.py @@ -15,24 +15,24 @@ from delayed_assert import assert_expectations, expect -from pytest_reportportal.service import _is_pytest_bdd_scenario_location +from pytest_reportportal.service import _is_pytest_bdd_scenario -def test_is_pytest_bdd_scenario_location_posix_path(): +def test_is_pytest_bdd_scenario_posix_path(): """pytest-bdd scenario items use forward slashes in location on POSIX.""" path = "/usr/lib/python3.12/site-packages/pytest_bdd/scenario.py" - assert _is_pytest_bdd_scenario_location(path) is True + assert _is_pytest_bdd_scenario(path) is True -def test_is_pytest_bdd_scenario_location_windows_path(): +def test_is_pytest_bdd_scenario_windows_path(): """pytest-bdd scenario items use backslashes in location on Windows (#418).""" path = r"C:\Python312\Lib\site-packages\pytest_bdd\scenario.py" - assert _is_pytest_bdd_scenario_location(path) is True + assert _is_pytest_bdd_scenario(path) is True -def test_is_pytest_bdd_scenario_location_regular_test_module(): +def test_is_pytest_bdd_scenario_regular_test_module(): """Regular tests must not be treated as pytest-bdd scenario glue.""" - assert _is_pytest_bdd_scenario_location("/project/tests/test_foo.py") is False + assert _is_pytest_bdd_scenario("/project/tests/test_foo.py") is False def test_get_item_parameters(mocked_item, rp_service): From 01f180e6da5a2f111a6bc4ea1e1a7c4568042785 Mon Sep 17 00:00:00 2001 From: drcrazy Date: Wed, 8 Apr 2026 00:04:03 +0300 Subject: [PATCH 4/6] Fix unit tests --- tests/unit/test_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/test_service.py b/tests/unit/test_service.py index 9df96a6..18e0c23 100644 --- a/tests/unit/test_service.py +++ b/tests/unit/test_service.py @@ -26,7 +26,7 @@ def test_is_pytest_bdd_scenario_posix_path(): def test_is_pytest_bdd_scenario_windows_path(): """pytest-bdd scenario items use backslashes in location on Windows (#418).""" - path = r"C:\Python312\Lib\site-packages\pytest_bdd\scenario.py" + path = "C:\\Python312\\Lib\\site-packages\\pytest_bdd\\scenario.py" assert _is_pytest_bdd_scenario(path) is True From f419251d4af52353b8b1c7d6c3d9506c9922c042 Mon Sep 17 00:00:00 2001 From: drcrazy Date: Wed, 8 Apr 2026 13:39:05 +0300 Subject: [PATCH 5/6] Use os.path.join to build BDD path --- pytest_reportportal/service.py | 2 +- tests/unit/test_service.py | 13 ++++--------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/pytest_reportportal/service.py b/pytest_reportportal/service.py index 2073f2a..b93d96b 100644 --- a/pytest_reportportal/service.py +++ b/pytest_reportportal/service.py @@ -100,7 +100,7 @@ def _is_pytest_bdd_scenario(location_path: str) -> bool: ``Item.location[0]`` uses OS-native separators (backslashes on Windows), so a plain suffix check with ``/`` is not portable. See #418. """ - return location_path.replace(os.path.sep, "/").endswith("/pytest_bdd/scenario.py") + return location_path.endswith(os.path.join("pytest_bdd", "scenario.py")) def trim_docstring(docstring: str) -> str: diff --git a/tests/unit/test_service.py b/tests/unit/test_service.py index 18e0c23..4690dc8 100644 --- a/tests/unit/test_service.py +++ b/tests/unit/test_service.py @@ -13,20 +13,15 @@ """This module includes unit tests for the service.py module.""" +import os from delayed_assert import assert_expectations, expect from pytest_reportportal.service import _is_pytest_bdd_scenario -def test_is_pytest_bdd_scenario_posix_path(): - """pytest-bdd scenario items use forward slashes in location on POSIX.""" - path = "/usr/lib/python3.12/site-packages/pytest_bdd/scenario.py" - assert _is_pytest_bdd_scenario(path) is True - - -def test_is_pytest_bdd_scenario_windows_path(): - """pytest-bdd scenario items use backslashes in location on Windows (#418).""" - path = "C:\\Python312\\Lib\\site-packages\\pytest_bdd\\scenario.py" +def test_is_pytest_bdd_scenario_path(): + """pytest-bdd scenario items use forward slashes in location on POSIX and backslashes on Windows.""" + path = os.path.join("project", "pytest_bdd", "scenario.py") assert _is_pytest_bdd_scenario(path) is True From 7527f7fd96fe401ca0625621fb644e702ddc5798 Mon Sep 17 00:00:00 2001 From: drcrazy Date: Wed, 8 Apr 2026 13:47:11 +0300 Subject: [PATCH 6/6] Fix isort --- tests/unit/test_service.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/test_service.py b/tests/unit/test_service.py index 4690dc8..f28e18a 100644 --- a/tests/unit/test_service.py +++ b/tests/unit/test_service.py @@ -14,6 +14,7 @@ """This module includes unit tests for the service.py module.""" import os + from delayed_assert import assert_expectations, expect from pytest_reportportal.service import _is_pytest_bdd_scenario