Leader uses three layers of automated tests: unit/component tests, integration tests (real PostgreSQL via testcontainers), and end-to-end tests (Playwright against a production build). All tests run in CI on every pull request.
# Unit & component tests (all packages, via Turborepo)
bun run test
# Integration tests (packages/db — requires Docker)
bun --cwd packages/db run test:integration
# E2E tests (apps/web — requires Docker)
bun --cwd apps/web run test:e2eAll unit, component, and integration tests use Bun's built-in test runner (bun:test). There is no vitest dependency. E2E tests use Playwright.
The root bun run test command runs turbo run test, which executes the test script in every workspace package.
| Package | Command | What it tests |
|---|---|---|
packages/db |
bun test ./**/*.test.ts |
ID generation, schema validation |
packages/auth |
bun test |
Email template rendering |
packages/logging |
bun test |
Dynatrace sink formatting, log filters |
packages/ui |
bun test --conditions browser |
Svelte component rendering (Badge, Button, Card, etc.) |
apps/web |
bun test --conditions browser |
Schemas, server utils, email logic, route components |
Component tests use @testing-library/svelte with happy-dom for DOM emulation. A shared preload script at the repo root (svelte-test-setup.ts) handles:
- DOM cleanup between tests via
@happy-dom/global-registrator - Svelte compilation via a Bun plugin that compiles
.svelteand.svelte.tsfiles on the fly - SvelteKit module mocks for
$app/navigation,$app/stores,$app/environment, and$app/server
Packages that run component tests (packages/ui, apps/web) reference this preload in their bunfig.toml:
[test]
preload = ["../../svelte-test-setup.ts"]The --conditions browser flag is required so SvelteKit resolves browser-side exports.
Note:
apps/web/bunfig.tomlalso setsroot = "./src"to prevent Bun's test discovery from picking up Playwright.spec.tsfiles in thetests/directory.
Integration tests in packages/db run against a real PostgreSQL instance managed by testcontainers. They require Docker to be running.
bun --cwd packages/db run test:integrationTestcontainers automatically:
- Pulls and starts a PostgreSQL container
- Runs Drizzle migrations
- Executes tests against the live database
- Tears down the container when done
These tests validate schema migrations, complex queries, and ORM behavior against a real database — no mocking.
End-to-end tests use Playwright against a production build of the SvelteKit app.
- Docker (for the E2E PostgreSQL instance)
- Playwright browsers:
bunx playwright install --with-deps chromium
bun --cwd apps/web run test:e2eThis single command:
- Builds the app with
vite build - Copies Drizzle migration files into the build output
- Starts a PostgreSQL container via
docker compose(port 5433) - Runs Playwright tests against the production build served on port 3000
- Tears down the database container
The E2E setup follows a Page Object Model pattern with Playwright projects for ordered setup:
apps/web/
├── playwright.config.ts # Central config (4 projects)
└── tests/
├── docker-compose.e2e.yml # PostgreSQL 18 on port 5433 (tmpfs)
├── config/
│ ├── global.setup.ts # Runs DB migrations + seeding via bun subprocess
│ ├── global.teardown.ts # Cleanup
│ ├── setup-db.ts # Standalone bun script (migrations + seed)
│ └── seed.ts # Creates test users via Better Auth API
├── auth.setup.ts # Authenticates users, saves storage states
├── fixtures/
│ ├── index.ts # test.extend with POM fixtures + constants
│ ├── utils.ts # waitForHydration() helper
│ ├── projects-page.ts # Page Object Model for projects
│ ├── leads-page.ts # Page Object Model for leads
│ └── settings-page.ts # Page Object Model for settings
├── auth.spec.ts # Authentication flow tests
├── navigation.spec.ts # Navigation and routing tests
├── projects.spec.ts # Project CRUD tests
├── leads.spec.ts # Lead management tests
└── settings.spec.ts # Settings page tests
The config defines 4 projects that run in order:
- setup — Starts DB, runs migrations, seeds test users
- auth setup — Logs in as test user/admin, saves browser storage states to
tests/.auth/ - chromium — Runs all
.spec.tstests using saved auth states - teardown — Cleanup
| Role | Password | |
|---|---|---|
| User | test@leader.local |
Test@123456 |
| Admin | testadmin@leader.local |
Admin@123456 |
| Guest | (no login) | (empty storage state) |
The app's bootstrap process creates
admin@leader.localwith a random password at startup. E2E tests usetestadmin@leader.localto avoid collisions.
- Playwright runs on Node.js, not Bun. Since
@leader/dbusesimport { SQL } from "bun", DB setup runs as a Bun subprocess viaexecSync("bun run tests/config/setup-db.ts"). - The database must be running before the web server starts because
hooks.server.tsruns migrations on startup. - Production build paths: Drizzle migration files are copied to
build/server/drizzle/aftervite buildbecauseimport.meta.urlresolves differently in bundled code.
To run E2E tests manually (useful for debugging):
cd apps/web
# Start the E2E database
docker compose -f tests/docker-compose.e2e.yml up -d --wait
# Build the app
vite build && cp -r ../../packages/db/drizzle build/server/drizzle
# Run specific tests with UI mode
DATABASE_URL=postgres://test:test@localhost:5433/e2e_test bunx playwright test --ui
# Or run a single spec
DATABASE_URL=postgres://test:test@localhost:5433/e2e_test bunx playwright test auth.spec.ts
# Clean up
docker compose -f tests/docker-compose.e2e.yml down -vAll test layers run automatically on pull requests via GitHub Actions (.github/workflows/ci.yml). The pipeline runs four parallel jobs:
- Lint, Typecheck & Build —
bun run lint+bun run check+bun run build - Unit & Component Tests —
bun run test(all packages via Turborepo) - Integration Tests —
bun --cwd packages/db run test:integration(testcontainers + Docker) - E2E Tests —
bun --cwd apps/web run test:e2e(Playwright + Docker Compose)
All jobs must pass before a PR can be merged.