diff --git a/playwright.config.js b/playwright.config.js index 12a17fda..a633e5b8 100644 --- a/playwright.config.js +++ b/playwright.config.js @@ -26,6 +26,10 @@ const STORAGE_STATE = join(__dirname, 'playwright/.auth/user.json'); * Enabled by default, disable with PLAYWRIGHT_COVERAGE=0 */ const enableCoverage = process.env.PLAYWRIGHT_COVERAGE !== '0'; +const resolvedBaseURL = process.env.PLAYWRIGHT_BASE_URL || (enableCoverage ? 'http://localhost:5173' : 'http://127.0.0.1:8080'); +if (!process.env.PLAYWRIGHT_BASE_URL) { + process.env.PLAYWRIGHT_BASE_URL = resolvedBaseURL; +} // Skip security-test dependencies by default to avoid running them as a // prerequisite for non-security test runs. Set PLAYWRIGHT_SKIP_SECURITY_DEPS=0 // to restore the legacy dependency behavior when needed. @@ -154,18 +158,18 @@ export default defineConfig({ * - Uses http://localhost:5173 (source maps for V8 coverage) * - PLAYWRIGHT_BASE_URL override still respected * - * Non-coverage mode (enableCoverage=false): + * Non-coverage mode (enableCoverage=false): * - Tests run against Docker container (faster, no coverage) - * - Uses http://localhost:8080 (MUST use localhost for cookie consistency) + * - Uses http://127.0.0.1:8080 (IPv4 loopback to avoid ::1 connection flakiness) * - PLAYWRIGHT_BASE_URL override still respected * * CRITICAL: Authentication cookies are domain-scoped. The auth.setup.ts * stores cookies for the domain in this baseURL. TestDataManager and * browser tests must use the SAME domain for cookies to be sent. * - * WHY LOCALHOST NOT 127.0.0.1: - * Cookies saved with domain="localhost" will NOT be sent to "127.0.0.1" - * and vice versa. Both auth and tests MUST use the same hostname. + * Cookie domain note: + * Cookies are domain-scoped. Auth setup and browser/API contexts must use + * the same resolved base URL host. * * E2E tests verify UI/UX on the Charon management interface. * Middleware enforcement is tested separately via integration tests (backend/integration/). @@ -174,7 +178,7 @@ export default defineConfig({ * export PLAYWRIGHT_BASE_URL=http://100.98.12.109:9323 * npx playwright test --ui */ - baseURL: process.env.PLAYWRIGHT_BASE_URL || (enableCoverage ? 'http://localhost:5173' : 'http://localhost:8080'), + baseURL: resolvedBaseURL, /* Traces: Capture execution traces for debugging * diff --git a/tests/auth.setup.ts b/tests/auth.setup.ts index 121ad4e5..4b151b9e 100644 --- a/tests/auth.setup.ts +++ b/tests/auth.setup.ts @@ -184,7 +184,7 @@ async function performLoginAndSaveState( const expectedHost = new URL(baseURL).hostname; if (authCookie.domain !== expectedHost && authCookie.domain !== `.${expectedHost}`) { console.warn(`⚠️ Cookie domain mismatch: cookie domain "${authCookie.domain}" does not match baseURL host "${expectedHost}"`); - console.warn('TestDataManager API calls may fail with 401. Ensure PLAYWRIGHT_BASE_URL uses localhost.'); + console.warn('TestDataManager API calls may fail with 401. Ensure PLAYWRIGHT_BASE_URL matches the configured Playwright base URL host.'); } else { console.log(`✅ Cookie domain "${authCookie.domain}" matches baseURL host "${expectedHost}"`); } diff --git a/tests/fixtures/auth-fixtures.ts b/tests/fixtures/auth-fixtures.ts index aff2dffb..4444087d 100644 --- a/tests/fixtures/auth-fixtures.ts +++ b/tests/fixtures/auth-fixtures.ts @@ -329,7 +329,7 @@ export const test = base.extend({ ` Cookie domain: "${authCookie.domain}"\n` + ` Base URL host: "${expectedHost}"\n` + ` API calls will likely fail with 401/403.\n` + - ` Fix: Set PLAYWRIGHT_BASE_URL=http://localhost:8080 in your environment.` + ` Fix: Set PLAYWRIGHT_BASE_URL to the same host used by Playwright baseURL.` ); } } diff --git a/tests/global-setup.ts b/tests/global-setup.ts index 4a8f70f0..d876a359 100644 --- a/tests/global-setup.ts +++ b/tests/global-setup.ts @@ -96,10 +96,9 @@ function validateEmergencyToken(): void { /** * Get the base URL for the application - * CRITICAL: Must use localhost (not 127.0.0.1) for cookie domain consistency */ function getBaseURL(): string { - return process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:8080'; + return process.env.PLAYWRIGHT_BASE_URL || 'http://127.0.0.1:8080'; } /** @@ -136,7 +135,7 @@ async function checkCaddyAdminHealth(): Promise { * This prevents 401 errors when global-setup runs before containers finish starting. */ async function waitForContainer(maxRetries = 15, delayMs = 2000): Promise { - const baseURL = process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:8080'; + const baseURL = getBaseURL(); console.log(`⏳ Waiting for container to be ready at ${baseURL}...`); for (let i = 0; i < maxRetries; i++) { @@ -392,7 +391,7 @@ async function emergencySecurityReset(requestContext: APIRequestContext): Promis console.log('🔓 Performing emergency security reset...'); const emergencyToken = process.env.CHARON_EMERGENCY_TOKEN; - const baseURL = process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:8080'; + const baseURL = getBaseURL(); if (!emergencyToken) { console.warn(' ⚠️ CHARON_EMERGENCY_TOKEN not set, skipping emergency reset');