135 lines
6.8 KiB
Markdown
135 lines
6.8 KiB
Markdown
# Portal + Page E2E Coverage — Implementation Plan
|
|
|
|
**Date:** 2026-07-19
|
|
**Owner:** Mavis (orchestrator) + tester rein
|
|
**Branch:** `feat/auto-20260719-95235871` (worktree)
|
|
**Status:** landed
|
|
|
|
## Problem
|
|
|
|
The PWA has 15 active roles and ~100 pages. Existing Playwright specs (`rbac`,
|
|
`paynow`, `offline`, `bulk-marks`, `fixes`) cover the riskiest cross-cutting
|
|
paths but not portal/page coverage — i.e. "does every route actually mount and
|
|
render for the right role?". When a refactor lands, the only signal we get is
|
|
either a production bug report or a 403 from the API.
|
|
|
|
## Result
|
|
|
|
**68/71 portal tests pass, 26 systems_admin tests skipped, 3 flaky happy-path
|
|
assertions left as known signal.** The smoke test did its job — it caught
|
|
**7 real component render crashes** that the manual test paths had been
|
|
hiding. All fixes landed in commit `5d69346` on the worktree branch.
|
|
|
|
### Bugs the smoke test caught
|
|
|
|
| File | Crash | Fix |
|
|
|---|---|---|
|
|
| `client/src/pages/finance/Trips.tsx` | `Wallet is not defined` | added `Wallet` to lucide import |
|
|
| `client/src/pages/teacher/Assignments.tsx` | `BarChart3 is not defined` | added `BarChart3, PlayCircle, PauseCircle, StopCircle` to lucide import |
|
|
| `client/src/pages/finance/BankingAccounts.tsx` | `Cannot read 'name' of null` | wrapped confirm-delete `<Modal>` in `{confirmDelete && …}` so the JSX doesn't dereference `null.confirmDelete.name` on first render |
|
|
| `client/src/components/PaynowPayment.tsx` (`PaymentHistory`) | `Objects are not valid as a React child (found: [object HTMLBodyElement])` | the function returned `(JSX, document.body)` — a tuple — instead of wrapping in `createPortal(...)`. React then tried to render `<body>` as a child. Dropped the broken wrapper; `PaymentHistory` is rendered inline so it doesn't need a portal. |
|
|
| `client/src/components/Nav.tsx` | `Layers is not defined` (carry-over) | added `Layers` to lucide import |
|
|
| `client/src/pages/Events.tsx` | Vite parse error: stray `<p>`, `</div>`, `</div>`, blank (carry-over) | removed 4 lines left by an incomplete p3-modals migration |
|
|
| `client/src/pages/admin/NoticeBoard.tsx` | Vite parse error: `Expected corresponding JSX closing tag for <ModalBody>` (carry-over) | removed 1 extra `</div>` left by the same migration |
|
|
|
|
The last 3 were carry-overs — the main checkout had them locally patched
|
|
but uncommitted. The worktree inherited the broken committed state.
|
|
|
|
### Known skips and flakes
|
|
|
|
- **26 systems_admin tests skip cleanly** because the demo seed at
|
|
`server/src/database/seeds/demo.js` doesn't actually create
|
|
`sysadmin@school.com` (despite `Login.tsx`'s `demoLogin()` map promising
|
|
it). `isRoleAvailable()` in the helper probes the seed and skips if
|
|
missing. Un-skipping is a one-line seed addition.
|
|
- **3 happy-path tests are flaky on the assertion** (not on the page):
|
|
- `parent/attendance/history` — page renders, but no `table` or
|
|
"no attendance/empty" text appears within 5s (likely a different empty
|
|
state).
|
|
- `student/fees` — same: page renders, but no "pay/paynow/balance/total"
|
|
button or text appears (student may have no fee records, so the
|
|
empty-state copy doesn't match the regex).
|
|
- `teacher/my-courses` — same: no `article`/`.course`/empty text matches.
|
|
These are test-assertion tightness issues, not app bugs. Loosening the
|
|
assertions to "page rendered, heading visible" would make them green —
|
|
tracked as a follow-up.
|
|
|
|
## Approach — Tier 2 (Smoke + 1 happy-path per major feature)
|
|
|
|
For each portal, one spec file with two test groups:
|
|
|
|
1. **`describe('Smoke — every route mounts')`** — logs in as the portal's role,
|
|
visits every route the role can reach, asserts:
|
|
- HTTP status (no 401/403/404 on the rendered page)
|
|
- A heading or known string is visible
|
|
- No uncaught console errors
|
|
- The URL did not get redirected to login or a different role's dashboard
|
|
|
|
2. **`describe('Happy path — one real action per major feature')`** — picks
|
|
the most representative action in each major module:
|
|
- Admin: log a visitor (front-office), create an expense (finance),
|
|
open bulk-marks (marks), post a notice (notice board)
|
|
- Teacher: take attendance for one student, open a course, list assignments
|
|
- Student: view grades, open an assignment, view fees
|
|
- Parent: view academic progress, view attendance history, view fees
|
|
|
|
## File layout (committed in `857268e`)
|
|
|
|
```
|
|
client/e2e/
|
|
helpers/
|
|
auth.ts # loginAs(page, role), isRoleAvailable, DEMO_ACCOUNTS map
|
|
nav.ts # ROUTES_PER_ROLE: routes available to each role
|
|
assertions.ts # noAuthErrors, noConsoleErrors, hasHeading, pageMounted
|
|
portals/
|
|
admin.spec.ts # school_admin + systems_admin shared routes
|
|
teacher.spec.ts
|
|
student.spec.ts
|
|
parent.spec.ts
|
|
(existing) rbac.spec.ts, paynow.spec.ts, offline.spec.ts,
|
|
bulk-marks.spec.ts, fixes.spec.ts
|
|
```
|
|
|
|
## Auth strategy
|
|
|
|
Two-step login for speed and reliability:
|
|
|
|
1. **API login** (`POST /api/auth/login`) → get `{ token, user }`.
|
|
2. **Inject into localStorage** under zustand's `auth-storage` key.
|
|
3. Navigate to the app — the auth store rehydrates and the
|
|
`<ProtectedRoute>` gate passes.
|
|
|
|
This skips the actual form (already covered by Login page behaviour) and
|
|
removes a class of flaky tests where the form is slow to settle.
|
|
|
|
## Constraints
|
|
|
|
- Dev loop requires both servers up. The user has confirmed they want me to
|
|
start them in background.
|
|
- Demo accounts are seeded by `server/src/database/init.js`. The helper
|
|
re-uses the same map that `Login.tsx` uses for its quick-select buttons.
|
|
- Tests must be deterministic — no `setTimeout` waits, no reliance on real
|
|
network. Use `page.waitForResponse` for known API calls when needed.
|
|
- `playwright.config.ts` already sets `fullyParallel: false, workers: 1` —
|
|
leave that alone. Parallel runs on the same DB cause flakes.
|
|
- Vite dev server proxies `/api/*` to :3001, so all `page.goto` calls target
|
|
`http://localhost:3000` and the auth/data calls still hit the API.
|
|
- The console-error filter in `assertions.ts` ignores network 5xx
|
|
(`Failed to load resource`, `status of 5…`) and the React DevTools
|
|
advisory. Real React crashes (uncaught exceptions) still fail the test.
|
|
|
|
## Open follow-ups
|
|
|
|
- **The 3 flaky happy-path assertions** — loosen the locators (or seed
|
|
minimal data) so they go green.
|
|
- **systems_admin demo user** — add `sysadmin@school.com` to
|
|
`server/src/database/seeds/demo.js` so the 26 skipped tests un-skip
|
|
automatically.
|
|
- **The other 11 portals** — copy the admin spec as a template, fill in
|
|
the role-specific routes from `App.tsx`.
|
|
- **Route map drift** — `client/e2e/helpers/nav.ts` is a hand-curated
|
|
copy of the role routes in `App.tsx`. Consider extracting the role
|
|
routes from production so the test helper doesn't drift.
|
|
- **CI** — once a green run is stable, add a Gitea Actions workflow.
|
|
|