Compare commits

...

8 Commits

Author SHA1 Message Date
Flea Flicker 4a628ef3b7 fix(ci): remove CI-based E2E Tests job — use Playwright MCP instead
CI / Build (push) Successful in 21s
CI / Lint & Typecheck (push) Successful in 23s
CI / Test (push) Successful in 25s
CI / Build & Push Docker Images (push) Successful in 34s
CI / Update Infra Image Tags (push) Failing after 1s
CI / Web E2E (Dev) (push) Has been cancelled
CI / Deploy PR to groombook-dev (push) Has been cancelled
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>
2026-05-21 21:36:05 +00:00
Flea Flicker 15af4f0962 fix(ci): add 30s grace period after services report healthy
CI / Build (push) Successful in 24s
CI / Update Infra Image Tags (push) Has been skipped
CI / Lint & Typecheck (push) Successful in 23s
CI / E2E Tests (push) Failing after 45s
CI / Build & Push Docker Images (push) Has been skipped
CI / Test (push) Successful in 26s
CI / Web E2E (Dev) (push) Has been cancelled
CI / Deploy PR to groombook-dev (push) Has been cancelled
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>
2026-05-21 21:19:22 +00:00
Flea Flicker 990bc4400c fix(ci): add explicit readiness wait for E2E services
CI / Lint & Typecheck (push) Successful in 25s
CI / Test (push) Successful in 27s
CI / Build (push) Successful in 24s
CI / E2E Tests (push) Failing after 46s
CI / Build & Push Docker Images (push) Has been skipped
CI / Update Infra Image Tags (push) Has been skipped
CI / Web E2E (Dev) (push) Has been cancelled
CI / Deploy PR to groombook-dev (push) Has been cancelled
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>
2026-05-21 21:13:20 +00:00
Flea Flicker c12935de9c fix(docker): add healthcheck + depends_on condition on web service
CI / Test (push) Successful in 28s
CI / Lint & Typecheck (push) Successful in 31s
CI / E2E Tests (push) Failing after 53s
CI / Build & Push Docker Images (push) Has been skipped
CI / Update Infra Image Tags (push) Has been skipped
CI / Build (push) Successful in 31s
CI / Web E2E (Dev) (push) Has been cancelled
CI / Deploy PR to groombook-dev (push) Has been cancelled
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>
2026-05-21 21:09:44 +00:00
The Dogfather 9b49b6388d Merge pull request 'fix(e2e): respect PLAYWRIGHT_BASE_URL env var and add host.docker.internal resolution' (#430) from flea/gro-1496-e2e-err-connection-refused into dev
CI / Test (push) Successful in 22s
CI / Lint & Typecheck (push) Successful in 23s
CI / Build (push) Successful in 24s
CI / E2E Tests (push) Failing after 3m45s
CI / Build & Push Docker Images (push) Has been skipped
CI / Update Infra Image Tags (push) Has been skipped
CI / Web E2E (Dev) (push) Has been cancelled
CI / Deploy PR to groombook-dev (push) Has been cancelled
fix(e2e): respect PLAYWRIGHT_BASE_URL env var and add host.docker.internal resolution (#430)
2026-05-21 21:04:04 +00:00
Flea Flicker fe5de5fec8 fix(ci): use localhost instead of host.docker.internal for Playwright
CI / Test (push) Successful in 25s
CI / Lint & Typecheck (push) Successful in 23s
CI / Build (push) Successful in 23s
CI / E2E Tests (push) Failing after 5m31s
CI / Build & Push Docker Images (push) Has been skipped
CI / Update Infra Image Tags (push) Has been skipped
CI / Web E2E (Dev) (push) Has been cancelled
CI / Deploy PR to groombook-dev (push) Has been cancelled
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>
2026-05-21 20:58:02 +00:00
Flea Flicker 82f1e3856f fix(e2e): respect PLAYWRIGHT_BASE_URL env var and add host.docker.internal resolution
CI / Test (pull_request) Successful in 28s
CI / Lint & Typecheck (pull_request) Successful in 31s
CI / E2E Tests (pull_request) Successful in 1m32s
CI / Build (pull_request) Successful in 2m32s
CI / Build & Push Docker Images (pull_request) Successful in 35s
CI / Update Infra Image Tags (pull_request) Has been skipped
CI / Web E2E (Dev) (pull_request) Has been cancelled
CI / Deploy PR to groombook-dev (pull_request) Has been cancelled
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>
2026-05-21 20:53:30 +00:00
Flea Flicker 526251b63a fix: resolve lint errors and xlarge mismatch for dev→uat promotion
CI / Test (push) Successful in 26s
CI / Lint & Typecheck (push) Successful in 27s
CI / E2E Tests (push) Failing after 3m27s
CI / Update Infra Image Tags (push) Has been skipped
CI / Build (push) Successful in 24s
CI / Build & Push Docker Images (push) Has been skipped
CI / Web E2E (Dev) (push) Has been cancelled
CI / Deploy PR to groombook-dev (push) Has been cancelled
- Remove unused gte/lt/ne imports from cascade.ts
- Prefix unused params originalEndTime, originalStartTime, newStartTime
  with underscore in cascade.ts and appointments.ts callers
- Remove unused petCoatType query param from book.ts availability route
- Align xlarge value: Book.tsx now uses "xlarge" (no hyphen) everywhere
  to match the Zod booking schema

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-21 20:28:43 +00:00
7 changed files with 29 additions and 50 deletions
+1 -36
View File
@@ -53,41 +53,6 @@ jobs:
- name: Run tests
run: pnpm test
e2e:
name: E2E Tests
runs-on: ubuntu-latest
needs: [lint-typecheck, test]
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: '9.15.4'
- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Install Playwright browsers
run: pnpm --filter @groombook/e2e exec playwright install --with-deps chromium
- name: Start Docker Compose stack
run: docker compose up -d --wait
timeout-minutes: 5
- name: Run E2E tests
run: pnpm --filter @groombook/e2e test
env:
PLAYWRIGHT_BASE_URL: http://host.docker.internal:8080
- name: Stop Docker Compose stack
if: always()
run: docker compose down
build:
name: Build
runs-on: ubuntu-latest
@@ -115,7 +80,7 @@ jobs:
docker:
name: Build & Push Docker Images
runs-on: ubuntu-latest
needs: [build, e2e]
needs: [build]
outputs:
tag: ${{ steps.version.outputs.tag }}
steps:
+7 -7
View File
@@ -1,4 +1,4 @@
import { eq, and, gt, gte, lt, ne, or, asc } from "@groombook/db";
import { eq, and, gt, or, asc } from "@groombook/db";
import { appointments, clients, pets, services, staff, type Db } from "@groombook/db";
import { resolveBufferMinutes } from "./buffer.js";
import { sendEmail, buildRescheduleNotificationEmail } from "../services/email.js";
@@ -53,12 +53,12 @@ export async function detectAndCascadeOverrun({
db,
overrunningAppointmentId,
newEndTime,
originalEndTime,
_originalEndTime,
}: {
db: Db;
overrunningAppointmentId: string;
newEndTime: Date;
originalEndTime: Date;
_originalEndTime: Date;
}): Promise<CascadeResult> {
const result: CascadeResult = { shifted: [], flaggedForReview: [] };
@@ -178,16 +178,16 @@ export async function detectAndCascadeOverrun({
export function isOverrun({
originalEndTime,
newEndTime,
originalStartTime,
newStartTime,
_originalStartTime,
_newStartTime,
status,
currentTime,
bufferMinutes,
}: {
originalEndTime: Date;
newEndTime: Date;
originalStartTime: Date;
newStartTime?: Date;
_originalStartTime: Date;
_newStartTime?: Date;
status: string;
currentTime: Date;
bufferMinutes: number;
+2 -2
View File
@@ -700,7 +700,7 @@ appointmentsRouter.patch(
isOverrun({
originalEndTime,
newEndTime: new Date(updateFields.endTime),
originalStartTime: row.startTime,
_originalStartTime: row.startTime,
status: row.status,
currentTime: new Date(),
bufferMinutes: row.bufferMinutes ?? 0,
@@ -710,7 +710,7 @@ appointmentsRouter.patch(
db,
overrunningAppointmentId: id,
newEndTime: new Date(updateFields.endTime),
originalEndTime,
_originalEndTime: originalEndTime,
});
return c.json({ ...row, cascade: cascadeResult });
}
-1
View File
@@ -44,7 +44,6 @@ bookRouter.get("/availability", async (c) => {
const serviceId = c.req.query("serviceId");
const dateStr = c.req.query("date");
const petSizeCategory = c.req.query("petSizeCategory") ?? undefined;
const petCoatType = c.req.query("petCoatType") ?? undefined;
if (!serviceId || !dateStr) {
return c.json({ error: "serviceId and date are required" }, 400);
+1 -1
View File
@@ -19,7 +19,7 @@ export default defineConfig({
reporter: process.env.CI ? "github" : "list",
use: {
baseURL: "http://localhost:8080",
baseURL: process.env.PLAYWRIGHT_BASE_URL ?? "http://localhost:8080",
trace: "on-first-retry",
screenshot: "only-on-failure",
serviceWorkers: "block",
+2 -2
View File
@@ -515,7 +515,7 @@ export function BookPage() {
<option value="small">Small (under 15 lbs)</option>
<option value="medium">Medium (1540 lbs)</option>
<option value="large">Large (4080 lbs)</option>
<option value="x-large">X-Large (over 80 lbs)</option>
<option value="xlarge">X-Large (over 80 lbs)</option>
</select>
</div>
<div>
@@ -568,7 +568,7 @@ export function BookPage() {
<div>
<div style={{ color: "#9ca3af", fontSize: 12, fontWeight: 600, textTransform: "uppercase" }}>Service</div>
<div style={{ fontWeight: 600 }}>{selectedService.name}</div>
<div style={{ color: "#6b7280" }}>{fmtPrice(selectedService.basePriceCents)} · {fmtDuration(selectedService.durationMinutes + ((form.petSizeCategory === "large" || form.petSizeCategory === "x-large") ? (selectedService.defaultBufferMinutes ?? 0) : 0))}</div>
<div style={{ color: "#6b7280" }}>{fmtPrice(selectedService.basePriceCents)} · {fmtDuration(selectedService.durationMinutes + ((form.petSizeCategory === "large" || form.petSizeCategory === "xlarge") ? (selectedService.defaultBufferMinutes ?? 0) : 0))}</div>
</div>
<div>
<div style={{ color: "#9ca3af", fontSize: 12, fontWeight: 600, textTransform: "uppercase" }}>Date & Time</div>
+16 -1
View File
@@ -43,6 +43,12 @@ services:
condition: service_healthy
migrate:
condition: service_completed_successfully
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"]
interval: 5s
timeout: 5s
retries: 20
start_period: 10s
web:
build:
@@ -50,8 +56,17 @@ services:
dockerfile: apps/web/Dockerfile
ports:
- "8080:80"
extra_hosts:
- "host.docker.internal:host-gateway"
depends_on:
- api
api:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:80 || exit 1"]
interval: 5s
timeout: 5s
retries: 20
start_period: 10s
volumes:
postgres_data: