import { test, expect } from '@playwright/test'; import * as fs from 'fs'; import * as path from 'path'; import { fileURLToPath } from 'url'; /** * 用户核心旅程测试(严格模式 - 固定版本) * * 双模式执行: * - 无真实凭证:显式跳过(test.skip) * - 有真实凭证:严格断言 2xx/3xx */ const API_BASE_URL = process.env.API_BASE_URL || 'http://localhost:8080'; const FRONTEND_URL = process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:5176'; const DEFAULT_TEST_API_KEY = 'test-api-key-000000000000'; const DEFAULT_TEST_USER_TOKEN = 'test-e2e-token'; interface TestData { activityId: number; apiKey: string; userToken: string; userId: number; shortCode: string; baseUrl: string; apiBaseUrl: string; } function loadTestData(): TestData { const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const testDataPath = path.join(__dirname, '..', '.e2e-test-data.json'); const defaultData: TestData = { activityId: 1, apiKey: DEFAULT_TEST_API_KEY, userToken: process.env.E2E_USER_TOKEN || DEFAULT_TEST_USER_TOKEN, userId: 10001, shortCode: 'test123', baseUrl: process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:5176', apiBaseUrl: process.env.API_BASE_URL || 'http://localhost:8080', }; try { if (fs.existsSync(testDataPath)) { const data = JSON.parse(fs.readFileSync(testDataPath, 'utf-8')); return { ...defaultData, ...data }; } } catch (error) { console.warn('无法加载测试数据,使用默认值'); } return defaultData; } function hasRealApiCredentials(data: TestData): boolean { return Boolean( data.apiKey && data.userToken && data.apiKey !== DEFAULT_TEST_API_KEY && data.userToken !== DEFAULT_TEST_USER_TOKEN ); } // 加载测试数据 const testData = loadTestData(); const useRealCredentials = hasRealApiCredentials(testData); const E2E_STRICT = process.env.E2E_STRICT === 'true'; test.describe('🎯 用户核心旅程测试(严格模式)', () => { // 首页不需要凭证,始终执行 test('🏠 首页应可访问(无需凭证)', async ({ page }) => { await page.goto('/'); await page.waitForLoadState('networkidle'); await expect(page.locator('#app')).toBeAttached(); }); if (!useRealCredentials) { // 严格模式下无真实凭证时必须失败,非严格模式才跳过 if (E2E_STRICT) { test('📊 活动列表API(需要真实凭证)', async () => { throw new Error('严格模式需要真实凭证(E2E_USER_TOKEN),但未提供有效凭证,测试失败'); }); } else { test.skip('📊 活动列表API(需要真实凭证)', async ({ request }) => { // 此测试需要真实凭证,无凭证时跳过 }); } } else { // 有真实凭证时严格断言 test('📊 活动列表API - 严格断言', async ({ request }) => { const response = await request.get(`${API_BASE_URL}/api/v1/activities`, { headers: { 'X-API-Key': testData.apiKey, 'Authorization': `Bearer ${testData.userToken}`, }, }); const status = response.status(); // 严格断言:只接受 2xx/3xx expect( status, `活动列表API应返回2xx/3xx,实际${status}` ).toBeGreaterThanOrEqual(200); expect( status, `活动列表API应返回2xx/3xx,实际${status}` ).toBeLessThan(400); }); test('后端健康检查应正常 - 严格断言', async ({ request }) => { const response = await request.get(`${API_BASE_URL}/actuator/health`); expect( response.status(), `健康检查应返回200,实际${response.status()}` ).toBe(200); const body = await response.json(); expect( body.status, `健康检查状态应为UP,实际${body.status}` ).toBe('UP'); }); test('前端服务应可访问 - 严格断言', async ({ page }) => { const response = await page.goto(FRONTEND_URL); expect( response, '页面响应不应为null' ).not.toBeNull(); expect( response?.status(), `前端应返回2xx/3xx,实际${response?.status()}` ).toBeGreaterThanOrEqual(200); expect( response?.status(), `前端应返回2xx/3xx,实际${response?.status()}` ).toBeLessThan(400); }); } });