Mandates groombook-playwright MCP for all browser interaction during UAT.
Documents available MCP tools, execution workflow, and environment URLs.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
UAT is now executed by Shedward Scissorhands via the groombook-playwright
MCP server. Legacy scripted Playwright suites remain for CI regression
only. Added Section 2 documenting the MCP tools, how test cases map to
MCP calls, and the role of legacy CI tests.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
E2E testing moved to Playwright MCP with Shedward Scissorhands in UAT
per GRO-904. The e2e job was blocking the docker job, which blocked the
entire release pipeline.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Even after nginx is listening on port 80, there can be a brief window
where the first Playwright requests hit still-warming router logic or
upstream connection pool setup, causing inconsistent E2E failures.
Now the readiness step:
1. Polls until both http://localhost:8080 and http://localhost:3000/health
return HTTP 200 (up to 60 attempts = 10 min max)
2. Once both are confirmed up, sleeps 30 additional seconds before
proceeding to E2E tests — a settling period for nginx and the Node
server to fully stabilize
Co-Authored-By: Paperclip <noreply@paperclip.ing>
returns immediately after Docker reports
containers started, not after services inside those containers are actually
listening. This causes Playwright to hit nginx before it's ready.
Now:
- Start containers with (no --wait)
- Poll http://localhost:8080 AND http://localhost:3000/health every 10s,
up to 30 attempts (5 minutes total)
- Only proceed to E2E tests once both are reachable
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Previously web started immediately after the api container launched, not
after it was ready. Playwright tests then hit the web server before the
nginx process had fully started, causing connection refused errors.
Now:
- api has a 30s startup grace via start_period and 20 retries
- web waits for api to be healthy (not just started)
- both services verify readiness before dependent steps proceed
Co-Authored-By: Paperclip <noreply@paperclip.ing>
host.docker.internal is a Docker Desktop feature unavailable on Gitea Actions
ubuntu-latest runners. Linux runners can reach the Docker Compose service
via localhost when using docker compose expose/published ports.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
The Playwright config hardcoded localhost:8080 as baseURL, ignoring
the PLAYWRIGHT_BASE_URL env var set in CI. Docker Compose was also
missing extra_hosts to resolve host.docker.internal on Gitea Actions
runners (which use DIND).
Fixes GRO-1496.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
* feat(GRO-106): messaging schema + migrations
- Add conversations, messages, message_attachments, message_consent_events tables
- Add messagingChannelEnum, messageDirectionEnum, messageStatusEnum, messageConsentKindEnum
- Extend business_settings with messagingPhoneNumber and telnyxMessagingProfileId columns
- Add required indexes and unique constraints with cascade-on-delete FKs
- Add migration 0030_messaging.sql
* fix(GRO-981): restore journal entries and add DESC to indexes
- _journal.json: restore idx 28 (0028_sms_reminders), add idx 29
(0029_db_indexes_constraints), renumber 0030_messaging to idx 30
(was missing 0028 and 0029 entries — they were silently skipped)
- schema.ts: add .desc() to conversations.lastMessageAt and
messages.createdAt indexes per spec
- 0030_messaging.sql: add DESC to both generated index statements
* feat(GRO-106): inbound Telnyx webhook + persistence
- Add POST /api/webhooks/telnyx/messaging route with HMAC signature verification
- Add services/messaging/inbound.ts: findOrCreateConversation, upsertMessage (idempotent on providerMessageId), delivery receipt handling
- Register telnyxWebhooksRouter in index.ts (before auth middleware)
- Add unit tests for signature validation, find-or-create, idempotent insert, delivery receipt
* fix(GRO-982): address all QA blocking failures
- #7: Extract validateTelnyxSignature in sms.ts as standalone exported fn,
reuse in TelnyxProvider.validateWebhookSignature and telnyx.ts route
- #1: Replace uuid v4 import with crypto.randomUUID() (built-in, no dep)
- #2: Remove updatedAt from messages update in handleMessageFinalized
(no such column exists)
- #3: Fix test import path ../../ → ../../../ for telnyx route import
- #4: validateTelnyxSignature accepts string | undefined | null to match
Hono c.req.header() return type
- #5&6: Add null guards for .returning() results in findOrCreateConversation
and upsertMessage
- #8: Remove dead buildFindOrCreateConversationParams function
- #9: Remove unused imports (messageDirectionEnum, messageStatusEnum,
resolveBusinessIdByMessagingNumber in test)
- #10: Wrap upsertMessage insert in try/catch; unique violation returns
{isNew: false} instead of crashing
- #11: Add EOF newlines to all modified files
* chore: add uuid dependency for messaging services
* fix(GRO-982): address 5 test failures in inbound webhook
- Fix signature route tests: use /messaging not full mount path
- Fix handleMessageReceived mock order: business lookup first
- Fix stale mock state: add full mockReset in handleMessageFinalized beforeEach
- Fix delivery logic: set delivered for all message.finalized events
- Deduplicate test that was accidentally added twice
* fix(GRO-982): look up or create client by phone before inserting conversation
Fixes FK constraint violation where clientId was set to businessSettings.id
or a random UUID. Now looks up clients.phone = clientPhone first; if no match,
creates a placeholder client with phone as name and a placeholder email.
* fix(GRO-982): address QA round 4 blocking failures
- Fix URL in signature tests: use /messaging not full path
- Reorder mocks: businessSettings first, then conversations, clients, messages
- Add mockDb.mockReset in handleMessageFinalized beforeEach
- Remove direction guard: set delivered for any message.finalized
* fix(GRO-982): add missing message insert mock in handleMessageReceived test
* fix(GRO-982): simplify test mocks to match actual code flow
---------
Co-authored-by: groombook-engineer[bot] <269742240+groombook-engineer[bot]@users.noreply.github.com>
Co-authored-by: Chris Farhood <chris@farhood.org>
Co-authored-by: Paperclip <noreply@paperclip.ing>
GroomBook is an open-source, self-hostable pet grooming business management & CRM platform. The monorepo contains the Hono API (`apps/api`), React PWA web app (`apps/web`), E2E tests (`apps/e2e`), and shared packages (`packages/db`, `packages/types`). Tech stack: Hono + React 19 + Vite + PostgreSQL + Drizzle ORM + Authentik OIDC.
## 2. Environments
## 2. Execution Method
All UAT is executed by **Shedward Scissorhands** via the **groombook-playwright MCP server**. No manual browser checks or scripted Playwright suites are used for UAT.
### MCP Tools
Shedward uses the `mcp__playwright-groombook__*` tool family:
| Tool | Purpose |
|------|---------|
| `browser_navigate` | Navigate to a URL |
| `browser_snapshot` | Capture accessibility snapshot (preferred over screenshot) |
| `browser_take_screenshot` | Capture visual screenshot when needed |
| `browser_click` | Click an element by ref or selector |
| `browser_fill_form` | Fill form fields |
| `browser_type` | Type text into focused element |
| `browser_evaluate` | Run JavaScript in page context |
| `browser_tabs` | Manage browser tabs |
| `browser_close` | Close browser |
### How Test Cases Map to MCP Calls
Each test case in Section 4 describes steps like "Navigate to X" or "Click Y". Shedward translates these to MCP tool calls:
- **"Navigate to [URL]"** → `browser_navigate` with the environment URL
- **"Click [element]"** → `browser_snapshot` to find the element ref, then `browser_click`
- **"Fill in [field]"** → `browser_fill_form` or `browser_click` + `browser_type`
- **"Verify [state]"** → `browser_snapshot` and inspect the accessibility tree
- **"Check for errors"** → `browser_console_messages` + `browser_snapshot`
Shedward reads this playbook, executes each test case via MCP tools, captures evidence (snapshots/screenshots), and reports pass/fail per test case.
### Legacy CI Tests
The scripted Playwright suites in `apps/e2e/` and `apps/web/e2e/` are retained for CI regression testing only. They are **not** the primary UAT mechanism. UAT is exclusively MCP-driven by Shedward.
## 3. Environments
| Environment | URL | Notes |
|-------------|-----|-------|
@@ -14,7 +56,7 @@ GroomBook is an open-source, self-hostable pet grooming business management & CR
**Local Development:** Run `docker compose up --build` at repository root. Web app available at `localhost:8080`, API at `localhost:3000`.
## 3. Pre-conditions
## 4. Pre-conditions
- UAT environment is accessible at `https://uat.groombook.dev`
- Test accounts are seeded with the following personas:
@@ -29,7 +71,7 @@ GroomBook is an open-source, self-hostable pet grooming business management & CR
- Stripe test keys are configured for payment flow testing
- Email/SMS providers (Telnyx, etc.) are configured for notification testing
## 4. Test Cases
## 5. Test Cases
### 4.1 Authentication
@@ -252,7 +294,7 @@ GroomBook is an open-source, self-hostable pet grooming business management & CR
| TC-APP-4.21.10 | Whitespace trimming | 1. Send ` START ` or `\tSTOP\n` | Keywords are trimmed before matching |
| TC-APP-4.21.11 | Non-keyword messages ignored | 1. Send `STOP IT`, `help me`, `hello` | Returns null from `detectKeyword`, no consent event inserted, no reply sent |
| TC-APP-4.21.12 | Consent event audit log | 1. After any keyword, query `messageConsentEvents` table | Record exists with correct `clientId`, `businessId`, `kind`, and `source: "sms_keyword"` |
## 5. Pass/Fail Criteria
## 6. Pass/Fail Criteria
**Pass:** All test cases execute without errors. Expected results match actual results. No regressions are observed. All functionality works as documented.
@@ -265,7 +307,7 @@ GroomBook is an open-source, self-hostable pet grooming business management & CR
**Regressions:** If a previously working feature fails during this UAT run, it is considered a regression and must be addressed before the release can proceed.
## 6. Update Policy
## 7. Update Policy
**Any PR that changes user-facing behaviour MUST update this file.**
@@ -275,4 +317,4 @@ When modifying features that affect:
- Configuration (settings, integrations)
- Data visibility (reports, search, filtering)
The corresponding test case(s) in Section 4 must be updated to reflect the new behaviour. The PR description must reference which playbook section was updated (e.g., "Updated UAT_PLAYBOOK.md §4.5 — new appointment group scheduling feature").
The corresponding test case(s) in Section 5 must be updated to reflect the new behaviour. The PR description must reference which playbook section was updated (e.g., "Updated UAT_PLAYBOOK.md §4.5 — new appointment group scheduling feature").
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.