# LEDGER — UI E2E Automation (Playwright + in-app Tests module)

**Feature:** أتمتة اختبار الواجهة E2E بزر تشغيل من داخل النظام
**Date started:** 2026-07-15
**Source plan:** https://moonui.elbaset.com/his-analysis/ui-e2e-automation-plan.html (approved by owner)
**Install:** moonui · BE `/home/moonui/moon-erp-be` (hazemdev) · FE `/home/moonui/public_html/moon-erp` (hazemdev) · dev DB `moonui_dev_be`
**Owner acceptance:** the owner opens `/app/e2e-tests` (owner-only), clicks **Run**, and after a minute sees each scenario listed green/red with the last-run time — the first scenario being a real LIS flow (login → create test-company patient → create lab request → assert it shows in reception). ⛔ no real data harmed; runner OFF by default on client servers.

## Decisions (from approved plan + WP0 pre-flight — do NOT re-litigate)
- **Architecture: Playwright CLI-first + thin UI trigger** (Fable ruling A). Specs runnable from CLI; the UI button is a run+report layer.
- **Safe data: write-isolated** — a dedicated **test company + test user inside `moonui_dev_be`**; scenarios create their own data scoped to it; NEVER touch shared settings/catalogs/sequences. ⛔ never migrate:fresh.
- **Runner security: `E2E_RUNNER_ENABLED` env flag, default OFF** (protects client servers shipped via MoonStack) + **owner-only permission** + **single-flight lock**.
- **WP0 finding — no queue worker runs** (`QUEUE_CONNECTION=database` but no `queue:work` process). → Runner does NOT block FPM and does NOT rely on a queue worker: the endpoint writes an `e2e_runs` row (status=queued) and **spawns a detached background process** (`Symfony\Process ->start()` disowned, or `nohup php artisan e2e:run {id} &`) that runs Playwright and updates the row. FE polls `GET /e2e/runs/latest`.
- **WP0 finding — Playwright browsers are root-owned** (`/root/.cache/ms-playwright`). → Use **`executablePath: '/usr/bin/chromium-browser'`** (Chromium 147 present) in `playwright.config.ts`; do NOT rely on installing browsers as `moonui`.
- **WP0 finding — runtime user = `moonui`** (cPanel LSAPI); node/npx at `/usr/bin`.
- **Target: the deployed `/app`** (what /fullpush produces) — the gate runs AFTER deploy.

## WP table
| WP | Scope (one dense line) | Repo | Depends | Review | Migration? | Status | Commits |
|----|------------------------|------|---------|--------|-----------|--------|---------|
| WP0 | Pre-flight: FPM user / queue / browsers — verified; decisions recorded above | infra | — | — | no | ✅ done (findings above) | — |
| WP1a | Playwright scaffold: `e2e/` dir + `playwright.config.ts` (executablePath system chromium, baseURL /app, animations off) + `helpers/` (API login → inject token) + first spec (login + create test-co patient + create lab request + assert in reception) + `@playwright/test` devDep + `test:e2e` script; runnable from CLI | FE | WP0 | Codex/native | no | ✅ done | FE `152d0bb` — spec 1 passed 8.2s, ng build green. Codex wrapper timed out → native review clean (token/X-Auth/localStorage/write-isolation/build-safety/real-UI-assert all ✅). Deviation: create via API, assert via UI (brief-permitted; UI-drive deferred to WP4). Test co id **8**, user `e2e@moonerp.test`/`E2e!Pass123`. |
| WP1b | BE runner module `Modules/E2eRunner`: migration `e2e_runs` table, `POST /e2e/run` (gated: env OFF + owner perm + single-flight, writes row + spawns detached `e2e:run` command), `GET /e2e/runs/latest`, artisan `e2e:run {id}` (runs playwright json reporter, parses, updates row) | BE | WP1a | Codex (Fable=emergency only per owner) | **yes** (e2e_runs) | ✅ done | BE `bdda8b78b` — migration ran on dev (e2e_runs, 13 cols); Pest 4 pass/18 assert (no regress: RoleApi 20, AuthorityLimit 16); **full e2e verified** (POST 0.29s non-block → detached queued→running→passed; single-flight 200 already_running; 403-when-disabled real curl; flag left OFF, test rows cleaned). Codex blocked (bwrap ENOSPC [[codex-blocked-on-server]]) → **native security review clean**: 3 gates ✅, injection-safe (only `%d` int + escapeshellarg + array-form playwright) ✅. Notes: single-flight=200 not 409 (brief allowed); child PATH fix for FPM; pkill chromium scoped to moonui user. No CHANGELOG (owner-only, off-by-default dev tool). |
| WP1c | FE Tests module: `features/e2e-tests/` screen (Run button + per-scenario green/red + last-run + polling), `e2e-runner.service.ts`, route `/core/e2e-tests` (ownerGuard), nav entry, i18n `E2E.*` | FE | WP1b | Codex/native | no | ✅ done | FE `fc6e994` — ng build green; native review clean (route+ownerGuard ✅, nav ownerOnly ✅, 16 E2E i18n keys ×2 files + NAV.E2E_TESTS ✅, dynamic STATUS_ pipe all 5 keys exist ✅, clearInterval on destroy ✅). Note: no CHANGELOG (FE repo has none + owner-only off-by-default tool). |

## DAG / serialization
- WP1a → WP1b → WP1c strictly sequential (WP1b consumes the spec from WP1a; WP1c consumes the endpoints from WP1b).
- One writer per repo at a time. WP1a+WP1c are FE, WP1b is BE — still run sequentially (WP1c depends on WP1b contract).

## Baseline
- FE: `ng build` green (confirmed all session — last bundle main-6V24WGZJ.js).
- BE: Pest on sqlite — E2eRunner is a NEW module; its own tests start at 0. Existing suite unaffected (WP1b adds tests, doesn't touch shared code). Record counts when WP1b runs.

## Deferrals
- **WP2** — more LIS scenarios (sample → result → validation → print, incl. histopath/culture).
- **WP3** — selective run + run history + screenshots-on-fail.
- **WP4** — drive create dialogs through the UI (needs data-testid hooks deployed) + other modules.

## Phase C — FINISH ✅ (2026-07-15)
WP1 complete (WP0+1a+1b+1c). Final: FE `ng build` green; BE Pest 4 pass, no regressions; migration on dev; `E2E_RUNNER_ENABLED` OFF in live .env; `e2e_runs` 0 rows; test co 8 isolated. CHANGELOG bilingual note (BE `9a49fd7d4`). RESUME rewritten as end-state; KB INDEX updated (Testing/QA section). Codex blocked (bwrap) → native reviews clean on all WPs; Fable NOT used (owner emergency-only). ⛔ NOT deployed/merged — owner runs `/fullpush`, then sets `E2E_RUNNER_ENABLED=true` on dev to use.

## WP2 (from plan) — scenario 2: result → review → preview ✅ (2026-07-15)
Second Playwright scenario `e2e/specs/lis-result-review-flow.spec.ts` + `helpers/lisSetup.ts` (createReceivedRequest). **UI-driven** (29 UI actions vs 2 API setup): result entry typed into the real `/lab/worklist` grid, review opened on `/lab/validation`, **Preview** clicked + report iframe asserted (the step that would catch preview-duplication / histopath-empty). No data-testid needed (stable existing locators). FE `c5b1cd8`. **Verified live via runner: run #6 = passed 2/2.** Native review clean (UI-driven confirmed, write-isolated co 8). Codex reserved (bwrap).
- ⚠️ **ISO-15189 four-eyes gap:** `lis.allow_self_verification=false` for co 8 → the enterer can't self-validate, so the spec asserts the review screen renders + Validate action is live (a 2nd reviewer would click) but does NOT drive entered→validated→released. To complete the full status-advance: owner authorizes a 2nd reviewer user in `seed-e2e-company.php` (agent prototyped + reverted; awaiting go-ahead). Preview engine for co 8 = PDF → in-iframe text assert soft-skipped (blob-src assert carries it); switch co 8 to HTML template to deep-check content.

## WP2 completion — full four-eyes cycle + flake fix ✅ (2026-07-15)
Extended scenario 2 to the FULL ISO-15189 cycle: **enter (A) → validate (B) → approve (A) → release (B)** — the four-eyes guard applies at EVERY transition (each hop needs a different actor), so a 2nd reviewer user was seeded. All transitions UI-driven (Validate All / Approve All / Release All buttons on /lab/validation, alternating users), status asserted via badges + API. FE `b117ce6` (+ seed reviewer). **Flake fixed** (`c065e0e`): the result-entry step intermittently hit "element(s) not found" for `.result-input` when the grid lagged after opening the card → wrapped card-open in a retry-until-grid-paints `expect().toPass()`; bumped config `retries:1→2` + per-test `timeout:60s→120s` (the long cycle needs it). Stress: 4/5 raw (`--retries=0`, was 3/5) + green with real retries. **Verified live: runner run #10 = passed 2/2 (64s).**
- **2nd reviewer seeded:** `e2e-reviewer@moonerp.test` / `E2e!Review123` (id 30, E2E company 8, role owner) — in `seed-e2e-company.php`, env-overridable (`E2E_REVIEWER_EMAIL/PASSWORD`).
- ⚠️ flake note: ~1/5 raw failures remain on the entry step under back-to-back load; retries:2 absorbs it. If it ever reddens spuriously, that step is the suspect (grid repaint latency), not a product regression.
