6.8 KiB
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.jsdoesn't actually createsysadmin@school.com(despiteLogin.tsx'sdemoLogin()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 notableor "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: noarticle/.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:
-
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
-
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:
- API login (
POST /api/auth/login) → get{ token, user }. - Inject into localStorage under zustand's
auth-storagekey. - 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 thatLogin.tsxuses for its quick-select buttons. - Tests must be deterministic — no
setTimeoutwaits, no reliance on real network. Usepage.waitForResponsefor known API calls when needed. playwright.config.tsalready setsfullyParallel: false, workers: 1— leave that alone. Parallel runs on the same DB cause flakes.- Vite dev server proxies
/api/*to :3001, so allpage.gotocalls targethttp://localhost:3000and the auth/data calls still hit the API. - The console-error filter in
assertions.tsignores 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.comtoserver/src/database/seeds/demo.jsso 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.tsis a hand-curated copy of the role routes inApp.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.