Database tests
The pgTAP tests in apps/web/supabase/tests/database: what they check, how to run them, and how to add one for your own table.
The database has its own test suite, written in SQL with pgTAP. It lives in apps/web/supabase/tests/database/: 23 test files plus 00000-test-helpers.sql, which installs the shared helpers. Each test file runs inside a transaction that is rolled back.
Run them
With the local stack running (pnpm supabase:web:start):
pnpm supabase:web:test
The tests sign in as the users from seed.sql. If they stop early with missing users, your local database was seeded differently: pnpm supabase:web:reset rebuilds it (and wipes local data).
What they check
| Area | Files |
|---|---|
| Every table has RLS on | schema-conditions.test.sql |
Default TRUNCATE, REFERENCES and TRIGGER privileges are gone | privileges.test.sql |
| Accounts and teams | personal-accounts, team-accounts, account-slug, account-permissions, transfer-ownership |
| Memberships | memberships, update-membership, delete-membership |
| Invitations | invitations |
| Billing | personal-billing-subscriptions, personal-billing-orders, team-billing-subscriptions, team-billing-orders |
| Notifications, files, one-time codes | notifications, storage, otp |
| Super admin | super-admin, super-admin-edge-cases |
| Schema and triggers | schema, triggers-timestamps-simple, triggers-user-tracking-accounts |
13 of the 23 files sign in as one user and try to read or change another account's data, and assert that it fails: account-permissions, memberships, personal-accounts, notifications, delete-membership, invitations, storage, super-admin, team-accounts and the four billing files. The other files test behaviour: triggers, slugs, one-time codes and similar.
Helpers
From 00000-test-helpers.sql (built on the Basejump Supabase test helpers):
| Helper | Does |
|---|---|
tests.create_supabase_user(id, email) | Creates a throwaway user |
test_helpers.set_identifier(id, email) | Gives a seeded user a short name |
test_helpers.authenticate_as(id) | Runs the next statements as that user |
test_helpers.get_account_id_by_slug(slug) | Looks up an account id |
test_helpers.set_mfa_factor(id), test_helpers.set_session_aal(level), test_helpers.set_super_admin() | Set up two-step sign-in and super-admin cases |
Add a test for your table
Put a new file next to the others. A cross-account check for a projects table:
begin;
select no_plan();
select tests.create_supabase_user('stranger', 'stranger@example.com');
-- someone outside the team must not see its projects
select test_helpers.authenticate_as('stranger');
select is_empty(
$$ select * from public.projects
where account_id = test_helpers.get_account_id_by_slug('acme') $$,
'A stranger cannot read another team''s projects'
);
select * from finish();
rollback;
Test writes as well as reads: an insert, an update that changes account_id, and a delete, each as a user who should be refused. The /rls-review agent skill writes tests like these for you and is required by AGENTS.md after any database change.
End-to-end tests
Playwright tests in apps/e2e/tests/ drive the real web app: sign-in and sign-up, password reset, account settings, team accounts and invitations, two-step sign-in for invited members, billing, the admin panel and the live demo. Run them against a running app with pnpm --filter web-e2e test. Billing specs need ENABLE_BILLING_TESTS=true and Stripe set up; the demo spec needs E2E_DEMO_MODE=true.
In CI
.github/workflows/workflow.yml has a job that starts Supabase, builds and starts the app, then runs the Playwright tests and pnpm --filter web supabase:test. It runs only when the repository variable ENABLE_E2E_JOB is true.
The live demo has its own tests, outside this suite: see Live demo.