# Session Handoff: Playwright E2E Setup **Date:** 2026-04-19 **Session Focus:** Set up Playwright E2E testing; write and pass auth flow tests ## What Was Accomplished 1. **Installed `@playwright/test` 1.59.1** + Chromium headless shell binary 2. **Created `playwright.config.ts`** — Pixel 5 viewport, `reuseExistingServer` for local dev, `github` reporter in CI, 30s timeout, 1 worker (serial) 3. **Created `tests/e2e/helpers/mailpit.ts`** — `deleteAllMail()` and `getMagicLink(email)` against Mailpit at `http://127.0.0.1:54324` 4. **Created `tests/e2e/auth.spec.ts`** — 2 tests: full magic link flow + protected route redirect 5. **Fixed `supabase/config.toml`** — `site_url` changed from `http://127.0.0.1:3000` to `http://localhost:3000`; `additional_redirect_urls` updated to include `http://localhost:3000/auth/callback` and `http://127.0.0.1:3000/auth/callback` 6. **Added scripts to `package.json`**: `test:e2e`, `test:e2e:ui`, `test:e2e:headed` 7. **Both tests pass: 2/2** ## Key Debugging Discoveries | Problem | Root Cause | Fix | |---------|-----------|-----| | `getByRole('button', { name: 'Log In' })` not found | `IonButton` with `router-link` renders as `` (role=`link`) | Changed to `getByRole('link', { name: 'Log In' })` | | `getByLabel('Email address')` not found | `IonLabel` is not associated via `for`/`id` with `IonInput` | Changed to `getByPlaceholder('you@example.com')` | | `page.goto(magicLink)` → `ERR_CONNECTION_REFUSED` to `127.0.0.1:54321` | Playwright's Chromium headless shell cannot reach Supabase local auth server directly | Use Node.js `fetch(magicLink, { redirect: 'manual' })` to follow redirect server-side; navigate browser to the resulting app callback URL | | Callback URL was `/?code=` instead of `/auth/callback?code=` | `emailRedirectTo` not whitelisted in `supabase/config.toml`; Supabase fell back to `site_url` | Fixed `config.toml` redirect URLs; required `supabase stop && supabase start` | ## Ionic-Specific Playwright Patterns (confirmed working) - `IonButton` with `router-link` → use `getByRole('link', { name: '...' })` - `IonButton` without `router-link` → use `getByRole('button', { name: '...' })` - `IonInput` → use `getByPlaceholder(...)` (label association not standard HTML) - Never navigate browser directly to Supabase local auth URL — follow redirect server-side first ## Files Created or Modified | File | Action | |------|--------| | `playwright.config.ts` | Created | | `tests/e2e/helpers/mailpit.ts` | Created | | `tests/e2e/auth.spec.ts` | Created | | `supabase/config.toml` | Modified — site_url and redirect URLs | | `package.json` | Modified — added test:e2e scripts | ## What the NEXT Session Should Do **Option A — CI pipeline:** 1. Create `.gitea/workflows/build.yaml` — unit tests + build + E2E (read bab-app pipeline as reference) 2. Note: E2E in CI requires local Supabase running in the pipeline — confirm if feasible or skip E2E in CI for now **Option B — Home page content:** 1. Implement authenticated home content in `app/pages/index.vue` (currently "Welcome to OYS Borrow a Boat" placeholder) 2. Likely a boat list or dashboard — check `docs/planning/` for persona requirements ## Open Questions - [ ] Should E2E tests run in CI (requires Supabase in pipeline) or local-only? — OPEN - [ ] What is the authenticated home content? Boat list, dashboard, or something else? — check planning docs ## Dev Environment Reference ```bash # Required before running E2E: DOCKER_HOST=unix:///run/user/1000/podman/podman.sock npx supabase start # Run E2E: yarn test:e2e # headless yarn test:e2e:headed # visible browser yarn test:e2e:ui # Playwright UI mode ```