#!/usr/bin/env python3 """Self-check for infra/playwright-summary.py — stdlib asserts, no framework. Run: python3 infra/test_playwright_summary.py (also runs in `make unit`). A red e2e is only useful if the job summary says WHY it failed: #161 lost a 36-minute verify-stack job whose only surviving output was one ✘ line with no assertion detail. """ import importlib.util import io import json import os import tempfile from contextlib import redirect_stdout # The script's filename is not a valid module name, so load it by path. spec = importlib.util.spec_from_file_location( "playwright_summary", os.path.join(os.path.dirname(os.path.abspath(__file__)), "playwright-summary.py"), ) summary = importlib.util.module_from_spec(spec) spec.loader.exec_module(summary) def render(report): """Run the renderer over a report dict and return its markdown.""" with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False) as fh: json.dump(report, fh) path = fh.name try: out = io.StringIO() with redirect_stdout(out): summary.main(path) return out.getvalue() finally: os.unlink(path) def spec_entry(title, status, errors=()): return { "title": title, "file": "catalogus.spec.ts", "ok": status == "expected", "tests": [{"status": status, "results": [{"errors": [{"message": m} for m in errors]}]}], } def test_failing_spec_reports_why(): md = render({ "stats": {"expected": 4, "unexpected": 1, "flaky": 0, "skipped": 0, "duration": 108_000}, "suites": [{"file": "catalogus.spec.ts", "specs": [ spec_entry("a beheerder sees the published zaaktypen in the catalogus", "unexpected", ["locator.fill: Test timeout of 90000ms exceeded.\n" "Call log:\n - waiting for locator('#username')\n"]), ]}], }) assert "❌" in md, md # The point of the slice: the summary names the cause, not just the verdict. assert "Test timeout of 90000ms exceeded" in md, md assert "waiting for locator('#username')" in md, md # A multi-line Playwright error must not break out of its table row. assert not any(line.startswith("Call log:") for line in md.splitlines()), md def test_passing_run_stays_quiet(): md = render({ "stats": {"expected": 1, "unexpected": 0, "flaky": 0, "skipped": 0, "duration": 5_000}, "suites": [{"file": "catalogus.spec.ts", "specs": [spec_entry("a beheerder sees the catalogus", "expected")]}], }) assert "✅" in md, md assert "timeout" not in md.lower(), md def test_missing_report_is_not_a_crash(): out = io.StringIO() with redirect_stdout(out): rc = summary.main("/nonexistent/playwright-report.json") assert rc == 0 assert "did not reach the e2e step" in out.getvalue() if __name__ == "__main__": for name, fn in sorted(globals().items()): if name.startswith("test_") and callable(fn): fn() print(f" ok {name}") print("playwright-summary self-check passed")