# LEDGER — System Tests screen v2 (batch 1: WP1+WP2+WP4)

**Feature:** ترقية شاشة اختبارات النظام — أقسام + تشغيل انتقائي + تفاصيل/drill-down + مقياس
**Date:** 2026-07-15 · **Install:** moonui · BE `hazemdev` · FE `hazemdev` · dev `moonui_dev_be`
**Source plan:** https://moonui.elbaset.com/his-analysis/e2e-tests-screen-v2-plan.html (approved; owner chose BATCH 1 = WP1+WP2+WP4, WP3 deferred)
**Owner acceptance:** owner opens `/core/e2e-tests`, sees scenarios grouped by module in collapsible sections, can Run **all / a group / a single test**, clicks a test to see its description + steps, and the page scales to hundreds via virtual scroll.

## Decisions (owner defaults — do NOT re-litigate)
- **Group by MODULE** primarily (LIS/Clinic/…) via Playwright tags (`@lis @results @smoke`) + optional type tags. Parse group from tags.
- **Filter passed to runner** as `--grep <tag|title>` in **ARRAY form** (injection-safe — the runner already uses array-form Process). Record filter in a new `e2e_runs.filter` column (migration, run on dev, never fresh).
- **JSON column** for scenarios (not a separate table) for now.
- **Keep single-flight** on the runner.
- ⛔ **WP3 (live streaming reporter + failure-screenshot transfer/display) is DEFERRED to batch 2** — do NOT build now, but WP4 must not block it (leave room in the model/layout for per-step live updates + a screenshot slot).
- Fable emergency-only. Codex per WP (native if bwrap-blocked). CDK virtual scroll (`@angular/cdk` v21 present).

## Facts (verified)
- BE parser `RunE2eCommand::parse*` emits `scenarios: [{title, status, duration_ms}]`; runs `['npx','playwright','test','--config',cfg]` array-form (add `--grep`,filter here). Command signature `e2e:run {run}` → add `{--filter=}`. Controller `store` writes the run row + spawns detached.
- FE `E2eScenario {title,status,duration_ms}`; `E2eRun {..scenarios[]}`; screen `features/e2e-tests/` (144 ts / 87 html), signal-based, polls `latest()`.
- Specs use `test.step('...')` already (esp. lis-result-review-flow) — steps exist to surface.

## WP table
| WP | Scope (one dense line) | Repo | Depends | Review | Migration? | Status | Commits |
|----|------------------------|------|---------|--------|-----------|--------|---------|
| WP1 | Tag + describe each existing spec: add `@lis @smoke`/`@results` tags to test titles + a short description annotation; keep titles readable | specs (FE repo) | — | Codex/native | no | ✅ done | FE `b6189a4` — tags @lis @smoke/@results + description annotation; grep --list verified (@lis=2, @results=1); specs still green (1 pass/1 flaky-retry-pass). **JSON shape for WP4:** tags→`specs[].tags[]` (no @), description→`tests[].annotations[type==description]`, steps→`results[].steps[]` (test.step only). |
| WP2 | BE runner filter: `store` accepts optional `filter`; `RunE2eCommand {--filter=}` → `--grep <filter>` array-form; migration `e2e_runs.filter` col (run on dev); Pest for filtered run + injection-safety | BE | WP1 | Codex/native | **yes** (filter col) | ✅ done | BE `9a7e2b693` — migration `e2e_runs.filter` ran on dev; `--grep` array-form (cmd) + escapeshellarg (spawn), filter capped 200, latest() surfaces it. **Live verified:** POST {filter:@results} → total=1 passed=1. Pest 7 pass (+fixed 2 pre-existing UserFactory-owner-role fails). Native security review clean.
| WP4 | FE redesign `features/e2e-tests`: collapsible module groups (parse group from tag), CDK virtual scroll, per-test drill-down (description+steps) on click, Run buttons (all/group/single → run(filter)); extend `E2eScenario` (group,description,steps?); `e2e-runner.service.ts run(filter?)`; i18n | FE | WP2 | Codex/native | no | ✅ done | FE `6bab294` + BE `11e5e04af` (Option B: parser emits group/description/steps). Groups collapsible + counts, cdk-virtual-scroll (uniform 44px rows), drill-down (desc+steps), 3-level run (all/@group/exact-title), runDisabled single-flight, 37 i18n×2, WP3 screenshot slot left. ng build green. **Live verified:** run #13 {filter:@smoke} → group=lis, description populated, filter correct. Native review clean.
| WP3a | BE: streaming reporter (`e2e/reporters/streaming-reporter.ts`) writes per-test result on onTestEnd → RunE2eCommand reads incrementally + writes scenarios to `e2e_runs.results_json` LIVE during the run (not only at end); transfer failure screenshot (`test-results/`→`public/e2e-artifacts/{run}/`) + trace, record paths per scenario | BE+specs | WP4 | Codex/native | no | ✅ done | FE `5e90fb2` (streaming reporter → live.json) + BE `74e93704c` (start()+poll loop writes results_json progressively; transferArtifacts copies failure png+trace to public/e2e-artifacts/{runId}/, stamps URLs). **Live verified:** run #14 total climbed 0→1→2 mid-run; forced-fail run: screenshot 200 image/png 203KB + trace 200 zip 365KB over HTTP; passing run leaves artifacts empty. Pest 7 pass. Native review (Codex bwrap-blocked): runId-int-only in file ops, safe.
| WP3b | FE: live per-scenario progress (polling shows scenarios flipping running→passed/failed one-by-one + progress widget «N/M · current»); failure-screenshot display (zoomable) in drill-down + trace download link; extend model (screenshot,trace,currentStep) | FE | WP3a | Codex/native | no | ⬜ pending | |

## DAG / serialization
- WP1 → WP2 → WP4 sequential (WP2 consumes WP1's tags; WP4 consumes WP2's filter endpoint). WP1+WP4 FE repo, WP2 BE — one writer per repo, run in order.

## Baseline
- FE `ng build` green (session). BE Pest: E2eRunner module 4 pass (from WP1b). WP2 adds filter test.

## Deferrals
- (WP3 now IN PROGRESS — batch 2 started 2026-07-15)
- ~~**WP3 (batch 2):**~~ streaming reporter (live per-scenario progress widget) + failure-screenshot transfer (`screenshot:'only-on-failure'` already on) + trace download. Design WP4 to accommodate (model has room for steps + a screenshot field later).

## Post-deploy fix (2026-07-15) — single-test run regex bug
Owner reported: running ONE test ran none/both. **Root cause:** `--grep` treats the filter as a REGEX; the 2nd scenario's title contains `(UI)` and `->` — the unescaped `(` opened a capture group so `--grep` matched 0 tests (run showed total=0). **Fix (FE `d134e69`):** `onRunScenario` escapes regex metachars in the title before sending as filter (`replace(/[.*+?^${}()|[\]\\]/g, '\\$&')`). **Verified live:** run #24 (scenario 2 single, escaped) → total=1 passed, 8 steps (was total=0). Deployed `main-JYBLIGNQ.js`. The details drill-down was already correct (steps render for the 2nd scenario which has 8 test.steps; the 1st has 0 → shows description + "no steps") — the "no details" report was the stale pre-WP3b bundle / cache before this deploy.

## Post-deploy enhancement (2026-07-15) — always-show-all-scenarios (catalog)
Owner: "المفروض أشوف كله قدامي، واللي رنّيته هو بس اللي يعمل loading." **Problem:** the screen built its list only from the last run's scenarios → a single-test filtered run made all others vanish. **Fix:** new BE `GET /e2e/catalog` (`playwright --list --reporter=json`, no browser, cached 5min) returns ALL scenarios {title,group,description}; FE merges catalog (always visible) with the latest run BY TITLE — run match wins for status/steps/screenshot/trace, non-matched = `'not_run'` (grey, no spinner). Only in-run scenarios show running/loading. BE `7b2cf398e` + FE `32e6540`. Deployed `main-Z62CXFZQ.js`. Live: catalog returns 2 scenarios always.

## Post-deploy: screenshot for every test (2026-07-15) — owner asked to see pass screenshots too
Changed `e2e/playwright.config.ts` `screenshot: 'only-on-failure' → 'on'` (FE `cbdf239`) — captures a screenshot for EVERY test. The runner's `specArtifactSources`/`transferArtifacts` had NO failed-only filter, so it copies whatever screenshot each test produced — no runner change needed. FE (WP3b) already renders `sc.screenshot` on-demand (thumbnail → lightbox), not tied to failure. **Verified live:** run #29 (passing scenario) → screenshot present, 200 image/png 92KB over HTTP. On-demand (loads only when the test row is opened → no screen load cost).
- ⚠️ Cost note: `screenshot:'on'` adds per-test capture time + disk. Run #28 hit 210s (the pre-existing search-race flake + slower capture pushed it toward the runner timeout). The scenario is inherently flaky (LR-number search race; retries:2 usually absorb it). If flakiness/time becomes a problem at scale, revisit (per-test screenshot at the last step only, or back to on-failure + a manual "capture" toggle).

## Post-deploy: screenshot display fix (2026-07-15)
Owner "how do I see the screenshot?": label was "Failure screenshot" (failure-only wording) + thumbnail was tiny 30px. Fixed: generic `E2E.SCREENSHOT` label ("Screenshot"/"لقطة الشاشة") + thumbnail 30→34px (kept within the 44px uniform virtual-scroll row). FE `2524292`+`c15bb43`. Deployed `main-ZQLMOTJT.js`. To see it: run a test → open it → scroll to "Screenshot:" thumbnail → click to open lightbox. Screenshots now captured for pass+fail (`screenshot:'on'`), verified reachable (run #29 passing → 200 image/png 92KB).
