From 119ff2ebb79c639cfe0273d559fb176de780c255 Mon Sep 17 00:00:00 2001 From: "reportportal.io" Date: Tue, 8 Jul 2025 10:45:22 +0000 Subject: [PATCH 01/20] Changelog update --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d2b28b7..5f4e6cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Changelog ## [Unreleased] + +## [5.5.2] ### Fixed - Issue [#397](https://github.com/reportportal/agent-python-pytest/issues/397) pytest launch stuck with xdist, by @HardNorth From 5e652faa203493720c5d243303c74698c087af1d Mon Sep 17 00:00:00 2001 From: "reportportal.io" Date: Tue, 8 Jul 2025 10:45:23 +0000 Subject: [PATCH 02/20] Version update --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 95cf923..fb8562a 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ from setuptools import setup -__version__ = "5.5.2" +__version__ = "5.5.3" def read_file(fname): From fed6c4960abc268d82e50d674d535fec25337ee6 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Tue, 8 Jul 2025 13:49:16 +0300 Subject: [PATCH 03/20] Add PYTEST_BDD condition to speed up code a bit without it --- pytest_reportportal/service.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pytest_reportportal/service.py b/pytest_reportportal/service.py index 3068da5..d3e4baf 100644 --- a/pytest_reportportal/service.py +++ b/pytest_reportportal/service.py @@ -434,7 +434,10 @@ def _merge_code(self, test_tree: Dict[str, Any]) -> None: def _build_item_paths(self, leaf: Dict[str, Any], path: List[Dict[str, Any]]) -> None: children = leaf.get("children", {}) - all_background_steps = all([isinstance(child, Background) for child in children.keys()]) + if PYTEST_BDD: + all_background_steps = all([isinstance(child, Background) for child in children.keys()]) + else: + all_background_steps = False if len(children) > 0 and not all_background_steps: path.append(leaf) for name, child_leaf in leaf["children"].items(): From b261cad295169e6381c731a3f835c1e28f15a9e7 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Thu, 11 Sep 2025 14:03:09 +0300 Subject: [PATCH 04/20] Remove redundant comment --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a81c860..b44120a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,5 @@ [build-system] requires = [ - # sync with setup.py until we discard non-pep-517/518 "setuptools>=68.0.0", "setuptools-scm", "wheel==0.40.0", From 1c8b3e0ae1c45f3888bb33c34e8c717f58348493 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Thu, 11 Sep 2025 14:17:35 +0300 Subject: [PATCH 05/20] Ignore a flake rule --- .flake8 | 1 + 1 file changed, 1 insertion(+) diff --git a/.flake8 b/.flake8 index 791f075..dfa2842 100644 --- a/.flake8 +++ b/.flake8 @@ -1,2 +1,3 @@ [flake8] +ignore = E203 max-line-length = 119 From 8a5f39b84f824fc801ec16baa90c8438e833e2b0 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Tue, 16 Sep 2025 15:04:13 +0300 Subject: [PATCH 06/20] Ignore a flake rule --- .flake8 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.flake8 b/.flake8 index dfa2842..43e8b9d 100644 --- a/.flake8 +++ b/.flake8 @@ -1,3 +1,3 @@ [flake8] -ignore = E203 +ignore = E203, W503 max-line-length = 119 From a61e4fa5acf850f8c3453d094d762db9c00d0787 Mon Sep 17 00:00:00 2001 From: breburdaTom Date: Wed, 1 Oct 2025 09:13:31 +0200 Subject: [PATCH 07/20] allow rp_endpoint default value to be overriden by an env var --- pytest_reportportal/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytest_reportportal/config.py b/pytest_reportportal/config.py index 092fe87..af0e521 100644 --- a/pytest_reportportal/config.py +++ b/pytest_reportportal/config.py @@ -73,7 +73,7 @@ class AgentConfig: def __init__(self, pytest_config: Config) -> None: """Initialize required attributes.""" self.rp_rerun = pytest_config.option.rp_rerun or pytest_config.getini("rp_rerun") - self.rp_endpoint = self.find_option(pytest_config, "rp_endpoint") + self.rp_endpoint = getenv("RP_ENDPOINT") or self.find_option(pytest_config, "rp_endpoint") self.rp_hierarchy_code = to_bool(self.find_option(pytest_config, "rp_hierarchy_code")) self.rp_dir_level = int(self.find_option(pytest_config, "rp_hierarchy_dirs_level")) self.rp_hierarchy_dirs = to_bool(self.find_option(pytest_config, "rp_hierarchy_dirs")) From 24c8d1ef38188caaa94b4cd9660aceae11f93735 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Thu, 6 Nov 2025 14:39:01 +0300 Subject: [PATCH 08/20] .gitignore update --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 7f38cff..eb64fa1 100644 --- a/.gitignore +++ b/.gitignore @@ -105,6 +105,10 @@ celerybeat.pid # Environments .env .venv +.venv38 +.venv39 +.venv310 +.venv311 env/ venv/ ENV/ From a2dadccd5a941dced5bd6c1f66a914ee663a566f Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Tue, 11 Nov 2025 12:31:33 +0300 Subject: [PATCH 09/20] Client version update, property rename. --- CHANGELOG.md | 4 ++++ pytest_reportportal/config.py | 10 +++++----- pytest_reportportal/plugin.py | 2 +- pytest_reportportal/service.py | 7 ++++--- requirements.txt | 2 +- 5 files changed, 15 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f4e6cd..7e15550 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Changelog ## [Unreleased] +### Changed +- Client version updated to [5.6.6](https://github.com/reportportal/client-Python/releases/tag/5.6.6), by @HardNorth +### Fixed +- Some configuration parameter names, which are different in the client, by @HardNorth ## [5.5.2] ### Fixed diff --git a/pytest_reportportal/config.py b/pytest_reportportal/config.py index af0e521..efcc8da 100644 --- a/pytest_reportportal/config.py +++ b/pytest_reportportal/config.py @@ -53,7 +53,7 @@ class AgentConfig: rp_tests_attributes: Optional[List[str]] rp_launch_description: str rp_log_batch_size: int - rp_log_batch_payload_size: int + rp_log_batch_payload_limit: int rp_log_level: Optional[int] rp_log_format: Optional[str] rp_mode: str @@ -100,11 +100,11 @@ def __init__(self, pytest_config: Config) -> None: self.rp_tests_attributes = self.find_option(pytest_config, "rp_tests_attributes") self.rp_launch_description = self.find_option(pytest_config, "rp_launch_description") self.rp_log_batch_size = int(self.find_option(pytest_config, "rp_log_batch_size")) - batch_payload_size = self.find_option(pytest_config, "rp_log_batch_payload_size") - if batch_payload_size: - self.rp_log_batch_payload_size = int(batch_payload_size) + batch_payload_size_limit = self.find_option(pytest_config, "rp_log_batch_payload_limit") + if batch_payload_size_limit: + self.rp_log_batch_payload_limit = int(batch_payload_size_limit) else: - self.rp_log_batch_payload_size = MAX_LOG_BATCH_PAYLOAD_SIZE + self.rp_log_batch_payload_limit = MAX_LOG_BATCH_PAYLOAD_SIZE self.rp_log_level = get_actual_log_level(pytest_config, "rp_log_level") self.rp_log_format = self.find_option(pytest_config, "rp_log_format") self.rp_thread_logging = to_bool(self.find_option(pytest_config, "rp_thread_logging") or False) diff --git a/pytest_reportportal/plugin.py b/pytest_reportportal/plugin.py index 84832c2..9b41560 100644 --- a/pytest_reportportal/plugin.py +++ b/pytest_reportportal/plugin.py @@ -617,7 +617,7 @@ def add_shared_option(name, help_str, default=None, action="store"): parser.addini("rp_tests_attributes", type="args", help="Attributes for all tests items, e.g. Smoke") parser.addini("rp_log_batch_size", default="20", help="Size of batch log requests in async mode") parser.addini( - "rp_log_batch_payload_size", + "rp_log_batch_payload_limit", default=str(MAX_LOG_BATCH_PAYLOAD_SIZE), help="Maximum payload size in bytes of async batch log requests", ) diff --git a/pytest_reportportal/service.py b/pytest_reportportal/service.py index d3e4baf..c41f1c1 100644 --- a/pytest_reportportal/service.py +++ b/pytest_reportportal/service.py @@ -285,7 +285,7 @@ def _create_leaf( :param leaf_type: the leaf type :param parent_item: parent pytest.Item of the current leaf - :param item: leaf's pytest.Item + :param item: the leaf's pytest.Item :return: a leaf """ return { @@ -1126,7 +1126,8 @@ def start_bdd_scenario(self, feature: Feature, scenario: Scenario) -> None: root_leaf = self._bdd_tree if not root_leaf: self._bdd_tree = root_leaf = self._create_leaf(LeafType.ROOT, None, None, item_id=self.parent_item_id) - children_leafs = root_leaf["children"] + # noinspection PyTypeChecker + children_leafs: Dict[Any, Any] = root_leaf["children"] if feature in children_leafs: feature_leaf = children_leafs[feature] else: @@ -1392,7 +1393,7 @@ def start(self) -> None: retries=self._config.rp_api_retries, verify_ssl=self._config.rp_verify_ssl, launch_uuid=launch_id, - log_batch_payload_size=self._config.rp_log_batch_payload_size, + log_batch_payload_limit=self._config.rp_log_batch_payload_limit, launch_uuid_print=self._config.rp_launch_uuid_print, print_output=self._config.rp_launch_uuid_print_output, http_timeout=self._config.rp_http_timeout, diff --git a/requirements.txt b/requirements.txt index 4c00464..49b7ebc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ dill>=0.3.6 pytest>=4.6.10 -reportportal-client~=5.6.4 +reportportal-client~=5.6.6 aenum>=3.1.0 From c6c4e5b10566ebfefaa344f1ee0cd28ff32f319c Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Tue, 11 Nov 2025 13:14:45 +0300 Subject: [PATCH 10/20] OAuth 2.0 Password Grant authentication --- CHANGELOG.md | 2 ++ pytest_reportportal/config.py | 35 +++++++++++++------- pytest_reportportal/plugin.py | 60 +++++++++------------------------- pytest_reportportal/service.py | 7 ++++ 4 files changed, 48 insertions(+), 56 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e15550..f67423f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Changelog ## [Unreleased] +### Added +- OAuth 2.0 Password Grant authentication, by @HardNorth ### Changed - Client version updated to [5.6.6](https://github.com/reportportal/client-Python/releases/tag/5.6.6), by @HardNorth ### Fixed diff --git a/pytest_reportportal/config.py b/pytest_reportportal/config.py index efcc8da..d64eef4 100644 --- a/pytest_reportportal/config.py +++ b/pytest_reportportal/config.py @@ -32,6 +32,7 @@ class AgentConfig: """Storage for the RP agent initialization attributes.""" + rp_enabled: bool rp_client_type: Optional[ClientType] rp_rerun: Optional[bool] pconfig: Config @@ -61,8 +62,18 @@ class AgentConfig: rp_project: str rp_rerun_of: Optional[str] rp_api_retries: int - rp_skip_connection_test: bool - rp_api_key: str + + # API key auth parameter + rp_api_key: Optional[str] + + # OAuth 2.0 parameters + rp_oauth_uri: Optional[str] + rp_oauth_username: Optional[str] + rp_oauth_password: Optional[str] + rp_oauth_client_id: Optional[str] + rp_oauth_client_secret: Optional[str] + rp_oauth_scope: Optional[str] + rp_verify_ssl: Union[bool, str] rp_launch_timeout: int rp_launch_uuid_print: bool @@ -72,6 +83,7 @@ class AgentConfig: def __init__(self, pytest_config: Config) -> None: """Initialize required attributes.""" + self.rp_enabled = to_bool(self.find_option(pytest_config, "rp_enabled", True)) self.rp_rerun = pytest_config.option.rp_rerun or pytest_config.getini("rp_rerun") self.rp_endpoint = getenv("RP_ENDPOINT") or self.find_option(pytest_config, "rp_endpoint") self.rp_hierarchy_code = to_bool(self.find_option(pytest_config, "rp_hierarchy_code")) @@ -112,7 +124,6 @@ def __init__(self, pytest_config: Config) -> None: self.rp_parent_item_id = self.find_option(pytest_config, "rp_parent_item_id") self.rp_project = self.find_option(pytest_config, "rp_project") self.rp_rerun_of = self.find_option(pytest_config, "rp_rerun_of") - self.rp_skip_connection_test = to_bool(self.find_option(pytest_config, "rp_skip_connection_test")) rp_api_retries_str = self.find_option(pytest_config, "rp_api_retries") rp_api_retries = rp_api_retries_str and int(rp_api_retries_str) @@ -134,6 +145,7 @@ def __init__(self, pytest_config: Config) -> None: else: self.rp_api_retries = 0 + # API key auth parameter self.rp_api_key = getenv("RP_API_KEY") or self.find_option(pytest_config, "rp_api_key") if not self.rp_api_key: self.rp_api_key = getenv("RP_UUID") or self.find_option(pytest_config, "rp_uuid") @@ -146,15 +158,14 @@ def __init__(self, pytest_config: Config) -> None: DeprecationWarning, 2, ) - else: - warnings.warn( - "Argument `rp_api_key` is `None` or empty string, " - "that is not supposed to happen because Report " - "Portal is usually requires an authorization key. " - "Please check your configuration.", - RuntimeWarning, - 2, - ) + + # OAuth 2.0 parameters + self.rp_oauth_uri = self.find_option(pytest_config, "rp_oauth_uri") + self.rp_oauth_username = self.find_option(pytest_config, "rp_oauth_username") + self.rp_oauth_password = self.find_option(pytest_config, "rp_oauth_password") + self.rp_oauth_client_id = self.find_option(pytest_config, "rp_oauth_client_id") + self.rp_oauth_client_secret = self.find_option(pytest_config, "rp_oauth_client_secret") + self.rp_oauth_scope = self.find_option(pytest_config, "rp_oauth_scope") rp_verify_ssl = self.find_option(pytest_config, "rp_verify_ssl", True) try: diff --git a/pytest_reportportal/plugin.py b/pytest_reportportal/plugin.py index 9b41560..44c7e46 100644 --- a/pytest_reportportal/plugin.py +++ b/pytest_reportportal/plugin.py @@ -24,7 +24,6 @@ import pytest # noinspection PyPackageRequirements -import requests from pytest import Item, Session from reportportal_client import RP, RPLogHandler from reportportal_client.errors import ResponseError @@ -48,13 +47,6 @@ LOGGER: Logger = logging.getLogger(__name__) -MANDATORY_PARAMETER_MISSED_PATTERN: str = ( - "One of the following mandatory parameters is unset: " - + "rp_project: {}, " - + "rp_endpoint: {}, " - + "rp_api_key: {}" -) - FAILED_LAUNCH_WAIT: str = ( "Failed to initialize reportportal-client service. " + "Waiting for Launch start timed out. " @@ -182,25 +174,6 @@ def register_markers(config) -> None: config.addinivalue_line("markers", "name(name): report the test case with a custom Name.") -def check_connection(agent_config: AgentConfig): - """Check connection to RP using provided options. - - If connection is successful returns True either False. - :param agent_config: Instance of the AgentConfig class - :return True on successful connection check, either False - """ - url = "{0}/api/v1/project/{1}".format(agent_config.rp_endpoint, agent_config.rp_project) - headers = {"Authorization": "bearer {0}".format(agent_config.rp_api_key)} - try: - resp = requests.get(url, headers=headers, verify=agent_config.rp_verify_ssl) - resp.raise_for_status() - return True - except requests.exceptions.RequestException as exc: - LOGGER.exception(exc) - LOGGER.error("Unable to connect to Report Portal, the launch won't be reported") - return False - - # no 'config' type for backward compatibility for older pytest versions # noinspection PyProtectedMember def pytest_configure(config) -> None: @@ -210,30 +183,17 @@ def pytest_configure(config) -> None: """ register_markers(config) + agent_config = AgentConfig(config) + config._rp_enabled = not ( config.getoption("--collect-only", default=False) or config.getoption("--setup-plan", default=False) - or not config.option.rp_enabled + or not agent_config.rp_enabled ) if not config._rp_enabled: - return - - agent_config = AgentConfig(config) - - cond = (agent_config.rp_project, agent_config.rp_endpoint, agent_config.rp_api_key) - config._rp_enabled = all(cond) - if not config._rp_enabled: - LOGGER.debug(MANDATORY_PARAMETER_MISSED_PATTERN.format(*cond)) LOGGER.debug("Disabling reporting to RP.") return - if not agent_config.rp_skip_connection_test: - config._rp_enabled = check_connection(agent_config) - - if not config._rp_enabled: - LOGGER.debug("Failed to establish connection with RP. " "Disabling reporting.") - return - config._reporter_config = agent_config if is_control(config): @@ -612,6 +572,19 @@ def add_shared_option(name, help_str, default=None, action="store"): name="rp_launch_uuid_print_output", help_str="Launch UUID print output. Default `stdout`. Possible values: [stderr, stdout]", ) + add_shared_option( + name="rp_enabled", + help_str="Enable reportportal plugin", + default=True, + ) + + # OAuth 2.0 parameters + parser.addini("rp_oauth_uri", type="args", help="OAuth 2.0 token endpoint URL for password grant authentication") + parser.addini("rp_oauth_username", type="args", help="OAuth 2.0 username for password grant authentication") + parser.addini("rp_oauth_password", type="args", help="OAuth 2.0 password for password grant authentication") + parser.addini("rp_oauth_client_id", type="args", help="OAuth 2.0 client identifier") + parser.addini("rp_oauth_client_secret", type="args", help="OAuth 2.0 client secret") + parser.addini("rp_oauth_scope", type="args", help="OAuth 2.0 access token scope") parser.addini("rp_launch_attributes", type="args", help="Launch attributes, i.e Performance Regression") parser.addini("rp_tests_attributes", type="args", help="Attributes for all tests items, e.g. Smoke") @@ -669,7 +642,6 @@ def add_shared_option(name, help_str, default=None, action="store"): parser.addini("rp_issue_id_marks", type="bool", default=True, help="Add tag with issue id to the test") parser.addini("retries", default="0", help="Deprecated: use `rp_api_retries` instead") parser.addini("rp_api_retries", default="0", help="Amount of retries for performing REST calls to RP server") - parser.addini("rp_skip_connection_test", default=False, type="bool", help="Skip Report Portal connection test") parser.addini( "rp_launch_timeout", default=86400, diff --git a/pytest_reportportal/service.py b/pytest_reportportal/service.py index c41f1c1..710c591 100644 --- a/pytest_reportportal/service.py +++ b/pytest_reportportal/service.py @@ -1398,6 +1398,13 @@ def start(self) -> None: print_output=self._config.rp_launch_uuid_print_output, http_timeout=self._config.rp_http_timeout, mode=self._config.rp_mode, + # OAuth 2.0 parameters + oauth_uri=self._config.rp_oauth_uri, + oauth_username=self._config.rp_oauth_username, + oauth_password=self._config.rp_oauth_password, + oauth_client_id=self._config.rp_oauth_client_id, + oauth_client_secret=self._config.rp_oauth_client_secret, + oauth_scope=self._config.rp_oauth_scope, ) if hasattr(self.rp, "get_project_settings"): self.project_settings = self.rp.get_project_settings() From e7edd8d6eb2fef1ad3b2a5188e523c3bedce1715 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Tue, 11 Nov 2025 13:15:01 +0300 Subject: [PATCH 11/20] Add black and isort dev requirements --- requirements-dev.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/requirements-dev.txt b/requirements-dev.txt index 8cddeb9..1ce1013 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,3 +1,5 @@ delayed-assert pytest-cov pytest-parallel +black +isort From fde5708b55dd7f86a999c8ebc6f4abef2aec2b20 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Tue, 11 Nov 2025 13:19:48 +0300 Subject: [PATCH 12/20] Remove `rp_uuid` param support --- CHANGELOG.md | 2 ++ pytest_reportportal/config.py | 11 ----------- pytest_reportportal/plugin.py | 6 ------ 3 files changed, 2 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f67423f..3dfa957 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ - Client version updated to [5.6.6](https://github.com/reportportal/client-Python/releases/tag/5.6.6), by @HardNorth ### Fixed - Some configuration parameter names, which are different in the client, by @HardNorth +### Removed +- `rp_uuid` param support, as it was deprecated pretty while ago, by @HardNorth ## [5.5.2] ### Fixed diff --git a/pytest_reportportal/config.py b/pytest_reportportal/config.py index d64eef4..28ed379 100644 --- a/pytest_reportportal/config.py +++ b/pytest_reportportal/config.py @@ -147,17 +147,6 @@ def __init__(self, pytest_config: Config) -> None: # API key auth parameter self.rp_api_key = getenv("RP_API_KEY") or self.find_option(pytest_config, "rp_api_key") - if not self.rp_api_key: - self.rp_api_key = getenv("RP_UUID") or self.find_option(pytest_config, "rp_uuid") - if self.rp_api_key: - warnings.warn( - "Parameter `rp_uuid` is deprecated since 5.1.9 " - "and will be subject for removing in the next " - "major version. Use `rp_api_key` argument " - "instead.", - DeprecationWarning, - 2, - ) # OAuth 2.0 parameters self.rp_oauth_uri = self.find_option(pytest_config, "rp_oauth_uri") diff --git a/pytest_reportportal/plugin.py b/pytest_reportportal/plugin.py index 44c7e46..1f3309c 100644 --- a/pytest_reportportal/plugin.py +++ b/pytest_reportportal/plugin.py @@ -552,7 +552,6 @@ def add_shared_option(name, help_str, default=None, action="store"): name="rp_parent_item_id", help_str="Create all test item as child items of the given (already " "existing) item.", ) - add_shared_option(name="rp_uuid", help_str="Deprecated: use `rp_api_key` " "instead.") add_shared_option(name="rp_api_key", help_str="API key of Report Portal. Usually located on UI profile " "page.") add_shared_option(name="rp_endpoint", help_str="Server endpoint") add_shared_option(name="rp_mode", help_str="Visibility of current launch [DEFAULT, DEBUG]", default="DEFAULT") @@ -572,11 +571,6 @@ def add_shared_option(name, help_str, default=None, action="store"): name="rp_launch_uuid_print_output", help_str="Launch UUID print output. Default `stdout`. Possible values: [stderr, stdout]", ) - add_shared_option( - name="rp_enabled", - help_str="Enable reportportal plugin", - default=True, - ) # OAuth 2.0 parameters parser.addini("rp_oauth_uri", type="args", help="OAuth 2.0 token endpoint URL for password grant authentication") From 63ed3e602e4d3dad92eb58a2fc9f2b88ac871e3a Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Tue, 11 Nov 2025 13:51:21 +0300 Subject: [PATCH 13/20] Fix tests --- pytest_reportportal/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytest_reportportal/config.py b/pytest_reportportal/config.py index 28ed379..a16413d 100644 --- a/pytest_reportportal/config.py +++ b/pytest_reportportal/config.py @@ -83,7 +83,7 @@ class AgentConfig: def __init__(self, pytest_config: Config) -> None: """Initialize required attributes.""" - self.rp_enabled = to_bool(self.find_option(pytest_config, "rp_enabled", True)) + self.rp_enabled = to_bool(getattr(pytest_config.option, "rp_enabled", True)) self.rp_rerun = pytest_config.option.rp_rerun or pytest_config.getini("rp_rerun") self.rp_endpoint = getenv("RP_ENDPOINT") or self.find_option(pytest_config, "rp_endpoint") self.rp_hierarchy_code = to_bool(self.find_option(pytest_config, "rp_hierarchy_code")) From 34edbba1520e9dc00f35e9cfe7ad57ee7bfa1c56 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Tue, 11 Nov 2025 13:58:46 +0300 Subject: [PATCH 14/20] Fix tests --- pytest_reportportal/plugin.py | 11 +++++++++++ tests/unit/test_plugin.py | 20 +++----------------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/pytest_reportportal/plugin.py b/pytest_reportportal/plugin.py index 1f3309c..b43bf89 100644 --- a/pytest_reportportal/plugin.py +++ b/pytest_reportportal/plugin.py @@ -47,6 +47,10 @@ LOGGER: Logger = logging.getLogger(__name__) +MANDATORY_PARAMETER_MISSED_PATTERN: str = ( + "One of the following mandatory parameters is unset: " + "rp_project: {}, rp_endpoint: {}" +) + FAILED_LAUNCH_WAIT: str = ( "Failed to initialize reportportal-client service. " + "Waiting for Launch start timed out. " @@ -194,6 +198,13 @@ def pytest_configure(config) -> None: LOGGER.debug("Disabling reporting to RP.") return + cond = (agent_config.rp_project, agent_config.rp_endpoint) + config._rp_enabled = all(cond) + if not config._rp_enabled: + LOGGER.debug(MANDATORY_PARAMETER_MISSED_PATTERN.format(*cond)) + LOGGER.debug("Disabling reporting to RP.") + return + config._reporter_config = agent_config if is_control(config): diff --git a/tests/unit/test_plugin.py b/tests/unit/test_plugin.py index 9fc77a6..a7241eb 100644 --- a/tests/unit/test_plugin.py +++ b/tests/unit/test_plugin.py @@ -134,13 +134,7 @@ def test_pytest_configure_misssing_rp_endpoint(mocked_log, mocked_config): assert mocked_config._rp_enabled is False mocked_log.debug.assert_has_calls( [ - mock.call( - MANDATORY_PARAMETER_MISSED_PATTERN.format( - mocked_config.option.rp_project, - None, - mocked_config.option.rp_api_key, - ) - ), + mock.call(MANDATORY_PARAMETER_MISSED_PATTERN.format(mocked_config.option.rp_project, None)), mock.call("Disabling reporting to RP."), ] ) @@ -164,13 +158,7 @@ def test_pytest_configure_misssing_rp_project(mocked_log, mocked_config): assert mocked_config._rp_enabled is False mocked_log.debug.assert_has_calls( [ - mock.call( - MANDATORY_PARAMETER_MISSED_PATTERN.format( - None, - mocked_config.option.rp_endpoint, - mocked_config.option.rp_api_key, - ) - ), + mock.call(MANDATORY_PARAMETER_MISSED_PATTERN.format(None, mocked_config.option.rp_endpoint)), mock.call("Disabling reporting to RP."), ] ) @@ -196,9 +184,7 @@ def test_pytest_configure_misssing_rp_uuid(mocked_log, mocked_config): [ mock.call( MANDATORY_PARAMETER_MISSED_PATTERN.format( - mocked_config.option.rp_project, - mocked_config.option.rp_endpoint, - None, + mocked_config.option.rp_project, mocked_config.option.rp_endpoint ) ), mock.call("Disabling reporting to RP."), From 2402d1db87532433ad9ee9263e6d10496c6f6cf0 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Tue, 11 Nov 2025 14:03:43 +0300 Subject: [PATCH 15/20] Fix tests --- tests/unit/test_plugin.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tests/unit/test_plugin.py b/tests/unit/test_plugin.py index a7241eb..d6de4f8 100644 --- a/tests/unit/test_plugin.py +++ b/tests/unit/test_plugin.py @@ -313,17 +313,22 @@ def test_pytest_addoption_adds_correct_ini_file_arguments(): "rp_rerun", "rp_rerun_of", "rp_parent_item_id", - "rp_uuid", "rp_api_key", "rp_endpoint", "rp_mode", "rp_thread_logging", "rp_launch_uuid_print", "rp_launch_uuid_print_output", + "rp_oauth_uri", + "rp_oauth_username", + "rp_oauth_password", + "rp_oauth_client_id", + "rp_oauth_client_secret", + "rp_oauth_scope", "rp_launch_attributes", "rp_tests_attributes", "rp_log_batch_size", - "rp_log_batch_payload_size", + "rp_log_batch_payload_limit", "rp_ignore_attributes", "rp_is_skipped_an_issue", "rp_hierarchy_code", @@ -339,7 +344,6 @@ def test_pytest_addoption_adds_correct_ini_file_arguments(): "rp_issue_id_marks", "retries", "rp_api_retries", - "rp_skip_connection_test", "rp_launch_timeout", "rp_client_type", "rp_connect_timeout", @@ -368,7 +372,6 @@ def test_pytest_addoption_adds_correct_command_line_arguments(): "--rp-rerun", "--rp-rerun-of", "--rp-parent-item-id", - "--rp-uuid", "--rp-api-key", "--rp-endpoint", "--rp-mode", From 2e591df1e931f3981a1d122a271eedc53b2cca39 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Tue, 11 Nov 2025 14:26:23 +0300 Subject: [PATCH 16/20] Fix tests --- pytest_reportportal/plugin.py | 5 ++--- tests/unit/test_plugin.py | 25 +------------------------ 2 files changed, 3 insertions(+), 27 deletions(-) diff --git a/pytest_reportportal/plugin.py b/pytest_reportportal/plugin.py index b43bf89..f400d33 100644 --- a/pytest_reportportal/plugin.py +++ b/pytest_reportportal/plugin.py @@ -187,17 +187,16 @@ def pytest_configure(config) -> None: """ register_markers(config) - agent_config = AgentConfig(config) - config._rp_enabled = not ( config.getoption("--collect-only", default=False) or config.getoption("--setup-plan", default=False) - or not agent_config.rp_enabled + or not config.option.rp_enabled ) if not config._rp_enabled: LOGGER.debug("Disabling reporting to RP.") return + agent_config = AgentConfig(config) cond = (agent_config.rp_project, agent_config.rp_endpoint) config._rp_enabled = all(cond) if not config._rp_enabled: diff --git a/tests/unit/test_plugin.py b/tests/unit/test_plugin.py index d6de4f8..ea598cb 100644 --- a/tests/unit/test_plugin.py +++ b/tests/unit/test_plugin.py @@ -19,7 +19,6 @@ import pytest from _pytest.config.argparsing import Parser from delayed_assert import assert_expectations, expect -from reportportal_client.errors import ResponseError from requests.exceptions import RequestException from pytest_reportportal.config import AgentConfig @@ -73,25 +72,6 @@ def test_logger_handle_no_attachment(mock_handler, logger, log_level): assert_expectations() -@mock.patch("pytest_reportportal.plugin.requests.get", mock.Mock()) -@mock.patch("pytest_reportportal.plugin.PyTestService") -def test_portal_on_maintenance(mocked_service_class, mocked_config, mocked_session): - """Test session configuration if RP is in maintenance mode. - - :param mocked_session: pytest fixture - """ - mocked_config.option.rp_enabled = True - mocked_config.option.rp_project = None - - mocked_service = mocked_service_class.return_value - mocked_config.py_test_service = mocked_service - mocked_service.start.side_effect = ResponseError("Report Portal - Maintenance") - pytest_sessionstart(mocked_session) - assert mocked_config.py_test_service.rp is None - - -@mock.patch("pytest_reportportal.plugin.requests.Session.get", mock.Mock()) -@mock.patch("pytest_reportportal.plugin.requests.get", mock.Mock()) def test_pytest_configure(mocked_config): """Test plugin successful configuration. @@ -108,16 +88,13 @@ def test_pytest_configure(mocked_config): ) -@mock.patch("pytest_reportportal.plugin.requests.get") def test_pytest_configure_dry_run(mocked_config): """Test plugin configuration in case of dry-run execution.""" - mocked_config.getoption.return_value = True + mocked_config.getoption.side_effect = lambda opt, default: True pytest_configure(mocked_config) assert mocked_config._rp_enabled is False -@mock.patch("pytest_reportportal.plugin.requests.get", mock.Mock()) -@mock.patch("pytest_reportportal.plugin.LOGGER", wraps=LOGGER) def test_pytest_configure_misssing_rp_endpoint(mocked_log, mocked_config): """Test plugin configuration in case of missing rp_endpoint. From 82b118512772d8cf6a7aa5f5ee8625a071f7c337 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Tue, 11 Nov 2025 14:32:14 +0300 Subject: [PATCH 17/20] Fix tests --- tests/unit/test_plugin.py | 50 --------------------------------------- 1 file changed, 50 deletions(-) diff --git a/tests/unit/test_plugin.py b/tests/unit/test_plugin.py index ea598cb..124f3a4 100644 --- a/tests/unit/test_plugin.py +++ b/tests/unit/test_plugin.py @@ -19,7 +19,6 @@ import pytest from _pytest.config.argparsing import Parser from delayed_assert import assert_expectations, expect -from requests.exceptions import RequestException from pytest_reportportal.config import AgentConfig from pytest_reportportal.plugin import ( @@ -117,8 +116,6 @@ def test_pytest_configure_misssing_rp_endpoint(mocked_log, mocked_config): ) -@mock.patch("pytest_reportportal.plugin.requests.get", mock.Mock()) -@mock.patch("pytest_reportportal.plugin.LOGGER", wraps=LOGGER) def test_pytest_configure_misssing_rp_project(mocked_log, mocked_config): """Test plugin configuration in case of missing rp_project. @@ -141,53 +138,6 @@ def test_pytest_configure_misssing_rp_project(mocked_log, mocked_config): ) -@mock.patch("pytest_reportportal.plugin.requests.get", mock.Mock()) -@mock.patch("pytest_reportportal.plugin.LOGGER", wraps=LOGGER) -def test_pytest_configure_misssing_rp_uuid(mocked_log, mocked_config): - """Test plugin configuration in case of missing rp_uuid. - - The value of the _reportportal_configured attribute of the pytest Config - object should be changed to False, stopping plugin configuration, if - rp_uuid is not set. - - :param mocked_config: Pytest fixture - """ - mocked_config.option.rp_enabled = True - mocked_config.option.rp_api_key = None - mocked_config.getini.return_value = 0 - pytest_configure(mocked_config) - assert mocked_config._rp_enabled is False - mocked_log.debug.assert_has_calls( - [ - mock.call( - MANDATORY_PARAMETER_MISSED_PATTERN.format( - mocked_config.option.rp_project, mocked_config.option.rp_endpoint - ) - ), - mock.call("Disabling reporting to RP."), - ] - ) - - -@mock.patch("pytest_reportportal.plugin.requests.get") -def test_pytest_configure_on_conn_error(mocked_get, mocked_config): - """Test plugin configuration in case of HTTP error. - - The value of the _reportportal_configured attribute of the pytest Config - object should be changed to False, stopping plugin configuration, if HTTP - error occurs getting HTTP response from the ReportPortal. - :param mocked_get: Instance of the MagicMock - :param mocked_config: Pytest fixture - """ - mock_response = mock.Mock() - mock_response.raise_for_status.side_effect = RequestException() - mocked_get.return_value = mock_response - mocked_config.option.rp_enabled = True - mocked_config.option.rp_skip_connection_test = "False" - pytest_configure(mocked_config) - assert mocked_config._rp_enabled is False - - @mock.patch("pytest_reportportal.plugin.LAUNCH_WAIT_TIMEOUT", 1) @mock.patch("pytest_reportportal.plugin.time") def test_wait_launch(time_mock): From 5916d7fa7fab2041d7a1b25bc2c69a9dd4cb6be9 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Tue, 11 Nov 2025 16:11:41 +0300 Subject: [PATCH 18/20] Fix tests --- tests/unit/test_plugin.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/unit/test_plugin.py b/tests/unit/test_plugin.py index 124f3a4..5d3f745 100644 --- a/tests/unit/test_plugin.py +++ b/tests/unit/test_plugin.py @@ -94,6 +94,7 @@ def test_pytest_configure_dry_run(mocked_config): assert mocked_config._rp_enabled is False +@mock.patch("pytest_reportportal.plugin.LOGGER", wraps=LOGGER) def test_pytest_configure_misssing_rp_endpoint(mocked_log, mocked_config): """Test plugin configuration in case of missing rp_endpoint. @@ -116,6 +117,7 @@ def test_pytest_configure_misssing_rp_endpoint(mocked_log, mocked_config): ) +@mock.patch("pytest_reportportal.plugin.LOGGER", wraps=LOGGER) def test_pytest_configure_misssing_rp_project(mocked_log, mocked_config): """Test plugin configuration in case of missing rp_project. From bf3bb49a597077970179516a4ac8a4ff38cd971e Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Tue, 11 Nov 2025 17:43:41 +0300 Subject: [PATCH 19/20] Client version update --- CHANGELOG.md | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3dfa957..1187c3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ### Added - OAuth 2.0 Password Grant authentication, by @HardNorth ### Changed -- Client version updated to [5.6.6](https://github.com/reportportal/client-Python/releases/tag/5.6.6), by @HardNorth +- Client version updated to [5.6.7](https://github.com/reportportal/client-Python/releases/tag/5.6.7), by @HardNorth ### Fixed - Some configuration parameter names, which are different in the client, by @HardNorth ### Removed diff --git a/requirements.txt b/requirements.txt index 49b7ebc..cf5fbf4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ dill>=0.3.6 pytest>=4.6.10 -reportportal-client~=5.6.6 +reportportal-client~=5.6.7 aenum>=3.1.0 From edffd1e3c0982a469eea6f31604935b7803622c0 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Tue, 11 Nov 2025 17:43:47 +0300 Subject: [PATCH 20/20] Fix tests --- tests/__init__.py | 1 - tests/integration/test_config_handling.py | 95 +++++++---------------- 2 files changed, 29 insertions(+), 67 deletions(-) diff --git a/tests/__init__.py b/tests/__init__.py index 5ada03e..cc2c4a7 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -14,4 +14,3 @@ """This package contains tests for the project.""" REPORT_PORTAL_SERVICE = "reportportal_client.RPClient" -REQUESTS_SERVICE = "reportportal_client.client.requests.Session" diff --git a/tests/integration/test_config_handling.py b/tests/integration/test_config_handling.py index fe27aa8..a5bfaf1 100644 --- a/tests/integration/test_config_handling.py +++ b/tests/integration/test_config_handling.py @@ -21,17 +21,17 @@ from reportportal_client import OutputType from examples.test_rp_logging import LOG_MESSAGE -from tests import REPORT_PORTAL_SERVICE, REQUESTS_SERVICE +from tests import REPORT_PORTAL_SERVICE from tests.helpers import utils TEST_LAUNCH_ID = "test_launch_id" -@mock.patch(REQUESTS_SERVICE) -def test_rp_launch_id(mock_requests_init): +@mock.patch(REPORT_PORTAL_SERVICE) +def test_rp_launch_uuid(mock_client_init): """Verify that RP plugin does not start/stop launch if 'rp_launch_id' set. - :param mock_requests_init: mocked requests lib + :param mock_client_init: mocked client class """ variables = dict() variables["rp_launch_id"] = TEST_LAUNCH_ID @@ -39,11 +39,10 @@ def test_rp_launch_id(mock_requests_init): result = utils.run_pytest_tests(tests=["examples/test_simple.py"], variables=variables) assert int(result) == 0, "Exit code should be 0 (no errors)" - mock_requests = mock_requests_init.return_value - assert mock_requests.post.call_count == 1 - item_start = mock_requests.post.call_args_list[0] - assert item_start[0][0].endswith("/item") - assert item_start[1]["json"]["launchUuid"] == TEST_LAUNCH_ID + assert mock_client_init.call_count == 1 + constructor_call = mock_client_init.call_args_list[0] + assert "launch_uuid" in constructor_call[1] + assert constructor_call[1]["launch_uuid"] == TEST_LAUNCH_ID @mock.patch(REPORT_PORTAL_SERVICE) @@ -72,11 +71,11 @@ def test_rp_parent_item_id(mock_client_init): assert_expectations() -@mock.patch(REQUESTS_SERVICE) -def test_rp_parent_item_id_and_rp_launch_id(mock_requests_init): +@mock.patch(REPORT_PORTAL_SERVICE) +def test_rp_parent_item_id_and_rp_launch_id(mock_client_init): """Verify RP handles both conf props 'rp_parent_item_id' & 'rp_launch_id'. - :param mock_requests_init: mocked requests lib + :param mock_client_init: mocked client class """ parent_id = "parent_id" variables = dict() @@ -86,11 +85,18 @@ def test_rp_parent_item_id_and_rp_launch_id(mock_requests_init): result = utils.run_pytest_tests(tests=["examples/test_simple.py"], variables=variables) assert int(result) == 0, "Exit code should be 0 (no errors)" - mock_requests = mock_requests_init.return_value - assert mock_requests.post.call_count == 1 - item_start = mock_requests.post.call_args_list[0] - assert item_start[0][0].endswith(f"/item/{parent_id}") - assert item_start[1]["json"]["launchUuid"] == TEST_LAUNCH_ID + assert mock_client_init.call_count == 1 + constructor_call = mock_client_init.call_args_list[0] + assert "launch_uuid" in constructor_call[1] + assert constructor_call[1]["launch_uuid"] == TEST_LAUNCH_ID + + mock_client = mock_client_init.return_value + + assert mock_client.start_test_item.call_count > 0 + item_create = mock_client.start_test_item.call_args_list[0] + call_kwargs = item_create[1] + assert "parent_item_id" in call_kwargs + assert call_kwargs["parent_item_id"] == parent_id @mock.patch(REPORT_PORTAL_SERVICE) @@ -112,9 +118,9 @@ def test_rp_log_format(mock_client_init): @mock.patch(REPORT_PORTAL_SERVICE) -def test_rp_log_batch_payload_size(mock_client_init): +def test_rp_log_batch_payload_limit(mock_client_init): log_size = 123456 - variables = {"rp_log_batch_payload_size": log_size} + variables = {"rp_log_batch_payload_limit": log_size} variables.update(utils.DEFAULT_VARIABLES.items()) result = utils.run_pytest_tests(["examples/test_rp_logging.py"], variables=variables) @@ -123,7 +129,7 @@ def test_rp_log_batch_payload_size(mock_client_init): expect(mock_client_init.call_count == 1) constructor_args = mock_client_init.call_args_list[0][1] - expect(constructor_args["log_batch_payload_size"] == log_size) + expect(constructor_args["log_batch_payload_limit"] == log_size) assert_expectations() @@ -156,56 +162,13 @@ def test_rp_api_key(mock_client_init): assert_expectations() -@mock.patch(REPORT_PORTAL_SERVICE) -def test_rp_uuid(mock_client_init): - api_key = "rp_api_key" - variables = dict(utils.DEFAULT_VARIABLES) - del variables["rp_api_key"] - variables.update({"rp_uuid": api_key}.items()) - - with warnings.catch_warnings(record=True) as w: - result = utils.run_pytest_tests(["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) - - constructor_args = mock_client_init.call_args_list[0][1] - expect(constructor_args["api_key"] == api_key) - expect(len(filter_agent_calls(w)) == 1) - assert_expectations() - - -@mock.patch(REPORT_PORTAL_SERVICE) -def test_rp_api_key_priority(mock_client_init): - api_key = "rp_api_key" - variables = dict(utils.DEFAULT_VARIABLES) - variables.update({"rp_api_key": api_key, "rp_uuid": "rp_uuid"}.items()) - - with warnings.catch_warnings(record=True) as w: - result = utils.run_pytest_tests(["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) - - constructor_args = mock_client_init.call_args_list[0][1] - expect(constructor_args["api_key"] == api_key) - expect(len(filter_agent_calls(w)) == 0) - assert_expectations() - - -@mock.patch(REPORT_PORTAL_SERVICE) -def test_rp_api_key_empty(mock_client_init): +def test_rp_api_key_empty(): api_key = "" variables = dict(utils.DEFAULT_VARIABLES) variables.update({"rp_api_key": api_key}.items()) - with warnings.catch_warnings(record=True) as w: - result = utils.run_pytest_tests(["examples/test_rp_logging.py"], variables=variables) - assert int(result) == 0, "Exit code should be 0 (no errors)" - - expect(mock_client_init.call_count == 0) - expect(len(filter_agent_calls(w)) == 1) - assert_expectations() + result = utils.run_pytest_tests(["examples/test_rp_logging.py"], variables=variables) + assert int(result) == 3, "Exit code should be 3 (exited with internal error)" @mock.patch(REPORT_PORTAL_SERVICE)