import { defineConfig, devices } from '@playwright/test' import fs from 'node:fs' import path from 'node:path' const loadLocalEnvironment = () => { const filename = process.env.E2E_ENV_FILE?.trim() || path.resolve('.env') if (!fs.existsSync(filename)) return for (const line of fs.readFileSync(filename, 'utf8').split(/\r?\n/)) { const match = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*?)\s*$/) if (!match || match[1]!.startsWith('#') || process.env[match[1]!]) continue const raw = match[2]! process.env[match[1]!] = raw.length >= 2 && ((raw.startsWith('"') && raw.endsWith('"')) || (raw.startsWith("'") && raw.endsWith("'"))) ? raw.slice(1, -1) : raw } } loadLocalEnvironment() const rawApplicationBaseUrl = process.env.BASE_URL?.trim() || 'http://127.0.0.1:6180' let applicationUrl: URL try { applicationUrl = new URL(rawApplicationBaseUrl) } catch { throw new Error('BASE_URL must be an absolute HTTP(S) URL') } if (!['http:', 'https:'].includes(applicationUrl.protocol) || applicationUrl.username || applicationUrl.password) { throw new Error('BASE_URL must be an HTTP(S) URL without embedded credentials') } const applicationLoopbackHosts = new Set(['127.0.0.1', 'localhost', '::1', '[::1]']) if ( !applicationLoopbackHosts.has(applicationUrl.hostname.toLowerCase()) && applicationUrl.protocol !== 'https:' && process.env.E2E_ALLOW_INSECURE_REMOTE !== '1' ) { throw new Error('Remote BASE_URL must use HTTPS unless E2E_ALLOW_INSECURE_REMOTE=1 explicitly accepts the risk') } const baseURL = applicationUrl.toString().replace(/\/$/, '') export default defineConfig({ testDir: './tests', outputDir: './artifacts/test-results', fullyParallel: false, workers: 1, retries: process.env.CI ? 1 : 0, timeout: 60_000, expect: { timeout: 10_000, }, reporter: [ ['list'], ['html', { outputFolder: 'artifacts/html-report', open: 'never' }], ['json', { outputFile: 'artifacts/results.json' }], ], use: { baseURL, locale: 'zh-CN', timezoneId: 'Asia/Shanghai', actionTimeout: 10_000, navigationTimeout: 20_000, screenshot: 'off', // Auth traces contain request bodies and bearer tokens. Keep them disabled // by default; an operator may opt in only in a protected local/CI workspace. trace: process.env.E2E_TRACE === '1' ? 'retain-on-failure' : 'off', video: 'retain-on-failure', }, projects: [ { name: 'chromium', use: { ...devices['Desktop Chrome'] }, }, ], })