From 3214932098d86a33ece538061e23607768094c36 Mon Sep 17 00:00:00 2001 From: Dominik Klotz Date: Thu, 20 Nov 2025 00:46:07 +0100 Subject: [PATCH 1/5] feat: add allure reporter --- src/askui/reporting.py | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/askui/reporting.py b/src/askui/reporting.py index 524ff630..358b10e7 100644 --- a/src/askui/reporting.py +++ b/src/askui/reporting.py @@ -1,4 +1,5 @@ import base64 +import io import json import platform import random @@ -347,3 +348,40 @@ def generate(self) -> None: f"{random.randint(0, 1000):03}.html" ) report_path.write_text(html) + + +class AllureReporter(Reporter): + + def __init__(self) -> None: + try: + import allure # type: ignore + except ImportError: + raise ImportError( + "AllureReporter requires the allure-python-commons' , 'allure-pytest' or 'allure-behave' package. " + "Please install it via 'pip install allure-python-commons'." + ) from None + + self.allure = allure + + @override + def add_message( + self, + role: str, + content: Union[str, dict[str, Any], list[Any]], + image: Optional[Image.Image | list[Image.Image]] = None, + ) -> None: + with self.allure.step(f"{role}: {str(content)}"): + if image: + images = image if isinstance(image, list) else [image] + for img in images: + img_bytes = io.BytesIO() + img.save(img_bytes, format='PNG') + self.allure.attach( + img_bytes.getvalue(), + name="screenshot", + attachment_type=self.allure.attachment_type.PNG, + ) + + @override + def generate(self) -> None: + pass From 5268c5f3438f630822aceb23a350c2aa1cf8d322 Mon Sep 17 00:00:00 2001 From: Dominik Klotz Date: Thu, 20 Nov 2025 00:57:17 +0100 Subject: [PATCH 2/5] Fix lint errors --- src/askui/reporting.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/askui/reporting.py b/src/askui/reporting.py index 358b10e7..1be6777a 100644 --- a/src/askui/reporting.py +++ b/src/askui/reporting.py @@ -351,15 +351,15 @@ def generate(self) -> None: class AllureReporter(Reporter): - def __init__(self) -> None: try: import allure # type: ignore except ImportError: - raise ImportError( - "AllureReporter requires the allure-python-commons' , 'allure-pytest' or 'allure-behave' package. " + msg = ( + "AllureReporter requires the allure-python-commons', 'allure-pytest' or 'allure-behave' package. " "Please install it via 'pip install allure-python-commons'." - ) from None + ) + raise ImportError(msg) from None self.allure = allure @@ -375,7 +375,7 @@ def add_message( images = image if isinstance(image, list) else [image] for img in images: img_bytes = io.BytesIO() - img.save(img_bytes, format='PNG') + img.save(img_bytes, format="PNG") self.allure.attach( img_bytes.getvalue(), name="screenshot", From 58f1ce4084ca5161670394f770cf13445ee4d10d Mon Sep 17 00:00:00 2001 From: Dominik Klotz Date: Thu, 20 Nov 2025 01:02:37 +0100 Subject: [PATCH 3/5] add pydoc --- src/askui/reporting.py | 79 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 78 insertions(+), 1 deletion(-) diff --git a/src/askui/reporting.py b/src/askui/reporting.py index 1be6777a..2d83d335 100644 --- a/src/askui/reporting.py +++ b/src/askui/reporting.py @@ -351,7 +351,46 @@ def generate(self) -> None: class AllureReporter(Reporter): + """A reporter that integrates with Allure Framework for test reporting. + + This reporter creates Allure test reports by recording agent interactions as test steps + and attaching screenshots. It requires one of the allure Python packages to be installed. + + The AllureReporter uses eager loading - it immediately checks for the allure dependency + during initialization and raises an ImportError if not found. + + Raises: + ImportError: If none of the required allure packages are installed during initialization. + + Example: + ```python + from askui import VisionAgent + from askui.reporting import AllureReporter + + with VisionAgent(reporter=[AllureReporter()]) as agent: + agent.act("Click the login button") + # Each action becomes an allure step with screenshots attached + ``` + + Note: + This reporter requires one of the following packages to be installed: + - allure-python-commons + - allure-pytest + - allure-behave + + Install via: `pip install allure-python-commons` + """ + def __init__(self) -> None: + """Initialize the AllureReporter and import the allure module. + + Performs eager loading of the allure module. If the module is not available, + raises ImportError immediately during initialization. + + Raises: + ImportError: If the allure module cannot be imported. The error message + provides installation instructions. + """ try: import allure # type: ignore except ImportError: @@ -370,6 +409,37 @@ def add_message( content: Union[str, dict[str, Any], list[Any]], image: Optional[Image.Image | list[Image.Image]] = None, ) -> None: + """Add a message to the Allure report as a test step. + + Creates an Allure test step with the provided role and content. If images + are provided, they are attached to the step as PNG screenshots. + + Args: + role (str): The role of the message sender (e.g., "User", "Assistant"). + This becomes part of the step name in the format "{role}: {content}". + content (str | dict | list): The message content. Complex objects are + converted to strings for the step name. + image (PIL.Image.Image | list[PIL.Image.Image], optional): PIL Image(s) + to attach as screenshots. Each image is converted to PNG format and + attached with the name "screenshot". + + Example: + ```python + reporter = AllureReporter() + + # Add a simple text message + reporter.add_message("User", "Click the submit button") + + # Add message with screenshot + from PIL import Image + screenshot = Image.open("screenshot.png") + reporter.add_message("Agent", "Clicked button", image=screenshot) + + # Add message with multiple screenshots + images = [Image.open("before.png"), Image.open("after.png")] + reporter.add_message("Agent", "Action completed", image=images) + ``` + """ with self.allure.step(f"{role}: {str(content)}"): if image: images = image if isinstance(image, list) else [image] @@ -384,4 +454,11 @@ def add_message( @override def generate(self) -> None: - pass + """Generate the final Allure report. + + For AllureReporter, this method is a no-op since Allure reports are generated + in real-time as steps are added via `add_message()`. The actual report + generation is handled by the Allure framework itself when tests complete. + + This method exists to satisfy the Reporter interface contract. + """ From 5c83b3413e68da8a3804332d82eb296e8b1be74b Mon Sep 17 00:00:00 2001 From: Dominik Klotz Date: Thu, 20 Nov 2025 01:04:04 +0100 Subject: [PATCH 4/5] remove unnessary pydocs --- src/askui/reporting.py | 42 +++--------------------------------------- 1 file changed, 3 insertions(+), 39 deletions(-) diff --git a/src/askui/reporting.py b/src/askui/reporting.py index 2d83d335..c58df432 100644 --- a/src/askui/reporting.py +++ b/src/askui/reporting.py @@ -409,37 +409,7 @@ def add_message( content: Union[str, dict[str, Any], list[Any]], image: Optional[Image.Image | list[Image.Image]] = None, ) -> None: - """Add a message to the Allure report as a test step. - - Creates an Allure test step with the provided role and content. If images - are provided, they are attached to the step as PNG screenshots. - - Args: - role (str): The role of the message sender (e.g., "User", "Assistant"). - This becomes part of the step name in the format "{role}: {content}". - content (str | dict | list): The message content. Complex objects are - converted to strings for the step name. - image (PIL.Image.Image | list[PIL.Image.Image], optional): PIL Image(s) - to attach as screenshots. Each image is converted to PNG format and - attached with the name "screenshot". - - Example: - ```python - reporter = AllureReporter() - - # Add a simple text message - reporter.add_message("User", "Click the submit button") - - # Add message with screenshot - from PIL import Image - screenshot = Image.open("screenshot.png") - reporter.add_message("Agent", "Clicked button", image=screenshot) - - # Add message with multiple screenshots - images = [Image.open("before.png"), Image.open("after.png")] - reporter.add_message("Agent", "Action completed", image=images) - ``` - """ + """Add a message as an Allure step with optional screenshots.""" with self.allure.step(f"{role}: {str(content)}"): if image: images = image if isinstance(image, list) else [image] @@ -454,11 +424,5 @@ def add_message( @override def generate(self) -> None: - """Generate the final Allure report. - - For AllureReporter, this method is a no-op since Allure reports are generated - in real-time as steps are added via `add_message()`. The actual report - generation is handled by the Allure framework itself when tests complete. - - This method exists to satisfy the Reporter interface contract. - """ + """No-op for AllureReporter as reports are generated in real-time.""" + pass From 370c72b51501f2749141d32d0f7218db8abcc2f7 Mon Sep 17 00:00:00 2001 From: Dominik Klotz <105296959+programminx-askui@users.noreply.github.com> Date: Thu, 20 Nov 2025 11:09:14 +0100 Subject: [PATCH 5/5] remove pass --- src/askui/reporting.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/askui/reporting.py b/src/askui/reporting.py index c58df432..9d4e56ed 100644 --- a/src/askui/reporting.py +++ b/src/askui/reporting.py @@ -425,4 +425,3 @@ def add_message( @override def generate(self) -> None: """No-op for AllureReporter as reports are generated in real-time.""" - pass