---
title: Database Setup
description: Complete guide for setting up and managing the PostgreSQL test database for e2e tests in both local and CI environments.
navigation:
  icon: i-lucide-database
---

# Test Database Setup

This guide explains how to set up and manage the PostgreSQL test database for E2E tests, including troubleshooting common issues and understanding the differences between local and CI environments.

## Overview

E2E tests require a PostgreSQL database. We provide a Docker Compose setup that automatically:
- Starts a PostgreSQL 18 container
- Starts a Mailpit email testing server
- Creates the `test_db` database
- Runs migrations
- Stops and cleans up after tests

This setup mirrors the GitHub Actions CI environment for consistency.

## Prerequisites

**Docker Desktop** must be installed and running:
- **macOS**: Download from [docker.com/products/docker-desktop](https://www.docker.com/products/docker-desktop)
- **Linux**: Install via package manager (`apt install docker.io docker-compose` or similar)
- **Windows**: Download Docker Desktop for Windows

Verify Docker is running:
```bash [Terminal]
docker ps
```

## Quick Start

### Automated Test Execution

The easiest way - database and email server are automatically managed:

```bash [Terminal]
pnpm test:e2e
```

This command:
1. Starts PostgreSQL in Docker (`test:db:up`)
2. Starts Mailpit for email testing
3. Waits for services to be ready
4. Runs migrations (`test:db:migrate`)
5. Executes Playwright tests
6. Stops all services (`test:db:down`)

### Manual Database Management

For development/debugging, you can manage the database manually:

```bash [Terminal]
# Start test database and Mailpit (stays running)
pnpm test:db:up

# Run tests against running database
pnpm test:e2e:headed

# When done, stop services
pnpm test:db:down
```

## Available Commands

### Database Lifecycle

| Command | Description |
|---------|-------------|
| `pnpm test:db:up` | Start PostgreSQL + Mailpit containers + run migrations |
| `pnpm test:db:down` | Stop and remove all test containers |
| `pnpm test:db:wait` | Wait for database to be ready (used internally) |
| `pnpm test:db:migrate` | Run Drizzle migrations on test database |
| `pnpm test:db:reset` | **Destroy all data** and recreate database |
| `pnpm test:db:logs` | View PostgreSQL container logs (live tail) |

### Email Testing Utilities

| Command | Description |
|---------|-------------|
| `pnpm test:mailpit:open` | Open Mailpit web UI (http://localhost:8025) |
| `pnpm test:mailpit:logs` | View Mailpit container logs |
| `pnpm test:mailpit:wait` | Wait for Mailpit to be ready (used internally) |

## Database Configuration

### Local Environment

Tests use credentials from `.env.test`:

```env
# Local uses port 54322 to avoid conflicts with local PostgreSQL
DATABASE_URL=postgresql://test:test@localhost:54322/test_db?sslmode=disable
NUXT_DATABASE_URL=postgresql://test:test@localhost:54322/test_db?sslmode=disable
```

| Setting | Value |
|---------|-------|
| **Host** | `localhost` |
| **Port** | `54322` (mapped to container's 5432) |
| **Database** | `test_db` |
| **User** | `test` |
| **Password** | `test` |

::note
The local environment uses port **54322** to avoid conflicts with any PostgreSQL instances running on your machine. The Docker container internally uses port 5432, which is mapped to 54322 on your host.
::

### CI Environment

GitHub Actions uses port 5432 directly:

```env
DATABASE_URL=postgresql://test:test@localhost:5432/test_db
```

::warning{icon="i-lucide-alert-triangle"}
**Critical**: The `playwright.config.ts` file conditionally loads `.env.test` **only in local environments** (`if (!process.env.CI)`). This prevents local configuration from overriding CI environment variables, which was the cause of previous test failures in CI.
::

### Docker Compose File

`docker-compose.test.yml` defines the test infrastructure:

**PostgreSQL Service:**
- **Image**: `postgres:18` (same as CI)
- **Container Name**: `harmonic-test-db`
- **Port Mapping**: `54322:5432` (host:container)
- **Data Persistence**: Named volume `postgres_test_data`
- **Health Check**: Ensures database is ready before tests run

**Mailpit Service:**
- **Image**: `axllent/mailpit`
- **Container Name**: `harmonic-test-mailpit`
- **Ports**:
  - `1025`: SMTP server
  - `8025`: Web UI
- **Health Check**: Polls `/api/v1/messages` endpoint

## Email Testing with Mailpit

E2E tests use **Mailpit** for real SMTP email testing (matches CI environment).

### Configuration

`.env.test` contains Mailpit SMTP settings:

```env
NUXT_MOCK_EMAIL=false
NUXT_SMTP_HOST=localhost
NUXT_SMTP_PORT=1025
NUXT_SMTP_SECURE=false
NUXT_FROM_EMAIL=noreply@test.local
```

### Viewing Emails

```bash [Terminal]
pnpm test:mailpit:open  # Open web UI in browser
```

The web UI at http://localhost:8025 shows all sent emails with full content, headers, and attachments.

### Email Helper Implementation

Tests use the unified `createEmailHelper()` factory from `test/e2e/helpers/email.ts`:
- Automatically selects `MailpitHelper` for e2e tests
- Supports `MockEmailHelper` for development (`pnpm dev` with `NUXT_MOCK_EMAIL=true`)
- Same interface for both modes (seamless switching)

## Troubleshooting

### Port 54322 Already in Use

If you see "port already in use" errors:

```bash [Terminal]
# Check what's using the port
lsof -i :54322

# Stop the test database if it's running
pnpm test:db:down

# Or stop all Docker containers
docker stop $(docker ps -q)
```

### Port 5432 Conflicts (Local PostgreSQL)

If you have PostgreSQL running locally on port 5432, it won't conflict because we use port 54322 for the test database. However, if you want to change the port:

**Edit `docker-compose.test.yml`:**
```yaml
ports:
  - "5433:5432"  # Use 5433 instead
```

**Update `.env.test`:**
```env
DATABASE_URL=postgresql://test:test@localhost:5433/test_db
```

### Docker Not Running

```
Error: Cannot connect to the Docker daemon
```

**Solution**: Start Docker Desktop application

### Database Won't Start

```bash [Terminal]
# View container logs
pnpm test:db:logs

# Reset everything
pnpm test:db:reset
```

### Stale Database Data

If tests fail due to unexpected data:

```bash [Terminal]
# Destroy volume and recreate database
pnpm test:db:reset
```

### CI Tests Passing Locally But Failing in GitHub Actions

This was a common issue caused by environment variable conflicts. The fix:

**Problem**: `playwright.config.ts` was loading `.env.test` in ALL environments, causing local config (port 54322) to override CI config (port 5432).

**Solution**: Modified `playwright.config.ts` to conditionally load `.env.test`:

```typescript
// Load test environment variables only in local environments
if (!process.env.CI) {
  dotenv.config({
    path: resolve(__dirname, '.env.test'),
    override: true,
    quiet: true
  })
}
```

::tip{icon="i-lucide-lightbulb"}
If you encounter CI-specific failures, check that environment-specific configuration files (like `.env.test`) are not being loaded in CI environments.
::

### Permission Errors (Linux)

Add your user to the `docker` group:

```bash [Terminal]
sudo usermod -aG docker $USER
newgrp docker
```

## CI/CD Integration

### GitHub Actions

The [CI workflow](/.github/workflows/ci.yml) uses PostgreSQL and Mailpit service containers:

```yaml
services:
  postgres:
    image: postgres:18
    env:
      POSTGRES_USER: test
      POSTGRES_PASSWORD: test
      POSTGRES_DB: test_db
    ports:
      - 5432:5432
    options: >-
      --health-cmd pg_isready
      --health-interval 10s
      --health-timeout 5s
      --health-retries 5

  mailpit:
    image: axllent/mailpit
    ports:
      - 1025:1025
      - 8025:8025
```

This matches our local Docker Compose setup exactly (except for port mapping).

### Environment Variables in CI

GitHub Actions sets these environment variables:

```yaml
env:
  CI: true
  DATABASE_URL: postgresql://test:test@localhost:5432/test_db
  NUXT_DATABASE_URL: postgresql://test:test@localhost:5432/test_db
  NUXT_MOCK_EMAIL: false
  NUXT_SMTP_HOST: localhost
  NUXT_SMTP_PORT: 1025
  NUXT_SMTP_SECURE: false
  NUXT_FROM_EMAIL: noreply@test.local
  # ... other auth variables ...
```

::note
The `CI` environment variable is crucial - it tells `playwright.config.ts` NOT to load `.env.test`, allowing GitHub Actions environment variables to take precedence.
::

### Custom CI Environments

If using other CI providers:

1. Start PostgreSQL 18 and Mailpit with the credentials above
2. Set `CI=true` environment variable
3. Set `DATABASE_URL` and SMTP environment variables
4. Run `pnpm db:migrate` before tests
5. Execute `npx playwright test`

## Data Persistence

By default, database data persists between test runs via the `postgres_test_data` Docker volume.

### Fresh Database Every Run

Edit `docker-compose.test.yml` and comment out the volume:

```yaml
volumes:
  # Comment this line to use ephemeral storage
  # - postgres_test_data:/var/lib/postgresql
```

Or use the reset command:

```bash [Terminal]
pnpm test:db:reset
```

## Best Practices

::steps
1. **Use automated commands** (`pnpm test:e2e`) for CI and quick runs
2. **Use manual commands** (`pnpm test:db:up` + `pnpm test:e2e:headed`) when debugging specific tests
3. **Reset database** (`pnpm test:db:reset`) when migrations change or data becomes inconsistent
4. **Check logs** (`pnpm test:db:logs`, `pnpm test:mailpit:logs`) if services behave unexpectedly
5. **Keep Docker running** in the background when actively developing tests
6. **Never commit `.env.test`** changes that would break CI (always use environment-agnostic config)
::

## Architecture Diagram

```mermaid
graph TD
    A[pnpm test:e2e] --> B[pnpm test:db:up]
    B --> C[docker compose up -d]
    C --> D[PostgreSQL Container<br/>port 54322:5432]
    C --> E[Mailpit Container<br/>ports 1025, 8025]
    B --> F[pnpm test:db:wait]
    B --> G[pnpm test:db:migrate]
    A --> H[pnpm test:mailpit:wait]
    A --> I[npx playwright test]
    I --> J[Start Nuxt dev server]
    I --> K[Run tests vs localhost:3000]
    K --> L[Query emails via Mailpit API]
    A --> M[pnpm test:db:down]
    M --> N[docker compose down]
```

## Further Reading

::card-group
  ::card
  ---
  title: E2E Quick Start
  icon: i-lucide-play-circle
  to: /testing/e2e-quick-start
  ---
  Get started with running E2E tests
  ::

  ::card
  ---
  title: CI/CD Workflow
  icon: i-lucide-workflow
  to: /testing/ci-cd-workflow
  ---
  Understand the GitHub Actions pipeline
  ::

  ::card
  ---
  title: Playwright Documentation
  icon: i-lucide-external-link
  to: https://playwright.dev
  target: _blank
  ---
  Official Playwright testing framework docs
  ::

  ::card
  ---
  title: Drizzle ORM Migrations
  icon: i-lucide-database
  to: https://orm.drizzle.team/docs/migrations
  target: _blank
  ---
  Learn about database migrations
  ::
::
