Testing
Unit Tests
Run Vitest for unit tests:
pnpm test
E2E Tests
E2E tests run with Playwright. Playwright ships browsers separately from its npm package, so a one-time install is required after cloning.
Scripts
| Script | Purpose |
|---|---|
pnpm test:e2e:install | One-time install of the Chromium binary. Re-run after upgrading @playwright/test (browser version is pinned to the package version). |
pnpm test:e2e | Run all tests headless. Use this in CI. |
pnpm test:e2e:headed | Run tests with a visible browser window. Useful for watching a flow or debugging a flaky test. |
pnpm test:e2e:ui | Open Playwright's interactive UI runner with time-travel debugging, watch mode, and step-by-step inspection. Best for writing new tests. |
pnpm test:e2e:report | Open the HTML report from the last run (screenshots, traces, videos of failures). |
Typical Workflow
- First time only:
pnpm test:e2e:install - Writing tests:
pnpm test:e2e:ui - CI / full sweep:
pnpm test:e2e - Investigating a failure:
pnpm test:e2e:report
Only test:e2e and test:e2e:install are strictly required - the rest are convenience wrappers around playwright CLI flags.
Local Auth Without Google (Seed Data)
Real-mode (PowerSync) testing needs a signed-in session, but Google OAuth can't be automated. The local stack ships a fixed email/password test user plus a demo-derived dataset, so agents / Playwright / manual runs can enter real mode without Google.
Email/password is enabled in the Supabase backend but intentionally not exposed in the app UI (Google + Demo only). The seed user exists for local tests; its password is local-only and not a secret.
Seed
app/supabase/seed.sql (generated, idempotent, transactional) creates:
- a confirmed test user -
e2e@finapp.local/finapp-e2e(fixed UUID), and - its demo dataset: 8 wallets, 32 categories, ~860 transactions.
It is applied automatically by supabase db reset ([db.seed] in app/supabase/config.toml). To (re)apply against a running DB without a full reset:
docker exec -i supabase_db_app psql -U postgres -d postgres < app/supabase/seed.sql
The seed user's email/password works via the Supabase password grant even with email signup disabled ([auth.email] enable_signup = false), because the user is pre-created. The GoTrue token columns are seeded as '' (not NULL) - a NULL triggers Database error querying schema on sign-in.
Regenerating the seed from demo data
seed.sql is produced from a snapshot of the real demo generator by app/scripts/gen-seed.mjs:
- Open the app in demo mode, read the Pinia stores via devtools, and save
{ wallets, categories, trns }toapp/scripts/.demo-snapshot.json(gitignored). node app/scripts/gen-seed.mjs- writesapp/supabase/seed.sql.
Synthetic adjustment / transfer categories are skipped (they are trns.categoryId literals, not real category rows).
Signing in as the test user
app/scripts/dev-login.mjs mints a session via the password grant and prints the value for localStorage['finapp.auth']:
node app/scripts/dev-login.mjs --raw # prints the raw session JSON
In the browser, set that value, clear the finapp.isDemo cookie, and reload - the app boots into real PowerSync mode as the test user and the seeded data streams in. Credentials/URL are read from app/.env (overridable via E2E_EMAIL / E2E_PASSWORD).
This is also the basis for a Playwright storageState global-setup: sign in once via the grant, then reuse the session across tests.