diff --git a/generate_static_allure.py b/generate_static_allure.py index 74d2407d..038cf9ab 100644 --- a/generate_static_allure.py +++ b/generate_static_allure.py @@ -1,101 +1,100 @@ -import json, os +import json +import os +from pathlib import Path +from collections import defaultdict import matplotlib.pyplot as plt -def parse_results(path="allure-results"): +RESULTS_DIR = Path("allure-results") +REPORT_DIR = Path("ci-reports") +REPORT_DIR.mkdir(parents=True, exist_ok=True) + +def parse_allure_results(): results = [] - stats = {"passed": 0, "failed": 0, "broken": 0, "skipped": 0} - failures = [] + for file in RESULTS_DIR.glob("*.json"): + if file.name.startswith("result"): + with open(file) as f: + results.append(json.load(f)) + return results - for file in os.listdir(path): - if file.endswith("-result.json"): - with open(os.path.join(path, file)) as f: - d = json.load(f) - name = d.get("fullName", d.get("name", "Unnamed")) - status = d.get("status", "unknown") - duration = (d.get("stop", 0) - d.get("start", 0)) / 1000 - stats[status] = stats.get(status, 0) + 1 +def summarize_results(results): + summary = {"passed": 0, "failed": 0, "skipped": 0, "total": 0} + durations = [] + grouped = defaultdict(list) - parameters = d.get("parameters", []) - details = d.get("statusDetails", {}) + for res in results: + status = res.get("status", "unknown") + summary["total"] += 1 + summary[status] += 1 + time = res.get("stop", 0) - res.get("start", 0) + durations.append((res["name"], time / 1000)) + grouped[res["fullName"]].append({ + "name": res["name"], + "status": status, + "time": time / 1000, + "parameters": res.get("parameters", []), + }) + return summary, durations, grouped - results.append((name, status, duration)) +def generate_donut_chart(summary): + labels = ["Passed", "Failed", "Skipped"] + sizes = [summary["passed"], summary["failed"], summary["skipped"]] + colors = ["#8BC34A", "#F44336", "#FFC107"] + total = sum(sizes) - if status in ["failed", "broken"]: - failures.append({ - "name": name, - "status": status, - "duration": duration, - "parameters": parameters, - "message": details.get("message", "No message"), - "trace": details.get("trace", "No trace") - }) - - return results, stats, failures - -def donut_plot(stats, output="summary.png"): - total = sum(stats.values()) - passed = stats.get("passed", 0) - success_rate = passed / total if total else 0 fig, ax = plt.subplots() - ax.pie([success_rate, 1 - success_rate], startangle=90, - colors=["#96d38c", "#f5543d"], wedgeprops={'width':0.3}) - ax.text(0, 0, f"{success_rate*100:.1f}%", ha='center', va='center', fontsize=20) - plt.axis("equal") - plt.savefig(output, bbox_inches='tight') + ax.pie(sizes, labels=None, colors=colors, startangle=90, + wedgeprops=dict(width=0.3)) + ax.text(0, 0, f'{summary["passed"]/total*100:.1f}%', ha='center', va='center', fontsize=20) + plt.savefig(REPORT_DIR / "summary.png", bbox_inches='tight') plt.close() -def timeline_plot(results, output="timeline.png"): - names = [r[0] for r in results] - durations = [r[2] for r in results] - plt.figure(figsize=(8, len(results) * 0.3)) - plt.barh(names, durations, color="#76b5c5") +def generate_timeline_chart(durations): + names, times = zip(*sorted(durations, key=lambda x: x[1], reverse=True)) + plt.figure(figsize=(10, max(4, len(times) * 0.3))) + plt.barh(names, times, color="#2196F3") plt.xlabel("Duration (s)") - plt.title("โฑ Test Duration Timeline") + plt.title("Test Execution Timeline") plt.tight_layout() - plt.savefig(output) + plt.savefig(REPORT_DIR / "timeline.png") plt.close() -def markdown_report(stats, results, failures, md="report.md"): - with open(md, "w") as f: - f.write("# ๐Ÿงช Test Report\n\n") - f.write("## โœ… Summary\n\n") - f.write("![Success Rate](summary.png)\n\n") - for k, v in stats.items(): - f.write(f"- **{k.capitalize()}**: {v}\n") +def format_parameters(params): + return ", ".join(f"{p['name']}={p['value']}" for p in params) - f.write("\n## โฑ Timeline\n\n") - f.write("![Timeline](timeline.png)\n\n") +def write_report(summary, grouped): + with open(REPORT_DIR / "report.md", "w") as f: + f.write(f"# ๐Ÿงช Test Summary\n\n") + f.write(f"![Summary](summary.png)\n\n") + f.write(f"![Timeline](timeline.png)\n\n") + f.write(f"**Total:** {summary['total']} | โœ… Passed: {summary['passed']} | โŒ Failed: {summary['failed']} | โš ๏ธ Skipped: {summary['skipped']}\n\n") + f.write("
\n๐Ÿ“‹ Test Details\n\n") + for suite, tests in grouped.items(): + f.write(f"
\n{suite}\n\n") + for test in tests: + emoji = "โœ…" if test["status"] == "passed" else "โŒ" if test["status"] == "failed" else "โš ๏ธ" + params = format_parameters(test["parameters"]) + f.write(f"- {emoji} `{test['name']}` ({test['time']}s)") + if params: + f.write(f" \n โ†ช๏ธ *Params:* {params}") + f.write("\n") + f.write("
\n\n") + f.write("
\n") - f.write("\n## ๐Ÿ“‹ Detailed Results\n") - for name, status, duration in results: - emoji = {"passed": "โœ…", "failed": "โŒ", "broken": "โš ๏ธ", "skipped": "โญ๏ธ"}.get(status, "โ“") - f.write(f"- {emoji} `{name}` โ†’ **{status.upper()}**, {duration:.2f}s\n") +def write_failures(grouped): + with open(REPORT_DIR / "failures.md", "w") as f: + f.write("# โŒ Failed Tests\n\n") + for suite, tests in grouped.items(): + for test in tests: + if test["status"] == "failed": + f.write(f"- `{suite}::{test['name']}`\n") - f.write("\n## โŒ Failures / Errors\n") - if failures: - f.write("For details, see [failures.md](failures.md)\n") - else: - f.write("No failures! ๐ŸŽ‰\n") +def main(): + results = parse_allure_results() + summary, durations, grouped = summarize_results(results) + generate_donut_chart(summary) + generate_timeline_chart(durations) + write_report(summary, grouped) + write_failures(grouped) -def failure_details(failures, output="failures.md"): - with open(output, "w") as f: - f.write("# โŒ Detailed Failures\n\n") - for fail in failures: - f.write(f"## ๐Ÿ”ป {fail['name']}\n") - f.write(f"**Status**: {fail['status'].upper()}\n\n") - f.write(f"**Duration**: {fail['duration']:.2f}s\n\n") - if fail['parameters']: - f.write("**Parameters:**\n") - for p in fail['parameters']: - f.write(f"- `{p.get('name', '?')}`: `{p.get('value', '?')}`\n") - else: - f.write("_No parameters._\n") - f.write("\n**Message:**\n```\n" + fail['message'] + "\n```\n") - f.write("\n**Trace:**\n```\n" + fail['trace'] + "\n```\n\n---\n\n") - -# Full pipeline -results, stats, failures = parse_results() -donut_plot(stats) -timeline_plot(results) -markdown_report(stats, results, failures) -failure_details(failures) +if __name__ == "__main__": + main()