Update generate_static_allure.py
Run Pytest and Serve Report via LocalTunnel / tests (push) Failing after 54s
Run Pytest and Serve Report via LocalTunnel / tests (push) Failing after 54s
This commit is contained in:
+82
-83
@@ -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("\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("\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"\n\n")
|
||||
f.write(f"\n\n")
|
||||
f.write(f"**Total:** {summary['total']} | ✅ Passed: {summary['passed']} | ❌ Failed: {summary['failed']} | ⚠️ Skipped: {summary['skipped']}\n\n")
|
||||
f.write("<details>\n<summary><strong>📋 Test Details</strong></summary>\n\n")
|
||||
for suite, tests in grouped.items():
|
||||
f.write(f"<details>\n<summary><code>{suite}</code></summary>\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("</details>\n\n")
|
||||
f.write("</details>\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()
|
||||
|
||||
Reference in New Issue
Block a user