# 测试套件修复计划 > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. **Goal:** 修复测试套件中的关键问题,使整体测试通过率达到95%以上,E2E测试可正常运行,符合金融级测试标准。 **Architecture:** 采用分层修复策略,从基础设施配置开始,逐步修复依赖管理和测试隔离问题,最后优化测试覆盖率和稳定性。 **Tech Stack:** Playwright (E2E), Vitest (前端单元测试), pytest (API测试), TypeScript, Python --- ## 执行策略 本计划按照优先级分为三个阶段: - **P0阶段**:修复阻塞测试运行的基础设施问题(预计2-3小时) - **P1阶段**:提升测试稳定性和通过率(预计4-6小时) - **P2阶段**:优化测试架构和覆盖率(预计8-12小时) 每个任务都遵循TDD原则:先写失败测试,再实现最小化修复,最后验证通过。 --- ## P0阶段:基础设施修复(立即执行) ### Task 1: 创建Playwright配置文件 **Files:** - Create: `everything-is-suitable-admin/playwright.config.ts` **Step 1: 创建配置文件基础结构** ```typescript import { defineConfig, devices } from '@playwright/test'; export default defineConfig({ testDir: './e2e', fullyParallel: true, forbidOnly: !!process.env.CI, retries: process.env.CI ? 2 : 0, workers: process.env.CI ? 1 : undefined, reporter: [ ['html'], ['list'], ['json', { outputFile: 'test-results/results.json' }] ], use: { baseURL: process.env.E2E_BASE_URL || 'http://localhost:5174', trace: 'on-first-retry', screenshot: 'only-on-failure', video: 'retain-on-failure', }, projects: [ { name: 'chromium', use: { ...devices['Desktop Chrome'] }, }, { name: 'firefox', use: { ...devices['Desktop Firefox'] }, }, { name: 'webkit', use: { ...devices['Desktop Safari'] }, }, ], webServer: { command: 'npm run dev', url: 'http://localhost:5174', reuseExistingServer: !process.env.CI, timeout: 120000, }, }); ``` **Step 2: 验证配置文件语法** Run: `cd everything-is-suitable-admin && npx playwright test --config=playwright.config.ts --dry-run` Expected: 配置文件语法正确,无错误输出 **Step 3: 运行单个E2E测试验证配置** Run: `cd everything-is-suitable-admin && npx playwright test e2e/auth.spec.ts:89 --reporter=list` Expected: 测试开始执行(可能失败,但配置生效) **Step 4: 提交配置文件** ```bash git add everything-is-suitable-admin/playwright.config.ts git commit -m "feat: add Playwright configuration for E2E tests" ``` --- ### Task 2: 修复E2E测试中的相对路径问题 **Files:** - Modify: `everything-is-suitable-admin/e2e/auth.spec.ts:86` - Modify: `everything-is-suitable-admin/e2e/user.spec.ts` - Modify: `everything-is-suitable-admin/e2e/role.spec.ts` - Modify: `everything-is-suitable-admin/e2e/menu.spec.ts` **Step 1: 修改auth.spec.ts中的导航路径** 查找所有 `page.goto('/login')` 并修改为: ```typescript // 修改前 await page.goto('/login'); // 修改后 await page.goto('/login'); ``` 注意:由于配置了baseURL,相对路径应该可以工作。如果仍有问题,使用绝对路径: ```typescript await page.goto('http://localhost:5174/login'); ``` **Step 2: 检查其他测试文件的路径** Run: `cd everything-is-suitable-admin && grep -n "page.goto" e2e/*.spec.ts` Expected: 列出所有page.goto调用 **Step 3: 统一修改所有测试文件的路径** 根据grep结果,逐个文件修改路径问题。 **Step 4: 运行E2E测试验证修复** Run: `cd everything-is-suitable-admin && npx playwright test e2e/auth.spec.ts --reporter=list` Expected: 至少第一个测试应该能够加载登录页面 **Step 5: 提交修复** ```bash git add everything-is-suitable-admin/e2e/ git commit -m "fix: resolve navigation path issues in E2E tests" ``` --- ### Task 3: 创建缺失的前端测试工具模块 **Files:** - Create: `everything-is-suitable-admin/src/utils/formRules.ts` - Create: `everything-is-suitable-admin/src/utils/formValidator.ts` - Create: `everything-is-suitable-admin/src/utils/passwordValidator.ts` **Step 1: 创建formRules.ts** ```typescript import { Rule } from 'ant-design-vue/es/form'; export const formRules: Record = { username: [ { required: true, message: '请输入用户名', trigger: 'blur' }, { min: 3, max: 20, message: '用户名长度在3到20个字符', trigger: 'blur' }, ], password: [ { required: true, message: '请输入密码', trigger: 'blur' }, { min: 6, message: '密码长度不能少于6个字符', trigger: 'blur' }, ], email: [ { required: true, message: '请输入邮箱', trigger: 'blur' }, { type: 'email', message: '请输入有效的邮箱地址', trigger: 'blur' }, ], phone: [ { required: true, message: '请输入手机号', trigger: 'blur' }, { pattern: /^1[3-9]\d{9}$/, message: '请输入有效的手机号', trigger: 'blur' }, ], }; export default formRules; ``` **Step 2: 创建formValidator.ts** ```typescript export interface ValidationResult { valid: boolean; message?: string; } export const formValidator = { username: (value: string): ValidationResult => { if (!value) { return { valid: false, message: '用户名不能为空' }; } if (value.length < 3 || value.length > 20) { return { valid: false, message: '用户名长度在3到20个字符' }; } return { valid: true }; }, email: (value: string): ValidationResult => { if (!value) { return { valid: false, message: '邮箱不能为空' }; } const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(value)) { return { valid: false, message: '请输入有效的邮箱地址' }; } return { valid: true }; }, phone: (value: string): ValidationResult => { if (!value) { return { valid: false, message: '手机号不能为空' }; } const phoneRegex = /^1[3-9]\d{9}$/; if (!phoneRegex.test(value)) { return { valid: false, message: '请输入有效的手机号' }; } return { valid: true }; }, required: (value: any, fieldName: string = '字段'): ValidationResult => { if (value === null || value === undefined || value === '') { return { valid: false, message: `${fieldName}不能为空` }; } return { valid: true }; }, }; export default formValidator; ``` **Step 3: 创建passwordValidator.ts** ```typescript export interface PasswordValidationResult { valid: boolean; score: number; message?: string; suggestions?: string[]; } export const passwordValidator = { validate: (password: string): PasswordValidationResult => { if (!password) { return { valid: false, score: 0, message: '密码不能为空', }; } let score = 0; const suggestions: string[] = []; if (password.length >= 8) score += 1; else suggestions.push('密码长度至少8个字符'); if (password.length >= 12) score += 1; else suggestions.push('建议使用12个字符以上的密码'); if (/[a-z]/.test(password)) score += 1; else suggestions.push('添加小写字母'); if (/[A-Z]/.test(password)) score += 1; else suggestions.push('添加大写字母'); if (/[0-9]/.test(password)) score += 1; else suggestions.push('添加数字'); if (/[^a-zA-Z0-9]/.test(password)) score += 1; else suggestions.push('添加特殊字符'); const valid = score >= 4 && password.length >= 8; return { valid, score, message: valid ? '密码强度良好' : '密码强度不足', suggestions: valid ? [] : suggestions, }; }, getStrengthLabel: (score: number): string => { if (score <= 2) return '弱'; if (score <= 4) return '中'; if (score <= 5) return '强'; return '非常强'; }, getStrengthColor: (score: number): string => { if (score <= 2) return '#ff4d4f'; if (score <= 4) return '#faad14'; if (score <= 5) return '#52c41a'; return '#1890ff'; }, }; export default passwordValidator; ``` **Step 4: 验证模块导入** Run: `cd everything-is-suitable-admin && npm run test -- src/utils/__tests__/formRules.test.ts` Expected: 测试能够运行(可能失败,但模块可以导入) **Step 5: 提交新模块** ```bash git add everything-is-suitable-admin/src/utils/formRules.ts git add everything-is-suitable-admin/src/utils/formValidator.ts git add everything-is-suitable-admin/src/utils/passwordValidator.ts git commit -m "feat: add missing form validation utility modules" ``` --- ### Task 4: 修复dayjs本地化模块导入 **Files:** - Modify: `everything-is-suitable-admin/src/utils/date.ts` **Step 1: 检查当前导入语句** Run: `cd everything-is-suitable-admin && grep -n "dayjs/locale" src/utils/date.ts` Expected: 找到问题导入语句 **Step 2: 修改导入语句** ```typescript // 修改前 import 'dayjs/locale/zh-cn'; // 修改后 import 'dayjs/locale/zh-cn.js'; ``` 或者使用正确的包名: ```typescript import dayjs from 'dayjs'; import 'dayjs/locale/zh-cn'; dayjs.locale('zh-cn'); ``` **Step 3: 验证修复** Run: `cd everything-is-suitable-admin && npm run test -- src/utils/date.test.ts` Expected: 测试通过,无模块导入错误 **Step 4: 提交修复** ```bash git add everything-is-suitable-admin/src/utils/date.ts git commit -m "fix: resolve dayjs locale import issue" ``` --- ## P1阶段:测试稳定性提升(本周执行) ### Task 5: 实现测试数据清理机制 **Files:** - Create: `everything-is-suitable-admin/src/test/test-setup.ts` - Create: `everything-is-suitable-admin/src/test/test-cleanup.ts` **Step 1: 创建测试数据清理工具** ```typescript import { cleanup } from '@testing-library/vue'; export const testDataCleanup = { cleanupTestData: async () => { cleanup(); localStorage.clear(); sessionStorage.clear(); }, generateUniqueUsername: (prefix: string = 'test'): string => { const timestamp = Date.now(); const random = Math.floor(Math.random() * 1000); return `${prefix}_${timestamp}_${random}`; }, generateUniqueEmail: (prefix: string = 'test'): string => { const timestamp = Date.now(); const random = Math.floor(Math.random() * 1000); return `${prefix}_${timestamp}_${random}@example.com`; }, }; export default testDataCleanup; ``` **Step 2: 修改测试文件使用唯一数据** 查找所有硬编码的用户名和邮箱: Run: `cd everything-is-suitable-admin && grep -rn "testuser\|test@example.com" src/test/` Expected: 列出所有硬编码的测试数据 **Step 3: 替换硬编码数据为唯一生成** 在每个测试文件中导入并使用: ```typescript import { testDataCleanup } from '@/test/test-cleanup'; const username = testDataCleanup.generateUniqueUsername(); const email = testDataCleanup.generateUniqueEmail(); ``` **Step 4: 在vitest配置中添加全局清理** Modify: `everything-is-suitable-admin/vitest.config.ts` ```typescript export default defineConfig({ test: { setupFiles: ['./src/test/setup.ts'], teardownFiles: ['./src/test/test-cleanup.ts'], // ... 其他配置 }, }); ``` **Step 5: 验证数据隔离** Run: `cd everything-is-suitable-admin && npm run test -- src/services/__tests__/user.service.management.test.ts` Expected: 无重复键错误 **Step 6: 提交修复** ```bash git add everything-is-suitable-admin/src/test/ git add everything-is-suitable-admin/vitest.config.ts git commit -m "feat: implement test data cleanup and isolation mechanism" ``` --- ### Task 6: 修复Vitest与Playwright全局对象冲突 **Files:** - Modify: `everything-is-suitable-admin/vitest.config.ts` - Create: `everything-is-suitable-admin/vitest.setup.ts` **Step 1: 创建Vitest独立设置文件** ```typescript import { vi } from 'vitest'; // 隔离Vitest的全局对象 global.expect = vi.expect; global.test = vi.test; global.describe = vi.describe; global.it = vi.it; // 清理可能的冲突 beforeEach(() => { vi.clearAllMocks(); }); ``` **Step 2: 修改vitest配置使用独立设置** ```typescript import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { setupFiles: ['./vitest.setup.ts'], globals: true, environment: 'jsdom', include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts}'], exclude: ['node_modules', 'dist', 'e2e'], coverage: { provider: 'v8', reporter: ['text', 'json', 'html'], exclude: [ 'node_modules/', 'src/test/', '**/*.d.ts', '**/*.config.*', '**/mockData.ts', ], }, }, }); ``` **Step 3: 验证配置修复** Run: `cd everything-is-suitable-admin && npm run test -- src/test/auth.service.test.ts` Expected: 无`$$jest-matchers-object`错误 **Step 4: 提交修复** ```bash git add everything-is-suitable-admin/vitest.config.ts git add everything-is-suitable-admin/vitest.setup.ts git commit -m "fix: resolve Vitest and Playwright global object conflicts" ``` --- ### Task 7: 修复Vue组件测试中的语法解析错误 **Files:** - Modify: `everything-is-suitable-admin/src/test/sidebar.component.test.ts` - Modify: `everything-is-suitable-admin/src/views/__tests__/UserManagement.management.test.ts` **Step 1: 检查Sidebar.vue语法** Run: `cd everything-is-suitable-admin && cat src/components/Sidebar.vue | head -20` Expected: 确认Vue组件语法 **Step 2: 修改测试文件配置** 在测试文件中添加正确的Vue编译器配置: ```typescript import { describe, it, expect, beforeEach, vi } from 'vitest'; import { mount, VueWrapper } from '@vue/test-utils'; import { createPinia, setActivePinia } from 'pinia'; import { createRouter, createMemoryHistory } from 'vue-router'; import { createI18n } from 'vue-i18n'; // 添加Vue组件编译配置 const router = createRouter({ history: createMemoryHistory(), routes: [], }); const i18n = createI18n({ legacy: false, locale: 'zh-CN', messages: { 'zh-CN': {}, }, }); describe('Sidebar Component', () => { beforeEach(() => { setActivePinia(createPinia()); }); it('should render correctly', async () => { const wrapper = mount(Sidebar, { global: { plugins: [router, i18n], stubs: { 'a-layout-sider': true, 'a-menu': true, 'a-menu-item': true, }, }, }); expect(wrapper.exists()).toBe(true); }); }); ``` **Step 3: 验证修复** Run: `cd everything-is-suitable-admin && npm run test -- src/test/sidebar.component.test.ts` Expected: 无语法解析错误 **Step 4: 提交修复** ```bash git add everything-is-suitable-admin/src/test/sidebar.component.test.ts git add everything-is-suitable-admin/src/views/__tests__/UserManagement.management.test.ts git commit -m "fix: resolve Vue component syntax parsing errors in tests" ``` --- ### Task 8: 提升E2E测试通过率到60%+ **Files:** - Modify: `everything-is-suitable-admin/e2e/auth.spec.ts` - Modify: `everything-is-suitable-admin/e2e/mock-manager.ts` **Step 1: 修复Mock服务配置** 确保Mock服务正确拦截API请求: ```typescript // mock-manager.ts export class MockManager { private mockResponses: Map = new Map(); addMockResponse(config: MockConfig) { const key = `${config.method}:${config.url}`; this.mockResponses.set(key, config.response); } async interceptAPIRequest(page: Page) { await page.route('**/api/**', async (route) => { const url = route.request().url(); const method = route.request().method(); const key = `${method}:${url}`; if (this.mockResponses.has(key)) { const response = this.mockResponses.get(key); await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(response), }); } else { await route.continue(); } }); } } ``` **Step 2: 修复auth.spec.ts测试用例** 确保测试等待和断言正确: ```typescript test('应该显示登录页面', async ({ page }) => { await expect(page).toHaveTitle(/管理系统/); const usernameInput = page.locator('input[placeholder="请输入用户名"]'); const passwordInput = page.locator('input[placeholder="请输入密码"]'); const loginButton = page.locator('button[type="submit"]'); await expect(usernameInput).toBeVisible({ timeout: 10000 }); await expect(passwordInput).toBeVisible({ timeout: 10000 }); await expect(loginButton).toBeVisible({ timeout: 10000 }); }); ``` **Step 3: 运行所有E2E测试** Run: `cd everything-is-suitable-admin && npx playwright test --reporter=list` Expected: 至少60%的测试通过 **Step 4: 提交修复** ```bash git add everything-is-suitable-admin/e2e/ git commit -m "fix: improve E2E test stability and pass rate to 60%+" ``` --- ### Task 9: 提升前端单元测试通过率到90%+ **Files:** - Modify: `everything-is-suitable-admin/src/services/__tests__/*.test.ts` - Modify: `everything-is-suitable-admin/src/stores/__tests__/*.test.ts` **Step 1: 识别失败的测试** Run: `cd everything-is-suitable-admin && npm run test 2>&1 | grep "FAIL" | head -20` Expected: 列出所有失败的测试 **Step 2: 逐个修复失败的测试** 对于每个失败测试: 1. 查看错误信息 2. 检查测试代码 3. 修复问题(Mock、断言、异步处理等) 示例修复: ```typescript // 修复前 it('should login successfully', async () => { const result = await authService.login('admin', 'password'); expect(result).toBeDefined(); }); // 修复后 it('should login successfully', async () => { vi.spyOn(axios, 'post').mockResolvedValue({ data: { token: 'mock-token', userInfo: { id: 1 } } }); const result = await authService.login('admin', 'password'); expect(result.token).toBe('mock-token'); }); ``` **Step 3: 验证修复** Run: `cd everything-is-suitable-admin && npm run test` Expected: 至少90%的测试通过 **Step 4: 提交修复** ```bash git add everything-is-suitable-admin/src/ git commit -m "fix: improve unit test pass rate to 90%+" ``` --- ## P2阶段:测试架构优化(本月执行) ### Task 10: 实现测试环境隔离 **Files:** - Create: `everything-is-suitable-admin/src/test/test-environment.ts` - Create: `everything-is-suitable-admin/docker-compose.test.yml` **Step 1: 创建测试环境配置** ```typescript export const testEnvironment = { isTest: process.env.NODE_ENV === 'test', isE2E: process.env.VITE_E2E_TEST === 'true', getBaseURL: (): string => { return process.env.E2E_BASE_URL || 'http://localhost:5174'; }, getAPIBaseURL: (): string => { return process.env.API_BASE_URL || 'http://localhost:8080'; }, reset: async () => { localStorage.clear(); sessionStorage.clear(); }, }; ``` **Step 2: 创建Docker测试环境** ```yaml version: '3.8' services: test-db: image: postgres:15 environment: POSTGRES_DB: everything_test POSTGRES_USER: test_user POSTGRES_PASSWORD: test_password ports: - "5433:5432" test-redis: image: redis:7 ports: - "6380:6379" test-api: build: ./everything-is-suitable-api environment: SPRING_PROFILES_ACTIVE: test DB_HOST: test-db REDIS_HOST: test-redis depends_on: - test-db - test-redis ports: - "8081:8080" ``` **Step 3: 验证测试环境** Run: `cd everything-is-suitable-admin && docker-compose -f docker-compose.test.yml up -d` Expected: 测试环境启动成功 **Step 4: 提交配置** ```bash git add everything-is-suitable-admin/src/test/test-environment.ts git add everything-is-suitable-admin/docker-compose.test.yml git commit -m "feat: implement isolated test environment" ``` --- ### Task 11: 补充金融级测试场景 **Files:** - Create: `everything-is-suitable-admin/e2e/financial.spec.ts` - Create: `everything-is-suitable-admin/e2e/security.spec.ts` - Create: `everything-is-suitable-admin/e2e/compliance.spec.ts` **Step 1: 创建金融交易测试** ```typescript import { test, expect } from '@playwright/test'; test.describe('金融交易测试', () => { test('应该正确处理资金转账', async ({ page }) => { await page.goto('/transfer'); await page.fill('input[name="amount"]', '1000.00'); await page.fill('input[name="recipient"]', 'test_account'); await page.click('button[type="submit"]'); await expect(page.locator('.success-message')).toBeVisible(); await expect(page.locator('.transaction-id')).toContainText(/TXN\d+/); }); test('应该验证交易金额精度', async ({ page }) => { await page.goto('/transfer'); await page.fill('input[name="amount"]', '1000.123456'); await page.click('button[type="submit"]'); await expect(page.locator('.error-message')).toContainText('金额精度错误'); }); test('应该防止重复交易', async ({ page }) => { const transactionId = 'TXN123456'; await page.goto('/transfer'); await page.fill('input[name="transactionId"]', transactionId); await page.click('button[type="submit"]'); await expect(page.locator('.error-message')).toContainText('交易已存在'); }); }); ``` **Step 2: 创建安全测试** ```typescript test.describe('安全测试', () => { test('应该正确处理SQL注入攻击', async ({ page }) => { await page.goto('/login'); await page.fill('input[name="username']', "admin' OR '1'='1"); await page.fill('input[name="password"]', 'password'); await page.click('button[type="submit"]'); await expect(page.locator('.error-message')).toContainText('登录失败'); }); test('应该正确处理XSS攻击', async ({ page }) => { await page.goto('/profile'); await page.fill('input[name="bio"]', ''); await page.click('button[type="submit"]'); const bio = await page.locator('.bio-display').textContent(); expect(bio).not.toContain('