Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [ '3.8', '3.9', '3.10', '3.11', '3.12', '3.13' ]
python-version: [ '3.9', '3.10', '3.11', '3.12', '3.13', '3.14' ]
steps:
- name: Checkout repository
uses: actions/checkout@v4
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,6 @@ dmypy.json

# Pyre type checker
.pyre/

AGENTS.md
PROMPTS.md
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

## [Unreleased]
### Added
- Official `Python 3.14` support, by @HardNorth
- Issue [#396](https://github.com/reportportal/agent-python-pytest/issues/396) parametrize marker IDs, by @HardNorth
- Custom log level handling with `rp_log_custom_levels` configuration parameter, by @HardNorth
### Removed
- `Python 3.8` support, by @HardNorth
- Deprecated `retries` parameter, by @HardNorth

## [5.5.4]
### Added
- Return back deprecated `rp_log_batch_payload_size` parameter for sake of backward compatibility, by @HardNorth

## [5.5.3]
Expand Down
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,20 @@ py.test -c config.cfg

The `pytest.ini` file should have next mandatory fields:

- `rp_api_key` - value could be found in the User Profile section
- `rp_project` - name of project in ReportPortal
- `rp_endpoint` - address of ReportPortal Server

And one type of authorization: API Key or OAuth 2.0 Password grant. You can do this by setting:
- `rp_api_key` or `RP_API_KEY` environment variable. You can get it in the User Profile section on the UI.

Or:
- `rp_oauth_uri` - OAuth 2.0 token endpoint URL for password grant authentication. **Required** if API key is not used.
- `rp_oauth_username` - OAuth 2.0 username for password grant authentication. **Required** if OAuth 2.0 is used.
- `rp_oauth_password` - OAuth 2.0 password for password grant authentication. **Required** if OAuth 2.0 is used.
- `rp_oauth_client_id` - OAuth 2.0 client identifier. **Required** if OAuth 2.0 is used.
- `rp_oauth_client_secret` - OAuth 2.0 client secret. **Optional** for OAuth 2.0 authentication.
- `rp_oauth_scope` - OAuth 2.0 access token scope. **Optional** for OAuth 2.0 authentication.

Example of `pytest.ini`:

```text
Expand All @@ -51,8 +61,6 @@ rp_launch_description = 'Smoke test'
rp_ignore_attributes = 'xfail' 'usefixture'
```

- The `rp_api_key` can also be set with the environment variable `RP_API_KEY`. This will override the value set for `rp_api_key` in pytest.ini

There are also optional parameters:
https://reportportal.io/docs/log-data-in-reportportal/test-framework-integration/Python/pytest/

Expand Down
23 changes: 23 additions & 0 deletions examples/test_case_id/test_case_id_decorator_params_mark.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""A simple example test with Test Case ID decorator and parameters."""

# Copyright (c) 2022 https://reportportal.io .
# 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

import pytest

TEST_CASE_ID = "ISSUE-231"


@pytest.mark.parametrize(("param1", "param2"), [("value1", "value2")], ids=[TEST_CASE_ID])
def test_case_id_decorator(param1, param2):
assert True
25 changes: 25 additions & 0 deletions examples/test_rp_custom_logging.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Copyright (c) 2022 https://reportportal.io .
# 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

import logging

logging.basicConfig(level=logging.INFO)

logger = logging.getLogger(__name__)

LOG_LEVEL: int = 35
LOG_MESSAGE: str = "Assertion error"


def test_report_portal_logging():
logger.log(LOG_LEVEL, LOG_MESSAGE)
5 changes: 5 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,8 @@ skip_gitignore = true
[tool.black]
line-length = 119
target-version = ["py310"]

[tool.pytest.ini_options]
minversion = "6.0"
required_plugins = "pytest-cov"
testpaths = ["tests"]
42 changes: 19 additions & 23 deletions pytest_reportportal/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

import warnings
from os import getenv
from typing import Any, List, Optional, Tuple, Union
from typing import Any, Optional, Union

from _pytest.config import Config
from reportportal_client import ClientType, OutputType
Expand Down Expand Up @@ -50,8 +50,8 @@ class AgentConfig:
rp_bts_url: str
rp_launch: str
rp_launch_id: Optional[str]
rp_launch_attributes: Optional[List[str]]
rp_tests_attributes: Optional[List[str]]
rp_launch_attributes: Optional[list[str]]
rp_tests_attributes: Optional[list[str]]
rp_launch_description: str
rp_log_batch_size: int
rp_log_batch_payload_limit: int
Expand All @@ -78,9 +78,12 @@ class AgentConfig:
rp_launch_timeout: int
rp_launch_uuid_print: bool
rp_launch_uuid_print_output: Optional[OutputType]
rp_http_timeout: Optional[Union[Tuple[float, float], float]]
rp_http_timeout: Optional[Union[tuple[float, float], float]]
rp_report_fixtures: bool

# Custom log levels and overrides
rp_log_custom_levels: Optional[dict[int, str]]

def __init__(self, pytest_config: Config) -> None:
"""Initialize required attributes."""
self.rp_enabled = to_bool(getattr(pytest_config.option, "rp_enabled", True))
Expand Down Expand Up @@ -138,25 +141,8 @@ def __init__(self, pytest_config: Config) -> None:
self.rp_project = self.find_option(pytest_config, "rp_project")
self.rp_rerun_of = self.find_option(pytest_config, "rp_rerun_of")

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)
if rp_api_retries and rp_api_retries > 0:
self.rp_api_retries = rp_api_retries
else:
rp_api_retries_str = self.find_option(pytest_config, "retries")
rp_api_retries = rp_api_retries_str and int(rp_api_retries_str)
if rp_api_retries and rp_api_retries > 0:
self.rp_api_retries = rp_api_retries
warnings.warn(
"Parameter `retries` is deprecated since 5.1.9 "
"and will be subject for removing in the next "
"major version. Use `rp_api_retries` argument "
"instead.",
DeprecationWarning,
2,
)
else:
self.rp_api_retries = 0
rp_api_retries_str = self.find_option(pytest_config, "rp_api_retries", "0")
self.rp_api_retries = rp_api_retries_str and int(rp_api_retries_str)

# API key auth parameter
self.rp_api_key = getenv("RP_API_KEY") or self.find_option(pytest_config, "rp_api_key")
Expand Down Expand Up @@ -194,6 +180,16 @@ def __init__(self, pytest_config: Config) -> None:
self.rp_http_timeout = connect_timeout or read_timeout
self.rp_report_fixtures = to_bool(self.find_option(pytest_config, "rp_report_fixtures", False))

# Custom log levels and overrides
log_custom_levels = self.find_option(pytest_config, "rp_log_custom_levels")
self.rp_log_custom_levels = None
if log_custom_levels:
levels = {}
for custom_level in log_custom_levels:
level, level_name = str(custom_level).split(":", maxsplit=1)
levels[int(level)] = level_name
self.rp_log_custom_levels = levels

# noinspection PyMethodMayBeStatic
def find_option(self, pytest_config: Config, option_name: str, default: Any = None) -> Any:
"""
Expand Down
14 changes: 10 additions & 4 deletions pytest_reportportal/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import os.path
import time
from logging import Logger
from typing import Any, Callable, Dict, Generator
from typing import Any, Callable, Generator

import _pytest.logging
import dill as pickle
Expand Down Expand Up @@ -254,6 +254,7 @@ def pytest_runtest_protocol(item: Item) -> Generator[None, Any, None]:
filter_client_logs=True,
endpoint=agent_config.rp_endpoint,
ignored_record_names=("reportportal_client", "pytest_reportportal"),
custom_levels=agent_config.rp_log_custom_levels,
)
log_format = agent_config.rp_log_format
if log_format:
Expand Down Expand Up @@ -410,7 +411,7 @@ def pytest_bdd_after_step(
scenario: Scenario,
step: Step,
step_func: Callable[..., Any],
step_func_args: Dict[str, Any],
step_func_args: dict[str, Any],
) -> Generator[None, Any, None]:
"""Report BDD step finish.

Expand Down Expand Up @@ -439,7 +440,7 @@ def pytest_bdd_step_error(
scenario: Scenario,
step: Step,
step_func: Callable[..., Any],
step_func_args: Dict[str, Any],
step_func_args: dict[str, Any],
exception,
) -> Generator[None, Any, None]:
"""Report BDD step error.
Expand Down Expand Up @@ -600,6 +601,12 @@ def add_shared_option(name, help_str, default=None, action="store"):
"rp_log_batch_payload_size",
help="DEPRECATED: Maximum payload size in bytes of async batch log requests",
)
parser.addini(
"rp_log_custom_levels",
type="args",
help="Custom log levels specified as 'int level:string'. E.G.: '35:ASSERTION'. Overrides existing level if int"
" level matches.",
)
parser.addini("rp_ignore_attributes", type="args", help="Ignore specified pytest markers, i.e parametrize")
parser.addini(
"rp_is_skipped_an_issue", default=True, type="bool", help="Treat skipped tests as required investigation"
Expand Down Expand Up @@ -646,7 +653,6 @@ def add_shared_option(name, help_str, default=None, action="store"):
"directory with certificates of trusted CAs.",
)
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_launch_timeout",
Expand Down
4 changes: 2 additions & 2 deletions pytest_reportportal/rp_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import threading
from contextlib import contextmanager
from functools import wraps
from typing import Any, Dict, List
from typing import Any

from reportportal_client import RPLogger, current, set_current
from reportportal_client.core.worker import APIWorker
Expand Down Expand Up @@ -114,7 +114,7 @@ def patching_logger_class():

def wrap_log(original_func):
@wraps(original_func)
def _log(self, *args: List[Any], **kwargs: Dict[str, Any]):
def _log(self, *args: list[Any], **kwargs: dict[str, Any]):
my_kwargs = kwargs.copy()
attachment = my_kwargs.pop("attachment", None)
if attachment is not None:
Expand Down
Loading