3397767a01
The /api/portal/appointments endpoint returns ISO startTime/endTime plus nested pet/service/staff objects, but the portal client Appointment type expected flat date/time/petName fields. isUpcoming() read appt.date/appt.time (both undefined), so parseTimeTo24Hour(undefined) threw a TypeError; the useEffect try/catch set the error state and the success-path-only Book New button became unreachable. - Add normalizeAppointment() at the fetch boundary mapping the API shape to the flat Appointment shape (derives display date/time from startTime, duration from the start/end delta), tolerant of the legacy flat shape. - Prefer absolute startTime in isUpcoming(); fall back to date/time. - Harden parseTimeTo24Hour against blank/undefined input (no NaN). - Add Appointment.startTime/endTime to the type. - Tests: normalizeAppointment + isUpcoming(startTime) + parseTimeTo24Hour safety. - Update UAT_PLAYBOOK.md §5.12.2 and new §5.12d regression cases. Co-Authored-By: Paperclip <noreply@paperclip.ing>
469 lines
42 KiB
Markdown
469 lines
42 KiB
Markdown
# UAT Playbook — GroomBook Web
|
||
|
||
## 1. Overview
|
||
|
||
GroomBook Web is the React 19 PWA frontend for the GroomBook pet grooming management platform. Built with Vite, it provides the UI for client/pet management, appointment scheduling, invoicing, staff management, and the customer portal. Extracted from the `groombook/app` monorepo.
|
||
|
||
## 2. Environments
|
||
|
||
| Environment | URL | Purpose |
|
||
|-------------|-----|---------|
|
||
| Dev | `https://dev.groombook.dev` | Development environment for daily development |
|
||
| UAT | `https://uat.groombook.dev` | User Acceptance Testing environment |
|
||
| Prod | `https://demo.groombook.app` | Production/demo environment |
|
||
|
||
## 3. Pre-conditions
|
||
|
||
- UAT environment is accessible and running
|
||
- Test accounts are seeded with appropriate personas (manager, staff, client)
|
||
- OIDC authentication is configured and functional
|
||
- GroomBook API service is running and healthy
|
||
- Required test data exists (clients, pets, appointments, services, staff)
|
||
|
||
## 4. Auth Base URL Resolution
|
||
|
||
The auth client resolves its API base URL based on the `VITE_API_URL` environment variable:
|
||
|
||
- **When `VITE_API_URL` is set:** Uses the configured URL as the auth base URL.
|
||
- **When `VITE_API_URL` is unset:** Falls back to `window.location.origin`.
|
||
|
||
This allows the app to work correctly in both:
|
||
- **Dev/PR deployments:** Where `VITE_API_URL` is explicitly set to the deployed API endpoint.
|
||
- **Local development:** Where `VITE_API_URL` is not set, using the same origin as the web app.
|
||
|
||
### Auth Client Configuration (src/lib/auth-client.ts)
|
||
|
||
```typescript
|
||
import { createAuthClient } from "better-auth/react";
|
||
|
||
export const authClient = createAuthClient({
|
||
baseURL: import.meta.env.VITE_API_URL ?? "",
|
||
});
|
||
|
||
export const { signIn, signOut, useSession, changePassword } = authClient;
|
||
```
|
||
|
||
## 5. Test Cases
|
||
|
||
### 5.1 Authentication UI
|
||
|
||
| # | Scenario | Steps | Expected |
|
||
|---|----------|-------|----------|
|
||
| TC-WEB-5.1.1 | Login page loads | Navigate to UAT URL | Login form is displayed with OIDC provider button(s) |
|
||
| TC-WEB-5.1.2 | OIDC redirect | Click OIDC login button | Redirected to OIDC provider, then back to app with session established |
|
||
| TC-WEB-5.1.3 | Logout | Click logout button | Session cleared, redirected to login page |
|
||
| TC-WEB-5.1.4 | Session indicator | After successful login | User info/initials visible in UI indicating active session |
|
||
| TC-WEB-5.1.5 | Unauthenticated `/login` renders the form (GRO-2011) | In a private/incognito window with no session cookie, navigate to UAT `/login` | React root mounts; the GroomBook sign-in card with the OIDC button is visible. Network tab shows `/api/auth/get-session` 200, `/api/setup/status` 200, and the login form is rendered (NOT a blank white viewport). |
|
||
| TC-WEB-5.1.6 | Swallowed render error surfaces in DOM (GRO-2094) | Trigger a render-time exception in the React tree (e.g. via temporary throw in a child component on a test build) and load `/login` in a clean context | Either the login form renders normally (happy path) OR the top-level `ErrorBoundary` testid `error-boundary` is visible with a populated `error-boundary-message` pre block showing the exception name/message/stack. **NEVER** a blank `<div id="root">` with no error indicator. Browser console must contain either zero render errors or a `[ErrorBoundary]` line plus the raw exception. |
|
||
| TC-WEB-5.1.7 | Global `error` and `unhandledrejection` listeners are wired (GRO-2094) | In a clean browser context, load `/login`, then trigger `setTimeout(() => { throw new Error("synthetic") }, 0)` from the console and `Promise.reject(new Error("synthetic-promise"))` | Browser console shows `[window.error]` and `[unhandledrejection]` log lines with the thrown values. Confirms global listeners are active in production. |
|
||
|
||
### 5.2 Authentication — VITE_API_URL Set
|
||
|
||
| # | Scenario | Steps | Expected |
|
||
|---|----------|-------|----------|
|
||
| TC-AUTH-5.2.1 | Auth client uses configured API URL | Configure `VITE_API_URL=https://api.example.com`, load app | Auth client sends requests to `https://api.example.com` |
|
||
| TC-AUTH-5.2.2 | Sign-in flow with configured API | Sign in when `VITE_API_URL` is set | Auth requests go to configured URL |
|
||
| TC-AUTH-5.2.3 | Sign-out flow with configured API | Sign out when `VITE_API_URL` is set | Auth requests go to configured URL |
|
||
|
||
### 5.3 Authentication — VITE_API_URL Unset (Fallback)
|
||
|
||
| # | Scenario | Steps | Expected |
|
||
|---|----------|-------|----------|
|
||
| TC-AUTH-5.3.1 | Auth client falls back to window.location.origin | Do not set `VITE_API_URL`, load app | Auth client uses `window.location.origin` as base URL |
|
||
| TC-AUTH-5.3.2 | Sign-in on localhost | Load app without `VITE_API_URL` on localhost:3000 | Auth client uses `http://localhost:3000` as base URL |
|
||
| TC-AUTH-5.3.3 | Sign-in on dev environment | Load app without `VITE_API_URL` on `https://dev.groombook.dev` | Auth client uses `https://dev.groombook.dev` as base URL |
|
||
| TC-AUTH-5.3.4 | SSO cookie set after Authentik callback (GRO-1592) | Complete Authentik SSO login on UAT without `VITE_API_URL` set | `__Secure-better-auth.session_token` cookie is present in browser; subsequent `/api/*` calls include the cookie and return 200 |
|
||
|
||
### 5.4 Session Persistence
|
||
|
||
| # | Scenario | Steps | Expected |
|
||
|---|----------|-------|----------|
|
||
| TC-AUTH-5.4.1 | Session persists across page reload | Sign in, reload page | Session remains active |
|
||
| TC-AUTH-5.4.2 | Session clears on sign-out | Sign in, sign out | User is logged out, redirected to login |
|
||
|
||
### 5.4.1 SSO Login Journey (Authentik OIDC end-to-end)
|
||
|
||
| # | Scenario | Steps | Pass Criteria | Fail Criteria |
|
||
|---|----------|-------|---------------|---------------|
|
||
| TC-WEB-SSO-1 | Sign-in page shows SSO button | Navigate to app root URL | Sign-in page displayed with "Sign in with SSO" button visible | No SSO button, 403 before page loads |
|
||
| TC-WEB-SSO-2 | Click SSO redirects to Authentik | Click "Sign in with SSO" button | Browser redirected to Authentik login at auth.farh.net | No redirect, error shown, button does nothing |
|
||
| TC-WEB-SSO-3 | Valid OIDC credentials authenticate | At Authentik, enter valid credentials and authenticate | Redirected back to app with active session | Redirect loop, 403, session not established |
|
||
| TC-WEB-SSO-4 | Post-login dashboard accessible | After SSO flow completes, dashboard loads | Dashboard displays correctly with user identity shown | Blank page, 403, session not active |
|
||
| TC-WEB-SSO-5 | User identity displayed correctly | After SSO login, check header/nav | User name/email/initials shown in nav, role reflected in UI | No user indicator, wrong user shown |
|
||
|
||
### 5.4.2 OOBE Flow Post-Login
|
||
|
||
| # | Scenario | Steps | Pass Criteria | Fail Criteria |
|
||
|---|----------|-------|---------------|---------------|
|
||
| TC-WEB-OOBE-1 | Fresh DB shows setup wizard | On fresh DB (no super user), navigate to app | Setup wizard / OOBE screen displayed | Regular login page shown instead of setup |
|
||
| TC-WEB-OOBE-2 | Configure OIDC via setup | During OOBE, configure OIDC auth provider via /api/setup/auth-provider | OIDC configured successfully, no 403 | 403 during setup, config rejected |
|
||
| TC-WEB-OOBE-3 | Setup completes and redirects | Complete OOBE setup with business name | Redirected to app dashboard as super user, setup bypassed on reload | Setup errors, wrong redirect, setup reappears |
|
||
| TC-WEB-OOBE-4 | Admin panel accessible after setup | After completing OOBE, navigate to admin panel | Admin features accessible | 403 on admin panel, insufficient permissions |
|
||
| TC-WEB-OOBE-5 | SSO login during OOBE does not interfere | During fresh OOBE, attempt SSO login before completing setup | SSO login redirected appropriately, setup can still complete | Auto-provision creates staff prematurely, setup flow broken |
|
||
|
||
### 5.5 Dashboard
|
||
|
||
| # | Scenario | Steps | Expected |
|
||
|---|----------|-------|----------|
|
||
| TC-WEB-5.5.1 | Dashboard loads after login | Complete authentication | Dashboard page loads without errors |
|
||
| TC-WEB-5.5.2 | Key metrics visible | View dashboard | Revenue, appointments, clients, and other key metrics displayed |
|
||
| TC-WEB-5.5.3 | No blank state | On fresh login | Dashboard shows meaningful data, not empty/blank state |
|
||
|
||
### 5.6 Client Management UI
|
||
|
||
| # | Scenario | Steps | Expected |
|
||
|---|----------|-------|----------|
|
||
| TC-WEB-5.6.1 | Client list loads | Navigate to Clients section | List of clients is displayed |
|
||
| TC-WEB-5.6.2 | Create client | Click "New Client", fill form, submit | Client created successfully, appears in list |
|
||
| TC-WEB-5.6.3 | Edit client | Click on client, modify details, save | Client updated successfully |
|
||
| TC-WEB-5.6.4 | Search clients | Enter search term in search box | List filters to matching clients |
|
||
| TC-WEB-5.6.5 | Archive client | Click archive on client record | Client marked as archived, removed from active list |
|
||
|
||
### 5.7 Pet Management UI
|
||
|
||
| # | Scenario | Steps | Expected |
|
||
|---|----------|-------|----------|
|
||
| TC-WEB-5.7.1 | Pet profiles visible | Open client details | All pets for client displayed with basic info |
|
||
| TC-WEB-5.7.2 | Add pet | Click "Add Pet", fill form, submit | Pet created and linked to client |
|
||
| TC-WEB-5.7.3 | Edit pet details | Click on pet, modify details, save | Pet updated successfully |
|
||
| TC-WEB-5.7.4 | Grooming history view | View pet profile | Past appointments/grooming sessions displayed |
|
||
| TC-WEB-5.7.5 | Add pet with size/coat | Create pet with Size Category and Coat Type filled | Size and coat type persisted, visible on pet profile |
|
||
| TC-WEB-5.7.6 | Edit pet size/coat | Edit existing pet, change size/coat dropdowns | Updated values saved to pet record |
|
||
| TC-WEB-5.7.7 | Size/coat optional | Create pet without selecting size or coat | Pet created successfully, fields remain unset |
|
||
|
||
### 5.8.1 Buffer Rules Management UI (GRO-1173)
|
||
|
||
| # | Scenario | Steps | Expected |
|
||
|---|----------|-------|----------|
|
||
| TC-WEB-5.8.2 | Buffer rules section visible | Navigate to Settings | "Buffer Rules" section shown with description |
|
||
| TC-WEB-5.8.3 | Create buffer rule | Click "+ Add Rule", select service and buffer minutes, submit | Rule appears in list, matches service/size/coat |
|
||
| TC-WEB-5.8.4 | Edit buffer minutes inline | Click Edit on a rule, change minutes, click Save | New buffer value reflected in list |
|
||
| TC-WEB-5.8.5 | Delete buffer rule | Click Delete, confirm | Rule removed from list |
|
||
| TC-WEB-5.8.6 | Create rule with size/coat | Create rule with Size Category or Coat Type specified | Rule shows size/coat tags in list |
|
||
| TC-WEB-5.8.7 | Empty state | Navigate to Settings with no rules | "No buffer rules configured yet" message shown |
|
||
|
||
### 5.8 Appointment Scheduling UI
|
||
|
||
| # | Scenario | Steps | Expected |
|
||
|---|----------|-------|----------|
|
||
| TC-WEB-5.8.1 | Calendar view loads | Navigate to Appointments | Calendar view displays appointments |
|
||
| TC-WEB-5.8.2 | Create booking | Click "New Appointment", fill details, submit | Appointment created and appears on calendar |
|
||
| TC-WEB-5.8.3 | Modify appointment | Click on appointment, change details, save | Appointment updated successfully |
|
||
| TC-WEB-5.8.4 | Cancel appointment | Click cancel on appointment | Appointment marked as cancelled |
|
||
| TC-WEB-5.8.5 | Appointment groups | View grouped appointments | Related appointments display as group |
|
||
|
||
### 5.9 Service Management UI
|
||
|
||
| # | Scenario | Steps | Expected |
|
||
|---|----------|-------|----------|
|
||
| TC-WEB-5.9.1 | Service catalog loads | Navigate to Services | List of available services displayed |
|
||
| TC-WEB-5.9.2 | Create service | Click "New Service", fill form, submit | Service created successfully |
|
||
| TC-WEB-5.9.3 | Edit service | Click on service, modify details, save | Service updated successfully |
|
||
| TC-WEB-5.9.4 | Create service with default buffer | Create service with "Default buffer time" filled | Buffer shown in service list and form after save |
|
||
| TC-WEB-5.9.5 | Edit service buffer | Open existing service, change default buffer minutes | Updated value persisted after save |
|
||
|
||
### 5.10 Staff Management UI
|
||
|
||
| # | Scenario | Steps | Expected |
|
||
|---|----------|-------|----------|
|
||
| TC-WEB-5.10.1 | Staff list loads | Navigate to Staff | List of staff members displayed |
|
||
| TC-WEB-5.10.2 | Role display | View staff member | Staff role/permissions clearly visible |
|
||
|
||
### 5.11 Invoicing & Payments UI
|
||
|
||
| # | Scenario | Steps | Expected |
|
||
|---|----------|-------|----------|
|
||
| TC-WEB-5.11.1 | Invoice list loads | Navigate to Invoices | List of invoices displayed with status |
|
||
| TC-WEB-5.11.2 | Payment flow | Click "Pay" on unpaid invoice, complete payment | Payment processed, invoice marked as paid |
|
||
| TC-WEB-5.11.3 | Receipts view | View paid invoice | Receipt/payment details displayed |
|
||
|
||
### 5.12 Customer Portal UI
|
||
|
||
| # | Scenario | Steps | Expected |
|
||
|---|----------|-------|----------|
|
||
| TC-WEB-5.12.1 | Client-facing view | Log in as client persona | Customer portal UI displayed |
|
||
| TC-WEB-5.12.2 | Appointment list | View client portal appointments | List of client's appointments visible — each card shows pet name, service, formatted date/time, and groomer (no "Failed to load appointments" error, no blank screen). "Book New" button is visible and clickable. See 5.12d. |
|
||
| TC-WEB-5.12.3 | Confirm appointment | Click confirm on pending appointment | Appointment status updated to confirmed |
|
||
| TC-WEB-5.12.4 | Cancel appointment | Click cancel on appointment | Appointment marked as cancelled |
|
||
|
||
#### 5.12b Dynamic Portal Time Slots (GRO-1793, GRO-2105)
|
||
|
||
| # | Scenario | Steps | Expected |
|
||
|---|----------|-------|----------|
|
||
| TC-WEB-5.12.5 | BookingFlow dynamic slots | Open Book New, select pet and service, pick a date | `GET /api/book/availability?serviceId=<selected>&date=<picked>`; "Checking availability…" shown while loading; slot list rendered |
|
||
| TC-WEB-5.12.6 | BookingFlow slots match wizard | Compare BookingFlow slot times with public booking wizard for same date | Same slots displayed |
|
||
| TC-WEB-5.12.7 | BookingFlow error state | Mock API failure on availability fetch (4xx/5xx OR a 200 with non-array body) | "Failed to load time slots" error shown and the page stays interactive (no white screen) |
|
||
| TC-WEB-5.12.8 | BookingFlow no slots | Select date with no availability | "No available slots on this date" shown |
|
||
| TC-WEB-5.12.9 | RescheduleFlow dynamic slots | Open reschedule, pick a new date | `GET /api/book/availability?serviceId=<appt.serviceId>&date=<picked>`; loading state shown; slot list rendered |
|
||
| TC-WEB-5.12.10 | RescheduleFlow error state | Mock API failure on availability fetch (4xx/5xx OR a 200 with non-array body) | "Failed to load time slots" error shown and the page stays interactive (no white screen) |
|
||
| TC-WEB-5.12.11 | RescheduleFlow no slots | Select date with no availability | "No available slots on this date" shown |
|
||
|
||
> **GRO-2105 regression note:** prior to the fix, both `BookingFlow` and
|
||
> `RescheduleFlow` called `/api/book/availability` with only `date=…`, so the
|
||
> API responded 400 `{error:"serviceId and date are required"}`. The React
|
||
> handler then `.map()`'d that error object, throwing `TypeError: ee.map is
|
||
> not a function` and wiping `<div id="root">`. The fix ensures both flows
|
||
> include `serviceId` in the query string and surface the API's error string
|
||
> (or "Failed to load time slots") instead of crashing.
|
||
|
||
#### 5.12c Waitlist/Booking Status Badges (GRO-1795)
|
||
|
||
| # | Scenario | Steps | Expected |
|
||
|---|----------|-------|----------|
|
||
| TC-WEB-5.12.12 | Confirmed badge | View appointment card with confirmed status | Green "Confirmed" badge displayed |
|
||
| TC-WEB-5.12.13 | Pending badge | View appointment card with pending status | Amber "Pending" badge displayed |
|
||
| TC-WEB-5.12.14 | Waitlisted badge | View appointment card with waitlisted status | Blue "Waitlisted" badge displayed |
|
||
| TC-WEB-5.12.15 | Badge uses CSS classes | Inspect badge element | Badge uses CSS variable-based classes (e.g., bg-green-100, text-amber-600), not hardcoded colors |
|
||
| TC-WEB-5.12.16 | Badge status from data | Compare badge label to appointment.status field | Badge label matches the API appointment status exactly |
|
||
| TC-WEB-5.12.17 | Unknown status fallback | Render badge with unknown status value | Badge renders with the raw status string as label and fallback CSS class |
|
||
|
||
#### 5.12d Appointment API Shape Normalization (GRO-2180)
|
||
|
||
| # | Scenario | Steps | Expected |
|
||
|---|----------|-------|----------|
|
||
| TC-WEB-5.12.18 | Portal appointments load (regression) | Sign in as `uat-customer@groombook.dev`, open `Appointments` | List renders without the "Failed to load appointments. Please try again." error; "Book New" button is visible and clickable |
|
||
| TC-WEB-5.12.19 | Card fields populated from API | Inspect an appointment card | Pet name, service, formatted date (e.g. "Mon, Jun 1, 2026"), time (e.g. "10:00 AM"), and groomer name render — derived from the API's `startTime`/`endTime`/nested `pet`/`staff` objects |
|
||
| TC-WEB-5.12.20 | Upcoming vs Past split | View both tabs | Future, non-cancelled/non-completed appointments appear under "Upcoming"; past/completed/cancelled under "Past" (classification uses absolute `startTime`) |
|
||
| TC-WEB-5.12.21 | Reschedule from card | Expand an upcoming appointment, click Reschedule, pick a date | `GET /api/book/availability?serviceId=<appt.serviceId>&date=<picked>` fires with a non-empty `serviceId` (sourced from the API's nested `service.id`) |
|
||
|
||
> **GRO-2180 regression note:** `/api/portal/appointments` returns ISO
|
||
> `startTime`/`endTime` and nested `pet`/`service`/`staff` objects, but the portal
|
||
> client `Appointment` type expected flat `date`/`time`/`petName` fields.
|
||
> `isUpcoming()` read `appt.date`/`appt.time` (both `undefined`), so
|
||
> `parseTimeTo24Hour(undefined)` threw `TypeError`, the `useEffect` `try/catch`
|
||
> set the error state, and the "Book New" button (only rendered in the success
|
||
> path) became unreachable. The fix normalizes the API response into the flat
|
||
> `Appointment` shape at the fetch boundary (`normalizeAppointment`), prefers the
|
||
> absolute `startTime` in `isUpcoming`, and hardens `parseTimeTo24Hour` against
|
||
> blank/undefined input.
|
||
|
||
### 5.13 Reports UI
|
||
|
||
| # | Scenario | Steps | Expected |
|
||
|---|----------|-------|----------|
|
||
| TC-WEB-5.13.1 | Revenue charts | Navigate to Reports | Revenue charts display with data |
|
||
| TC-WEB-5.13.2 | Utilization graphs | View reports | Staff/resource utilization graphs visible |
|
||
|
||
### 5.14 Settings UI
|
||
|
||
| # | Scenario | Steps | Expected |
|
||
|---|----------|-------|----------|
|
||
| TC-WEB-5.14.1 | Configuration page | Navigate to Settings | Settings page loads without errors |
|
||
| TC-WEB-5.14.2 | Form interactions | Modify settings, save | Settings saved successfully, changes reflected |
|
||
|
||
### 5.15 Navigation
|
||
|
||
| # | Scenario | Steps | Expected |
|
||
|---|----------|-------|----------|
|
||
| TC-WEB-5.15.1 | Sidebar/menu links | Click navigation items | Each section loads correctly |
|
||
| TC-WEB-5.15.2 | All sections reachable | Navigate through all menu items | All sections accessible, no 404 errors |
|
||
| TC-WEB-5.15.3 | No broken links | Test all navigation paths | All links work, no broken routes |
|
||
|
||
### 5.16 Mobile / PWA
|
||
|
||
| # | Scenario | Steps | Expected |
|
||
|---|----------|-------|----------|
|
||
| TC-WEB-5.16.1 | Responsive at 390x844 | Resize viewport to mobile dimensions | Layout adapts correctly, no horizontal scroll |
|
||
| TC-WEB-5.16.2 | PWA install prompt | Load app on supported browser | Install prompt appears when criteria met |
|
||
| TC-WEB-5.16.3 | Touch interactions | Use touch gestures on mobile | All interactions work with touch input |
|
||
|
||
### 5.17 Error & Empty States
|
||
|
||
| # | Scenario | Steps | Expected |
|
||
|---|----------|-------|----------|
|
||
| TC-WEB-5.17.1 | Form validation | Submit form with invalid data | Appropriate validation errors displayed |
|
||
| TC-WEB-5.17.2 | Missing data | Navigate to section with no data | Empty state message displayed, not blank page |
|
||
| TC-WEB-5.17.3 | Error boundaries | Trigger error condition | Friendly error message displayed, app doesn't crash |
|
||
|
||
### 5.18 Pet Profile UI — Enhanced Fields (GRO-1178)
|
||
|
||
| # | Scenario | Steps | Expected |
|
||
|---|----------|-------|----------|
|
||
| TC-WEB-5.18.1 | Coat type displayed in Grooming tab | Open pet profile, go to Grooming tab | Coat type shown (e.g. "Curly", "Double") |
|
||
| TC-WEB-5.18.2 | Preferred cuts displayed | Open Grooming tab | Preferred cuts shown as tags/chips |
|
||
| TC-WEB-5.18.3 | Temperament score displayed (read-only) | Open Basic Info tab | 1–5 star display with score label "(N/5 · staff-set)" |
|
||
| TC-WEB-5.18.4 | Temperament flags displayed (read-only) | Open Basic Info tab | Flag chips shown (e.g. "Anxious", "Good with kids") |
|
||
| TC-WEB-5.18.5 | Medical alerts in Medical tab | Open Medical tab | Alert cards with type, description, severity badge |
|
||
| TC-WEB-5.18.6 | Medical alert severity badges | View Medical tab | Low=green, Medium=amber, High=red badges |
|
||
| TC-WEB-5.18.7 | Edit pet — coat type dropdown | Click Edit on pet, select coat type | Coat type persisted on save |
|
||
| TC-WEB-5.18.8 | Edit pet — add medical alert | Click Edit, add alert with type + severity, save | Alert appears in Medical tab after save |
|
||
| TC-WEB-5.18.9 | Edit pet — remove medical alert | Click Edit, remove an alert, save | Alert removed after save |
|
||
| TC-WEB-5.18.10 | Edit pet — add preferred cut (Enter) | Click Edit, type cut name, press Enter | Cut tag added; persists after save |
|
||
| TC-WEB-5.18.11 | Edit pet — remove preferred cut | Click Edit, click X on cut tag | Cut removed; not persisted after save |
|
||
| TC-WEB-5.18.12 | Medical alert validation | Click Edit, add alert with empty type, try to save | Error "Type is required"; form not submitted |
|
||
| TC-WEB-5.18.13 | Temperament fields read-only | View edit form for pet with temperament data | Temperament score and flags not editable (display only) |
|
||
|
||
### 5.19 Booking Wizard — Pet Size & Coat (GRO-1174)
|
||
|
||
| # | Scenario | Steps | Expected |
|
||
|---|----------|-------|----------|
|
||
| TC-WEB-5.19.1 | Pet size dropdown visible | Step 3 of booking wizard (pet details) | Pet size dropdown shown after breed field with options: Small, Medium, Large, X-Large |
|
||
| TC-WEB-5.19.2 | Coat type dropdown visible | Step 3 of booking wizard | Coat type dropdown shown after pet size with options: Smooth, Double, Curly, Wire, Long, Hairless |
|
||
| TC-WEB-5.19.3 | Size/coat pre-fill from URL | Navigate to booking with `?petSizeCategory=large&petCoatType=curly` | Fields pre-filled with provided values |
|
||
| TC-WEB-5.19.4 | Size/coat optional | Proceed through booking without selecting size/coat | Booking completes successfully |
|
||
| TC-WEB-5.19.5 | Confirmation shows appointment duration | Confirm booking step | Service duration shown as "X min appointment" (buffer not exposed) |
|
||
| TC-WEB-5.19.6 | Confirmation shows pet size/coat | Confirm booking with size/coat selected | Size and coat type shown on pet card in confirmation |
|
||
| TC-WEB-5.19.7 | Availability uses buffer for large/x-large | Select large or x-large size, check availability | Availability slots reflect service duration + buffer for large/x-large |
|
||
| TC-WEB-5.19.8 | Form reset clears size/coat | Complete booking, click "Book another" | Size and coat fields reset to empty |
|
||
| TC-WEB-5.19.9 | New pet record has size/coat | Complete booking, view created pet in admin | Pet record shows selected size and coat type |
|
||
|
||
### 5.20 Buffer Rules Management — Admin UI (GRO-1173)
|
||
|
||
| # | Scenario | Steps | Expected |
|
||
|---|----------|-------|----------|
|
||
| TC-WEB-5.20.1 | Buffer rules section loads | Navigate to Settings page (admin) | "Buffer Rules" section visible with "+ Add Rule" button |
|
||
| TC-WEB-5.20.2 | Add rule — required fields only | Click "+ Add Rule", select a service, enter buffer minutes, submit | Rule created, appears in list below |
|
||
| TC-WEB-5.20.3 | Add rule — with size category | Add rule, select service + size category + buffer minutes | Rule created with size tag shown in list |
|
||
| TC-WEB-5.20.4 | Add rule — with coat type | Add rule, select service + coat type + buffer minutes | Rule created with coat tag shown in list |
|
||
| TC-WEB-5.20.5 | Add rule — with both size and coat | Add rule, select service + size + coat + buffer minutes | Rule created with both tags shown |
|
||
| TC-WEB-5.20.6 | Validation — missing service | Submit form without selecting service | Error: "Service and valid buffer minutes are required" |
|
||
| TC-WEB-5.20.7 | Validation — zero buffer | Submit form with 0 buffer minutes | Error: "Service and valid buffer minutes are required" |
|
||
| TC-WEB-5.20.8 | Edit rule inline | Click "Edit" on a rule, change buffer value, click "Save" | Rule updated in list |
|
||
| TC-WEB-5.20.9 | Cancel edit | Click "Edit", then "Cancel" | Original value unchanged |
|
||
| TC-WEB-5.20.10 | Delete rule — confirmation | Click "Delete" on a rule | Confirmation prompt appears |
|
||
| TC-WEB-5.20.11 | Confirm delete | On confirmation prompt, click "Confirm" | Rule removed from list |
|
||
| TC-WEB-5.20.12 | Cancel delete | On confirmation prompt, click "Cancel" | Rule remains in list |
|
||
| TC-WEB-5.20.13 | Empty state | No rules exist | Message: "No buffer rules configured yet." |
|
||
| TC-WEB-5.20.14 | Toggle form | Click "+ Add Rule", then "Cancel" | Form hidden, no rule created |
|
||
|
||
### 5.21 Service Default Buffer Minutes (GRO-1173)
|
||
|
||
| # | Scenario | Steps | Expected |
|
||
|---|----------|-------|----------|
|
||
| TC-WEB-5.21.1 | Default buffer shown in table | Navigate to Services page | "Default Buffer" column visible in services table |
|
||
| TC-WEB-5.21.2 | New service default is 0 | Click "+ Add Service" | Default Buffer field pre-filled with 0 |
|
||
| TC-WEB-5.21.3 | Create service with buffer | Fill service form, set Default Buffer = 10, submit | Service created with 10 min default buffer |
|
||
| TC-WEB-5.21.4 | Edit service — view buffer | Edit an existing service | Current default buffer value shown in form |
|
||
| TC-WEB-5.21.5 | Update buffer on existing service | Edit service, change Default Buffer to 15, save | Buffer updated, table shows 15 min |
|
||
| TC-WEB-5.21.6 | Buffer field — zero allowed | Set Default Buffer to 0, save | Service saved with 0 (no default buffer) |
|
||
| TC-WEB-5.21.7 | Buffer field — integer only | Enter non-integer value | Field restricts to integer values |
|
||
|
||
### 5.22 Pet Profile — Size Category & Coat Type (GRO-1173)
|
||
|
||
| # | Scenario | Steps | Expected |
|
||
|---|----------|-------|----------|
|
||
| TC-WEB-5.22.1 | Size category dropdown visible | Open Add Pet or Edit Pet form (portal) | "Size Category" dropdown visible with options: Small, Medium, Large, X-Large |
|
||
| TC-WEB-5.22.2 | Coat type dropdown visible | Open Add Pet or Edit Pet form | "Coat Type" dropdown visible with options: Smooth, Double, Curly, Wire, Long, Hairless |
|
||
| TC-WEB-5.22.3 | Size and coat both optional | Submit pet form without selecting size or coat | Pet saved successfully |
|
||
| TC-WEB-5.22.4 | Save pet with size category | Select "Large", fill required fields, save | Pet saved with size = "large" |
|
||
| TC-WEB-5.22.5 | Save pet with coat type | Select "Curly", fill required fields, save | Pet saved with coat = "curly" |
|
||
| TC-WEB-5.22.6 | Size and coat persisted | Save pet with size + coat, edit again | Both fields retain their selected values |
|
||
| TC-WEB-5.22.7 | Clear size | Select size, then clear back to default | Size cleared on save |
|
||
|
||
### 5.23 Pet Profile — API Persistence & Save UX (GRO-1470)
|
||
|
||
| # | Scenario | Steps | Expected |
|
||
|---|----------|-------|----------|
|
||
| TC-WEB-5.23.1 | Save pet — API persistence | Edit a pet, change a field (e.g. coat type), click Save, reload the page | Changed field retained after reload (proves PATCH round-trip to server) |
|
||
| TC-WEB-5.23.2 | Save pet — error state | Trigger an API save failure (e.g. network error) | Error message displayed; edit form stays open; no data cleared |
|
||
| TC-WEB-5.23.3 | Save pet — saving indicator | Click Save | Spinner/indicator shown while request is in flight; form controls disabled |
|
||
|
||
|
||
### 5.24 Booking Funnel Analytics Events (GRO-1794)
|
||
|
||
|
||
| # | Scenario | Steps | Expected |
|
||
|---|----------|-------|----------|
|
||
| TC-WEB-5.24.1 | booking_step_service — public | Select a service in the public booking wizard | `booking_step_service` CustomEvent fires with detail.step="service" and detail.flow="public" |
|
||
| TC-WEB-5.24.2 | booking_step_time — public | Select a time slot and click Continue | `booking_step_time` fires with detail.step="time" and detail.flow="public" |
|
||
| TC-WEB-5.24.3 | booking_step_contact — public | Fill in contact/pet form, click "Review booking" | `booking_step_contact` fires with detail.step="contact" and detail.flow="public" |
|
||
| TC-WEB-5.24.4 | booking_step_submit — public | Confirm and submit the booking | `booking_step_submit` fires with detail.step="submit" and detail.flow="public" |
|
||
| TC-WEB-5.24.5 | booking_confirmed — public | Navigate to /booking-confirmed | `booking_confirmed` fires once on mount with detail.step="confirmed" and detail.flow="public" |
|
||
| TC-WEB-5.24.6 | booking_error — public | Navigate to /booking-error | `booking_error` fires once on mount with detail.step="error" and detail.flow="public" |
|
||
| TC-WEB-5.24.7 | booking_step_service — portal | Select a pet in the portal BookingFlow | `booking_step_service` fires with detail.step="service" and detail.flow="portal" |
|
||
| TC-WEB-5.24.8 | booking_step_time — portal | Pick a date and time in portal BookingFlow | `booking_step_time` fires with detail.step="time" and detail.flow="portal" |
|
||
| TC-WEB-5.24.9 | booking_step_contact — portal | Proceed from groomer selection to review screen | `booking_step_contact` fires with detail.step="groomer" and detail.flow="portal" |
|
||
| TC-WEB-5.24.10 | booking_step_submit — portal | Submit booking in portal BookingFlow | `booking_step_submit` fires with detail.step="submit" and detail.flow="portal" |
|
||
| TC-WEB-5.24.11 | booking_confirmed — portal | Portal booking request succeeds | Inline success state is shown and `booking_confirmed` fires with detail.step="confirmed" and detail.flow="portal" |
|
||
| TC-WEB-5.24.12 | No PII in analytics payloads | Fire each event and inspect detail object | Payload contains only: step, flow, timestamp — no names, emails, phone numbers, or pet names |
|
||
| TC-WEB-5.24.13 | No-op safe | Trigger analytics with window.dispatchEvent blocked (e.g. CSP) | No error thrown; booking flow completes normally |
|
||
|
||
### 5.25 Customer Portal — Better Auth SSO Bridge (GRO-1867)
|
||
|
||
These cases cover the `CustomerPortal` initialisation path that bridges an Authentik / Better Auth session into a portal session via `POST /api/portal/session-from-auth`. The bridge runs after the URL-impersonation (`?sessionId=`) and dev-user paths have been ruled out.
|
||
|
||
**Pre-conditions:**
|
||
|
||
- UAT is configured with Authentik SSO. The seeded customer **Authentik** password lives in the `authentik-uat-users-credentials` Secret in the `groombook-uat` namespace (key `uat_customer_password`) — **NOT** in `seed-uat-passwords:customer-password` (that Secret holds the *Better Auth* email+password credential, a separate identity store; see GRO-2089). Pull the Authentik password at the start of every run:
|
||
```bash
|
||
CUSTOMER_AUTHENTIK=$(kubectl get secret authentik-uat-users-credentials -n groombook-uat \
|
||
-o jsonpath='{.data.uat_customer_password}' | base64 -d)
|
||
```
|
||
The Authentik user is provisioned by Terraform (`infra/terraform/users.tf`); the `lifecycle.ignore_changes = [password]` block means the password is set on initial creation and never auto-rotated, so the value held in the live Secret is the one Authentik itself has. If Authentik rejects it, the user was re-provisioned out-of-band via the Authentik admin UI and the Secret has drifted from the live identity — fix the Secret (or the admin-set password) and re-run.
|
||
- `POST /api/portal/session-from-auth` from [GRO-1866](https://paperclip.farhoodlabs.com/GRO/issues/GRO-1866) is deployed on UAT.
|
||
- Clear cookies and localStorage between cases unless otherwise noted.
|
||
|
||
| # | Scenario | Steps | Expected |
|
||
|---|----------|-------|----------|
|
||
| TC-WEB-5.25.1 | Authenticated customer reaches portal dashboard | 1. From clean state, navigate to UAT `/login`. 2. Click "Sign in with SSO" and complete Authentik flow with a seeded **customer** identity. 3. After callback, land on `/`. | Portal dashboard renders. No redirect to `/login`. No impersonation banner. Top-right greeting reads "Hi, <FirstName>". |
|
||
| TC-WEB-5.25.2 | Bridge call sequence | Repeat TC-WEB-5.25.1 with DevTools → Network open and the **All** tab filtered to `/api/`. | In order: `GET /api/auth/get-session` → 200. `POST /api/portal/session-from-auth` → 201 with body `{ sessionId, clientId, clientName }`. |
|
||
| TC-WEB-5.25.3 | Subsequent portal calls use the bridged session ID | After TC-WEB-5.25.1 succeeds, navigate to **Appointments**, **My Pets**, **Billing**, **Settings**. Inspect any `/api/portal/*` request in DevTools → Network. | Each portal API call carries an `X-Impersonation-Session-Id` header whose value equals the `sessionId` returned by `session-from-auth` (not a URL-param value). Each call returns 200 (or 404 for genuinely empty collections), never 401. |
|
||
| TC-WEB-5.25.4 | No impersonation chrome for the customer's own session | After TC-WEB-5.25.1, scan the portal UI. | No amber border around the page. No "STAFF VIEW" watermark. No "End Impersonation" button in the sidebar. The customer is themselves; only impersonation sessions started via `?sessionId=` show the banner. |
|
||
| TC-WEB-5.25.5 | 404 fallback for authenticated user with no client record | 1. Sign in via SSO with an Authentik account whose email is **not** present in `clients`. 2. Land on `/`. | `POST /api/portal/session-from-auth` returns 404. The portal renders a centred card titled **"Portal access not configured"** with the message about contacting the groomer and a **Sign out** button. No redirect loop, no portal chrome. |
|
||
| TC-WEB-5.25.6 | 404 fallback Sign-out escape hatch | From TC-WEB-5.25.5 click **Sign out**. | `POST /api/auth/sign-out` fires; browser navigates to `/login`; the Authentik session cookie is cleared. Reloading `/` no longer hits 404 (will show the login page). |
|
||
| TC-WEB-5.25.7 | Bridge precedence — impersonation URL wins | 1. Sign in via SSO as a customer. 2. Open a new tab to `https://uat.groombook.dev/?sessionId=<a-valid-staff-impersonation-session-id>`. | The impersonation path runs; the amber banner appears for the impersonated client. The Better Auth bridge is **not** called on this load (`session-from-auth` absent in Network). |
|
||
| TC-WEB-5.25.8 | Bridge precedence — dev user wins | In dev mode (e.g. local) with `localStorage["dev-user"]` set to a client persona, navigate to `/`. | The dev-session path runs (`POST /api/portal/dev-session`). The Better Auth bridge is **not** called (`session-from-auth` absent in Network). Staff dev users still redirect to `/admin`. |
|
||
| TC-WEB-5.25.9 | Staff Better Auth session does not run the customer bridge | Sign in via SSO with a staff identity. Navigate to `/`. | `App.tsx` routing redirects to `/admin`. `POST /api/portal/session-from-auth` is **not** called. |
|
||
| TC-WEB-5.25.10 | Unauthenticated user is sent to login (no infinite loop) | Without signing in, navigate directly to `/`. | `App.tsx` renders the LoginPage. `CustomerPortal` does not render. No `session-from-auth` request is made. |
|
||
| TC-WEB-5.25.11 | Session persists across reload via Better Auth cookie | After TC-WEB-5.25.1 succeeds, reload the page. | Portal dashboard re-renders. A fresh `GET /api/auth/get-session` + `POST /api/portal/session-from-auth` pair runs and yields 200/201. Greeting still reads "Hi, <FirstName>". |
|
||
|
||
### 5.27 Customer Portal — Authenticated HTML-route cold mount (GRO-2099)
|
||
|
||
These cases guard against the regression where a customer who had just completed SSO sign-in was bounced back to `/login` (with a blank React root) when navigating directly to `/portal`, `/book`, `/schedule`, or even `/login` itself. Root cause: `Dashboard.tsx`'s `!sessionId && !isImpersonating && !getDevUser()` guard fired during the CustomerPortal's bootstrap — before the SSO bridge resolved `portalSessionId` — and redirected to `/login`. The fix: `CustomerPortal` now shows a loading state while the bootstrap is in flight, so the portal chrome and its `!sessionId` child guards do not mount prematurely. App.tsx additionally redirects an authenticated user at `/login` to `/` instead of rendering `null`.
|
||
|
||
**Pre-conditions:**
|
||
|
||
- TC-WEB-5.25.1 — TC-WEB-5.25.3 must pass on the build under test.
|
||
- Clear cookies and localStorage between cases.
|
||
|
||
| # | Scenario | Steps | Expected |
|
||
|---|----------|-------|----------|
|
||
| TC-WEB-5.27.1 | Authenticated customer lands on `/portal` after direct nav | 1. From clean state, complete TC-WEB-5.25.1 (SSO sign-in as a customer). 2. Land on `/`. 3. `browser_navigate` (full page load) directly to `/portal`. | Final URL stays at `/portal`. The React root is non-empty. The portal dashboard renders with the customer's name. No `Navigate to /login` fires. |
|
||
| TC-WEB-5.27.2 | Authenticated customer lands on `/book` and `/schedule` after direct nav | From TC-WEB-5.27.1, `browser_navigate` to `/book` then `/schedule` (one fresh navigation each). | Each final URL stays at the navigated path. The portal chrome is visible. The page does not redirect to `/login`. |
|
||
| TC-WEB-5.27.3 | Authenticated customer at `/login` is auto-redirected to `/` | From TC-WEB-5.27.1, `browser_navigate` to `/login`. | The browser ends at `/` (not at a blank `/login`). The portal dashboard renders. No blank React root at `/login`. |
|
||
| TC-WEB-5.27.4 | Loading state is visible during the bootstrap, no portal chrome flash | 1. With the UAT build under test, open DevTools → Network and throttle to Slow 3G. 2. Sign in via SSO. 3. Land on `/`. | A "Loading…" element (`role="status"`) is briefly visible. The portal nav (Home / Appointments / etc.) is NOT visible during the loading window. No `Navigate to /login` fires during the bootstrap. |
|
||
| TC-WEB-5.27.5 | SSO bridge still runs and yields 201 | From TC-WEB-5.27.4 (or TC-WEB-5.27.1), inspect Network. | The same `GET /api/auth/get-session` (200) → `POST /api/portal/session-from-auth` (201) sequence from TC-WEB-5.25.2 still runs. The customer name appears in the greeting. |
|
||
| TC-WEB-5.27.6 | Unauthenticated direct nav to `/portal` still ends at `/login` (no regression) | Clear cookies. `browser_navigate` to `/portal`. | The portal briefly shows the loading state, then `CustomerPortal`'s `!session && !portalSessionId` guard redirects to `/login`. The login form renders. No infinite loop. |
|
||
| TC-WEB-5.27.7 | Groomer SSO still works (no regression) | 1. From clean state, sign in via SSO as the groomer identity (uat-groomer). 2. Land on `/`. | `App.tsx`'s staff check redirects to `/admin`. The groomer nav renders. No `CustomerPortal` flash. No `/portal` redirect loop. |
|
||
| TC-WEB-5.27.8 | Impersonation session still works (no regression) | 1. With an active impersonation session, open `/?sessionId=<id>`. | The amber "STAFF VIEW" chrome renders. The portal loads. No `/login` redirect. |
|
||
|
||
### 5.26 Customer Portal — RescheduleFlow under SSO Bridge (GRO-2012)
|
||
|
||
These cases guard against the regression where an SSO-bridge customer (no `?sessionId=` URL param, no impersonation session) could trigger the RescheduleFlow and have `RescheduleFlow` receive `sessionId={null}`, which caused the internal `/api/book/availability` call to send `X-Impersonation-Session-Id: ` (empty) and return 401. The fix: `CustomerPortal` now passes `sessionId={session?.id ?? portalSessionId}` to `<RescheduleFlow>` (matching the fallback `renderSection()` already used).
|
||
|
||
**Pre-conditions:**
|
||
|
||
- TC-WEB-5.25.1 — TC-WEB-5.25.3 must pass on the build under test.
|
||
- The seeded customer used has at least one upcoming, non-cancelled appointment with `status` ∈ {`pending`, `confirmed`}.
|
||
|
||
| # | Scenario | Steps | Expected |
|
||
|---|----------|-------|----------|
|
||
| TC-WEB-5.26.1 | RescheduleFlow receives portalSessionId (no 401) | 1. Complete TC-WEB-5.25.1 (SSO sign-in as a customer). 2. From the dashboard, click **Reschedule** on the next-upcoming appointment. 3. In the RescheduleFlow modal, pick a future date. 4. Open DevTools → Network and filter to `/api/`. | The `GET /api/book/availability?date=<picked>` request includes an `X-Impersonation-Session-Id` header whose value equals the `sessionId` from `session-from-auth`. The request returns 200. The time-slot list populates. No 401. |
|
||
| TC-WEB-5.26.2 | RescheduleFlow submit succeeds | From TC-WEB-5.26.1, pick a time slot and confirm. | `POST /api/portal/appointments/<id>/reschedule` (or the equivalent) includes the same `X-Impersonation-Session-Id` value. Returns 200. The modal closes and the appointment card reflects the new time. |
|
||
| TC-WEB-5.26.3 | Impersonation flow reschedule is unchanged (no regression) | 1. With an active impersonation session (`?sessionId=<active>`), load `/`. 2. Click **Reschedule** on an appointment. 3. Pick a date. | `GET /api/book/availability` includes `X-Impersonation-Session-Id` equal to the impersonation `sessionId` (not `portalSessionId`). Returns 200. Behaves identically to the pre-fix build. |
|
||
| TC-WEB-5.26.4 | No `X-Impersonation-Session-Id` is empty / null | From TC-WEB-5.26.1, inspect every `/api/portal/*` and `/api/book/*` request. | No request has an empty or `null` `X-Impersonation-Session-Id` header. |
|
||
|
||
## 6. Pass/Fail Criteria
|
||
|
||
**Pass:**
|
||
- All test cases execute without errors
|
||
- Expected results match actual results for all scenarios
|
||
- No visual regressions compared to baseline
|
||
- No console errors or warnings in browser DevTools
|
||
|
||
**Fail:**
|
||
- Any unexpected result with severity
|
||
- Steps to reproduce provided
|
||
- Screenshot or screen recording of failure
|
||
- Error details from browser console or network tab
|
||
|
||
## 7. Update Policy
|
||
|
||
**Any PR that changes user-facing behaviour MUST update this file.**
|
||
|
||
When modifying the GroomBook Web application in ways that affect the user interface or user experience:
|
||
1. Review all relevant test cases in this playbook
|
||
2. Add new test cases for new features or flows
|
||
3. Modify existing test cases if behaviour changes
|
||
4. Remove test cases for deprecated features
|
||
5. Reference the updated section(s) in the PR description (e.g., "Updated UAT_PLAYBOOK.md §5.5 — new appointment group feature") |