geocrop-platform./apps/nextgen/.harness/plans/2026-07-19-portal-e2e-cover...

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.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 driftclient/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.