import { defineConfig, devices } from '@playwright/test'; import { config as dotenvConfig } from 'dotenv'; import path from 'path'; // 加载项目根目录的 .env.local,使 Playwright webServer 与测试进程使用相同的 CMS_REVALIDATE_SECRET // 若 .env.local 不存在则回退到默认测试 secret dotenvConfig({ path: path.resolve(__dirname, '../.env.local') }); process.env.CMS_REVALIDATE_SECRET = process.env.CMS_REVALIDATE_SECRET || 'e2e-test-secret'; export default defineConfig({ testDir: './', fullyParallel: true, forbidOnly: !!process.env.CI, retries: process.env.CI ? 2 : 0, workers: process.env.CI ? 1 : undefined, reporter: [ ['html', { open: 'never' }], ['list', { printSteps: true }], ['allure-playwright', { outputFolder: 'allure-results' }], ], snapshotDir: './visual-snapshots', snapshotPathTemplate: '{snapshotDir}/{projectName}/{testFilePath}/{arg}-{projectName}{ext}', // 显式 outputDir 绝对路径(e2e/test-results),避免 playwright 在项目根创建 test-results // 触发 WorkBuddy 沙箱 safe-delete BULK_GUARD(node fs.rm ≥ 50 文件被拦,e2e 直接退出) outputDir: path.join(__dirname, 'test-results'), use: { baseURL: process.env.E2E_BASE_URL || 'http://localhost:3000', // 路径以配置文件所在目录为基准,兼容 `cd e2e` 与从项目根目录直接调用两种执行方式 storageState: path.resolve(__dirname, 'storageState.json'), trace: 'on-first-retry', screenshot: 'only-on-failure', ignoreHTTPSErrors: true, actionTimeout: 15000, navigationTimeout: 30000, }, expect: { toHaveScreenshot: { maxDiffPixels: 200, maxDiffPixelRatio: 0.005, threshold: 0.3, animations: 'disabled', caret: 'hide', scale: 'device', timeout: 20000, }, timeout: 15000, }, projects: [ { name: 'visual-chromium-desktop', use: { ...devices['Desktop Chrome'], viewport: { width: 1280, height: 800 }, }, testMatch: 'visual-regression.spec.ts', }, { name: 'visual-chromium-tablet', use: { ...devices['iPad Pro 11'], viewport: { width: 834, height: 1194 }, }, testMatch: 'visual-regression.spec.ts', }, { name: 'visual-chromium-mobile', use: { ...devices['iPhone 14'], viewport: { width: 390, height: 844 }, isMobile: true, hasTouch: true, }, testMatch: 'visual-regression.spec.ts', }, { name: 'visual-firefox-desktop', use: { ...devices['Desktop Firefox'], viewport: { width: 1280, height: 800 }, storageState: './storageState.firefox.json', }, testMatch: 'visual-regression.spec.ts', }, { name: 'visual-webkit-desktop', use: { ...devices['Desktop Safari'], viewport: { width: 1280, height: 800 }, storageState: './storageState.firefox.json', }, testMatch: 'visual-regression.spec.ts', }, { name: 'chromium', use: { ...devices['Desktop Chrome'] }, testIgnore: 'visual-regression.spec.ts', }, { name: 'chromium-mobile', use: { ...devices['iPhone 14'], viewport: { width: 390, height: 844 }, isMobile: true, hasTouch: true, }, testIgnore: 'visual-regression.spec.ts', }, { name: 'firefox', use: { ...devices['Desktop Firefox'], storageState: './storageState.firefox.json' }, testIgnore: 'visual-regression.spec.ts', }, { name: 'webkit', use: { ...devices['Desktop Safari'], storageState: './storageState.firefox.json' }, testIgnore: 'visual-regression.spec.ts', }, ], webServer: { // 使用 dev server 以支持 API 路由(admin 用户旅程等测试需要) // 若只运行非 API 测试(如 @journey @regression),可手动启动 npm run preview 并设置 reuseExistingServer: true command: 'npm run dev', url: 'http://localhost:3000', reuseExistingServer: true, timeout: 120000, cwd: '..', env: { CMS_REVALIDATE_SECRET: process.env.CMS_REVALIDATE_SECRET || 'e2e-test-secret', }, }, });