From fa7988882eeb98e3092dcd4c633becd75679e980 Mon Sep 17 00:00:00 2001 From: Maksim Stepanov <17935127+delatrie@users.noreply.github.com> Date: Fri, 24 Apr 2026 04:17:49 +0700 Subject: [PATCH 1/7] feat(commons): globals API with separate functions --- allure-behave/src/listener.py | 12 +++ allure-pytest-bdd/src/allure_api_listener.py | 15 ++++ allure-pytest-bdd/src/utils.py | 24 ++++++ allure-pytest/src/listener.py | 12 +++ allure-python-commons-test/src/report.py | 6 ++ allure-python-commons-test/src/result.py | 31 ++++++++ allure-python-commons/src/allure/__init__.py | 4 + .../src/allure_commons/_allure.py | 28 +++++++ .../src/allure_commons/_hooks.py | 16 ++++ .../src/allure_commons/lifecycle.py | 40 ++++++++-- .../src/allure_commons/logger.py | 10 +++ .../src/allure_commons/model2.py | 20 +++++ .../src/allure_commons/reporter.py | 40 ++++++++-- allure-robotframework/src/library/__init__.py | 4 +- .../src/library/allure_library.py | 14 +++- .../src/listener/allure_listener.py | 12 +++ .../behave_support/hooks/hook_test.py | 48 +++++++++++- .../attachment/attachment_hook_test.py | 77 ++++++++++++++++++- .../acceptance/attachments_test.py | 63 +++++++++++++++ .../allure_api/attachment/attachment_test.py | 43 ++++++++++- 20 files changed, 497 insertions(+), 22 deletions(-) diff --git a/allure-behave/src/listener.py b/allure-behave/src/listener.py index c584f6b5..ad543526 100644 --- a/allure-behave/src/listener.py +++ b/allure-behave/src/listener.py @@ -178,6 +178,18 @@ def attach_data(self, body, name, attachment_type, extension): def attach_file(self, source, name, attachment_type, extension): self.logger.attach_file(uuid4(), source, name=name, attachment_type=attachment_type, extension=extension) + @allure_commons.hookimpl + def global_attach_data(self, body, name, attachment_type, extension): + self.logger.global_attach_data(uuid4(), body, name=name, attachment_type=attachment_type, extension=extension) + + @allure_commons.hookimpl + def global_attach_file(self, source, name, attachment_type, extension): + self.logger.global_attach_file(uuid4(), source, name=name, attachment_type=attachment_type, extension=extension) + + @allure_commons.hookimpl + def global_error(self, message, trace): + self.logger.global_error(message=message, trace=trace) + @allure_commons.hookimpl def add_description(self, test_description): test_result = self.logger.get_test(None) diff --git a/allure-pytest-bdd/src/allure_api_listener.py b/allure-pytest-bdd/src/allure_api_listener.py index e132e8e2..0b980725 100644 --- a/allure-pytest-bdd/src/allure_api_listener.py +++ b/allure-pytest-bdd/src/allure_api_listener.py @@ -16,6 +16,9 @@ from .utils import apply_link_pattern from .utils import attach_data from .utils import attach_file +from .utils import global_attach_data +from .utils import global_attach_file +from .utils import global_error from .utils import get_link_patterns from .steps import start_step from .steps import stop_step @@ -117,3 +120,15 @@ def attach_data(self, body, name, attachment_type, extension): @allure_commons.hookimpl def attach_file(self, source, name, attachment_type, extension): attach_file(self.lifecycle, source, name, attachment_type, extension) + + @allure_commons.hookimpl + def global_attach_data(self, body, name, attachment_type, extension): + global_attach_data(self.lifecycle, body, name, attachment_type, extension) + + @allure_commons.hookimpl + def global_attach_file(self, source, name, attachment_type, extension): + global_attach_file(self.lifecycle, source, name, attachment_type, extension) + + @allure_commons.hookimpl + def global_error(self, message, trace): + global_error(self.lifecycle, message, trace) diff --git a/allure-pytest-bdd/src/utils.py b/allure-pytest-bdd/src/utils.py index 609a1509..43bc17a1 100644 --- a/allure-pytest-bdd/src/utils.py +++ b/allure-pytest-bdd/src/utils.py @@ -323,6 +323,30 @@ def attach_file(lifecycle, source, name, attachment_type, extension=None): ) +def global_attach_data(lifecycle, body, name, attachment_type, extension=None): + lifecycle.global_attach_data( + uuid4(), + body, + name=name, + attachment_type=attachment_type, + extension=extension, + ) + + +def global_attach_file(lifecycle, source, name, attachment_type, extension=None): + lifecycle.global_attach_file( + uuid4(), + source, + name=name, + attachment_type=attachment_type, + extension=extension, + ) + + +def global_error(lifecycle, message, trace=None): + lifecycle.global_error(message=message, trace=trace) + + def format_csv(rows): with io.StringIO() as buffer: writer = csv.writer(buffer) diff --git a/allure-pytest/src/listener.py b/allure-pytest/src/listener.py index 21c06750..bcee6a0f 100644 --- a/allure-pytest/src/listener.py +++ b/allure-pytest/src/listener.py @@ -257,6 +257,18 @@ def attach_data(self, body, name, attachment_type, extension): def attach_file(self, source, name, attachment_type, extension): self.allure_logger.attach_file(uuid4(), source, name=name, attachment_type=attachment_type, extension=extension) + @allure_commons.hookimpl + def global_attach_data(self, body, name, attachment_type, extension): + self.allure_logger.global_attach_data(uuid4(), body, name=name, attachment_type=attachment_type, extension=extension) + + @allure_commons.hookimpl + def global_attach_file(self, source, name, attachment_type, extension): + self.allure_logger.global_attach_file(uuid4(), source, name=name, attachment_type=attachment_type, extension=extension) + + @allure_commons.hookimpl + def global_error(self, message, trace): + self.allure_logger.global_error(message=message, trace=trace) + @allure_commons.hookimpl def add_title(self, test_title): test_result = self.allure_logger.get_test(None) diff --git a/allure-python-commons-test/src/report.py b/allure-python-commons-test/src/report.py index 70e8b637..1db9413d 100644 --- a/allure-python-commons-test/src/report.py +++ b/allure-python-commons-test/src/report.py @@ -99,6 +99,12 @@ def __init__(self, result): "*attachment.*" ) } + self.globals = [ + json.load(file) for _, file in self._report_items( + result, + "*globals.json" + ) + ] @staticmethod def _report_items(report_dir, glob): diff --git a/allure-python-commons-test/src/result.py b/allure-python-commons-test/src/result.py index 97803d21..a7ce4e08 100644 --- a/allure-python-commons-test/src/result.py +++ b/allure-python-commons-test/src/result.py @@ -201,6 +201,25 @@ def has_attachment_with_content( ) +def has_global_attachment_with_content( + attachments, + content_matcher, + attach_type=None, + name=None +): + return has_entry( + "attachments", + has_item( + all_of( + has_entry("name", name) if name else anything(), + has_entry("type", attach_type) if attach_type else anything(), + has_entry("timestamp", not_none()), + has_entry("source", maps_to(attachments, content_matcher)) + ) + ) + ) + + def with_id(): return has_entry("uuid", not_none()) @@ -213,6 +232,18 @@ def has_status_details(*matchers): return has_entry("statusDetails", all_of(*matchers)) +def has_global_error(*matchers): + return has_entry( + "errors", + has_item( + all_of( + has_entry("timestamp", not_none()), + *matchers + ) + ) + ) + + def with_message_contains(string): return has_entry("message", contains_string(string)) diff --git a/allure-python-commons/src/allure/__init__.py b/allure-python-commons/src/allure/__init__.py index 4e72e402..6fe2f270 100644 --- a/allure-python-commons/src/allure/__init__.py +++ b/allure-python-commons/src/allure/__init__.py @@ -10,6 +10,8 @@ from allure_commons._allure import Dynamic as dynamic from allure_commons._allure import step from allure_commons._allure import attach +from allure_commons._allure import global_attach +from allure_commons._allure import global_error from allure_commons._allure import manual from allure_commons.types import Severity as severity_level from allure_commons.types import AttachmentType as attachment_type @@ -38,6 +40,8 @@ "dynamic", "severity_level", "attach", + "global_attach", + "global_error", "attachment_type", "parameter_mode" ] diff --git a/allure-python-commons/src/allure_commons/_allure.py b/allure-python-commons/src/allure_commons/_allure.py index 08fb5a87..ce759674 100644 --- a/allure-python-commons/src/allure_commons/_allure.py +++ b/allure-python-commons/src/allure_commons/_allure.py @@ -4,6 +4,7 @@ from allure_commons._core import plugin_manager from allure_commons.types import LabelType, LinkType, ParameterMode from allure_commons.utils import uuid4 +from allure_commons.utils import format_exception, format_traceback from allure_commons.utils import func_parameters, represent _TFunc = TypeVar("_TFunc", bound=Callable[..., Any]) @@ -216,6 +217,33 @@ def file(self, source, name=None, attachment_type=None, extension=None): attach = Attach() +class GlobalAttach: + + def __call__(self, body, name=None, attachment_type=None, extension=None): + plugin_manager.hook.global_attach_data(body=body, name=name, attachment_type=attachment_type, extension=extension) + + def file(self, source, name=None, attachment_type=None, extension=None): + plugin_manager.hook.global_attach_file(source=source, name=name, attachment_type=attachment_type, extension=extension) + + +global_attach = GlobalAttach() + + +def _resolve_global_error_details(error_or_message, trace=None): + if isinstance(error_or_message, BaseException): + return ( + format_exception(type(error_or_message), error_or_message), + format_traceback(error_or_message.__traceback__), + ) + + return error_or_message, trace + + +def global_error(error_or_message, trace=None): + message, error_trace = _resolve_global_error_details(error_or_message, trace=trace) + plugin_manager.hook.global_error(message=message, trace=error_trace) + + class fixture: def __init__(self, fixture_function, parent_uuid=None, name=None): self._fixture_function = fixture_function diff --git a/allure-python-commons/src/allure_commons/_hooks.py b/allure-python-commons/src/allure_commons/_hooks.py index 0ff19a27..84e916d9 100644 --- a/allure-python-commons/src/allure_commons/_hooks.py +++ b/allure-python-commons/src/allure_commons/_hooks.py @@ -66,6 +66,18 @@ def attach_data(self, body, name, attachment_type, extension): def attach_file(self, source, name, attachment_type, extension): """ attach file """ + @hookspec + def global_attach_data(self, body, name, attachment_type, extension): + """ attach global data """ + + @hookspec + def global_attach_file(self, source, name, attachment_type, extension): + """ attach global file """ + + @hookspec + def global_error(self, message, trace): + """ global error """ + class AllureDeveloperHooks: @@ -100,3 +112,7 @@ def report_attached_file(self, source, file_name): @hookspec def report_attached_data(self, body, file_name): """ reporting """ + + @hookspec + def report_globals(self, globals_item): + """ reporting """ diff --git a/allure-python-commons/src/allure_commons/lifecycle.py b/allure-python-commons/src/allure_commons/lifecycle.py index f6a4ff33..04ae180a 100644 --- a/allure-python-commons/src/allure_commons/lifecycle.py +++ b/allure-python-commons/src/allure_commons/lifecycle.py @@ -4,6 +4,7 @@ from allure_commons.model2 import TestResultContainer from allure_commons.model2 import TestResult from allure_commons.model2 import Attachment, ATTACHMENT_PATTERN +from allure_commons.model2 import GlobalAttachment, GlobalError, Globals from allure_commons.model2 import TestStepResult from allure_commons.model2 import ExecutableItem from allure_commons.model2 import TestBeforeResult @@ -124,14 +125,7 @@ def stop_after_fixture(self, uuid=None): fixture.stop = now() def _attach(self, uuid, name=None, attachment_type=None, extension=None, parent_uuid=None): - mime_type = attachment_type - extension = extension if extension else "attach" - - if type(attachment_type) is AttachmentType: - extension = attachment_type.extension - mime_type = attachment_type.mime_type - - file_name = ATTACHMENT_PATTERN.format(prefix=uuid, ext=extension) + file_name, mime_type = self.__resolve_attachment_filename_and_type(uuid, attachment_type=attachment_type, extension=extension) attachment = Attachment(source=file_name, name=name, type=mime_type) last_uuid = parent_uuid if parent_uuid else self._last_item_uuid(ExecutableItem) self._items[last_uuid].attachments.append(attachment) @@ -147,3 +141,33 @@ def attach_data(self, uuid, body, name=None, attachment_type=None, extension=Non file_name = self._attach(uuid, name=name, attachment_type=attachment_type, extension=extension, parent_uuid=parent_uuid) plugin_manager.hook.report_attached_data(body=body, file_name=file_name) + + def global_attach_file(self, uuid, source, name=None, attachment_type=None, extension=None): + file_name, mime_type = self.__resolve_attachment_filename_and_type(uuid, attachment_type=attachment_type, extension=extension) + plugin_manager.hook.report_attached_file(source=source, file_name=file_name) + plugin_manager.hook.report_globals(globals_item=Globals(attachments=[ + GlobalAttachment(source=file_name, name=name, type=mime_type, timestamp=now()) + ])) + + def global_attach_data(self, uuid, body, name=None, attachment_type=None, extension=None): + file_name, mime_type = self.__resolve_attachment_filename_and_type(uuid, attachment_type=attachment_type, extension=extension) + plugin_manager.hook.report_attached_data(body=body, file_name=file_name) + plugin_manager.hook.report_globals(globals_item=Globals(attachments=[ + GlobalAttachment(source=file_name, name=name, type=mime_type, timestamp=now()) + ])) + + def global_error(self, message=None, trace=None): + plugin_manager.hook.report_globals(globals_item=Globals(errors=[ + GlobalError(message=message, trace=trace, timestamp=now()) + ])) + + def __resolve_attachment_filename_and_type(self, uuid, attachment_type=None, extension=None): + mime_type = attachment_type + extension = extension if extension else "attach" + + if type(attachment_type) is AttachmentType: + extension = attachment_type.extension + mime_type = attachment_type.mime_type + + file_name = ATTACHMENT_PATTERN.format(prefix=uuid, ext=extension) + return file_name, mime_type diff --git a/allure-python-commons/src/allure_commons/logger.py b/allure-python-commons/src/allure_commons/logger.py index da3df2da..4345bc77 100644 --- a/allure-python-commons/src/allure_commons/logger.py +++ b/allure-python-commons/src/allure_commons/logger.py @@ -47,6 +47,10 @@ def report_attached_data(self, body, file_name): else: attached_file.write(body) + @hookimpl + def report_globals(self, globals_item): + self._report_item(globals_item) + class AllureMemoryLogger: @@ -54,6 +58,7 @@ def __init__(self): self.test_cases = [] self.test_containers = [] self.attachments = {} + self.globals = [] @hookimpl def report_result(self, result): @@ -72,3 +77,8 @@ def report_attached_file(self, source, file_name): @hookimpl def report_attached_data(self, body, file_name): self.attachments[file_name] = body + + @hookimpl + def report_globals(self, globals_item): + data = asdict(globals_item, filter=lambda _, v: v or v is False) + self.globals.append(data) diff --git a/allure-python-commons/src/allure_commons/model2.py b/allure-python-commons/src/allure_commons/model2.py index a117948c..cd069b17 100644 --- a/allure-python-commons/src/allure_commons/model2.py +++ b/allure-python-commons/src/allure_commons/model2.py @@ -5,6 +5,7 @@ TEST_GROUP_PATTERN = "{prefix}-container.json" TEST_CASE_PATTERN = "{prefix}-result.json" ATTACHMENT_PATTERN = "{prefix}-attachment.{ext}" +GLOBALS_PATTERN = "{prefix}-globals.json" INDENT = 4 @@ -95,6 +96,7 @@ class StatusDetails: message = attrib(default=None) trace = attrib(default=None) + @attrs class Attachment: name = attrib(default=None) @@ -102,6 +104,24 @@ class Attachment: type = attrib(default=None) +@attrs +class GlobalAttachment(Attachment): + timestamp = attrib(default=None) + + +@attrs +class GlobalError(StatusDetails): + timestamp = attrib(default=None) + + +@attrs +class Globals: + file_pattern = GLOBALS_PATTERN + + attachments = attrib(default=Factory(list)) + errors = attrib(default=Factory(list)) + + class Status: FAILED = "failed" BROKEN = "broken" diff --git a/allure-python-commons/src/allure_commons/reporter.py b/allure-python-commons/src/allure_commons/reporter.py index 14bbeb17..15107349 100644 --- a/allure-python-commons/src/allure_commons/reporter.py +++ b/allure-python-commons/src/allure_commons/reporter.py @@ -5,6 +5,7 @@ from allure_commons.model2 import ExecutableItem from allure_commons.model2 import TestResult from allure_commons.model2 import Attachment, ATTACHMENT_PATTERN +from allure_commons.model2 import GlobalAttachment, GlobalError, Globals from allure_commons.utils import now from allure_commons._core import plugin_manager @@ -140,14 +141,7 @@ def stop_step(self, uuid, **kwargs): self._items.pop(uuid) def _attach(self, uuid, name=None, attachment_type=None, extension=None, parent_uuid=None): - mime_type = attachment_type - extension = extension if extension else "attach" - - if type(attachment_type) is AttachmentType: - extension = attachment_type.extension - mime_type = attachment_type.mime_type - - file_name = ATTACHMENT_PATTERN.format(prefix=uuid, ext=extension) + file_name, mime_type = self.__resolve_attachment_filename_and_type(uuid, attachment_type, extension) attachment = Attachment(source=file_name, name=name, type=mime_type) last_uuid = parent_uuid if parent_uuid else self._last_executable() self._items[last_uuid].attachments.append(attachment) @@ -163,3 +157,33 @@ def attach_data(self, uuid, body, name=None, attachment_type=None, extension=Non file_name = self._attach(uuid, name=name, attachment_type=attachment_type, extension=extension, parent_uuid=parent_uuid) plugin_manager.hook.report_attached_data(body=body, file_name=file_name) + + def global_attach_file(self, uuid, source, name=None, attachment_type=None, extension=None): + file_name, mime_type = self.__resolve_attachment_filename_and_type(uuid, attachment_type=attachment_type, extension=extension) + plugin_manager.hook.report_attached_file(source=source, file_name=file_name) + plugin_manager.hook.report_globals(globals_item=Globals(attachments=[ + GlobalAttachment(source=file_name, name=name, type=mime_type, timestamp=now()) + ])) + + def global_attach_data(self, uuid, body, name=None, attachment_type=None, extension=None): + file_name, mime_type = self.__resolve_attachment_filename_and_type(uuid, attachment_type=attachment_type, extension=extension) + plugin_manager.hook.report_attached_data(body=body, file_name=file_name) + plugin_manager.hook.report_globals(globals_item=Globals(attachments=[ + GlobalAttachment(source=file_name, name=name, type=mime_type, timestamp=now()) + ])) + + def global_error(self, message=None, trace=None): + plugin_manager.hook.report_globals(globals_item=Globals(errors=[ + GlobalError(message=message, trace=trace, timestamp=now()) + ])) + + def __resolve_attachment_filename_and_type(self, uuid, attachment_type=None, extension=None): + mime_type = attachment_type + extension = extension if extension else "attach" + + if type(attachment_type) is AttachmentType: + extension = attachment_type.extension + mime_type = attachment_type.mime_type + + file_name = ATTACHMENT_PATTERN.format(prefix=uuid, ext=extension) + return file_name, mime_type diff --git a/allure-robotframework/src/library/__init__.py b/allure-robotframework/src/library/__init__.py index ba680b46..d21bce6b 100644 --- a/allure-robotframework/src/library/__init__.py +++ b/allure-robotframework/src/library/__init__.py @@ -1,3 +1,3 @@ -from .allure_library import attach, attach_file +from .allure_library import attach, attach_file, global_attach, global_attach_file, global_error -__all__ = ["attach", "attach_file"] +__all__ = ["attach", "attach_file", "global_attach", "global_attach_file", "global_error"] diff --git a/allure-robotframework/src/library/allure_library.py b/allure-robotframework/src/library/allure_library.py index 2b77f5e7..f06f019a 100644 --- a/allure-robotframework/src/library/allure_library.py +++ b/allure-robotframework/src/library/allure_library.py @@ -1,7 +1,7 @@ import allure -__all__ = ["attach", "attach_file"] +__all__ = ["attach", "attach_file", "global_attach", "global_attach_file", "global_error"] def _attachment_type(name): @@ -17,3 +17,15 @@ def attach(data, name=None, attachment_type=None, extension=None): def attach_file(source, name=None, attachment_type=None, extension=None): allure.attach.file(source, name=name, attachment_type=_attachment_type(attachment_type), extension=extension) + + +def global_attach(data, name=None, attachment_type=None, extension=None): + allure.global_attach(data, name=name, attachment_type=_attachment_type(attachment_type), extension=extension) + + +def global_attach_file(source, name=None, attachment_type=None, extension=None): + allure.global_attach.file(source, name=name, attachment_type=_attachment_type(attachment_type), extension=extension) + + +def global_error(error_or_message, trace=None): + allure.global_error(error_or_message, trace=trace) diff --git a/allure-robotframework/src/listener/allure_listener.py b/allure-robotframework/src/listener/allure_listener.py index 045c491a..732a814f 100644 --- a/allure-robotframework/src/listener/allure_listener.py +++ b/allure-robotframework/src/listener/allure_listener.py @@ -239,6 +239,18 @@ def attach_data(self, body, name, attachment_type, extension): def attach_file(self, source, name, attachment_type, extension): self.lifecycle.attach_file(uuid4(), source, name=name, attachment_type=attachment_type, extension=extension) + @allure_commons.hookimpl + def global_attach_data(self, body, name, attachment_type, extension): + self.lifecycle.global_attach_data(uuid4(), body, name=name, attachment_type=attachment_type, extension=extension) + + @allure_commons.hookimpl + def global_attach_file(self, source, name, attachment_type, extension): + self.lifecycle.global_attach_file(uuid4(), source, name=name, attachment_type=attachment_type, extension=extension) + + @allure_commons.hookimpl + def global_error(self, message, trace): + self.lifecycle.global_error(message=message, trace=trace) + @allure_commons.hookimpl def start_step(self, uuid, title, params): with self.lifecycle.start_step() as step: diff --git a/tests/allure_behave/acceptance/behave_support/hooks/hook_test.py b/tests/allure_behave/acceptance/behave_support/hooks/hook_test.py index 197ed5a1..baf96117 100644 --- a/tests/allure_behave/acceptance/behave_support/hooks/hook_test.py +++ b/tests/allure_behave/acceptance/behave_support/hooks/hook_test.py @@ -1,11 +1,14 @@ import allure from tests.allure_behave.behave_runner import AllureBehaveRunner as Runner -from hamcrest import assert_that, all_of, not_, equal_to +from hamcrest import assert_that, all_of, not_, equal_to, has_item from allure_commons_test.container import has_container, has_before, has_after from allure_commons_test.report import has_test_case from allure_commons_test.result import with_status from allure_commons_test.result import has_attachment_with_content +from allure_commons_test.result import has_global_attachment_with_content +from allure_commons_test.result import has_global_error from allure_commons_test.result import has_step +from allure_commons_test.result import with_message_contains def test_global_hooks(behave_runner: Runner): @@ -111,6 +114,49 @@ def test_tag_hooks(behave_runner: Runner): ) +def test_global_attachment_and_error_hooks(behave_runner: Runner): + behave_runner.run_behave( + feature_literals=[ + """ + Feature: Global attachments and errors + Scenario: Global hooks + Given noop + """ + ], + step_literals=["given('noop')(lambda c: None)"], + environment_literal=""" +import allure + + +def before_all(context): + allure.global_attach("behave global attachment", name="behave global") + + +def after_all(context): + allure.global_error("behave global error") +""" + ) + + assert_that( + behave_runner.allure_results.globals, + has_item( + has_global_attachment_with_content( + behave_runner.allure_results.attachments, + equal_to("behave global attachment"), + name="behave global" + ) + ) + ) + assert_that( + behave_runner.allure_results.globals, + has_item( + has_global_error( + with_message_contains("behave global error") + ) + ) + ) + + def test_attachment_before_feature(behave_runner: Runner): behave_runner.run_behave( feature_paths=["./test-data/attachment-hook.feature"], diff --git a/tests/allure_pytest/acceptance/attachment/attachment_hook_test.py b/tests/allure_pytest/acceptance/attachment/attachment_hook_test.py index 40999e62..28702712 100644 --- a/tests/allure_pytest/acceptance/attachment/attachment_hook_test.py +++ b/tests/allure_pytest/acceptance/attachment/attachment_hook_test.py @@ -1,8 +1,12 @@ -from hamcrest import assert_that +from hamcrest import assert_that, has_item, all_of, has_entry, equal_to, ends_with, not_, anything from tests.allure_pytest.pytest_runner import AllurePytestRunner from allure_commons_test.report import has_test_case from allure_commons_test.result import has_attachment +from allure_commons_test.result import has_global_attachment_with_content +from allure_commons_test.result import has_global_error +from allure_commons_test.result import with_message_contains +from allure_commons_test.result import with_trace_contains def test_attach_from_runtest_teardown(allure_pytest_runner: AllurePytestRunner): @@ -55,3 +59,74 @@ def pytest_runtest_logfinish(*args, **kwargs): has_attachment(name="attachment from logfinish") ) ) + + +def test_globals_from_session_hooks(allure_pytest_runner: AllurePytestRunner): + allure_results = allure_pytest_runner.run_pytest( + """ + def test_globals_from_session_hooks(): + pass + """, + conftest_literal=( + """ + import allure + + + def pytest_sessionstart(session): + allure.global_attach(body="global body", name="global attachment") + allure.global_attach.file(__file__, name="global attachment file") + allure.global_error("message only error") + + + def pytest_sessionfinish(session, exitstatus): + try: + raise ValueError("error from exception") + except ValueError as error: + allure.global_error(error) + allure.global_error("message with trace", "explicit trace") + """ + ) + ) + + assert_that( + allure_results.globals, + all_of( + has_item( + has_global_attachment_with_content( + allure_results.attachments, + equal_to("global body"), + name="global attachment" + ) + ), + has_item( + has_global_attachment_with_content( + allure_results.attachments, + ends_with("conftest.py"), + name="global attachment file" + ) + ), + has_item( + has_entry( + "errors", + has_item( + all_of( + with_message_contains("message only error"), + not_(has_entry("trace", anything())) + ) + ) + ) + ), + has_item( + has_global_error( + with_message_contains("ValueError: error from exception"), + with_trace_contains("raise ValueError") + ) + ), + has_item( + has_global_error( + with_message_contains("message with trace"), + with_trace_contains("explicit trace") + ) + ) + ) + ) diff --git a/tests/allure_pytest_bdd/acceptance/attachments_test.py b/tests/allure_pytest_bdd/acceptance/attachments_test.py index 907e6604..8799f185 100644 --- a/tests/allure_pytest_bdd/acceptance/attachments_test.py +++ b/tests/allure_pytest_bdd/acceptance/attachments_test.py @@ -3,15 +3,19 @@ from hamcrest import assert_that from hamcrest import equal_to from hamcrest import ends_with +from hamcrest import has_item from hamcrest import not_ from allure_commons_test.content import csv_equivalent from allure_commons_test.report import has_test_case from allure_commons_test.result import has_attachment from allure_commons_test.result import has_attachment_with_content +from allure_commons_test.result import has_global_attachment_with_content +from allure_commons_test.result import has_global_error from allure_commons_test.result import has_step from allure_commons_test.result import has_parameter from allure_commons_test.result import doesnt_have_parameter +from allure_commons_test.result import with_message_contains from tests.allure_pytest.pytest_runner import AllurePytestRunner from tests.e2e import version_lt @@ -239,6 +243,65 @@ def pytest_runtest_teardown(item): ) +def test_global_attachment_and_error_from_hook(allure_pytest_bdd_runner: AllurePytestRunner): + feature_content = ( + """ + Feature: Foo + Scenario: Bar + Given noop + """ + ) + steps_content = ( + """ + from pytest_bdd import scenario, given + + @scenario("sample.feature", "Bar") + def test_scenario(): + pass + + @given("noop") + def given_noop(): + pass + """ + ) + conftest_content = ( + """ + import allure + + def pytest_sessionstart(session): + allure.global_attach("bdd global attachment", name="bdd global") + + def pytest_sessionfinish(session, exitstatus): + allure.global_error("bdd global error") + """ + ) + + allure_results = allure_pytest_bdd_runner.run_pytest( + ("sample.feature", feature_content), + steps_content, + conftest_literal=conftest_content, + ) + + assert_that( + allure_results.globals, + has_item( + has_global_attachment_with_content( + allure_results.attachments, + equal_to("bdd global attachment"), + name="bdd global", + ) + ) + ) + assert_that( + allure_results.globals, + has_item( + has_global_error( + with_message_contains("bdd global error") + ) + ) + ) + + @pytest.mark.skipif(version_lt("pytest-bdd", 8), reason="Data tables support added in 8.0.0") def test_attach_datatable(allure_pytest_bdd_runner: AllurePytestRunner): feature_content = ( diff --git a/tests/allure_robotframework/acceptance/allure_api/attachment/attachment_test.py b/tests/allure_robotframework/acceptance/allure_api/attachment/attachment_test.py index 7f016402..5d12a664 100644 --- a/tests/allure_robotframework/acceptance/allure_api/attachment/attachment_test.py +++ b/tests/allure_robotframework/acceptance/allure_api/attachment/attachment_test.py @@ -1,11 +1,14 @@ """ ./allure-robotframework/examples/attachment.rst """ from pytest import MonkeyPatch -from hamcrest import assert_that, equal_to +from hamcrest import assert_that, equal_to, has_item from tests.allure_robotframework.robot_runner import AllureRobotRunner from allure_commons_test.report import has_test_case from allure_commons_test.result import has_attachment_with_content +from allure_commons_test.result import has_global_attachment_with_content +from allure_commons_test.result import has_global_error from allure_commons_test.result import has_step +from allure_commons_test.result import with_message_contains from allure_commons_test.result import with_status @@ -144,3 +147,41 @@ def close_browser(*_):pass ) ) ) + + +def test_global_attachment_and_error(robot_runner: AllureRobotRunner): + robot_runner.run_robotframework( + suite_literals={ + "global-attachment.robot": ( + """ + *** Settings *** + Library AllureLibrary + Suite Setup Global Attach robot global attachment name=robot global + Suite Teardown Global Error robot global error + + *** Test Cases *** + Global Attachment + No Operation + """ + ) + } + ) + + assert_that( + robot_runner.allure_results.globals, + has_item( + has_global_attachment_with_content( + robot_runner.allure_results.attachments, + equal_to("robot global attachment"), + name="robot global" + ) + ) + ) + assert_that( + robot_runner.allure_results.globals, + has_item( + has_global_error( + with_message_contains("robot global error") + ) + ) + ) From 31a5157d1460db9ae5d49dcb47375da75df3d72c Mon Sep 17 00:00:00 2001 From: Maksim Stepanov <17935127+delatrie@users.noreply.github.com> Date: Fri, 24 Apr 2026 05:50:35 +0700 Subject: [PATCH 2/7] test: tighten globals tests a little --- allure-python-commons-test/src/result.py | 2 + .../behave_support/hooks/hook_test.py | 7 +++- .../attachment/attachment_hook_test.py | 21 +++++----- .../acceptance/attachments_test.py | 3 ++ .../allure_api/attachment/attachment_test.py | 40 +++++++++++++++++-- 5 files changed, 59 insertions(+), 14 deletions(-) diff --git a/allure-python-commons-test/src/result.py b/allure-python-commons-test/src/result.py index a7ce4e08..7012234f 100644 --- a/allure-python-commons-test/src/result.py +++ b/allure-python-commons-test/src/result.py @@ -251,6 +251,8 @@ def with_message_contains(string): def with_trace_contains(string): return has_entry("trace", contains_string(string)) +def with_no_trace(): + return not_(has_entry("trace", anything())) def with_excluded(): return has_entry("excluded", True) diff --git a/tests/allure_behave/acceptance/behave_support/hooks/hook_test.py b/tests/allure_behave/acceptance/behave_support/hooks/hook_test.py index baf96117..73445674 100644 --- a/tests/allure_behave/acceptance/behave_support/hooks/hook_test.py +++ b/tests/allure_behave/acceptance/behave_support/hooks/hook_test.py @@ -1,6 +1,6 @@ import allure from tests.allure_behave.behave_runner import AllureBehaveRunner as Runner -from hamcrest import assert_that, all_of, not_, equal_to, has_item +from hamcrest import assert_that, all_of, not_, equal_to, has_item, has_length from allure_commons_test.container import has_container, has_before, has_after from allure_commons_test.report import has_test_case from allure_commons_test.result import with_status @@ -137,6 +137,11 @@ def after_all(context): """ ) + assert_that( + behave_runner.allure_results.globals, + has_length(2), + ) + assert_that( behave_runner.allure_results.globals, has_item( diff --git a/tests/allure_pytest/acceptance/attachment/attachment_hook_test.py b/tests/allure_pytest/acceptance/attachment/attachment_hook_test.py index 28702712..96544b09 100644 --- a/tests/allure_pytest/acceptance/attachment/attachment_hook_test.py +++ b/tests/allure_pytest/acceptance/attachment/attachment_hook_test.py @@ -1,4 +1,6 @@ -from hamcrest import assert_that, has_item, all_of, has_entry, equal_to, ends_with, not_, anything +from hamcrest import assert_that, has_item, all_of +from hamcrest import has_entry, equal_to, ends_with +from hamcrest import not_, anything, has_length from tests.allure_pytest.pytest_runner import AllurePytestRunner from allure_commons_test.report import has_test_case @@ -7,6 +9,7 @@ from allure_commons_test.result import has_global_error from allure_commons_test.result import with_message_contains from allure_commons_test.result import with_trace_contains +from allure_commons_test.result import with_no_trace def test_attach_from_runtest_teardown(allure_pytest_runner: AllurePytestRunner): @@ -88,6 +91,11 @@ def pytest_sessionfinish(session, exitstatus): ) ) + assert_that( + allure_results.globals, + has_length(5), + ) + assert_that( allure_results.globals, all_of( @@ -106,14 +114,9 @@ def pytest_sessionfinish(session, exitstatus): ) ), has_item( - has_entry( - "errors", - has_item( - all_of( - with_message_contains("message only error"), - not_(has_entry("trace", anything())) - ) - ) + has_global_error( + with_message_contains("message only error"), + with_no_trace(), ) ), has_item( diff --git a/tests/allure_pytest_bdd/acceptance/attachments_test.py b/tests/allure_pytest_bdd/acceptance/attachments_test.py index 8799f185..6272dc95 100644 --- a/tests/allure_pytest_bdd/acceptance/attachments_test.py +++ b/tests/allure_pytest_bdd/acceptance/attachments_test.py @@ -5,6 +5,7 @@ from hamcrest import ends_with from hamcrest import has_item from hamcrest import not_ +from hamcrest import has_length from allure_commons_test.content import csv_equivalent from allure_commons_test.report import has_test_case @@ -282,6 +283,8 @@ def pytest_sessionfinish(session, exitstatus): conftest_literal=conftest_content, ) + assert_that(allure_results.globals, has_length(2)) + assert_that( allure_results.globals, has_item( diff --git a/tests/allure_robotframework/acceptance/allure_api/attachment/attachment_test.py b/tests/allure_robotframework/acceptance/allure_api/attachment/attachment_test.py index 5d12a664..2837ab70 100644 --- a/tests/allure_robotframework/acceptance/allure_api/attachment/attachment_test.py +++ b/tests/allure_robotframework/acceptance/allure_api/attachment/attachment_test.py @@ -1,7 +1,7 @@ """ ./allure-robotframework/examples/attachment.rst """ from pytest import MonkeyPatch -from hamcrest import assert_that, equal_to, has_item +from hamcrest import assert_that, equal_to, has_item, has_length from tests.allure_robotframework.robot_runner import AllureRobotRunner from allure_commons_test.report import has_test_case from allure_commons_test.result import has_attachment_with_content @@ -156,17 +156,31 @@ def test_global_attachment_and_error(robot_runner: AllureRobotRunner): """ *** Settings *** Library AllureLibrary + Library ./lib.py Suite Setup Global Attach robot global attachment name=robot global Suite Teardown Global Error robot global error *** Test Cases *** Global Attachment - No Operation + Add Globals From Code """ - ) - } + ), + }, + library_literals={ + "lib.py": ( + """ + import allure + + def add_globals_from_code(): + allure.global_attach(body="global body", name="global attachment") + allure.global_error("message only error") + """ + ), + }, ) + assert_that(robot_runner.allure_results.globals, has_length(4)) + assert_that( robot_runner.allure_results.globals, has_item( @@ -177,6 +191,16 @@ def test_global_attachment_and_error(robot_runner: AllureRobotRunner): ) ) ) + assert_that( + robot_runner.allure_results.globals, + has_item( + has_global_attachment_with_content( + robot_runner.allure_results.attachments, + equal_to("global body"), + name="global attachment" + ) + ) + ) assert_that( robot_runner.allure_results.globals, has_item( @@ -185,3 +209,11 @@ def test_global_attachment_and_error(robot_runner: AllureRobotRunner): ) ) ) + assert_that( + robot_runner.allure_results.globals, + has_item( + has_global_error( + with_message_contains("message only error") + ) + ) + ) From 3c9f60ab970339fd8dbfa2b0ae43c0b984167333 Mon Sep 17 00:00:00 2001 From: Maksim Stepanov <17935127+delatrie@users.noreply.github.com> Date: Fri, 24 Apr 2026 06:02:34 +0700 Subject: [PATCH 3/7] test: factor out globals tests --- .../acceptance/allure_api/globals/__init__.py | 0 .../allure_api/globals/globals_test.py | 56 +++++++++++++ .../behave_support/hooks/hook_test.py | 53 +----------- .../attachment/attachment_hook_test.py | 80 +------------------ .../acceptance/globals/__init__.py | 0 .../acceptance/globals/globals_test.py | 80 +++++++++++++++++++ .../acceptance/attachments_test.py | 66 --------------- .../acceptance/globals_test.py | 73 +++++++++++++++++ .../allure_api/attachment/attachment_test.py | 75 +---------------- .../acceptance/allure_api/globals/__init__.py | 0 .../allure_api/globals/globals_test.py | 77 ++++++++++++++++++ 11 files changed, 289 insertions(+), 271 deletions(-) create mode 100644 tests/allure_behave/acceptance/allure_api/globals/__init__.py create mode 100644 tests/allure_behave/acceptance/allure_api/globals/globals_test.py create mode 100644 tests/allure_pytest/acceptance/globals/__init__.py create mode 100644 tests/allure_pytest/acceptance/globals/globals_test.py create mode 100644 tests/allure_pytest_bdd/acceptance/globals_test.py create mode 100644 tests/allure_robotframework/acceptance/allure_api/globals/__init__.py create mode 100644 tests/allure_robotframework/acceptance/allure_api/globals/globals_test.py diff --git a/tests/allure_behave/acceptance/allure_api/globals/__init__.py b/tests/allure_behave/acceptance/allure_api/globals/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/allure_behave/acceptance/allure_api/globals/globals_test.py b/tests/allure_behave/acceptance/allure_api/globals/globals_test.py new file mode 100644 index 00000000..ce1a5f48 --- /dev/null +++ b/tests/allure_behave/acceptance/allure_api/globals/globals_test.py @@ -0,0 +1,56 @@ +import textwrap +from tests.allure_behave.behave_runner import AllureBehaveRunner as Runner +from hamcrest import assert_that, equal_to, has_item, has_length +from allure_commons_test.result import has_global_attachment_with_content +from allure_commons_test.result import has_global_error +from allure_commons_test.result import with_message_contains + + +def test_global_attachment_and_error_hooks(behave_runner: Runner): + behave_runner.run_behave( + feature_literals=[ + """ + Feature: Global attachments and errors + Scenario: Global hooks + Given noop + """ + ], + step_literals=["given('noop')(lambda c: None)"], + environment_literal=textwrap.dedent( + """ + import allure + + + def before_all(context): + allure.global_attach("behave global attachment", name="behave global") + + + def after_all(context): + allure.global_error("behave global error") + """ + ), + ) + + assert_that( + behave_runner.allure_results.globals, + has_length(2), + ) + + assert_that( + behave_runner.allure_results.globals, + has_item( + has_global_attachment_with_content( + behave_runner.allure_results.attachments, + equal_to("behave global attachment"), + name="behave global" + ) + ) + ) + assert_that( + behave_runner.allure_results.globals, + has_item( + has_global_error( + with_message_contains("behave global error") + ) + ) + ) diff --git a/tests/allure_behave/acceptance/behave_support/hooks/hook_test.py b/tests/allure_behave/acceptance/behave_support/hooks/hook_test.py index 73445674..197ed5a1 100644 --- a/tests/allure_behave/acceptance/behave_support/hooks/hook_test.py +++ b/tests/allure_behave/acceptance/behave_support/hooks/hook_test.py @@ -1,14 +1,11 @@ import allure from tests.allure_behave.behave_runner import AllureBehaveRunner as Runner -from hamcrest import assert_that, all_of, not_, equal_to, has_item, has_length +from hamcrest import assert_that, all_of, not_, equal_to from allure_commons_test.container import has_container, has_before, has_after from allure_commons_test.report import has_test_case from allure_commons_test.result import with_status from allure_commons_test.result import has_attachment_with_content -from allure_commons_test.result import has_global_attachment_with_content -from allure_commons_test.result import has_global_error from allure_commons_test.result import has_step -from allure_commons_test.result import with_message_contains def test_global_hooks(behave_runner: Runner): @@ -114,54 +111,6 @@ def test_tag_hooks(behave_runner: Runner): ) -def test_global_attachment_and_error_hooks(behave_runner: Runner): - behave_runner.run_behave( - feature_literals=[ - """ - Feature: Global attachments and errors - Scenario: Global hooks - Given noop - """ - ], - step_literals=["given('noop')(lambda c: None)"], - environment_literal=""" -import allure - - -def before_all(context): - allure.global_attach("behave global attachment", name="behave global") - - -def after_all(context): - allure.global_error("behave global error") -""" - ) - - assert_that( - behave_runner.allure_results.globals, - has_length(2), - ) - - assert_that( - behave_runner.allure_results.globals, - has_item( - has_global_attachment_with_content( - behave_runner.allure_results.attachments, - equal_to("behave global attachment"), - name="behave global" - ) - ) - ) - assert_that( - behave_runner.allure_results.globals, - has_item( - has_global_error( - with_message_contains("behave global error") - ) - ) - ) - - def test_attachment_before_feature(behave_runner: Runner): behave_runner.run_behave( feature_paths=["./test-data/attachment-hook.feature"], diff --git a/tests/allure_pytest/acceptance/attachment/attachment_hook_test.py b/tests/allure_pytest/acceptance/attachment/attachment_hook_test.py index 96544b09..40999e62 100644 --- a/tests/allure_pytest/acceptance/attachment/attachment_hook_test.py +++ b/tests/allure_pytest/acceptance/attachment/attachment_hook_test.py @@ -1,15 +1,8 @@ -from hamcrest import assert_that, has_item, all_of -from hamcrest import has_entry, equal_to, ends_with -from hamcrest import not_, anything, has_length +from hamcrest import assert_that from tests.allure_pytest.pytest_runner import AllurePytestRunner from allure_commons_test.report import has_test_case from allure_commons_test.result import has_attachment -from allure_commons_test.result import has_global_attachment_with_content -from allure_commons_test.result import has_global_error -from allure_commons_test.result import with_message_contains -from allure_commons_test.result import with_trace_contains -from allure_commons_test.result import with_no_trace def test_attach_from_runtest_teardown(allure_pytest_runner: AllurePytestRunner): @@ -62,74 +55,3 @@ def pytest_runtest_logfinish(*args, **kwargs): has_attachment(name="attachment from logfinish") ) ) - - -def test_globals_from_session_hooks(allure_pytest_runner: AllurePytestRunner): - allure_results = allure_pytest_runner.run_pytest( - """ - def test_globals_from_session_hooks(): - pass - """, - conftest_literal=( - """ - import allure - - - def pytest_sessionstart(session): - allure.global_attach(body="global body", name="global attachment") - allure.global_attach.file(__file__, name="global attachment file") - allure.global_error("message only error") - - - def pytest_sessionfinish(session, exitstatus): - try: - raise ValueError("error from exception") - except ValueError as error: - allure.global_error(error) - allure.global_error("message with trace", "explicit trace") - """ - ) - ) - - assert_that( - allure_results.globals, - has_length(5), - ) - - assert_that( - allure_results.globals, - all_of( - has_item( - has_global_attachment_with_content( - allure_results.attachments, - equal_to("global body"), - name="global attachment" - ) - ), - has_item( - has_global_attachment_with_content( - allure_results.attachments, - ends_with("conftest.py"), - name="global attachment file" - ) - ), - has_item( - has_global_error( - with_message_contains("message only error"), - with_no_trace(), - ) - ), - has_item( - has_global_error( - with_message_contains("ValueError: error from exception"), - with_trace_contains("raise ValueError") - ) - ), - has_item( - has_global_error( - with_message_contains("message with trace"), - with_trace_contains("explicit trace") - ) - ) - ) - ) diff --git a/tests/allure_pytest/acceptance/globals/__init__.py b/tests/allure_pytest/acceptance/globals/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/allure_pytest/acceptance/globals/globals_test.py b/tests/allure_pytest/acceptance/globals/globals_test.py new file mode 100644 index 00000000..a697ab30 --- /dev/null +++ b/tests/allure_pytest/acceptance/globals/globals_test.py @@ -0,0 +1,80 @@ +from hamcrest import assert_that, has_item, all_of +from hamcrest import equal_to, ends_with, has_length +from tests.allure_pytest.pytest_runner import AllurePytestRunner + +from allure_commons_test.result import has_global_attachment_with_content +from allure_commons_test.result import has_global_error +from allure_commons_test.result import with_message_contains +from allure_commons_test.result import with_trace_contains +from allure_commons_test.result import with_no_trace + + +def test_globals_from_session_hooks(allure_pytest_runner: AllurePytestRunner): + allure_results = allure_pytest_runner.run_pytest( + """ + def test_globals_from_session_hooks(): + pass + """, + conftest_literal=( + """ + import allure + + + def pytest_sessionstart(session): + allure.global_attach(body="global body", name="global attachment") + allure.global_attach.file(__file__, name="global attachment file") + allure.global_error("message only error") + + + def pytest_sessionfinish(session, exitstatus): + try: + raise ValueError("error from exception") + except ValueError as error: + allure.global_error(error) + allure.global_error("message with trace", "explicit trace") + """ + ) + ) + + assert_that( + allure_results.globals, + has_length(5), + ) + + assert_that( + allure_results.globals, + all_of( + has_item( + has_global_attachment_with_content( + allure_results.attachments, + equal_to("global body"), + name="global attachment" + ) + ), + has_item( + has_global_attachment_with_content( + allure_results.attachments, + ends_with("conftest.py"), + name="global attachment file" + ) + ), + has_item( + has_global_error( + with_message_contains("message only error"), + with_no_trace(), + ) + ), + has_item( + has_global_error( + with_message_contains("ValueError: error from exception"), + with_trace_contains("raise ValueError") + ) + ), + has_item( + has_global_error( + with_message_contains("message with trace"), + with_trace_contains("explicit trace") + ) + ) + ) + ) diff --git a/tests/allure_pytest_bdd/acceptance/attachments_test.py b/tests/allure_pytest_bdd/acceptance/attachments_test.py index 6272dc95..907e6604 100644 --- a/tests/allure_pytest_bdd/acceptance/attachments_test.py +++ b/tests/allure_pytest_bdd/acceptance/attachments_test.py @@ -3,20 +3,15 @@ from hamcrest import assert_that from hamcrest import equal_to from hamcrest import ends_with -from hamcrest import has_item from hamcrest import not_ -from hamcrest import has_length from allure_commons_test.content import csv_equivalent from allure_commons_test.report import has_test_case from allure_commons_test.result import has_attachment from allure_commons_test.result import has_attachment_with_content -from allure_commons_test.result import has_global_attachment_with_content -from allure_commons_test.result import has_global_error from allure_commons_test.result import has_step from allure_commons_test.result import has_parameter from allure_commons_test.result import doesnt_have_parameter -from allure_commons_test.result import with_message_contains from tests.allure_pytest.pytest_runner import AllurePytestRunner from tests.e2e import version_lt @@ -244,67 +239,6 @@ def pytest_runtest_teardown(item): ) -def test_global_attachment_and_error_from_hook(allure_pytest_bdd_runner: AllurePytestRunner): - feature_content = ( - """ - Feature: Foo - Scenario: Bar - Given noop - """ - ) - steps_content = ( - """ - from pytest_bdd import scenario, given - - @scenario("sample.feature", "Bar") - def test_scenario(): - pass - - @given("noop") - def given_noop(): - pass - """ - ) - conftest_content = ( - """ - import allure - - def pytest_sessionstart(session): - allure.global_attach("bdd global attachment", name="bdd global") - - def pytest_sessionfinish(session, exitstatus): - allure.global_error("bdd global error") - """ - ) - - allure_results = allure_pytest_bdd_runner.run_pytest( - ("sample.feature", feature_content), - steps_content, - conftest_literal=conftest_content, - ) - - assert_that(allure_results.globals, has_length(2)) - - assert_that( - allure_results.globals, - has_item( - has_global_attachment_with_content( - allure_results.attachments, - equal_to("bdd global attachment"), - name="bdd global", - ) - ) - ) - assert_that( - allure_results.globals, - has_item( - has_global_error( - with_message_contains("bdd global error") - ) - ) - ) - - @pytest.mark.skipif(version_lt("pytest-bdd", 8), reason="Data tables support added in 8.0.0") def test_attach_datatable(allure_pytest_bdd_runner: AllurePytestRunner): feature_content = ( diff --git a/tests/allure_pytest_bdd/acceptance/globals_test.py b/tests/allure_pytest_bdd/acceptance/globals_test.py new file mode 100644 index 00000000..d2ba6bc8 --- /dev/null +++ b/tests/allure_pytest_bdd/acceptance/globals_test.py @@ -0,0 +1,73 @@ +from hamcrest import assert_that +from hamcrest import equal_to +from hamcrest import has_item +from hamcrest import has_length + +from allure_commons_test.result import has_global_attachment_with_content +from allure_commons_test.result import has_global_error +from allure_commons_test.result import with_message_contains + +from tests.allure_pytest.pytest_runner import AllurePytestRunner +from tests.e2e import version_lt +from tests.e2e import version_gte + + +def test_global_attachment_and_error_from_hook(allure_pytest_bdd_runner: AllurePytestRunner): + feature_content = ( + """ + Feature: Foo + Scenario: Bar + Given noop + """ + ) + steps_content = ( + """ + from pytest_bdd import scenario, given + + @scenario("sample.feature", "Bar") + def test_scenario(): + pass + + @given("noop") + def given_noop(): + pass + """ + ) + conftest_content = ( + """ + import allure + + def pytest_sessionstart(session): + allure.global_attach("bdd global attachment", name="bdd global") + + def pytest_sessionfinish(session, exitstatus): + allure.global_error("bdd global error") + """ + ) + + allure_results = allure_pytest_bdd_runner.run_pytest( + ("sample.feature", feature_content), + steps_content, + conftest_literal=conftest_content, + ) + + assert_that(allure_results.globals, has_length(2)) + + assert_that( + allure_results.globals, + has_item( + has_global_attachment_with_content( + allure_results.attachments, + equal_to("bdd global attachment"), + name="bdd global", + ) + ) + ) + assert_that( + allure_results.globals, + has_item( + has_global_error( + with_message_contains("bdd global error") + ) + ) + ) diff --git a/tests/allure_robotframework/acceptance/allure_api/attachment/attachment_test.py b/tests/allure_robotframework/acceptance/allure_api/attachment/attachment_test.py index 2837ab70..7f016402 100644 --- a/tests/allure_robotframework/acceptance/allure_api/attachment/attachment_test.py +++ b/tests/allure_robotframework/acceptance/allure_api/attachment/attachment_test.py @@ -1,14 +1,11 @@ """ ./allure-robotframework/examples/attachment.rst """ from pytest import MonkeyPatch -from hamcrest import assert_that, equal_to, has_item, has_length +from hamcrest import assert_that, equal_to from tests.allure_robotframework.robot_runner import AllureRobotRunner from allure_commons_test.report import has_test_case from allure_commons_test.result import has_attachment_with_content -from allure_commons_test.result import has_global_attachment_with_content -from allure_commons_test.result import has_global_error from allure_commons_test.result import has_step -from allure_commons_test.result import with_message_contains from allure_commons_test.result import with_status @@ -147,73 +144,3 @@ def close_browser(*_):pass ) ) ) - - -def test_global_attachment_and_error(robot_runner: AllureRobotRunner): - robot_runner.run_robotframework( - suite_literals={ - "global-attachment.robot": ( - """ - *** Settings *** - Library AllureLibrary - Library ./lib.py - Suite Setup Global Attach robot global attachment name=robot global - Suite Teardown Global Error robot global error - - *** Test Cases *** - Global Attachment - Add Globals From Code - """ - ), - }, - library_literals={ - "lib.py": ( - """ - import allure - - def add_globals_from_code(): - allure.global_attach(body="global body", name="global attachment") - allure.global_error("message only error") - """ - ), - }, - ) - - assert_that(robot_runner.allure_results.globals, has_length(4)) - - assert_that( - robot_runner.allure_results.globals, - has_item( - has_global_attachment_with_content( - robot_runner.allure_results.attachments, - equal_to("robot global attachment"), - name="robot global" - ) - ) - ) - assert_that( - robot_runner.allure_results.globals, - has_item( - has_global_attachment_with_content( - robot_runner.allure_results.attachments, - equal_to("global body"), - name="global attachment" - ) - ) - ) - assert_that( - robot_runner.allure_results.globals, - has_item( - has_global_error( - with_message_contains("robot global error") - ) - ) - ) - assert_that( - robot_runner.allure_results.globals, - has_item( - has_global_error( - with_message_contains("message only error") - ) - ) - ) diff --git a/tests/allure_robotframework/acceptance/allure_api/globals/__init__.py b/tests/allure_robotframework/acceptance/allure_api/globals/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/allure_robotframework/acceptance/allure_api/globals/globals_test.py b/tests/allure_robotframework/acceptance/allure_api/globals/globals_test.py new file mode 100644 index 00000000..e9152ce2 --- /dev/null +++ b/tests/allure_robotframework/acceptance/allure_api/globals/globals_test.py @@ -0,0 +1,77 @@ +""" ./allure-robotframework/examples/attachment.rst """ + +from hamcrest import assert_that, equal_to, has_item, has_length +from tests.allure_robotframework.robot_runner import AllureRobotRunner +from allure_commons_test.result import has_global_attachment_with_content +from allure_commons_test.result import has_global_error +from allure_commons_test.result import with_message_contains + + +def test_global_attachment_and_error(robot_runner: AllureRobotRunner): + robot_runner.run_robotframework( + suite_literals={ + "global-attachment.robot": ( + """ + *** Settings *** + Library AllureLibrary + Library ./lib.py + Suite Setup Global Attach robot global attachment name=robot global + Suite Teardown Global Error robot global error + + *** Test Cases *** + Global Attachment + Add Globals From Code + """ + ), + }, + library_literals={ + "lib.py": ( + """ + import allure + + def add_globals_from_code(): + allure.global_attach(body="global body", name="global attachment") + allure.global_error("message only error") + """ + ), + }, + ) + + assert_that(robot_runner.allure_results.globals, has_length(4)) + + assert_that( + robot_runner.allure_results.globals, + has_item( + has_global_attachment_with_content( + robot_runner.allure_results.attachments, + equal_to("robot global attachment"), + name="robot global" + ) + ) + ) + assert_that( + robot_runner.allure_results.globals, + has_item( + has_global_attachment_with_content( + robot_runner.allure_results.attachments, + equal_to("global body"), + name="global attachment" + ) + ) + ) + assert_that( + robot_runner.allure_results.globals, + has_item( + has_global_error( + with_message_contains("robot global error") + ) + ) + ) + assert_that( + robot_runner.allure_results.globals, + has_item( + has_global_error( + with_message_contains("message only error") + ) + ) + ) From d108e55e8707c3d479efe3f360d071c0f4e4ca7f Mon Sep 17 00:00:00 2001 From: Maksim Stepanov <17935127+delatrie@users.noreply.github.com> Date: Fri, 24 Apr 2026 06:18:33 +0700 Subject: [PATCH 4/7] refactor: fix lint issues --- allure-pytest/src/listener.py | 16 ++++++++++++++-- .../src/allure_commons/_allure.py | 14 ++++++++++++-- .../src/allure_commons/lifecycle.py | 18 +++++++++++++++--- .../src/allure_commons/reporter.py | 4 ++-- .../src/listener/allure_listener.py | 16 ++++++++++++++-- .../acceptance/globals_test.py | 2 -- 6 files changed, 57 insertions(+), 13 deletions(-) diff --git a/allure-pytest/src/listener.py b/allure-pytest/src/listener.py index bcee6a0f..10ec29df 100644 --- a/allure-pytest/src/listener.py +++ b/allure-pytest/src/listener.py @@ -259,11 +259,23 @@ def attach_file(self, source, name, attachment_type, extension): @allure_commons.hookimpl def global_attach_data(self, body, name, attachment_type, extension): - self.allure_logger.global_attach_data(uuid4(), body, name=name, attachment_type=attachment_type, extension=extension) + self.allure_logger.global_attach_data( + uuid4(), + body, + name=name, + attachment_type=attachment_type, + extension=extension, + ) @allure_commons.hookimpl def global_attach_file(self, source, name, attachment_type, extension): - self.allure_logger.global_attach_file(uuid4(), source, name=name, attachment_type=attachment_type, extension=extension) + self.allure_logger.global_attach_file( + uuid4(), + source, + name=name, + attachment_type=attachment_type, + extension=extension, + ) @allure_commons.hookimpl def global_error(self, message, trace): diff --git a/allure-python-commons/src/allure_commons/_allure.py b/allure-python-commons/src/allure_commons/_allure.py index ce759674..d22a0a94 100644 --- a/allure-python-commons/src/allure_commons/_allure.py +++ b/allure-python-commons/src/allure_commons/_allure.py @@ -220,10 +220,20 @@ def file(self, source, name=None, attachment_type=None, extension=None): class GlobalAttach: def __call__(self, body, name=None, attachment_type=None, extension=None): - plugin_manager.hook.global_attach_data(body=body, name=name, attachment_type=attachment_type, extension=extension) + plugin_manager.hook.global_attach_data( + body=body, + name=name, + attachment_type=attachment_type, + extension=extension, + ) def file(self, source, name=None, attachment_type=None, extension=None): - plugin_manager.hook.global_attach_file(source=source, name=name, attachment_type=attachment_type, extension=extension) + plugin_manager.hook.global_attach_file( + source=source, + name=name, + attachment_type=attachment_type, + extension=extension, + ) global_attach = GlobalAttach() diff --git a/allure-python-commons/src/allure_commons/lifecycle.py b/allure-python-commons/src/allure_commons/lifecycle.py index 04ae180a..e2c2251e 100644 --- a/allure-python-commons/src/allure_commons/lifecycle.py +++ b/allure-python-commons/src/allure_commons/lifecycle.py @@ -125,7 +125,11 @@ def stop_after_fixture(self, uuid=None): fixture.stop = now() def _attach(self, uuid, name=None, attachment_type=None, extension=None, parent_uuid=None): - file_name, mime_type = self.__resolve_attachment_filename_and_type(uuid, attachment_type=attachment_type, extension=extension) + file_name, mime_type = self.__resolve_attachment_filename_and_type( + uuid, + attachment_type=attachment_type, + extension=extension, + ) attachment = Attachment(source=file_name, name=name, type=mime_type) last_uuid = parent_uuid if parent_uuid else self._last_item_uuid(ExecutableItem) self._items[last_uuid].attachments.append(attachment) @@ -143,14 +147,22 @@ def attach_data(self, uuid, body, name=None, attachment_type=None, extension=Non plugin_manager.hook.report_attached_data(body=body, file_name=file_name) def global_attach_file(self, uuid, source, name=None, attachment_type=None, extension=None): - file_name, mime_type = self.__resolve_attachment_filename_and_type(uuid, attachment_type=attachment_type, extension=extension) + file_name, mime_type = self.__resolve_attachment_filename_and_type( + uuid, + attachment_type=attachment_type, + extension=extension, + ) plugin_manager.hook.report_attached_file(source=source, file_name=file_name) plugin_manager.hook.report_globals(globals_item=Globals(attachments=[ GlobalAttachment(source=file_name, name=name, type=mime_type, timestamp=now()) ])) def global_attach_data(self, uuid, body, name=None, attachment_type=None, extension=None): - file_name, mime_type = self.__resolve_attachment_filename_and_type(uuid, attachment_type=attachment_type, extension=extension) + file_name, mime_type = self.__resolve_attachment_filename_and_type( + uuid, + attachment_type=attachment_type, + extension=extension, + ) plugin_manager.hook.report_attached_data(body=body, file_name=file_name) plugin_manager.hook.report_globals(globals_item=Globals(attachments=[ GlobalAttachment(source=file_name, name=name, type=mime_type, timestamp=now()) diff --git a/allure-python-commons/src/allure_commons/reporter.py b/allure-python-commons/src/allure_commons/reporter.py index 15107349..7e7b7594 100644 --- a/allure-python-commons/src/allure_commons/reporter.py +++ b/allure-python-commons/src/allure_commons/reporter.py @@ -159,14 +159,14 @@ def attach_data(self, uuid, body, name=None, attachment_type=None, extension=Non plugin_manager.hook.report_attached_data(body=body, file_name=file_name) def global_attach_file(self, uuid, source, name=None, attachment_type=None, extension=None): - file_name, mime_type = self.__resolve_attachment_filename_and_type(uuid, attachment_type=attachment_type, extension=extension) + file_name, mime_type = self.__resolve_attachment_filename_and_type(uuid, attachment_type, extension) plugin_manager.hook.report_attached_file(source=source, file_name=file_name) plugin_manager.hook.report_globals(globals_item=Globals(attachments=[ GlobalAttachment(source=file_name, name=name, type=mime_type, timestamp=now()) ])) def global_attach_data(self, uuid, body, name=None, attachment_type=None, extension=None): - file_name, mime_type = self.__resolve_attachment_filename_and_type(uuid, attachment_type=attachment_type, extension=extension) + file_name, mime_type = self.__resolve_attachment_filename_and_type(uuid, attachment_type, extension) plugin_manager.hook.report_attached_data(body=body, file_name=file_name) plugin_manager.hook.report_globals(globals_item=Globals(attachments=[ GlobalAttachment(source=file_name, name=name, type=mime_type, timestamp=now()) diff --git a/allure-robotframework/src/listener/allure_listener.py b/allure-robotframework/src/listener/allure_listener.py index 732a814f..6be71a19 100644 --- a/allure-robotframework/src/listener/allure_listener.py +++ b/allure-robotframework/src/listener/allure_listener.py @@ -241,11 +241,23 @@ def attach_file(self, source, name, attachment_type, extension): @allure_commons.hookimpl def global_attach_data(self, body, name, attachment_type, extension): - self.lifecycle.global_attach_data(uuid4(), body, name=name, attachment_type=attachment_type, extension=extension) + self.lifecycle.global_attach_data( + uuid4(), + body, + name=name, + attachment_type=attachment_type, + extension=extension, + ) @allure_commons.hookimpl def global_attach_file(self, source, name, attachment_type, extension): - self.lifecycle.global_attach_file(uuid4(), source, name=name, attachment_type=attachment_type, extension=extension) + self.lifecycle.global_attach_file( + uuid4(), + source, + name=name, + attachment_type=attachment_type, + extension=extension, + ) @allure_commons.hookimpl def global_error(self, message, trace): diff --git a/tests/allure_pytest_bdd/acceptance/globals_test.py b/tests/allure_pytest_bdd/acceptance/globals_test.py index d2ba6bc8..a0bc2d06 100644 --- a/tests/allure_pytest_bdd/acceptance/globals_test.py +++ b/tests/allure_pytest_bdd/acceptance/globals_test.py @@ -8,8 +8,6 @@ from allure_commons_test.result import with_message_contains from tests.allure_pytest.pytest_runner import AllurePytestRunner -from tests.e2e import version_lt -from tests.e2e import version_gte def test_global_attachment_and_error_from_hook(allure_pytest_bdd_runner: AllurePytestRunner): From 224d26c12d9707626288e8aa6215a61f9f936cea Mon Sep 17 00:00:00 2001 From: Maksim Stepanov <17935127+delatrie@users.noreply.github.com> Date: Fri, 24 Apr 2026 06:23:50 +0700 Subject: [PATCH 5/7] fix(pytest): remove deprecated package check --- allure-pytest/setup.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/allure-pytest/setup.py b/allure-pytest/setup.py index de5ea954..64f0c1b9 100644 --- a/allure-pytest/setup.py +++ b/allure-pytest/setup.py @@ -1,18 +1,5 @@ import os -import sys from setuptools import setup -from pkg_resources import require, DistributionNotFound, VersionConflict - -try: - require("pytest-allure-adaptor") - print(""" - You have pytest-allure-adaptor installed. - You need to remove pytest-allure-adaptor from your site-packages - before installing allure-pytest, or conflicts may result. - """) - sys.exit() -except (DistributionNotFound, VersionConflict): - pass PACKAGE = "allure-pytest" From 523aaf037df513aaa6df5b032757851a90d50fe4 Mon Sep 17 00:00:00 2001 From: Maksim Stepanov <17935127+delatrie@users.noreply.github.com> Date: Fri, 24 Apr 2026 16:08:37 +0700 Subject: [PATCH 6/7] refactor: declare global_error overloads --- .../src/allure_commons/_allure.py | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/allure-python-commons/src/allure_commons/_allure.py b/allure-python-commons/src/allure_commons/_allure.py index d22a0a94..607e1cb8 100644 --- a/allure-python-commons/src/allure_commons/_allure.py +++ b/allure-python-commons/src/allure_commons/_allure.py @@ -239,19 +239,24 @@ def file(self, source, name=None, attachment_type=None, extension=None): global_attach = GlobalAttach() -def _resolve_global_error_details(error_or_message, trace=None): - if isinstance(error_or_message, BaseException): - return ( - format_exception(type(error_or_message), error_or_message), - format_traceback(error_or_message.__traceback__), - ) +@overload +def global_error(value: BaseException) -> None: + ... - return error_or_message, trace + +@overload +def global_error(value: str, trace: Union[str, None] = None) -> None: + ... -def global_error(error_or_message, trace=None): - message, error_trace = _resolve_global_error_details(error_or_message, trace=trace) - plugin_manager.hook.global_error(message=message, trace=error_trace) +def global_error(value, trace=None): + message = None + if isinstance(value, BaseException): + message = format_exception(type(value), value) + trace = format_traceback(value.__traceback__) + else: + message = value + plugin_manager.hook.global_error(message=message, trace=trace) class fixture: From db3f3dc5e5dbfc273fe6fe5d822f1b8af142f663 Mon Sep 17 00:00:00 2001 From: Maksim Stepanov <17935127+delatrie@users.noreply.github.com> Date: Fri, 24 Apr 2026 16:10:13 +0700 Subject: [PATCH 7/7] fix(robotframework): rename global_error parameter --- allure-robotframework/src/library/allure_library.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/allure-robotframework/src/library/allure_library.py b/allure-robotframework/src/library/allure_library.py index f06f019a..e9d4f7b1 100644 --- a/allure-robotframework/src/library/allure_library.py +++ b/allure-robotframework/src/library/allure_library.py @@ -27,5 +27,5 @@ def global_attach_file(source, name=None, attachment_type=None, extension=None): allure.global_attach.file(source, name=name, attachment_type=_attachment_type(attachment_type), extension=extension) -def global_error(error_or_message, trace=None): - allure.global_error(error_or_message, trace=trace) +def global_error(message, trace=None): + allure.global_error(message, trace=trace)