import { defineConfig, devices } from '@playwright/test'; import path from 'path'; /** * Playwright configuration for HOA LedgerIQ E2E + API regression tests. * * Architecture: Docker Compose (nginx :80 -> backend :3000 + frontend :5173) * - Local dev: `docker-compose up` then `npm run test:e2e` * - CI: starts Docker services automatically * - Production: set BASE_URL to skip webServer start */ // Load test-specific env from .env.test (falls back to .env) require('dotenv').config({ path: path.resolve(__dirname, '.env.test') }); const BASE_URL = process.env.BASE_URL || 'http://localhost'; const IS_CI = !!process.env.CI; // Skip auto-starting services when pointing at an external URL const isExternalTarget = BASE_URL !== 'http://localhost' && BASE_URL !== 'http://localhost:80'; export default defineConfig({ testDir: './tests', testMatch: ['**/*.spec.ts'], /* Run tests in parallel where safe */ fullyParallel: true, /* Fail CI builds if test.only was left in */ forbidOnly: IS_CI, /* Retry on CI to handle transient failures */ retries: IS_CI ? 2 : 0, /* Limit parallel workers on CI */ workers: IS_CI ? 1 : undefined, /* Reporter configuration */ reporter: IS_CI ? [['github'], ['html', { open: 'never' }]] : [['list'], ['html', { open: 'on-failure' }]], /* Shared settings for all projects */ use: { baseURL: BASE_URL, /* Collect trace on first retry for debugging */ trace: 'on-first-retry', /* Screenshot on failure */ screenshot: 'only-on-failure', /* Video on failure in CI */ video: IS_CI ? 'on-first-retry' : 'off', /* Default timeout for actions (click, fill, etc.) */ actionTimeout: 10_000, /* Navigation timeout */ navigationTimeout: 30_000, }, /* Global test timeout */ timeout: 60_000, /* Assertion timeout */ expect: { timeout: 10_000, toHaveScreenshot: { maxDiffPixelRatio: 0.02, }, }, /* Browser projects */ projects: [ /* Auth setup — runs once, stores auth state for other tests */ { name: 'auth-setup', testMatch: /auth\.setup\.ts/, use: { ...devices['Desktop Chrome'] }, }, { name: 'chromium', use: { ...devices['Desktop Chrome'], storageState: 'tests/.auth/user.json', }, dependencies: ['auth-setup'], }, { name: 'firefox', use: { ...devices['Desktop Firefox'], storageState: 'tests/.auth/user.json', }, dependencies: ['auth-setup'], }, { name: 'webkit', use: { ...devices['Desktop Safari'], storageState: 'tests/.auth/user.json', }, dependencies: ['auth-setup'], }, /* API tests — no browser needed, runs in chromium for request context */ { name: 'api', testMatch: ['**/api/**/*.spec.ts'], use: { ...devices['Desktop Chrome'] }, dependencies: [], }, ], /* Start Docker services before tests when running locally */ ...(!isExternalTarget && { webServer: { command: 'docker-compose up -d && sleep 5 && docker-compose exec backend echo "ready"', url: BASE_URL, reuseExistingServer: !IS_CI, timeout: 120_000, stdout: 'pipe', stderr: 'pipe', }, }), });