From 3fd3ce7a48b9fe549020036ef04568b65a17cfa8 Mon Sep 17 00:00:00 2001 From: "reportportal.io" Date: Thu, 11 Sep 2025 14:32:28 +0000 Subject: [PATCH 1/7] Changelog update --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fff4c6..6be4414 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Changelog ## [Unreleased] + +## [5.0.0] ### Added - Support for `Python 3.13`, by @HardNorth ### Changed From ad5d34a8951baaf6f09c564c7c390bf70a48a4fc Mon Sep 17 00:00:00 2001 From: "reportportal.io" Date: Thu, 11 Sep 2025 14:32:30 +0000 Subject: [PATCH 2/7] Version update --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 9371ab4..29e0648 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ from setuptools import setup -__version__ = "5.0.0" +__version__ = "5.0.1" def read_file(fname): From 6cf0966dd197c5454b270ec83fc6ac09186f69bf Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Fri, 12 Sep 2025 12:56:31 +0300 Subject: [PATCH 3/7] Remove unused sections from README.md --- README.md | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/README.md b/README.md index 12d5976..2631379 100644 --- a/README.md +++ b/README.md @@ -9,16 +9,6 @@ Behave extension for reporting test results of Behave to the ReportPortal. -- **Usage** -- **Installation** -- **Configuration** -- **Launching** -- **Test item attributes** -- **Logging** -- **Test case ID** -- **Integration with GA** -- **Copyright Notice** - ## Usage ### Installation From cdb5bfc4735d1c58af609b5f0a0799c2359d4fb5 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Mon, 10 Nov 2025 17:33:51 +0300 Subject: [PATCH 4/7] OAuth 2.0 Password Grant authentication --- CHANGELOG.md | 6 +++ README.md | 19 +++++--- behave_reportportal/behave_agent.py | 13 ++++-- behave_reportportal/config.py | 69 ++++++++++++++++++----------- requirements.txt | 2 +- tests/features/environment.py | 7 +-- tests/units/test_rp_agent.py | 4 +- 7 files changed, 78 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6be4414..a2d97ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # 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 +- Some configuration parameter names, which are different in the client, by @HardNorth ## [5.0.0] ### Added diff --git a/README.md b/README.md index 2631379..801ee5b 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,6 @@ behave -D config_file= The `behave.ini` file should have the following mandatory fields under `[report_portal]` section: -- `api_key` - value can be found in the User Profile section - `project` - name of project in ReportPortal - `endpoint` - address of ReportPortal Server @@ -52,10 +51,20 @@ launch_description = Smoke test The following parameters are optional: -- `client_type = SYNC` - Type of the under-the-hood ReportPortal client implementation. Possible - values: [SYNC, ASYNC_THREAD, ASYNC_BATCHED]. +- `api_key` - value can be found in the User Profile section. **Required** if OAuth 2.0 is not configured. +- `oauth_uri = https://reportportal.example.com/uat/sso/oauth/token` - OAuth 2.0 token endpoint URL for password grant + authentication. **Required** if API key is not used. +- `oauth_username = my-username` - OAuth 2.0 username for password grant authentication. **Required** if OAuth 2.0 is + used. +- `oauth_password = my-password` - OAuth 2.0 password for password grant authentication. **Required** if OAuth 2.0 is + used. +- `oauth_client_id = client-id` - OAuth 2.0 client identifier. **Required** if OAuth 2.0 is used. +- `oauth_client_secret = client-id-secret` - OAuth 2.0 client secret. +- `oauth_scope = offline_access` - OAuth 2.0 access token scope. +- `client_type = SYNC` - Type of the under-the-hood ReportPortal client implementation. Possible values: + \[SYNC, ASYNC_THREAD, ASYNC_BATCHED]. - `launch_name = AnyLaunchName` - launch name (default value is 'Python Behave Launch') -- `launch_id = xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` - id of the existing launch (the session will not handle the +- `launch_uuid = xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` - UUID of the existing launch (the session will not handle the lifecycle of the given launch) - `launch_attributes = Smoke Env:Python3` - list of attributes for launch - `launch_description = Smoke test` - launch description @@ -71,7 +80,7 @@ The following parameters are optional: - `connect_timeout = 15` - Connection timeout to ReportPortal server. Default value is "10.0". - `read_timeout = 15` - Response read timeout for ReportPortal connection. Default value is "10.0". - `log_batch_size = 20` - maximum number of log entries which will be sent by the agent at once -- `log_batch_payload_size = 65000000` - maximum payload size of a log batch which will be sent by the agent at once +- `log_batch_payload_limit = 65000000` - maximum payload size of a log batch which will be sent by the agent at once If you would like to override the above parameters from command line, or from CI environment based on your build, then pass: diff --git a/behave_reportportal/behave_agent.py b/behave_reportportal/behave_agent.py index 02b68fc..bdffd71 100644 --- a/behave_reportportal/behave_agent.py +++ b/behave_reportportal/behave_agent.py @@ -61,14 +61,21 @@ def create_rp_service(cfg: Config) -> Optional[RP]: project=cfg.project, api_key=cfg.api_key, is_skipped_an_issue=cfg.is_skipped_an_issue, - launch_id=cfg.launch_id, + launch_uuid=cfg.launch_uuid, retries=cfg.retries, mode="DEBUG" if cfg.debug_mode else "DEFAULT", log_batch_size=cfg.log_batch_size, - log_batch_payload_size=cfg.log_batch_payload_size, + log_batch_payload_limit=cfg.log_batch_payload_limit, launch_uuid_print=cfg.launch_uuid_print, print_output=cfg.launch_uuid_print_output, http_timeout=cfg.http_timeout, + # OAuth 2.0 parameters + oauth_uri=cfg.oauth_uri, + oauth_username=cfg.oauth_username, + oauth_password=cfg.oauth_password, + oauth_client_id=cfg.oauth_client_id, + oauth_client_secret=cfg.oauth_client_secret, + oauth_scope=cfg.oauth_scope, ) return None @@ -90,7 +97,7 @@ class BehaveAgent(metaclass=Singleton): def __init__(self, cfg: Config, rp_service: Optional[RP] = None) -> None: """Initialize instance attributes.""" - self._rp = rp_service + self._rp = rp_service or create_rp_service(cfg) self._cfg = cfg self._handle_lifecycle = True self._launch_id = None diff --git a/behave_reportportal/config.py b/behave_reportportal/config.py index 8bc1a7e..6e83502 100644 --- a/behave_reportportal/config.py +++ b/behave_reportportal/config.py @@ -52,7 +52,7 @@ class Config(object): project: Optional[str] api_key: Optional[str] enabled: bool - launch_id: Optional[str] + launch_uuid: Optional[str] launch_name: str launch_description: Optional[str] launch_attributes: Optional[List[str]] @@ -62,19 +62,27 @@ class Config(object): rerun: bool rerun_of: Optional[str] log_batch_size: int - log_batch_payload_size: int + log_batch_payload_limit: int log_layout: LogLayout launch_uuid_print: bool launch_uuid_print_output: Optional[OutputType] client_type: ClientType http_timeout: Optional[Union[Tuple[float, float], float]] + # OAuth 2.0 parameters + oauth_uri: Optional[str] + oauth_username: Optional[str] + oauth_password: Optional[str] + oauth_client_id: Optional[str] + oauth_client_secret: Optional[str] + oauth_scope: Optional[str] + def __init__( self, endpoint: Optional[str] = None, project: Optional[str] = None, api_key: Optional[str] = None, - launch_id: Optional[str] = None, + launch_uuid: Optional[str] = None, launch_name: Optional[str] = None, launch_description: Optional[str] = None, launch_attributes: Optional[str] = None, @@ -86,18 +94,38 @@ def __init__( rerun: Optional[Union[str, bool]] = None, rerun_of: Optional[str] = None, log_batch_size: Optional[str] = None, - log_batch_payload_size: Optional[str] = None, + log_batch_payload_limit: Optional[str] = None, launch_uuid_print: Optional[str] = None, launch_uuid_print_output: Optional[str] = None, client_type: Optional[str] = None, connect_timeout: Optional[Union[str, float]] = None, read_timeout: Optional[Union[str, float]] = None, + # OAuth 2.0 parameters + oauth_uri: Optional[str] = None, + oauth_username: Optional[str] = None, + oauth_password: Optional[str] = None, + oauth_client_id: Optional[str] = None, + oauth_client_secret: Optional[str] = None, + oauth_scope: Optional[str] = None, + enabled: bool = True, **kwargs, ): """Initialize instance attributes.""" + self.enabled = enabled self.endpoint = endpoint self.project = project - self.launch_id = launch_id + self.launch_uuid = launch_uuid + if not self.launch_uuid: + if "launch_id" in kwargs: + warn( + message="Argument `launch_id` is deprecated since 5.0.1 and " + "will be subject for removing in the next major " + "version. Use `api_key` argument instead.", + category=DeprecationWarning, + stacklevel=2, + ) + self.launch_uuid = kwargs["launch_id"] + self.launch_name = launch_name or DEFAULT_LAUNCH_NAME self.launch_description = launch_description self.launch_attributes = launch_attributes and launch_attributes.split() @@ -107,8 +135,8 @@ def __init__( self.rerun = to_bool(rerun or "False") self.rerun_of = rerun_of self.log_batch_size = (log_batch_size and int(log_batch_size)) or 20 - self.log_batch_payload_size = ( - log_batch_payload_size and int(log_batch_payload_size) + self.log_batch_payload_limit = ( + log_batch_payload_limit and int(log_batch_payload_limit) ) or MAX_LOG_BATCH_PAYLOAD_SIZE if step_based and not log_layout: @@ -122,26 +150,15 @@ def __init__( self.log_layout = LogLayout(log_layout) self.api_key = api_key - if not self.api_key: - if "token" in kwargs: - warn( - message="Argument `token` is deprecated since 2.0.4 and " - "will be subject for removing in the next major " - "version. Use `api_key` argument instead.", - category=DeprecationWarning, - stacklevel=2, - ) - self.api_key = kwargs["token"] - if not self.api_key: - warn( - message="Argument `api_key` is `None` or empty string, " - "this is unexpected because ReportPortal usually requires an authorization key. " - "Please check your configuration.", - category=RuntimeWarning, - stacklevel=2, - ) - self.enabled = all([self.endpoint, self.project, self.api_key]) + # OAuth 2.0 parameters + self.oauth_uri = oauth_uri + self.oauth_username = oauth_username + self.oauth_password = oauth_password + self.oauth_client_id = oauth_client_id + self.oauth_client_secret = oauth_client_secret + self.oauth_scope = oauth_scope + self.launch_uuid_print = to_bool(launch_uuid_print or "False") launch_uuid_print_output_strip = launch_uuid_print_output.strip() if launch_uuid_print_output else "" self.launch_uuid_print_output = ( diff --git a/requirements.txt b/requirements.txt index 0c54688..642056a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ behave>=1.3.3,<2.0 prettytable -reportportal-client~=5.6.5 +reportportal-client~=5.6.6 diff --git a/tests/features/environment.py b/tests/features/environment.py index cf6d703..26f1a9a 100644 --- a/tests/features/environment.py +++ b/tests/features/environment.py @@ -11,20 +11,17 @@ # See the License for the specific language governing permissions and # limitations under the License -from behave_reportportal.behave_agent import BehaveAgent, create_rp_service +from behave_reportportal.behave_agent import BehaveAgent from behave_reportportal.config import read_config def before_all(context): - cfg = read_config(context) - context.rp_client = create_rp_service(cfg) - context.rp_agent = BehaveAgent(cfg, context.rp_client) + context.rp_agent = BehaveAgent(read_config(context)) context.rp_agent.start_launch(context) def after_all(context): context.rp_agent.finish_launch(context) - context.rp_client.close() def before_feature(context, feature): diff --git a/tests/units/test_rp_agent.py b/tests/units/test_rp_agent.py index 8082b37..961108c 100644 --- a/tests/units/test_rp_agent.py +++ b/tests/units/test_rp_agent.py @@ -181,7 +181,7 @@ def test_create_rp_service_init(mock_rps): retries=None, mode="DEFAULT", log_batch_size=20, - log_batch_payload_size=MAX_LOG_BATCH_PAYLOAD_SIZE, + log_batch_payload_limit=MAX_LOG_BATCH_PAYLOAD_SIZE, launch_uuid_print=False, print_output=None, http_timeout=None, @@ -338,7 +338,7 @@ def test_finish_launch(mock_timestamp, config): def test_skip_finish_launch(mock_timestamp, config): mock_timestamp.return_value = 123 mock_rps = mock.create_autospec(RPClient) - mock_rps.launch_id = "abc" + mock_rps.launch_uuid = "abc" mock_context = mock.Mock() ba = BehaveAgent(config, mock_rps) ba.start_launch(mock_context, some_key="some_value") From 30e224b739c1b5c09abc59a697670160356c12eb Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Mon, 10 Nov 2025 18:02:36 +0300 Subject: [PATCH 5/7] Fix tests --- CHANGELOG.md | 2 ++ README.md | 1 + behave_reportportal/behave_agent.py | 10 +++++-- tests/units/test_config.py | 45 +++-------------------------- tests/units/test_rp_agent.py | 17 ++++++++--- 5 files changed, 28 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a2d97ff..1b7f8e5 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 +- `token` param support, as it was deprecated pretty while ago, by @HardNorth ## [5.0.0] ### Added diff --git a/README.md b/README.md index 801ee5b..d439f56 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ launch_description = Smoke test The following parameters are optional: +- `enabled = True` - Enable / disable ReportPortal reporting. - `api_key` - value can be found in the User Profile section. **Required** if OAuth 2.0 is not configured. - `oauth_uri = https://reportportal.example.com/uat/sso/oauth/token` - OAuth 2.0 token endpoint URL for password grant authentication. **Required** if API key is not used. diff --git a/behave_reportportal/behave_agent.py b/behave_reportportal/behave_agent.py index bdffd71..34a3257 100644 --- a/behave_reportportal/behave_agent.py +++ b/behave_reportportal/behave_agent.py @@ -25,6 +25,9 @@ from behave.runner import Context from prettytable import MARKDOWN, PrettyTable from reportportal_client import RP, create_client + +# noinspection PyProtectedMember +from reportportal_client._internal.static.defines import NOT_SET from reportportal_client.helpers import ( dict_to_payload, gen_attributes, @@ -95,9 +98,12 @@ class BehaveAgent(metaclass=Singleton): _log_item_id: Optional[str] _ignore_tag_prefixes: List[str] - def __init__(self, cfg: Config, rp_service: Optional[RP] = None) -> None: + def __init__(self, cfg: Config, rp_service: Optional[RP] = NOT_SET) -> None: """Initialize instance attributes.""" - self._rp = rp_service or create_rp_service(cfg) + if rp_service is NOT_SET: + self._rp = create_rp_service(cfg) + else: + self._rp = rp_service self._cfg = cfg self._handle_lifecycle = True self._launch_id = None diff --git a/tests/units/test_config.py b/tests/units/test_config.py index ce9881b..22a8aae 100644 --- a/tests/units/test_config.py +++ b/tests/units/test_config.py @@ -182,7 +182,7 @@ def test_read_config_default_values(mock_cp): expect(cfg.retries is None) expect(cfg.rerun is False) expect(cfg.rerun_of is None) - expect(cfg.enabled is False) + expect(cfg.enabled is True) expect(cfg.launch_uuid_print is False) expect(cfg.launch_uuid_print_output is None) expect(cfg.client_type is ClientType.SYNC) @@ -216,41 +216,6 @@ def test_deprecated_step_based(mock_cp): assert len(w) == 1 -@mock.patch("behave_reportportal.config.ConfigParser", autospec=True) -def test_deprecated_token_param(mock_cp): - mock_context = mock.Mock() - mock_context._config.userdata = UserData.make({"config_file": "some_path"}) - mock_cp().__getitem__.return_value = { - "token": "api_key", - "endpoint": "endpoint", - "project": "project", - "launch_name": "launch_name", - } - - with warnings.catch_warnings(record=True) as w: - cfg = read_config(mock_context) - assert cfg.api_key == "api_key" - assert len(w) == 1 - - -@mock.patch("behave_reportportal.config.ConfigParser", autospec=True) -def test_api_key_token_param_priority(mock_cp): - mock_context = mock.Mock() - mock_context._config.userdata = UserData.make({"config_file": "some_path"}) - mock_cp().__getitem__.return_value = { - "api_key": "api_key", - "token": "token", - "endpoint": "endpoint", - "project": "project", - "launch_name": "launch_name", - } - - with warnings.catch_warnings(record=True) as w: - cfg = read_config(mock_context) - assert cfg.api_key == "api_key" - assert len(w) == 0 - - @mock.patch("behave_reportportal.config.ConfigParser", autospec=True) def test_empty_api_key(mock_cp): mock_context = mock.Mock() @@ -262,11 +227,9 @@ def test_empty_api_key(mock_cp): "launch_name": "launch_name", } - with warnings.catch_warnings(record=True) as w: - cfg = read_config(mock_context) - assert cfg.api_key == "" - assert cfg.enabled is False - assert len(w) == 1 + cfg = read_config(mock_context) + assert cfg.api_key == "" + assert cfg.enabled is True @mock.patch("behave_reportportal.config.ConfigParser", autospec=True) diff --git a/tests/units/test_rp_agent.py b/tests/units/test_rp_agent.py index 961108c..330085e 100644 --- a/tests/units/test_rp_agent.py +++ b/tests/units/test_rp_agent.py @@ -159,7 +159,9 @@ def test_get_parameters(): def test_create_rp_service_disabled_rp(): - assert create_rp_service(Config()) is None, "Service is not None for disabled integration with RP in config" + assert ( + create_rp_service(Config(enabled=False)) is None + ), "Service is not None for disabled integration with RP in config" def test_create_rp_service_enabled_rp(config): @@ -177,7 +179,7 @@ def test_create_rp_service_init(mock_rps): "C", api_key="B", is_skipped_an_issue=False, - launch_id=None, + launch_uuid=None, retries=None, mode="DEFAULT", log_batch_size=20, @@ -185,6 +187,12 @@ def test_create_rp_service_init(mock_rps): launch_uuid_print=False, print_output=None, http_timeout=None, + oauth_uri=None, + oauth_username=None, + oauth_password=None, + oauth_client_id=None, + oauth_client_secret=None, + oauth_scope=None, ) ], any_order=True, @@ -211,8 +219,9 @@ def test_create_rp_service_init_type(client_type, client_class): def test_init_invalid_config(): - ba = BehaveAgent(Config()) - assert ba._rp is None, "Incorrect initialization of agent" + with pytest.raises(ValueError) as exc_info: + ba = BehaveAgent(Config()) + assert "Authentication credentials are required" in str(exc_info.value) def test_init_valid_config(config): From caa6cefe1f47f16ec010095558ea55465d012cb6 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Mon, 10 Nov 2025 18:04:59 +0300 Subject: [PATCH 6/7] Fix flake8 --- tests/units/test_rp_agent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/units/test_rp_agent.py b/tests/units/test_rp_agent.py index 330085e..a02866e 100644 --- a/tests/units/test_rp_agent.py +++ b/tests/units/test_rp_agent.py @@ -220,7 +220,7 @@ def test_create_rp_service_init_type(client_type, client_class): def test_init_invalid_config(): with pytest.raises(ValueError) as exc_info: - ba = BehaveAgent(Config()) + BehaveAgent(Config()) assert "Authentication credentials are required" in str(exc_info.value) From 3938d0dcdd650d616868ad9bc9c5bd333d25ecfd Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Mon, 10 Nov 2025 18:07:59 +0300 Subject: [PATCH 7/7] Fix trailing spaces --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d439f56..b6b11b1 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ The following parameters are optional: - `oauth_username = my-username` - OAuth 2.0 username for password grant authentication. **Required** if OAuth 2.0 is used. - `oauth_password = my-password` - OAuth 2.0 password for password grant authentication. **Required** if OAuth 2.0 is - used. + used. - `oauth_client_id = client-id` - OAuth 2.0 client identifier. **Required** if OAuth 2.0 is used. - `oauth_client_secret = client-id-secret` - OAuth 2.0 client secret. - `oauth_scope = offline_access` - OAuth 2.0 access token scope.