import { defineConfig, devices } from '@playwright/test'; import path from 'path'; // Check if we should use full-stack mode (with backend) const USE_FULLSTACK = process.env.E2E_FULLSTACK === 'true'; // Check if we should use next dev (hot reload) instead of build + start const USE_DEV = process.env.E2E_USE_DEV === 'true'; const AUTH_FILE = path.join(__dirname, 'tests/e2e/.auth/user.json'); // Port configuration (set by scripts/test-e2e.sh via HDX_E2E_* env vars) const API_PORT = process.env.HDX_E2E_API_PORT || '21000'; const APP_PORT = process.env.HDX_E2E_APP_PORT || '21300'; const APP_LOCAL_PORT = process.env.HDX_E2E_APP_LOCAL_PORT || '21200'; // next dev loads .env.development, which sets HYPERDX_API_KEY, so without this the // e2e Next server and browser export HyperDX's own telemetry to collector ports that // do not exist in e2e. The failed exports add retries and load to parallel runs. const NO_SELF_TELEMETRY = 'HDX_EXPORTER_ENABLED=false'; // Timeout configuration constants (in milliseconds) const TEST_TIMEOUT_MS = 60 * 1000; // 60 seconds per test const API_SERVER_STARTUP_TIMEOUT_MS = 120 * 1000; // 2 minutes for API to start // The Next.js build alone exceeds 3 minutes on the 4-vCPU runner fork PRs use. const APP_SERVER_STARTUP_TIMEOUT_MS = 360 * 1000; /** * @see https://playwright.dev/docs/test-configuration */ export default defineConfig({ testDir: './tests/e2e', /* Global setup to ensure server is ready */ globalSetup: USE_FULLSTACK ? require.resolve('./tests/e2e/global-setup-fullstack.ts') : require.resolve('./tests/e2e/global-setup-local.ts'), /* Run tests in files in parallel */ fullyParallel: true, /* Fail the build on CI if you accidentally left test.only in the source code. */ forbidOnly: !!process.env.CI, /* Retry on CI only. Overridable via E2E_RETRIES (e.g. the nightly zero-retry * flake hunt). Honours an explicit 0; an unset/blank/negative/non-integer * value falls back to the default rather than silently coercing to 0. */ retries: (() => { const raw = process.env.E2E_RETRIES; const parsed = raw ? Number(raw) : NaN; if (Number.isInteger(parsed) && parsed >= 0) return parsed; return process.env.CI ? 2 : 1; })(), /* Use multiple workers on CI for faster execution. CI e2e shards run on * 8-core runners (see .github/workflows/e2e-tests.yml), so 4 workers leave * headroom for the app + ClickHouse + Mongo. Override with E2E_WORKERS * (a positive integer); anything else falls back to the default. */ workers: (() => { const raw = process.env.E2E_WORKERS; const parsed = raw ? Number(raw) : NaN; if (Number.isInteger(parsed) && parsed >= 1) return parsed; return process.env.CI ? 4 : undefined; })(), /* Reporter to use. See https://playwright.dev/docs/test-reporters */ reporter: [ ['html'], ['json', { outputFile: 'test-results/results.json' }], ...(process.env.CI ? [['github'] as const, ['list'] as const] : [['list'] as const]), ], /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Base URL to use in actions like `await page.goto('/')`. */ baseURL: USE_FULLSTACK ? `http://localhost:${APP_PORT}` : process.env.PLAYWRIGHT_BASE_URL || `http://localhost:${APP_LOCAL_PORT}`, /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: 'on-first-retry', /* Take screenshot on failure */ screenshot: 'only-on-failure', /* Record video on failure */ video: 'retain-on-failure', // Otherwise Chromium takes navigator.language from the host locale, so // number and date formatting would vary between machines. locale: 'en-US', }, /* Global test timeout - CI needs more time than local */ timeout: TEST_TIMEOUT_MS, /* Configure projects for different test environments */ projects: [ { name: 'chromium', use: { ...devices['Desktop Chrome'], // Use saved authentication state for full-stack mode ...(USE_FULLSTACK && { storageState: AUTH_FILE, }), }, }, ], /* Run your local dev server before starting the tests */ // Note: webServer array syntax requires Playwright v1.32.0+ (current: v1.57.0) webServer: USE_FULLSTACK ? [ // Full-stack mode: Start API and App servers (infrastructure started separately) { // Connections/sources come from env (injected by run-e2e.js from e2e-fixtures.json) command: `cd ../api && ${process.env.MONGO_URI ? `MONGO_URI="${process.env.MONGO_URI}"` : ''} DOTENV_CONFIG_PATH=.env.e2e npx ts-node --transpile-only -r tsconfig-paths/register -r dotenv-expand/config -r ./src/telemetry.ts src/index.ts`, port: parseInt(API_PORT, 10), reuseExistingServer: !process.env.CI, timeout: API_SERVER_STARTUP_TIMEOUT_MS, stdout: 'pipe', stderr: 'pipe', }, { // Full UI: Alerts + Dashboards. Not local mode; Alerts enabled; command: USE_DEV ? `${NO_SELF_TELEMETRY} SERVER_URL=http://localhost:${API_PORT} PORT=${APP_PORT} NEXT_DIST_DIR=.next-e2e NEXT_PUBLIC_ENABLE_ALERT_DETAILS=true NEXT_PUBLIC_ENABLE_PROMQL=true NEXT_PUBLIC_ENABLE_ADHOC_FILTERS=true next dev --webpack` : `${NO_SELF_TELEMETRY} SERVER_URL=http://localhost:${API_PORT} PORT=${APP_PORT} NEXT_DIST_DIR=.next-e2e NEXT_PUBLIC_ENABLE_ALERT_DETAILS=true NEXT_PUBLIC_ENABLE_PROMQL=true NEXT_PUBLIC_ENABLE_ADHOC_FILTERS=true yarn build && ${NO_SELF_TELEMETRY} SERVER_URL=http://localhost:${API_PORT} PORT=${APP_PORT} NEXT_DIST_DIR=.next-e2e NEXT_PUBLIC_ENABLE_ALERT_DETAILS=true NEXT_PUBLIC_ENABLE_PROMQL=true NEXT_PUBLIC_ENABLE_ADHOC_FILTERS=true yarn start`, port: parseInt(APP_PORT, 10), reuseExistingServer: !process.env.CI, timeout: APP_SERVER_STARTUP_TIMEOUT_MS, stdout: 'pipe', stderr: 'pipe', }, ] : { // Local mode: Frontend only command: USE_DEV ? `${NO_SELF_TELEMETRY} NEXT_PUBLIC_IS_LOCAL_MODE=true PORT=${APP_LOCAL_PORT} NEXT_DIST_DIR=.next-e2e next dev --webpack` : `${NO_SELF_TELEMETRY} NEXT_PUBLIC_IS_LOCAL_MODE=true NEXT_DIST_DIR=.next-e2e yarn build && ${NO_SELF_TELEMETRY} NEXT_PUBLIC_IS_LOCAL_MODE=true PORT=${APP_LOCAL_PORT} NEXT_DIST_DIR=.next-e2e yarn start`, port: parseInt(APP_LOCAL_PORT, 10), reuseExistingServer: !process.env.CI, timeout: APP_SERVER_STARTUP_TIMEOUT_MS, stdout: 'pipe', stderr: 'pipe', }, });