Update json_to_md.py
Run Pytest with Allure and Coverage Reports / tests (push) Successful in 38s

This commit is contained in:
2025-07-15 11:08:26 +02:00
parent 2d7335a483
commit cbdff2120e
+94 -85
View File
@@ -25,34 +25,14 @@ def normalize_nodeid(nodeid):
return f"{file_part}#{func_part}"
return None
def write_json_value(f, value, indent=0):
prefix = " " * indent
if isinstance(value, dict):
if not value:
f.write(f"{prefix}{{}}\n")
else:
for k, v in value.items():
if isinstance(v, (dict, list)):
f.write(f"{prefix}{k}:\n")
write_json_value(f, v, indent + 1)
else:
# valeur simple, on écrit sur la même ligne
if v is None:
f.write(f"{prefix}{k}: None\n")
else:
f.write(f"{prefix}{k}: {v}\n")
elif isinstance(value, list):
if not value:
f.write(f"{prefix}[]\n")
else:
for item in value:
write_json_value(f, item, indent)
else:
if value is None:
f.write(f"{prefix}None\n")
else:
f.write(f"{prefix}{value}\n")
def write_details(f, summary, body, level=0):
margin = 18 * level # 18px de marge/indent par niveau
border = f"border-left: 2px solid #eee;" if level > 0 else ""
f.write(f'<div style="margin-left: {margin}px; {border} padding-left: 8px;">\n')
f.write(f"<details>\n<summary>{summary}</summary>\n\n")
f.write(body)
f.write("</details>\n")
f.write("</div>\n\n")
def load_allure_metadata(allure_test_cases_dir):
allure_data = {}
@@ -98,6 +78,7 @@ def json_to_md_nested(json_path, md_path, allure_dir=None):
f.write(f"- **Total Duration**: `{duration:.3f}`s\n" if duration else "- **Total Duration**: `None`\n")
f.write("\n")
# --------- Tests section ----------
if "tests" in data and "summary" in data:
test_counter = 1
for test in data["tests"]:
@@ -122,7 +103,8 @@ def json_to_md_nested(json_path, md_path, allure_dir=None):
count = len(tests_by_status[status])
emoji = "" if status == "passed" else ""
status_label = status.capitalize().replace('_', ' ')
f.write(f"<details>\n<summary>{emoji} {status_label} ({count})</summary>\n\n")
body_status = ""
grouped = defaultdict(lambda: defaultdict(list))
for test in tests_by_status[status]:
@@ -133,51 +115,74 @@ def json_to_md_nested(json_path, md_path, allure_dir=None):
grouped[filename][funcname].append(test)
for filename, funcs in grouped.items():
f.write(f"<details>\n<summary>📁 {filename}</summary>\n\n")
body_file = ""
for funcname, tests in funcs.items():
f.write(f"<details>\n<summary>🔧 Function: `{funcname}`</summary>\n\n")
body_func = ""
for test in sorted(tests, key=lambda x: x['global_number']):
f.write(f"<details>\n<summary>{emoji} #{test['global_number']}</summary>\n\n")
body_test = ""
nodeid = test.get("nodeid", "")
f.write(f"- **Status:** {emoji} `{status}`\n")
body_test += f"- **Status:** {emoji} `{status}`\n"
duration = test.get("call", {}).get("duration")
f.write(f"- **Duration:** `{duration:.6f}` s\n" if duration else "- **Duration:** `None`\n")
body_test += f"- **Duration:** `{duration:.6f}` s\n" if duration else "- **Duration:** `None`\n"
full_name = normalize_nodeid(nodeid)
allure_info = allure_data.get(full_name)
if allure_info:
if allure_info["parameters"]:
f.write("- **Parameters (Allure):**\n")
body_test += "- **Parameters (Allure):**\n"
for param in allure_info["parameters"]:
name = param.get("name")
val = param.get("value")
f.write(f" - `{name}` = `{val}`\n")
f.write("\n")
body_test += f" - `{name}` = `{val}`\n"
body_test += "\n"
if allure_info["severity"]:
f.write(f"- **Severity:** `{allure_info['severity']}`\n")
body_test += f"- **Severity:** `{allure_info['severity']}`\n"
for phase in ['setup', 'call', 'teardown']:
if phase in test:
f.write(f"\n### 🔧 {phase.capitalize()} Phase\n\n")
body_test += f"\n### 🔧 {phase.capitalize()} Phase\n\n"
for field, value in test[phase].items():
if value is None:
f.write(f"- **{field.capitalize()}:** None\n")
body_test += f"- **{field.capitalize()}:** None\n"
else:
f.write(f"<details>\n<summary>📌 {field.capitalize()}</summary>\n\n")
f.write("```\n")
write_json_value(f, value)
f.write("```\n")
f.write("</details>\n\n")
f.write("</details>\n\n")
f.write("</details>\n\n")
f.write("</details>\n\n")
f.write("</details>\n\n")
phase_body = "```\n"
phase_body += stringify(value) + "\n"
phase_body += "```\n"
# détail du champ (champ imbriqué, donc niveau +1)
write_details(
f,
f"📌 {field.capitalize()}",
phase_body,
level=4
)
write_details(
f,
f"{emoji} #{test['global_number']}",
body_test,
level=3
)
write_details(
f,
f"🔧 Function: `{funcname}`",
"", # contenu injecté dans les tests
level=2
)
write_details(
f,
f"📁 {filename}",
"", # idem, contenu injecté dans les fonctions
level=1
)
write_details(
f,
f"{emoji} {status_label} ({count})",
"", # contenu injecté dans les fichiers
level=0
)
# ---------- Collectors section -----------
if "collectors" in data:
f.write("## 📚 Collected files\n")
grouped = defaultdict(list)
for collector in data["collectors"]:
nodeid = collector.get("nodeid", "unknown")
@@ -188,8 +193,8 @@ def json_to_md_nested(json_path, md_path, allure_dir=None):
for folder, collectors in grouped.items():
has_fail = any(c.get("outcome") != "passed" for c in collectors)
folder_emoji = "" if not has_fail else ""
f.write(f"<details>\n<summary>{folder_emoji} {folder} ({len(collectors)} tests)</summary>\n\n")
body_collectors = ""
outputs = []
for collector in collectors:
outcome = collector.get("outcome", "unknown")
@@ -204,9 +209,9 @@ def json_to_md_nested(json_path, md_path, allure_dir=None):
) + "\n```")
if outputs:
f.write("### 🧾 Error or Result Summary\n\n")
body_collectors += "### 🧾 Error or Result Summary\n\n"
for out in outputs:
f.write(out + "\n")
body_collectors += out + "\n"
collectors_sorted = sorted(collectors, key=lambda c: c.get("nodeid", "").split("[")[0])
for collector in collectors_sorted:
@@ -214,40 +219,49 @@ def json_to_md_nested(json_path, md_path, allure_dir=None):
emoji = "" if outcome == "passed" else ""
nodeid = collector.get("nodeid", "unknown")
short_node = nodeid.split("[")[0]
f.write(f"<details>\n<summary>{emoji} {short_node}</summary>\n\n")
f.write(f"- **Outcome:** `{outcome}`\n")
body_coll = f"- **Outcome:** `{outcome}`\n"
other_keys = {k: v for k, v in collector.items() if k not in {"nodeid", "outcome"}}
if other_keys:
f.write("- **Details:**\n")
f.write("```\n")
body_coll += "- **Details:**\n"
body_coll += "```\n"
for k, v in other_keys.items():
f.write(f"{k}:\n")
try:
if v is None:
f.write(" None\n")
else:
write_json_value(f, v, indent=1)
except Exception as e:
f.write(f" <error formatting value: {e}>\n")
f.write("\n")
f.write("```\n")
body_coll += f"{k}:\n"
if v is None:
body_coll += " None\n"
else:
body_coll += stringify(v) + "\n"
body_coll += "\n"
body_coll += "```\n"
else:
f.write("- **Details:** `None`\n")
f.write("</details>\n\n")
f.write("</details>\n\n")
body_coll += "- **Details:** `None`\n"
write_details(
f,
f"{emoji} {short_node}",
body_coll,
level=2
)
write_details(
f,
f"{folder_emoji} {folder} ({len(collectors)} tests)",
body_collectors,
level=1
)
# ---------- Warnings section -----------
if 'warnings' in data and data['warnings']:
f.write("## ⚠️ Warnings\n\n")
for i, warning in enumerate(data['warnings'], 1):
f.write(f"<details>\n<summary>Warning #{i}</summary>\n\n")
f.write("```\n")
warn_body = "```\n"
for k, v in warning.items():
f.write(f"{k}: {v}\n")
f.write("```\n")
f.write("</details>\n\n")
warn_body += f"{k}: {v}\n"
warn_body += "```\n"
write_details(
f,
f"Warning #{i}",
warn_body,
level=1
)
def run_pytest_and_generate_banner_with_logs(md_path, log_path):
exit_code = pytest.main(["tests/"])
@@ -321,8 +335,6 @@ def run_pytest_and_generate_banner_with_logs(md_path, log_path):
except Exception as e:
print(f"❌ Failed to update markdown report: {e}")
def main():
parser = argparse.ArgumentParser(description="Convert JSON test results to Markdown.")
parser.add_argument("--input", required=True, help="Path to pytest JSON file")
@@ -331,15 +343,12 @@ def main():
parser.add_argument("--log", required=False, help="Path to raw pytest output log (optional)")
parser.add_argument("--json", required=False, help="Path to pytest-report.json")
args = parser.parse_args()
json_to_md_nested(args.input, args.output, args.allure_dir)
run_pytest_and_generate_banner_with_logs(md_path=args.output, log_path=args.log)
print(f"✅ Report generated at {args.output}")
if __name__ == "__main__":
main()
main()