import { defineConfig, devices } from '@playwright/test' import path from 'node:path' import { fileURLToPath } from 'node:url' const __dirname = path.dirname(fileURLToPath(import.meta.url)) const authFile = path.join(__dirname, 'e2e/.auth/user.json') // NOTE: the frontend origin is deliberately `http://localhost:5173`, never // `127.0.0.1` — Vite's dev server binds IPv6 `::1`, and on macOS // `127.0.0.1` can silently miss it (see the matching comment in // vite.config.ts's proxy target, which uses 127.0.0.1 for the *backend* for // the opposite reason: to dodge a stray IPv6 listener there). const FRONTEND_URL = 'http://localhost:5173' const BACKEND_HEALTH_URL = 'http://127.0.0.1:8000/health' export default defineConfig({ testDir: './e2e', globalSetup: './e2e/global-setup.ts', fullyParallel: true, forbidOnly: !!process.env.CI, retries: process.env.CI ? 1 : 0, // A fresh suite against a single shared demo workspace — keep it to one // worker in CI until it's proven flake-free under parallel load. Left // unset locally so Playwright picks its usual CPU-based default. ...(process.env.CI ? { workers: 1 } : {}), reporter: process.env.CI ? [['github'], ['html', { open: 'never' }]] : 'list', use: { baseURL: FRONTEND_URL, // Pinned so translated labels/aria-labels used as selectors (e.g. // "Välj månad") are deterministic regardless of the host machine's // locale — the app defaults to Swedish or English based on // `navigator.language` (see LanguageContext.tsx). locale: 'sv-SE', trace: 'retain-on-failure', screenshot: 'only-on-failure', }, webServer: [ { command: 'uv run uvicorn app.main:app --port 8000', cwd: path.resolve(__dirname, '../backend'), url: BACKEND_HEALTH_URL, // The backend rate-limits per remote IP (slowapi's // `get_remote_address`), which assumes one IP is one client. This // suite breaks that assumption: every page load in the whole run // comes from 127.0.0.1. `POST /api/auth/refresh` fires once per page // load (AuthProvider re-obtains the access token on mount, see // AuthContext.tsx) and is capped at 30/minute, so a ~20-test run's // ~50 refreshes start returning 429 partway through — and a failed // refresh renders the login screen, which is why unrelated specs // failed on `getByTestId('dashboard')`/`('calendar')` not being // found. Raising the cap would only move the cliff and make green-ness // a function of machine speed; the limiter simply does not apply to a // single-IP test runner, so it is switched off here. env: { APP_RATE_LIMIT_ENABLED: 'false' }, reuseExistingServer: !process.env.CI, timeout: 60_000, }, { command: 'npx vite --port 5173 --strictPort', cwd: __dirname, url: FRONTEND_URL, reuseExistingServer: !process.env.CI, timeout: 60_000, }, ], projects: [ { name: 'setup', testMatch: /.*\.setup\.ts/ }, { name: 'chromium', use: { ...devices['Desktop Chrome'], storageState: authFile }, dependencies: ['setup'], }, ], })