Skip to main content
End-to-end tests verify your entire Vertz app — server, database, auth, and UI — from the user’s perspective. This guide covers setting up Playwright, authenticating test users programmatically, and managing test databases.
This page covers browser-based E2E tests with Playwright. For unit and integration tests, see the Test Runner guide. For type-safe server tests without a browser, see Server Testing.

Prerequisites

Install Playwright and its browser binaries:

Playwright configuration

Create playwright.config.ts at your project root:
The webServer option starts your dev server before tests run and shuts it down after. In local development, reuseExistingServer skips startup if the server is already running.

Database setup

For E2E tests, use a file-based SQLite database. Each dev server run creates its own database file, and tests run against that instance.
authModels provides the table definitions Vertz needs for sessions, users, and OAuth accounts. When you call app.initialize(), auth tables are created automatically.

Test isolation

Each test creates a unique user (via a timestamped email), so tests don’t interfere with each other. For seed data, insert it once on server startup:
To reset the database between test runs, delete the file and restart:

Authenticating test users

Vertz auth uses HttpOnly cookies, so you can’t set tokens from browser JavaScript. Instead, call the auth endpoints from your test setup and pass the cookies to Playwright’s browser context.

Enable email/password auth

The emailPassword: {} option in createAuth() enables the POST /api/auth/signup and POST /api/auth/signin endpoints. This is the simplest way to create test users programmatically:
Set cookie: { secure: false } for local development and testing. Without this, cookies won’t be sent over plain HTTP.
Auth responses include Set-Cookie headers. Parse them into the format Playwright expects:

Authenticate helper

Sign up a unique test user and return cookies ready for Playwright:
The X-VTZ-Request: 1 header is required — Vertz uses it for CSRF protection on mutation endpoints.

Using it in tests

Call authenticate() in beforeEach and add cookies to the browser context:
Every test gets a fresh user with a unique email, so tests are fully isolated.

Multi-tenant apps

If your app uses tenant scoping, the session JWT needs a tenantId. After signup, call the switch-tenant endpoint:
Use it in tests:
Tenant switching requires the tenant.verifyMembership callback in your server config. For testing, you can accept all tenants or check against a seed list.

Writing tests

Wait for data, not timers

Use Playwright’s built-in assertions with timeouts instead of page.waitForTimeout():

Use unique data per test

Timestamp your test data to avoid collisions:

Use data-testid for stable selectors

Prefer data-testid attributes over CSS classes or text content for elements that tests interact with:

Running tests

Add scripts to your package.json:
In CI, tests run headless with a single worker for stability. The webServer config ensures the dev server starts fresh.

Next steps

Test Runner

Built-in test runner with Vitest-compatible API, watch mode, and coverage.

Server Testing

Type-safe test client for entity CRUD, service actions, and raw HTTP requests.

Authentication

Full auth configuration — sessions, stores, rate limiting, and access control.

Entities

Define entities with models, access rules, and hooks.