E2E Testing with Playwright

The panel ships with a Playwright test harness that gives every test a real, pre-authenticated session for a specific role — no OAuth, no login page, no browser cookies to manage manually.

How It Works

pnpm seed:test-users          # one-time: create test users in the dev DB

npx playwright test           # each run:

global-setup.ts               # calls POST /api/auth/test-session per role
         ↓                    # saves signed session cookies to playwright/.auth/
playwright/.auth/superadmin.json
playwright/.auth/admin.json
playwright/.auth/user.json

your spec runs                # Playwright injects the right cookie automatically
                              # no login needed inside the test
The test-session endpoint is guarded by NODE_ENV !== 'production' — it is never reachable in production.

Roles Available

RoleEmailUse For
superadmintest-superadmin@test.localPlatform-level admin features
admintest-admin@test.localOrg admin features
usertest-user@test.localStandard member features

Writing a Test

1. Create the spec file

Place all E2E specs in frontend/solidstart/tests/e2e/. Follow the naming convention:
  • Single-role: <feature>.<role>.spec.ts — e.g. dashboard.superadmin.spec.ts
  • Multi-role: <feature>.multirole.spec.ts — when one test verifies permission differences across roles
// tests/e2e/dashboard.superadmin.spec.ts
import { test, expect } from '@playwright/test';

test('dashboard loads for superadmin', async ({ page }) => {
  await page.goto('/admin/dashboard', { waitUntil: 'commit' });

  await page.waitForTimeout(2000); // allow initial API calls to complete

  await expect(page.locator('h1')).toContainText('Dashboard');

  // Always assert no silent API errors at the end of each happy-path test
  await expect(page.locator('[data-testid="api-error-toast"]')).toHaveCount(0);
});
You do not need any login code inside the test. The session is injected automatically by Playwright’s storageState for the project you select at run time.

2. Run the test as a specific role

cd frontend/solidstart

# Run as superadmin
npx playwright test tests/e2e/dashboard.superadmin.spec.ts --project=superadmin

# Run as admin
npx playwright test tests/e2e/members.admin.spec.ts --project=admin

# Run all tests for a role
npx playwright test --project=user
The --project flag selects which stored auth state (and which role) Playwright loads. No auth code is needed in the test itself.

The [data-testid="api-error-toast"] Contract

In dev mode the panel wraps globalThis.fetch and appends a visible <div data-testid="api-error-toast"> to the DOM whenever any request returns a status ≥ 400. Every happy-path test MUST assert this element has count 0:
await expect(page.locator('[data-testid="api-error-toast"]')).toHaveCount(0);
This ensures silent 4xx/5xx errors — which would normally be invisible — are caught by Playwright screenshots and fail the test explicitly.
Omitting this assertion means broken API calls can go undetected even on a green test run.

Before You Start: One-Time Setup

Seed test users

cd backend/bun
pnpm seed:test-users
This upserts the three test users with the correct platform roles and credential accounts. Safe to re-run — it uses upsert.

Set environment variables

Copy .env.test.example from the monorepo root to .env.test and fill in your local values:
# .env.test
SMOKE_API_BASE_URL=http://localhost:3100    # Bun backend
PLAYWRIGHT_BASE_URL=http://localhost:3000   # SolidStart panel dev server
TEST_SESSION_ENDPOINT=http://localhost:3100/api/auth/test-session

Running Smoke Tests First

Before running Playwright, run the API smoke test to catch any broken endpoints early:
cd backend/bun
pnpm smoke:api
Expected output:
🔍 Smoke testing http://localhost:3100

✓ Pre-flight: test session created

METHOD  PATH                      EXPECTED  ACTUAL    STATUS
────────────────────────────────────────────────────────────
GET     /                         200       200       ✓ PASS
GET     /health                   200       200       ✓ PASS
GET     /health/ready             200       200       ✓ PASS
GET     /api/v1/users/me          200       200       ✓ PASS
GET     /api/v1/organizations/me  200       200       ✓ PASS

5/5 passed
✅ All endpoints passed
If any endpoint fails here, fix it before running Playwright — E2E tests will fail for the same reason.

Agent Definition of Done

Before handing off any frontend feature, complete all steps and paste terminal output as proof:
  1. Smoke tests passpnpm smoke:api exits 0
  2. Playwright spec passesnpx playwright test <spec> --project=<role> exits 0
  3. No API error toasts → final assertion in spec confirms count 0
Do not hand off if any step fails.

Troubleshooting

Global setup fails: “Test user not found”

The test users haven’t been seeded yet. Run:
cd backend/bun && pnpm seed:test-users

Global setup fails: “Sign-in failed”

The credential account for the test user is missing or has a stale password. Re-run the seed — it deletes and recreates credential accounts:
pnpm seed:test-users

Session returns 401 on protected endpoints

The cookie set by test-session needs to match the backend that the panel talks to. Make sure TEST_SESSION_ENDPOINT in .env.test points to the same Bun backend that the panel’s VITE_BETTER_AUTH_URL points to.

Auth state files are stale

Delete the auth directory and let global setup recreate it:
rm -rf frontend/solidstart/playwright/.auth/
npx playwright test

  • Adding Features
  • Build Scripts
  • Backend: backend/bun/AGENTS.md — Definition of Done for backend changes
  • Frontend: frontend/solidstart/AGENTS.md — Naming convention and Definition of Done