feat(3 U5.1+U5.2): per-recipe latest-level badge endpoint /badge/<recipe>.svg (R6, level-coloured, status fallback) + complete docs/results-ux.md §3-5 (card/screenshot/PR-comment/badge-embedding, R8); +2 badge unit tests
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
autonomic-bot
2026-05-31 10:04:14 +00:00
parent 9ca39dc179
commit 91a69b8971
3 changed files with 100 additions and 22 deletions

View File

@ -97,20 +97,64 @@ run's exit code (cosmetics never block the pipeline, R7).
## 3. Summary card + app screenshot (R3/R4)
<!-- TODO(U2/U1): finalize once wired — the card renderer (harness/card.py) builds an HTML results
card (recipe+version, level badge, per-stage/per-test ✔/✘ table, embedded app screenshot) and renders
it to PNG via the harness Playwright browser; the screenshot (harness/screenshot.py) is captured from
the live app before teardown, secret-safe (landing page by default; recipes needing a post-login view
opt into a SCREENSHOT hook that avoids credential pages). Document the stable serving URL
(/runs/<run_id>/summary.png) once the dashboard serves the artifact dir. -->
**App screenshot** (`runner/harness/screenshot.py`). After the app deploys and passes health/readiness
and **before any tier mutates state or teardown runs**, the harness captures a real Playwright
screenshot of the live app and writes `screenshot.png` to the run dir. It is **secret-safe by
default**: it shoots the **landing page** (login/setup forms show input *fields*, not secret values),
viewport-only (`full_page=False`, no scroll into a secrets panel), and the harness never auto-fills an
install wizard. A recipe whose landing page is uninformative may opt into a post-login view via an
optional `SCREENSHOT` hook in `tests/<recipe>/recipe_meta.py` — **that hook owns the no-credential-page
guarantee**. Capture is **best-effort**: any error returns `None`, writes no file, and never blocks the
run (R7); `results.json.screenshot` is set only when a file was actually produced.
**Summary card** (`runner/harness/card.py`). After `results.json` is written, the harness builds an
HTML results card — recipe + version, the level badge, a per-stage/per-test ✔/✘ table with timings,
the embedded app screenshot (base64 data-URI so the PNG is self-contained), and the invariant flags —
and screenshots that HTML to `summary.png` via the harness Playwright browser. The card **reports
`results.json` verbatim — it computes nothing**, so it can never show a run greener than its tests
(cardinal guardrail). Rendering is best-effort (returns `None` on failure → no card, run unaffected).
**Stable URLs.** The dashboard serves the run artifact dir read-only at:
```
https://ci.commoninternet.net/runs/<run_id>/summary.png # the card
https://ci.commoninternet.net/runs/<run_id>/screenshot.png # the app screenshot
https://ci.commoninternet.net/runs/<run_id>/badge.svg # the per-run level badge
https://ci.commoninternet.net/runs/<run_id>/results.json # the raw data
```
`<run_id>` is the Drone build number. The route is whitelist + traversal-guarded (filenames from a
fixed set; `run_id` charset-restricted; realpath must stay inside the runs dir) and read-only.
## 4. PR comment (R2)
<!-- TODO(U3): document the YunoHost-shaped comment — 🌻 marker + level/status badge + summary card
image, both linking to the run/dashboard; one comment per PR, updated in place; re-`!testme` refreshes
it; falls back to a text comment if image rendering fails. -->
On a `!testme` run the comment-bridge (`bridge/bridge.py`) maintains **one comment per PR, updated in
place** (it carries a hidden `<!-- cc-ci:testme -->` marker so re-`!testme` finds and refreshes the
same comment rather than stacking new ones):
1. **On start** — a 🌻 + ⏳ placeholder: `testing <recipe> @ <sha>` + a live-logs link, "level pending".
2. **On completion** — the same comment is edited to the YunoHost-shaped result: 🌻 + a **level badge**
image + the **summary card** image, **both linking to the run**, plus full-logs/dashboard links.
If the rendered card isn't served (render failed, build didn't finish), the comment **falls back to a
compact text verdict** with the run link (the bridge checks artifact availability with a cheap HEAD
request) — R7: a cosmetics failure degrades to text, never a broken image, never affecting the verdict.
## 5. Badges (R6) + how to embed one
<!-- TODO(U2/U5): document the per-recipe level/status SVG badge endpoint and the markdown snippet to
embed it in a recipe README. -->
Two SVG badge endpoints, both shields-style and coloured by level (`level_color`):
- **Per-recipe latest-level** (for a recipe README): `https://ci.commoninternet.net/badge/<recipe>.svg`
`cc-ci: <recipe> | level N` for that recipe's most recent run (falls back to a status badge if the
recipe has no level yet). Re-rendered live from the latest `results.json`.
- **Per-run** (pinned to one run, e.g. in the PR comment):
`https://ci.commoninternet.net/runs/<run_id>/badge.svg`.
Embed the per-recipe badge in a recipe README (Markdown), linking to the cc-ci dashboard:
```markdown
[![cc-ci level](https://ci.commoninternet.net/badge/<recipe>.svg)](https://ci.commoninternet.net/recipe/<recipe>)
```
The link target `…/recipe/<recipe>` is that recipe's run-history page (level/version/status per run,
with a link to each run's summary card).