Files
everything-is-suitable/docs/plans/2026-03-07-test-suite-fix-plan.md
T
张翔 08ea5fbe98 feat(admin): 添加用户管理相关文件
添加用户管理视图、API和状态管理文件
2026-03-28 14:37:29 +08:00

32 KiB

测试套件修复计划

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: 创建配置文件基础结构

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: 提交配置文件

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') 并修改为:

// 修改前
await page.goto('/login');

// 修改后
await page.goto('/login');

注意:由于配置了baseURL,相对路径应该可以工作。如果仍有问题,使用绝对路径:

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: 提交修复

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

import { Rule } from 'ant-design-vue/es/form';

export const formRules: Record<string, Rule[]> = {
  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

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

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: 提交新模块

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: 修改导入语句

// 修改前
import 'dayjs/locale/zh-cn';

// 修改后
import 'dayjs/locale/zh-cn.js';

或者使用正确的包名:

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: 提交修复

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: 创建测试数据清理工具

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: 替换硬编码数据为唯一生成

在每个测试文件中导入并使用:

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

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: 提交修复

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独立设置文件

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配置使用独立设置

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: 提交修复

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编译器配置:

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: 提交修复

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请求:

// mock-manager.ts
export class MockManager {
  private mockResponses: Map<string, any> = 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测试用例

确保测试等待和断言正确:

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: 提交修复

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、断言、异步处理等)

示例修复:

// 修复前
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: 提交修复

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: 创建测试环境配置

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测试环境

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: 提交配置

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: 创建金融交易测试

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: 创建安全测试

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"]', '<script>alert("XSS")</script>');
    await page.click('button[type="submit"]');

    const bio = await page.locator('.bio-display').textContent();
    expect(bio).not.toContain('<script>');
  });

  test('应该验证API请求签名', async ({ page }) => {
    await page.goto('/api/secure-data');
    const response = await page.evaluate(() => fetch('/api/secure-data'));

    expect(response.status).toBe(401);
  });
});

Step 3: 创建合规性测试

test.describe('合规性测试', () => {
  test('应该记录所有操作日志', async ({ page }) => {
    await page.goto('/dashboard');
    await page.click('button[data-action="export"]');

    const logs = await page.evaluate(() => {
      return JSON.parse(localStorage.getItem('operationLogs') || '[]');
    });

    expect(logs.length).toBeGreaterThan(0);
    expect(logs[0]).toHaveProperty('timestamp');
    expect(logs[0]).toHaveProperty('action');
  });

  test('应该验证数据保留期限', async ({ page }) => {
    await page.goto('/audit-logs');
    await page.fill('input[name="date"]', '2020-01-01');
    await page.click('button[type="search"]');

    await expect(page.locator('.no-data-message')).toBeVisible();
  });

  test('应该保护敏感数据', async ({ page }) => {
    await page.goto('/user-profile');
    const passwordField = await page.locator('input[type="password"]');
    const passwordValue = await passwordField.inputValue();

    expect(passwordValue).toBe('');
  });
});

Step 4: 运行金融级测试

Run: cd everything-is-suitable-admin && npx playwright test e2e/financial.spec.ts e2e/security.spec.ts e2e/compliance.spec.ts --reporter=list

Expected: 所有金融级测试通过

Step 5: 提交测试

git add everything-is-suitable-admin/e2e/
git commit -m "feat: add financial-grade test scenarios"

Task 12: 实现性能测试

Files:

  • Create: everything-is-suitable-admin/e2e/performance.spec.ts
  • Create: everything-is-suitable-admin/e2e/load-test.ts

Step 1: 创建性能测试

import { test, expect } from '@playwright/test';

test.describe('性能测试', () => {
  test('页面加载时间应小于2秒', async ({ page }) => {
    const startTime = Date.now();
    await page.goto('/dashboard');
    const loadTime = Date.now() - startTime;

    expect(loadTime).toBeLessThan(2000);
  });

  test('API响应时间应小于500ms', async ({ page }) => {
    await page.goto('/users');

    const responseTime = await page.evaluate(async () => {
      const start = performance.now();
      await fetch('/api/users');
      return performance.now() - start;
    });

    expect(responseTime).toBeLessThan(500);
  });

  test('应支持1000个并发用户', async ({ browser }) => {
    const context = await browser.newContext();
    const pages = await Promise.all(
      Array(1000).fill(0).map(() => context.newPage())
    );

    const results = await Promise.all(
      pages.map(async (page) => {
        const start = Date.now();
        await page.goto('/dashboard');
        return Date.now() - start;
      })
    );

    const avgLoadTime = results.reduce((a, b) => a + b) / results.length;
    expect(avgLoadTime).toBeLessThan(3000);
  });
});

Step 2: 创建负载测试脚本

import { chromium } from 'playwright';
import { performance } from 'perf_hooks';

async function runLoadTest() {
  const browser = await chromium.launch();
  const concurrentUsers = 100;
  const duration = 60; // seconds

  console.log(`Starting load test with ${concurrentUsers} concurrent users for ${duration}s`);

  const startTime = performance.now();
  const results = [];

  for (let i = 0; i < concurrentUsers; i++) {
    const context = await browser.newContext();
    const page = await context.newPage();

    const start = performance.now();
    await page.goto('http://localhost:5174/dashboard');
    const loadTime = performance.now() - start;

    results.push(loadTime);
    await context.close();
  }

  const endTime = performance.now();
  const totalTime = (endTime - startTime) / 1000;

  const avgLoadTime = results.reduce((a, b) => a + b) / results.length;
  const maxLoadTime = Math.max(...results);
  const minLoadTime = Math.min(...results);

  console.log(`Load test completed in ${totalTime.toFixed(2)}s`);
  console.log(`Average load time: ${avgLoadTime.toFixed(2)}ms`);
  console.log(`Max load time: ${maxLoadTime.toFixed(2)}ms`);
  console.log(`Min load time: ${minLoadTime.toFixed(2)}ms`);

  await browser.close();
}

runLoadTest().catch(console.error);

Step 3: 运行性能测试

Run: cd everything-is-suitable-admin && npx playwright test e2e/performance.spec.ts --reporter=list

Expected: 所有性能测试通过

Step 4: 运行负载测试

Run: cd everything-is-suitable-admin && node e2e/load-test.ts

Expected: 负载测试完成,性能指标达标

Step 5: 提交测试

git add everything-is-suitable-admin/e2e/
git commit -m "feat: add performance and load testing"

Task 13: 建立CI/CD质量门禁

Files:

  • Create: .github/workflows/test-quality-gate.yml
  • Modify: .github/workflows/coverage-check.yml

Step 1: 创建质量门禁工作流

name: Test Quality Gate

on:
  pull_request:
    branches: [main, develop]

jobs:
  test-quality-gate:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'

      - name: Install dependencies
        run: npm ci

      - name: Run API tests
        run: |
          cd everything-is-suitable-test/api
          python -m pytest tests/unit/ --cov=apitest --cov-report=xml
        continue-on-error: false

      - name: Check API coverage
        run: |
          cd everything-is-suitable-test/api
          coverage=$(python -c "import xml.etree.ElementTree as ET; tree = ET.parse('coverage.xml'); root = tree.getroot(); coverage = float(root.attrib.get('line-rate', 0)); print(f'{coverage * 100:.1f}')")
          if (( $(echo "$coverage < 90" | bc -l) )); then
            echo "API coverage ${coverage}% is below 90% threshold"
            exit 1
          fi

      - name: Run unit tests
        run: |
          cd everything-is-suitable-admin
          npm run test -- --coverage
        continue-on-error: false

      - name: Check unit test coverage
        run: |
          cd everything-is-suitable-admin
          coverage=$(cat coverage/coverage-summary.json | jq '.total.lines.pct')
          if (( $(echo "$coverage < 80" | bc -l) )); then
            echo "Unit test coverage ${coverage}% is below 80% threshold"
            exit 1
          fi

      - name: Run E2E tests
        run: |
          cd everything-is-suitable-admin
          npm run test:e2e:mock
        continue-on-error: false

      - name: Check E2E test pass rate
        run: |
          cd everything-is-suitable-admin
          total=$(npx playwright test --list | grep -c "test")
          passed=$(npx playwright test --reporter=json | jq '.stats.expected')
          pass_rate=$((passed * 100 / total))
          if (( $pass_rate < 60 )); then
            echo "E2E test pass rate ${pass_rate}% is below 60% threshold"
            exit 1
          fi

Step 2: 更新覆盖率检查工作流

name: Coverage Check

on:
  pull_request:
    branches: [main, develop]

jobs:
  coverage-check:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'

      - name: Setup Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.10'

      - name: Install dependencies
        run: |
          cd everything-is-suitable-admin
          npm ci
          cd ../everything-is-suitable-test/api
          pip install -r requirements.txt

      - name: Generate coverage reports
        run: |
          cd everything-is-suitable-admin
          npm run test -- --coverage
          cd ../everything-is-suitable-test/api
          python -m pytest tests/ --cov=apitest --cov-report=xml --cov-report=html

      - name: Upload coverage to Codecov
        uses: codecov/codecov-action@v3
        with:
          files: |
            everything-is-suitable-admin/coverage/coverage-final.json
            everything-is-suitable-test/api/coverage.xml
          flags: unittests
          name: codecov-umbrella

      - name: Comment PR with coverage
        uses: romeovs/lcov-reporter-action@v0.3.1
        with:
          lcov-file: ./everything-is-suitable-admin/coverage/lcov.info
          github-token: ${{ secrets.GITHUB_TOKEN }}

Step 3: 验证工作流配置

Run: cd everything-is-suitable-admin && act -l

Expected: 列出所有GitHub Actions工作流

Step 4: 提交工作流

git add .github/workflows/
git commit -m "feat: establish CI/CD quality gate with coverage thresholds"

验收标准

P0阶段验收

  • Playwright配置文件创建完成
  • E2E测试能够运行(至少1个测试通过)
  • 前端测试依赖模块全部创建
  • dayjs本地化导入问题修复

P1阶段验收

  • E2E测试通过率达到60%+
  • 前端单元测试通过率达到90%+
  • 测试数据冲突问题解决
  • Vitest与Playwright冲突解决

P2阶段验收

  • 测试环境完全隔离
  • 金融级测试场景覆盖完整
  • 性能测试和负载测试实现
  • CI/CD质量门禁建立

最终验收标准

  • 整体测试通过率≥95%
  • E2E测试通过率≥60%
  • API测试覆盖率≥90%
  • 前端单元测试覆盖率≥80%
  • 测试执行时间≤30分钟
  • CI/CD质量门禁生效

风险与缓解

风险1: E2E测试不稳定

缓解措施:

  • 增加重试机制
  • 使用更稳定的等待策略
  • 实现测试数据隔离

风险2: 测试执行时间过长

缓解措施:

  • 并行执行测试
  • 优化测试等待时间
  • 使用Mock服务减少网络依赖

风险3: 测试环境配置复杂

缓解措施:

  • 使用Docker容器化
  • 提供详细的配置文档
  • 实现环境自动化部署

时间估算

阶段 任务数 预计时间 优先级
P0 4 2-3小时 立即
P1 5 4-6小时 本周
P2 4 8-12小时 本月
总计 13 14-21小时 -

参考资料


计划完成日期: 2026-03-07 计划版本: 1.0 负责人: 测试团队 审核人: 技术负责人