Portal — system tests (POR-ST-*)
Human-driven protocol for @enode/portal in a desktop browser (plus one
mobile-viewport spot check) against backdev. Environment, accounts, and
run recording: system test plan. Requirements covered:
portal.md + shared.md.
Keep DevTools open (Console + Network) — a case passes only with a correct UI
and no console errors and 2xx (or documented 31x) responses. Case IDs
are POR-ST-<section>.<case> and are stable — append, never renumber.
Preconditions for the whole run: Pro coach/admin account on backdev with
≥2 athletes, ≥1 template, scheduled + recurring workouts, completed sessions
(run tracking protocol §7 first if history is empty); a One account; a
restricted (athlete) account; the roster CSV fixture.
1. Auth & entry
| # | Use case | Covers | Steps | Expected result | Result | Notes |
|---|
| 1.1 | Pro sign-in | SHR-AUTH-01, POR-AUTH-01 | Sign in with the Pro coach account at / | Redirects to /dashboard; sidebar shows privilege-appropriate entries | | [automate] |
| 1.2 | One sign-in | POR-AUTH-01 | Sign in with the One account | Login succeeds; migration entry is prominent (One users come to migrate) | | |
| 1.3 | Registration (Pro-only) | SHR-AUTH-03, POR-AUTH-01 | Walk the registration flow; test one invalid input | Pro registration completes; validation errors shown inline; no One registration offered | | |
| 1.4 | Cold-start redirect | SHR-AUTH-02 | With a stored token, open / | Redirects straight to /dashboard | | [automate] |
| 1.5 | Sign out & 401 bounce | SHR-AUTH-05 | Sign out; then open /dashboard/users directly | Back at login; the deep link bounces to login while unauthenticated | | |
| 1.6 | Device-limit dialog | SHR-AUTH-06, POR-AUTH-02 | Exceed the account's device sessions, then sign in | 310 → device-limit dialog; freeing a slot lets login continue | | |
| 1.7 | Invitation flow | SHR-AUTH-07, POR-AUTH-02 | Sign in with a freshly invited user | 312 → invitation dialog; accepting continues into the dashboard | | |
| 1.8 | Onboarding redirect | SHR-AUTH-08, POR-AUTH-02 | Sign in with an account with incomplete onboarding | 311 → onboarding flow before the dashboard | | |
2. Dashboard
| # | Use case | Covers | Steps | Expected result | Result | Notes |
|---|
| 2.1 | Range summaries | POR-DASH-01 | Open /dashboard; switch the time range | Athletes / sessions / volume / sets cards update per range | | |
| 2.2 | Trends vs prior period | POR-DASH-01 | Read the trend indicators | Trend deltas plausible vs the prior equal-length period (spot-check one against history) | | |
| 2.3 | Rankings | POR-DASH-02 | Check top exercises + athlete activity | Rankings populated and consistent with recent sessions | | |
| 2.4 | Sessions table | POR-DASH-02 | Check the completed-sessions table | Recent sessions listed; opening one shows its details | | |
3. Planner
Run the full Planner manual test protocol
(§1–§11) as part of a release run — it covers POR-PLAN-01..03 in depth.
| # | Use case | Covers | Steps | Expected result | Result | Notes |
|---|
| 3.1 | Drag & drop reschedule | POR-PLAN-02 | Drag a workout chip to another day (month view) | Chip moves; backend updated (2xx or 313 flow); persists on refresh | | not in the planner protocol |
4. Devices
| # | Use case | Covers | Steps | Expected result | Result | Notes |
|---|
| 4.1 | List & search | POR-DEVICES-01 | Open /dashboard/devices; search for a known device | Device sessions listed; search narrows correctly | | |
| 4.2 | Remove session | POR-DEVICES-01 | Remove a device session (e.g. the tracking test device) | Removed from the list; that device is signed out / must re-auth | | |
5. Users & roster
| # | Use case | Covers | Steps | Expected result | Result | Notes |
|---|
| 5.1 | Table search & role filter | POR-USERS-01 | Search a name; toggle admin/coach/athlete filters | Rows filter correctly; counts match | | [automate] |
| 5.2 | Create & edit user | POR-USERS-02 | Create an athlete with metadata; reopen and edit | Created user appears; edits persist after reload | | |
| 5.3 | Bulk add tags | POR-USERS-03 | Select 2+ users → bulk add a tag | Tag applied to all selected; visible in the table/drawer | | |
| 5.4 | Bulk delete | POR-USERS-03 | Select the test users → bulk delete | Confirmation required; users gone after confirm + reload | | |
| 5.5 | Roster CSV upload | POR-USERS-04, POR-NOTIF-02 | Upload the CSV fixture: dropzone → mapping → validation → review → upload | Columns auto-map; invalid rows flagged in review; upload runs async with live progress; users exist afterwards | | |
| 5.6 | Units in user drawer | SHR-UNITS-01, POR-USERS-02 | Toggle metric ↔ imperial; open a user's height/weight | Values convert correctly both ways | | |
| 5.7 | Per-user actions | POR-USERS-05 | From a user row: export data; view upcoming workouts | Export starts (see §13); upcoming workouts listed | | |
6. Exercises
| # | Use case | Covers | Steps | Expected result | Result | Notes |
|---|
| 6.1 | Search | POR-EXER-01 | Search by name and by tag | Matching exercises shown | | [automate] |
| 6.2 | Filters & favourites | POR-EXER-01 | Apply equipment + muscle-category filters; favourite an exercise | Filters combine correctly; favourite persists after reload | | |
| 6.3 | Custom overrides | POR-EXER-02 | In the detail drawer, set a custom name/details; save | Override shown in the catalogue; base catalogue data still visible in the drawer | | |
7. Workout templates
| # | Use case | Covers | Steps | Expected result | Result | Notes |
|---|
| 7.1 | List, search, tag filter | POR-WKT-01 | Search templates; filter by tag | Cards filter correctly | | |
| 7.2 | Create & edit | POR-WKT-01 | Create a template with 2 exercises; reopen and edit | Template persists; edits visible in the tracking app's template picker too | | |
| 7.3 | Bulk delete | POR-WKT-01 | Select test templates → delete | Confirmation; gone after reload | | |
| # | Use case | Covers | Steps | Expected result | Result | Notes |
|---|
| 8.1 | List & access-level filter | POR-TAGS-01 | Filter by visibility chips | Tags filter by access level | | |
| 8.2 | Create & edit | POR-TAGS-01 | Create a tag with color/details; edit it | Persists; color renders in pills; relations section populates where linked | | |
| 8.3 | Bulk delete | POR-TAGS-01 | Select test tags → delete | Confirmation; gone after reload; no orphaned pills on users/exercises | | |
9. Insights (custom charts)
| # | Use case | Covers | Steps | Expected result | Result | Notes |
|---|
| 9.1 | Build a chart | POR-INS-01 | Builder drawer → pick a chart type, metric, athletes → save | Chart renders with real data on the board | | |
| 9.2 | Board layout | POR-INS-01 | Drag a chart to a new position | Layout change persists after reload | | |
| 9.3 | Per-user persistence | POR-INS-01 | Reload; sign out/in | Charts and layout restored for the same user | | |
| # | Use case | Covers | Steps | Expected result | Result | Notes |
|---|
| 10.1 | Matrix renders | POR-PROF-01 | Open Profiles; pick axes with known data | Athlete × exercise matrix populates | | |
| 10.2 | List/grid persisted | POR-PROF-01 | Switch list ↔ grid; reload | Chosen view persists | | |
| 10.3 | Pair detail & trend | POR-PROF-01 | Open a cell/pair | Pair detail with per-athlete trend renders | | |
11. History & session details
| # | Use case | Covers | Steps | Expected result | Result | Notes |
|---|
| 11.1 | Search & filters | POR-HIST-01 | Filter history by athlete/exercise | Sessions filter correctly | | [automate] |
| 11.2 | Pagination & window | POR-HIST-01 | Page through results | Pagination works; results within the last-year default window | | |
| 11.3 | Session drawer | POR-HIST-02 | Open a session (one recorded in the tracking run) | Drawer opens with summary tab + technique board; set data matches what was tracked | | |
12. Migration
| # | Use case | Covers | Steps | Expected result | Result | Notes |
|---|
| 12.1 | Entry points | SHR-AUTH-04, POR-MIGR-01 | Open migration from the sidebar and from the auth-time entry (One account) | Wizard opens from both | | |
| 12.2 | Wizard flow | POR-MIGR-01 | Walk the wizard with a legacy test account (athletes/groups/history) | Steps complete; migrated entities appear (users/tags/history) | | needs legacy test data |
| 12.3 | Manual exercise mapping | POR-MIGR-02 | Reach the exercise-mapping step | No auto-suggestions (by design); coach picks each mapping manually; picks are applied | | |
13. Export
| # | Use case | Covers | Steps | Expected result | Result | Notes |
|---|
| 13.1 | Export flow + deep link | POR-EXP-01 | Run an export from the route; then open an export deep-link URL | Export produces a download; deep link seeds the flow's selections | | |
| 13.2 | PDF export | POR-EXP-01 | Export as PDF | Valid, readable PDF with the selected data | | |
14. Notifications
| # | Use case | Covers | Steps | Expected result | Result | Notes |
|---|
| 14.1 | Live arrival | POR-NOTIF-01 | Trigger a notification (e.g. finish a roster upload or export) while on another page | Arrival peek shows; bell unread badge increments without reload | | |
| 14.2 | Inbox operations | POR-NOTIF-01 | In the inbox: switch filter segments, mark read, delete | All three work; unread badge updates | | |
| 14.3 | Deep-link expand | POR-NOTIF-01 | Open a notification deep link (expand param) | Inbox opens with that notification expanded | | |
15. Privileges & roles
| # | Use case | Covers | Steps | Expected result | Result | Notes |
|---|
| 15.1 | Gated navigation | POR-RBAC-01 | Sign in with the restricted (athlete) account | Users/Insights/roster/migration/export entries hidden per missing privileges | | |
| 15.2 | Direct-route block | POR-RBAC-01 | As the restricted user, open /dashboard/users and /dashboard/data/insights directly | Blocked/redirected — no data renders | | |
| 15.3 | Role model | POR-RBAC-02 | As admin, inspect the Users role filters and an owner account | Filters show admin ("Facility")/coach/athlete; owner excluded from filters but visible with full access | | |
16. Cross-cutting
| # | Use case | Covers | Steps | Expected result | Result | Notes |
|---|
| 16.1 | Offline surface | SHR-ERR-01 | Toggle DevTools offline mode on the dashboard | Connection-loss card/banner appears; recovery restores data without reload artifacts | | |
| 16.2 | Error surfaces | SHR-ERR-02, SHR-ERR-03 | Provoke a failing request (e.g. save while offline / force a 5xx via DevTools blocking) | Toast/inline alert with backend message or fallback; Try again + Report offered on application errors; blocking dialog only for blocking class | | |
| 16.3 | i18n scan | SHR-I18N-01 | Scan all pages visited this run | No raw keys, undefined, or hardcoded dev strings | | |
| 16.4 | Reference-data gating | SHR-ERR-04 | Load a data page with its reference call blocked (DevTools request blocking), then unblock + retry | Explicit error/retry state (RequireData), never an empty-but-broken table; retry recovers | | |
| 16.5 | Debug absent in prod | — | On a production build, open /dashboard/debug | Renders nothing/404; no Debug nav entry | | |
| 16.6 | Mobile viewport spot check | — | Narrow to <1024px; navigate 3 pages; open a drawer | Sidebar becomes slide-in overlay and auto-collapses on navigation; no horizontal scroll; drawers usable | | |
17. Archived users
Precondition: one archived athlete with recorded history, and one scheduled
on a future workout. Archive via a user's drawer (needs writeUserStatus).
| # | Use case | Covers | Steps | Expected result | Result | Notes |
|---|
| 17.1 | Hidden from every live surface | SHR-ARCH-01 | Look for the archived athlete in: any athlete picker (planner, insights, export), the history search typeahead, and the Profiles athlete axis | Absent everywhere; only the Users page's Archived view lists them | | |
| 17.2 | History archived compartment | SHR-ARCH-03, POR-HIST-05 | On History, find a page containing their sessions; toggle the Archived chip | Default view hides their sessions; the Archived view shows only theirs; "Showing X of Y on this page" reflects the split | | archived pages are often empty — page around |
| 17.3 | Data stays exportable | SHR-ARCH-03 | Users → Archived → select the athlete → Export | The export flow opens seeded with them and produces their data (not an empty export) | | |
| 17.4 | Saving doesn't drop them | SHR-ARCH-04 | Planner: open a workout that has an archived participant, change only the name/time, save. Inspect the request body | The archived athlete's id is still in participants — they were not silently removed | | [automate] |
| 17.5 | Explicit edit drops them | SHR-ARCH-02 | Same workout: open the athlete picker and confirm without changing anything; save | The archived athlete is now removed (an explicit edit, not a silent one) | | |
| 17.6 | Names still resolve | SHR-ARCH-03 | Open a saved insights chart whose config names the archived athlete | The facet shows their name, not a raw UUID | | key regression |
Coverage table
| Requirement | Cases |
|---|
| POR-AUTH-01 | 1.1, 1.2, 1.3 |
| POR-AUTH-02 | 1.6, 1.7, 1.8 |
| POR-DASH-01 | 2.1, 2.2 |
| POR-DASH-02 | 2.3, 2.4 |
| POR-PLAN-01 | Planner protocol §1–§5 |
| POR-PLAN-02 | Planner protocol §6, §8, §9 + 3.1 |
| POR-PLAN-03 | Planner protocol §7 |
| POR-DEVICES-01 | 4.1, 4.2 |
| POR-USERS-01 | 5.1 |
| POR-USERS-02 | 5.2, 5.6 |
| POR-USERS-03 | 5.3, 5.4 |
| POR-USERS-04 | 5.5 |
| POR-USERS-05 | 5.7 |
| POR-EXER-01 | 6.1, 6.2 |
| POR-EXER-02 | 6.3 |
| POR-WKT-01 | 7.1, 7.2, 7.3 |
| POR-TAGS-01 | 8.1, 8.2, 8.3 |
| POR-INS-01 | 9.1, 9.2, 9.3 |
| POR-INS-02 | — mocked; known behaviour |
| POR-PROF-01 | 10.1, 10.2, 10.3 |
| POR-HIST-01 | 11.1, 11.2 |
| POR-HIST-02 | 11.3 |
| POR-HIST-03 | — wip; known behaviour |
| POR-HIST-04 | — wip |
| POR-HIST-05 | 17.2 |
| SHR-ARCH-01..04 | 17.1, 17.5, 17.2/17.3/17.6, 17.4 |
| POR-MIGR-01 | 12.1, 12.2 |
| POR-MIGR-02 | 12.3 |
| POR-EXP-01 | 13.1, 13.2 |
| POR-NOTIF-01 | 14.1, 14.2, 14.3 |
| POR-NOTIF-02 | 5.5 |
| POR-RBAC-01 | 15.1, 15.2 |
| POR-RBAC-02 | 15.3 |
| SHR-AUTH-01..08 | 1.1, 1.4, 1.3, 12.1, 1.5, 1.6, 1.7, 1.8 |
| SHR-I18N-01 | 16.3 |
| SHR-UNITS-01 | 5.6 |
| SHR-ERR-01..04 | 16.1, 16.2, 16.2, 16.4 |
Known/expected behaviours (not defects)
- Insights builder preview / empty state / walkthrough charts are synthetic
(POR-INS-02) — educational visuals, not backend data.
- Session-summary zone-distribution & muscle-map cards are WIP
(POR-HIST-03): real V2 DTO data, marked WIP in code; zone card only on
conventional-strength exercises. Oddities there are tracked as WIP, not run
defects — but crashes still count.
- History has no dedicated date-range picker yet (POR-HIST-04); default
window is the last year.
- The Workouts page carries an in-code "structural first pass" note; rough
edges beyond the tested cases in §7 should be logged as notes, not blockers.
- Roobert font 404s in dev are expected locally (see the planner protocol).