import { test as baseTest, expect, Page, APIRequestContext } from '@playwright/test'; import * as fs from 'fs'; import * as path from 'path'; import { fileURLToPath } from 'url'; /** * E2E测试夹具(Fixtures) * 提供测试数据、API客户端、认证信息等 */ // 测试数据接口 export interface TestData { activityId: number; apiKey: string; userToken: string; userId: number; shortCode: string; baseUrl: string; apiBaseUrl: string; } // API响应类型 export interface ApiResponse { code: number; message: string; data: T; } const DEFAULT_TEST_API_KEY = 'test-api-key-000000000000'; const DEFAULT_TEST_USER_TOKEN = 'test-e2e-token'; export function hasRealApiCredentials(data: TestData): boolean { return Boolean( data.apiKey && data.userToken && data.apiKey !== DEFAULT_TEST_API_KEY && data.userToken !== DEFAULT_TEST_USER_TOKEN ); } // API客户端类 export class ApiClient { constructor( private request: APIRequestContext, private apiKey: string, private userToken: string, private baseURL: string ) {} /** * 发送认证请求 */ async get(endpoint: string, headers?: Record): Promise> { const response = await this.request.get(`${this.baseURL}${endpoint}`, { headers: { 'X-API-Key': this.apiKey, 'Authorization': `Bearer ${this.userToken}`, ...headers, }, }); // 处理非200响应 if (!response.ok()) { return { code: response.status(), message: `HTTP ${response.status()}: ${response.statusText()}`, data: null as any }; } const text = await response.text(); if (!text) { return { code: response.status(), message: 'Empty response', data: null as any }; } return JSON.parse(text); } /** * 发送POST请求 */ async post(endpoint: string, data: any, headers?: Record): Promise> { const response = await this.request.post(`${this.baseURL}${endpoint}`, { data, headers: { 'Content-Type': 'application/json', 'X-API-Key': this.apiKey, 'Authorization': `Bearer ${this.userToken}`, ...headers, }, }); // 处理非200响应 if (!response.ok()) { return { code: response.status(), message: `HTTP ${response.status()}: ${response.statusText()}`, data: null as any }; } const text = await response.text(); if (!text) { return { code: response.status(), message: 'Empty response', data: null as any }; } return JSON.parse(text); } /** * 验证API Key */ async validateApiKey(apiKey: string): Promise { try { const response = await this.request.post(`${this.baseURL}/api/v1/keys/validate`, { data: { apiKey }, headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${this.userToken}`, }, }); return response.status() === 200; } catch (error) { return false; } } /** * 获取活动列表 */ async getActivities(): Promise> { return this.get('/api/v1/activities'); } /** * 获取活动详情 */ async getActivity(activityId: number): Promise> { return this.get(`/api/v1/activities/${activityId}`); } /** * 获取活动统计 */ async getActivityStats(activityId: number): Promise> { return this.get(`/api/v1/activities/${activityId}/stats`); } /** * 获取排行榜 */ async getLeaderboard(activityId: number, page: number = 0, size: number = 10): Promise> { return this.get(`/api/v1/activities/${activityId}/leaderboard?page=${page}&size=${size}`); } /** * 创建短链 */ async createShortLink(originalUrl: string, activityId: number): Promise> { return this.post('/api/v1/internal/shorten', { originalUrl, activityId, }); } /** * 获取分享指标 */ async getShareMetrics(activityId: number): Promise> { return this.get(`/api/v1/share/metrics?activityId=${activityId}`); } } /** * 加载测试数据 */ function loadTestData(): TestData { // ES模块中获取当前文件目录 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:5173', 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; } /** * 扩展的测试夹具类型 */ export interface TestFixtures { testData: TestData; apiClient: ApiClient; authenticatedPage: Page; } /** * 创建扩展的test对象 */ export const test = baseTest.extend({ // 测试数据 testData: async ({}, use) => { const data = loadTestData(); await use(data); }, // API客户端 apiClient: async ({ request, testData }, use) => { const client = new ApiClient( request, testData.apiKey, testData.userToken, testData.apiBaseUrl ); await use(client); }, // 已认证的页面 authenticatedPage: async ({ page, testData }, use) => { // 设置localStorage模拟登录状态 await page.addInitScript((data) => { localStorage.setItem('token', data.userToken); localStorage.setItem('userId', data.userId.toString()); localStorage.setItem('apiKey', data.apiKey); localStorage.setItem('activityId', data.activityId.toString()); }, testData); await use(page); }, }); export { expect };