This guide covers everything you need to know about running and writing tests for Composter.
- Overview
- Architecture
- Prerequisites
- Environment Setup
- Running Tests
- Writing Tests
- CI/CD Integration
- Troubleshooting
- Fedora / RHEL Setup
Composter uses Playwright for end-to-end (E2E) testing, covering both API endpoints and web UI flows. The test infrastructure follows a "Clean Room" strategy—tests run against an isolated database that is completely separate from your development environment.
- Isolation: Tests never touch your development database
- Reproducibility: Each test run starts with a clean slate
- Parallelization: API and web tests can run in parallel across browsers
- CI Parity: Local test environment mirrors the CI pipeline
The test infrastructure uses separate databases for development and testing to ensure complete isolation:
| Environment | Database | Port | Docker Compose File |
|---|---|---|---|
| Development | composter_db |
5432 |
docker-compose.yaml |
| Testing | composter_test |
5435 |
docker-compose.test.yaml |
┌─────────────────────────────────────────────────────────────────┐
│ Your Machine │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ Dev Database │ │ Test Database │ │
│ │ Port: 5432 │ │ Port: 5435 │ │
│ │ (Persistent) │ │ (Ephemeral/tmpfs)│ │
│ └──────────────────┘ └──────────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ npm run dev │ │ npm run test:e2e │ │
│ │ (apps/api/.env) │ │ (.env.test) │ │
│ └──────────────────┘ └──────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
- Port 5432: Standard PostgreSQL port for development. Data persists in Docker volumes.
- Port 5435: Test database uses
tmpfs(in-memory storage) for speed. Data is wiped when the container stops.
The Playwright configuration defines several test projects:
| Project | Directory | Description |
|---|---|---|
API |
tests/api/ |
Backend API endpoint tests |
web-chrome |
tests/web/ |
Frontend tests in Chrome |
web-firefox |
tests/web/ |
Frontend tests in Firefox |
web-webkit |
tests/web/ |
Frontend tests in Safari/WebKit |
Before running tests, ensure you have:
- Node.js v18+ (v22 recommended for MCP compatibility)
- Docker and Docker Compose installed and running
- npm v10+ installed
- All dependencies installed (
npm installfrom repo root)
Create apps/api/.env.test with the following content:
# Test Database (runs on port 5435)
DATABASE_URL="postgresql://composter:composter@localhost:5435/composter_test?schema=public"
# Better Auth Configuration
BETTER_AUTH_SECRET="test_secret_must_be_long_enough_for_testing_123"
# Server Configuration
NODE_ENV="test"
PORT=3000
# URLs
API_URL="http://localhost:3000"
BETTER_AUTH_URL="http://localhost:3000"
CLIENT_URL="http://localhost:5173"Make sure apps/web/.env contains:
VITE_API_BASE_URL="http://localhost:3000"
VITE_CLIENT_URL="http://localhost:5173"The project includes an automated bootstrap script that handles Docker, migrations, and schema sync:
# From the repository root
npm run setup:testWhat npm run setup:test does:
- ✅ Installs dependencies (if missing)
- 🐳 Tears down any existing test containers (
docker-compose.test.yaml down -v) - 🐳 Starts the test database container on port 5435
- ⏳ Waits for PostgreSQL to be ready
- 🔄 Runs Prisma migrations (
prisma migrate dev) - 🔄 Runs Better Auth migrations to create auth tables
npx playwright install --with-depsThis downloads Chromium, Firefox, and WebKit browsers needed for testing.
npm run test:e2eThis runs all test projects (API + all browser tests).
# API tests only
npx playwright test --project=API
# Chrome browser tests only
npx playwright test --project=web-chrome
# Firefox browser tests only
npx playwright test --project=web-firefox
# Safari/WebKit tests only
npx playwright test --project=web-webkit# Run a specific test file
npx playwright test tests/api/auth.spec.ts
# Run with a specific project
npx playwright test tests/web/web-auth-flow.spec.ts --project=web-chromenpx playwright test --uiOpens the Playwright Test UI for interactive debugging and test selection.
# Run with browser visible (headed mode)
npx playwright test --headed
# Run with Playwright Inspector
npx playwright test --debugAfter running tests, view the HTML report:
npm run show-report:e2e
# or
npx playwright show-reportTests are organized by type:
tests/
├── api/ # Backend API tests
│ ├── auth.spec.ts # Authentication endpoint tests
│ └── health.spec.ts # Health check tests
└── web/ # Frontend E2E tests
└── web-auth-flow.spec.ts # UI authentication flow tests
API tests use Playwright's request fixture to make HTTP calls directly:
import { test, expect, request } from '@playwright/test';
import dotenv from 'dotenv';
import path from 'path';
// Load test environment variables
dotenv.config({ path: path.resolve(__dirname, '../../apps/api/.env.test') });
test.describe('AUTH endpoints test', () => {
// Run tests serially when they depend on each other
test.describe.configure({ mode: 'serial' });
const API_URL = process.env.API_URL;
// Generate unique user for each test run
const user = {
name: `Test User ${Date.now()}`,
email: `test${Date.now()}@example.com`,
password: 'testpassword123'
};
test('should register a new user', async ({ request }) => {
const res = await request.post(`${API_URL}/api/auth/sign-up/email`, {
headers: { 'Content-Type': 'application/json' },
data: {
name: user.name,
email: user.email,
password: user.password,
}
});
expect(res.status()).toBe(200);
});
test('should login with correct credentials', async ({ request }) => {
const res = await request.post(`${API_URL}/api/auth/sign-in/email`, {
headers: { 'Content-Type': 'application/json' },
data: {
email: user.email,
password: user.password,
}
});
expect(res.status()).toBe(200);
});
});Web tests use Playwright's page fixture for browser automation:
import { test, expect } from "@playwright/test";
import dotenv from "dotenv";
import path from "path";
dotenv.config({ path: path.resolve(__dirname, '../../apps/api/.env.test') });
const getTestUser = () => ({
name: `UI Tester ${Date.now()}`,
email: `ui_test_${Date.now()}@example.com`,
password: 'Password123!'
});
test.describe('web auth flow test', () => {
test.describe.configure({ mode: 'serial' });
const USER = getTestUser();
test('register new user', async ({ page }) => {
await page.goto('/signup');
// Use accessible selectors (getByLabel, getByRole)
const nameInput = page.getByLabel(/name/i);
const emailInput = page.getByLabel(/email/i);
const passwordInput = page.getByRole('textbox', { name: 'Password' });
const submitBtn = page.getByRole('button', { name: /create/i });
await nameInput.fill(USER.name);
await emailInput.fill(USER.email);
await passwordInput.fill(USER.password);
await submitBtn.click();
// Wait for navigation after successful registration
await page.waitForURL('/app');
await expect(page).toHaveURL('/app');
});
});-
Use Serial Mode for Dependent Tests
test.describe.configure({ mode: 'serial' });
-
Generate Unique Test Data
const uniqueId = Date.now(); const email = `test${uniqueId}@example.com`;
-
Use Accessible Selectors
// ✅ Good - accessible and resilient page.getByLabel(/email/i) page.getByRole('button', { name: /submit/i }) // ❌ Avoid - brittle CSS selectors page.locator('.form-input-email')
-
Load Environment Variables
dotenv.config({ path: path.resolve(__dirname, '../../apps/api/.env.test') });
-
API-First Pattern for Complex Tests
For web tests that need specific data, create the state via API first:
test.beforeAll(async ({ request }) => { // Create test user via API before UI tests await request.post(`${API_URL}/api/auth/sign-up/email`, { data: { name: 'Test User', email: 'test@example.com', password: 'password' } }); });
GitHub Actions runs tests on every push to main and on pull requests. The CI pipeline mirrors the local test environment:
- Service Container: PostgreSQL 17 runs as a GitHub Actions service (equivalent to
docker-compose.test.yaml) - Port Mapping: The service container maps to port
5435, same as local tests - Environment Files: CI creates
.env.testandapps/web/.envdynamically - Playwright Browsers: Installed fresh in CI with
npx playwright install --with-deps
# .github/workflows/ci.yml (simplified)
services:
postgres:
image: postgres:17
env:
POSTGRES_USER: composter
POSTGRES_PASSWORD: composter
POSTGRES_DB: composter_test
ports:
- 5435:5432 # Same port as local testing
steps:
- name: Create .env.test
run: |
echo "DATABASE_URL=postgresql://composter:composter@localhost:5435/composter_test" >> apps/api/.env.test
echo "BETTER_AUTH_SECRET=test_secret_..." >> apps/api/.env.test
# ... more env vars
- name: Run Playwright Tests
run: npx playwright test
env:
CI: trueThe playwright.config.ts adjusts behavior based on the CI environment variable:
| Setting | Local | CI |
|---|---|---|
retries |
0 | 2 |
workers |
Auto | 1 |
reuseExistingServer |
true | false |
| Browser connection | WebSocket (port 3001) | Direct launch |
Failed test artifacts (screenshots, videos, traces) are uploaded to GitHub Actions:
- Go to the failed workflow run
- Download the
playwright-reportartifact - Extract and open
index.htmlin a browser
Or view traces directly:
npx playwright show-trace path/to/trace.zipCause: Test database container isn't running.
Solution:
# Start the test database
docker compose -f docker-compose.test.yaml up -d
# Or run the full setup
npm run setup:testCause: VITE_CLIENT_URL environment variable is not set in apps/web/.env.
Solution: Ensure apps/web/.env contains:
VITE_CLIENT_URL=http://localhost:5173Cause: The API or web dev server didn't start in time.
Solution:
- Increase the
timeoutinplaywright.config.tswebServer options - Pre-start servers manually:
# Terminal 1 npm run dev --prefix apps/api # Terminal 2 npm run dev --prefix apps/web # Terminal 3 npx playwright test
Cause: Database migrations haven't been applied to the test database.
Solution:
npm run setup:testCause: Playwright's browser binaries have dependency issues on Fedora, RHEL, and other RPM-based distributions. The bundled browsers may fail to launch due to missing or incompatible system libraries.
Solution: Use the Playwright Docker image to run a browser server, then connect your tests to it via WebSocket.
If you're on Fedora, RHEL, CentOS Stream, or similar RPM-based distributions, follow these steps to run browser tests successfully.
Playwright's native browser binaries are built for Debian/Ubuntu and may not work correctly on Fedora due to:
- Different library versions (glibc, NSS, etc.)
- Missing dependencies that
--with-depsdoesn't install on RPM systems - Sandbox permission issues
The workaround is to run browsers inside a Docker container that provides the correct environment.
1. Install Docker (if not already installed)
# Install Docker
sudo dnf install -y dnf-plugins-core
sudo dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo
sudo dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
# Start and enable Docker
sudo systemctl start docker
sudo systemctl enable docker
# Add your user to the docker group (logout/login required)
sudo usermod -aG docker $USER2. Install Node.js and npm
# Using dnf (Fedora 39+)
sudo dnf install -y nodejs npm
# Or use nvm for version management
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
nvm install 22
nvm use 223. Clone and Set Up the Project
git clone https://github.com/YOUR_USERNAME/Composter.git
cd Composter
npm install4. Set Up the Test Environment
# Create environment files
npm run setup:test5. Start the Playwright Browser Server (Docker)
In a separate terminal, run the Playwright Docker container:
docker run --rm --network host --init -it mcr.microsoft.com/playwright:v1.58.0-jammy \
/bin/sh -c "npx -y playwright@1.58.0 run-server --port 3001 --host 0.0.0.0"Important: Keep this terminal running while you execute tests. The browser server listens on port 3001.
What this does:
--rm: Automatically remove the container when it stops--network host: Share the host's network (required for localhost access)--init: Proper signal handling for clean shutdownmcr.microsoft.com/playwright:v1.58.0-jammy: Official Playwright Docker imagerun-server --port 3001: Start the browser server on port 3001
6. Run the Tests
In another terminal:
# Run all E2E tests
npm run test:e2e
# Or run specific projects
npx playwright test --project=web-chrome
npx playwright test --project=APIThe tests will automatically connect to the Docker browser server via WebSocket (configured in playwright.config.ts).
The playwright.config.ts is already configured to use the browser server locally:
use: {
// ...
connectOptions: process.env.CI ? undefined : {
wsEndpoint: 'ws://127.0.0.1:3001/',
},
},- Locally: Tests connect to the Docker browser server on port 3001
- In CI: Tests launch browsers directly (CI uses Ubuntu where Playwright works natively)
If you want a quick one-liner to start everything:
# Terminal 1: Start browser server
docker run --rm --network host --init -it mcr.microsoft.com/playwright:v1.58.0-jammy \
/bin/sh -c "npx -y playwright@1.58.0 run-server --port 3001 --host 0.0.0.0"
# Terminal 2: Run tests
npm run setup:test && npm run test:e2eIf you update Playwright in package.json, make sure to update the Docker image version too:
# Check your Playwright version
npm list @playwright/test
# Update the Docker command to match (e.g., v1.59.0)
docker run --rm --network host --init -it mcr.microsoft.com/playwright:v1.59.0-jammy \
/bin/sh -c "npx -y playwright@1.59.0 run-server --port 3001 --host 0.0.0.0""Cannot connect to browser server"
- Ensure the Docker container is running (
docker ps) - Check that port 3001 is not blocked by firewall:
sudo firewall-cmd --add-port=3001/tcp - Verify you can reach the WebSocket:
curl -I http://127.0.0.1:3001
"Permission denied" when running Docker
- Make sure you're in the
dockergroup:groups $USER - Log out and back in after adding yourself to the group
- Or use
sudo docker run ...
SELinux issues
# If you see permission errors, try:
sudo setenforce 0 # Temporarily disable SELinux (for testing only)
# For a permanent fix, configure proper SELinux policiesContainer exits immediately
- Check Docker logs:
docker logs <container_id> - Ensure you have enough memory (Playwright browsers need ~1GB+)
# View Playwright test trace
npx playwright show-trace test-results/*/trace.zip
# Open last HTML report
npx playwright show-report
# Run with full debug output
DEBUG=pw:api npx playwright test
# Check if test DB is accessible
docker exec -it composter-db-test psql -U composter -d composter_test -c "SELECT 1"| Command | Description |
|---|---|
npm run setup:test |
Bootstrap test environment (Docker + migrations) |
npm run test:e2e |
Run all E2E tests |
npx playwright test --project=API |
Run API tests only |
npx playwright test --project=web-chrome |
Run Chrome browser tests |
npx playwright test --ui |
Open interactive test UI |
npx playwright test --headed |
Run tests with visible browser |
npm run show-report:e2e |
View HTML test report |
npx playwright show-trace <path> |
View test trace file |