diff --git a/src/agentunit/reporting/html.py b/src/agentunit/reporting/html.py
new file mode 100644
index 0000000..2bbabba
--- /dev/null
+++ b/src/agentunit/reporting/html.py
@@ -0,0 +1,102 @@
+from __future__ import annotations
+
+from html import escape
+from typing import TYPE_CHECKING
+
+
+if TYPE_CHECKING:
+ from agentunit.reporting.results import SuiteResult
+
+
+def render_html_report(result: SuiteResult) -> str:
+ scenarios = result.scenarios
+
+ total_runs = sum(len(s.runs) for s in scenarios)
+ failed_runs = sum(1 for s in scenarios for r in s.runs if not r.success)
+ passed_runs = total_runs - failed_runs
+ duration = (result.finished_at - result.started_at).total_seconds()
+
+ scenario_rows: list[str] = []
+
+ for scenario in scenarios:
+ scenario_rows.append(
+ f"""
+
+ | {escape(scenario.name)} |
+ {scenario.success_rate:.2%} |
+ {len(scenario.runs)} |
+
+ """
+ )
+
+ return f"""
+
+
+
+ AgentUnit Report
+
+
+
+
+
+AgentUnit HTML Report
+
+ Duration: {duration:.2f}s · Total runs: {total_runs}
+
+
+Summary
+Passed: {passed_runs} · Failed: {failed_runs}
+
+
+
+
+Scenarios
+
+
+ | Name |
+ Success rate |
+ Runs |
+
+ {"".join(scenario_rows)}
+
+
+
+
+"""
diff --git a/src/agentunit/reporting/results.py b/src/agentunit/reporting/results.py
index 06f5f06..1228b7b 100644
--- a/src/agentunit/reporting/results.py
+++ b/src/agentunit/reporting/results.py
@@ -9,6 +9,8 @@
from pathlib import Path
from typing import TYPE_CHECKING
+from agentunit.reporting.html import render_html_report
+
if TYPE_CHECKING:
from collections.abc import Iterable
@@ -128,6 +130,16 @@ def to_junit(self, path: str | Path) -> Path:
tree.write(target, encoding="utf-8", xml_declaration=True)
return target
+ def to_html(self, path: str | Path) -> Path:
+ """
+ Export results as a self-contained HTML report.
+ """
+ target = Path(path)
+ target.parent.mkdir(parents=True, exist_ok=True)
+ html = render_html_report(self)
+ target.write_text(html, encoding="utf-8")
+ return target
+
def merge_results(results: Iterable[SuiteResult]) -> SuiteResult:
results = list(results)