a146006e42
- 统一测试框架:从Python+Pytest迁移到TypeScript+Playwright - 建立五层测试体系:Smoke、Regression、Performance、Security、Accessibility - 设计分层CI/CD流程:快速验证、完整测试、全面测试 - 制定四阶段迁移计划:评估准备、核心迁移、专项补充、CI集成 - 金融级质量保障:满足安全、合规、可靠性要求
27 KiB
27 KiB
Novalon Website E2E测试统一方案设计
创建时间: 2026-02-28
目标: 统一E2E测试框架,从Python+Pytest迁移到TypeScript+Playwright,建立金融级网站完整的测试体系
一、背景与目标
当前状态
- 两套测试框架并存:
- TypeScript + Playwright (
e2e/目录) - Python + Pytest (
e2e-tests/目录)
- TypeScript + Playwright (
- 维护成本高: 需要维护两套技术栈
- 测试覆盖不完整: 部分测试场景缺失或重复
核心目标
- 统一技术栈: 完全迁移到Playwright,与Next.js项目技术栈一致
- 全面测试覆盖: 建立业务流程、性能、安全、可访问性完整测试体系
- 金融级质量: 满足金融行业对安全、合规、可靠性的高要求
- CI/CD集成: 建立分层测试流程,平衡速度和覆盖率
二、技术选型决策
选择: TypeScript + Playwright
决策理由:
- ✅ 与项目技术栈一致(Next.js + TypeScript)
- ✅ 可共享类型定义,更好的类型安全
- ✅ Playwright功能强大,支持多浏览器、移动端、视觉测试
- ✅ 社区活跃,文档完善,生态成熟
- ✅ 原生支持并行执行,性能优异
放弃: Python + Pytest
- ❌ 与项目技术栈不一致,增加维护成本
- ❌ 无法共享类型定义,降低开发效率
- ❌ 需要维护两套依赖和环境
三、整体架构设计
3.1 分层架构
┌─────────────────────────────────────────────────┐
│ 报告与监控层 (Reports & Monitoring) │
│ HTML报告 | JSON/JUnit报告 | 性能指标 | 趋势分析 │
└─────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────┐
│ 测试用例层 (Test Cases) │
│ Smoke | Regression | Performance | Security │
│ Accessibility | Visual | Mobile │
└─────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────┐
│ 页面对象层 (Page Objects) │
│ BasePage | HomePage | ContactPage | Products │
│ Components (Header, Footer, Form) │
└─────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────┐
│ 基础设施层 (Infrastructure) │
│ Playwright配置 | 测试数据 | 工具库 | Fixtures │
└─────────────────────────────────────────────────┘
3.2 目录结构
e2e/
├── src/
│ ├── fixtures/ # 测试夹具
│ │ ├── base.fixture.ts # 基础fixture
│ │ ├── a11y.fixture.ts # 可访问性fixture
│ │ └── auth.fixture.ts # 认证fixture(如需要)
│ ├── pages/ # 页面对象
│ │ ├── BasePage.ts # 基础页面
│ │ ├── HomePage.ts # 首页
│ │ ├── ContactPage.ts # 联系页面
│ │ ├── ProductsPage.ts # 产品页面
│ │ ├── ServicesPage.ts # 服务页面
│ │ ├── AboutPage.ts # 关于页面
│ │ ├── CasesPage.ts # 案例页面
│ │ ├── SolutionsPage.ts # 解决方案页面
│ │ └── NewsPage.ts # 新闻页面
│ ├── tests/ # 测试用例
│ │ ├── smoke/ # 冒烟测试
│ │ ├── regression/ # 回归测试
│ │ ├── performance/ # 性能测试
│ │ ├── security/ # 安全测试
│ │ ├── accessibility/ # 可访问性测试
│ │ ├── visual/ # 视觉回归测试
│ │ ├── mobile/ # 移动端测试
│ │ ├── responsive/ # 响应式测试
│ │ └── error-handling/ # 错误处理测试
│ ├── types/ # 类型定义
│ │ └── index.ts # 共享类型
│ └── utils/ # 工具库
│ ├── PerformanceMonitor.ts # 性能监控
│ ├── TestDataGenerator.ts # 测试数据生成
│ ├── devices.ts # 设备配置
│ └── helpers.ts # 辅助函数
├── test-data/ # 测试数据文件
│ ├── contact-form.json # 联系表单数据
│ ├── products.json # 产品数据
│ └── performance-budgets.json # 性能预算
├── playwright.config.ts # Playwright配置
├── package.json # 依赖管理
└── tsconfig.json # TypeScript配置
四、测试分层策略
4.1 测试金字塔
┌─────────┐
│ Security│ (每周/发布前)
│ Access │ 10-15分钟
└─────────┘
┌───────────────┐
│ Performance │ (每日/发布前)
│ 10-20分钟 │
└───────────────┘
┌─────────────────────┐
│ Regression │ (PR合并前/每日)
│ 15-30分钟 │
└─────────────────────┘
┌───────────────────────────┐
│ Smoke │ (每次提交)
│ < 5分钟 │
└───────────────────────────┘
4.2 各层测试详解
Level 1: Smoke Tests(冒烟测试)
执行频率: 每次代码提交
执行时间: < 5分钟
标签: @smoke
覆盖范围:
- ✅ 所有关键页面可访问性(首页、产品、服务、联系)
- ✅ 核心导航功能
- ✅ 关键表单提交(联系表单)
- ✅ 页面基本渲染(无JS错误)
测试用例:
test.describe('Smoke Tests @smoke', () => {
test('首页可访问', async ({ page }) => {
await homePage.goto();
await expect(page).toHaveTitle(/Novalon/);
});
test('导航功能正常', async ({ page }) => {
await homePage.goto();
await homePage.navigateTo('产品');
await expect(page).toHaveURL(/products/);
});
test('联系表单可提交', async ({ page }) => {
await contactPage.goto();
await contactPage.fillForm({
name: '测试用户',
email: 'test@example.com',
message: '测试消息'
});
await contactPage.submit();
await expect(contactPage.successMessage).toBeVisible();
});
});
Level 2: Regression Tests(回归测试)
执行频率: PR合并前、每日构建
执行时间: 15-30分钟
标签: @regression
覆盖范围:
- ✅ 所有业务流程完整测试
- ✅ 表单验证(必填项、格式校验、错误提示)
- ✅ 页面间跳转和数据传递
- ✅ 响应式布局(桌面/平板/移动端)
- ✅ 多浏览器兼容性
测试用例:
test.describe('Contact Form Regression @regression', () => {
test('表单验证 - 必填项', async ({ page }) => {
await contactPage.goto();
await contactPage.submit();
await expect(contactPage.nameError).toHaveText('请输入姓名');
await expect(contactPage.emailError).toHaveText('请输入邮箱');
});
test('表单验证 - 邮箱格式', async ({ page }) => {
await contactPage.goto();
await contactPage.fillEmail('invalid-email');
await contactPage.submit();
await expect(contactPage.emailError).toHaveText('邮箱格式不正确');
});
test('表单提交成功', async ({ page }) => {
await contactPage.goto();
await contactPage.fillForm(testData.validContact);
await contactPage.submit();
await expect(contactPage.successMessage).toBeVisible();
});
});
Level 3: Performance Tests(性能测试)
执行频率: 每日构建、发布前
执行时间: 10-20分钟
标签: @performance
覆盖范围:
- ✅ 页面加载时间(FCP、LCP、TTI)
- ✅ 资源大小优化(图片、JS、CSS)
- ✅ 网络请求数量和瀑布流
- ✅ 核心交互响应时间
- ✅ 性能预算验证
性能预算:
{
"performanceBudgets": {
"home": {
"loadTime": 3000,
"fcP": 1500,
"lcp": 2500,
"tti": 3500,
"totalSize": 1500000
},
"contact": {
"loadTime": 2500,
"fcp": 1200,
"lcp": 2000,
"tti": 3000,
"totalSize": 1200000
}
}
}
测试用例:
test.describe('Performance Tests @performance', () => {
test('首页性能预算', async ({ page }) => {
const metrics = await performanceMonitor.measurePageLoad(page, '/');
expect(metrics.loadTime).toBeLessThan(budgets.home.loadTime);
expect(metrics.fcp).toBeLessThan(budgets.home.fcp);
expect(metrics.lcp).toBeLessThan(budgets.home.lcp);
});
test('图片优化验证', async ({ page }) => {
await page.goto('/');
const images = await page.$$eval('img', imgs =>
imgs.map(img => ({
src: img.src,
naturalWidth: img.naturalWidth,
naturalHeight: img.naturalHeight,
displayWidth: img.width,
displayHeight: img.height
}))
);
for (const img of images) {
expect(img.naturalWidth).toBeLessThanOrEqual(img.displayWidth * 2);
}
});
});
Level 4: Security Tests(安全测试)
执行频率: 每周、发布前
执行时间: 10-15分钟
标签: @security
覆盖范围:
- ✅ XSS漏洞检测(表单输入)
- ✅ CSRF保护验证
- ✅ 安全头验证(CSP、HSTS等)
- ✅ 敏感数据处理(联系表单数据加密)
- ✅ HTTPS强制跳转
测试用例:
test.describe('Security Tests @security', () => {
test('XSS防护 - 联系表单', async ({ page }) => {
await contactPage.goto();
await contactPage.fillForm({
name: '<script>alert("XSS")</script>',
email: 'test@example.com',
message: '<img src=x onerror=alert("XSS")>'
});
await contactPage.submit();
const pageContent = await page.content();
expect(pageContent).not.toContain('<script>alert');
expect(pageContent).not.toContain('onerror=alert');
});
test('安全头验证', async ({ page }) => {
const response = await page.goto('/');
const headers = response.headers();
expect(headers['x-frame-options']).toBeDefined();
expect(headers['x-content-type-options']).toBe('nosniff');
expect(headers['strict-transport-security']).toBeDefined();
});
test('HTTPS强制跳转', async ({ page }) => {
await page.goto('http://localhost:3000');
expect(page.url()).toMatch(/^https:/);
});
});
Level 5: Accessibility Tests(可访问性测试)
执行频率: 每周、发布前
执行时间: 10-15分钟
标签: @accessibility
覆盖范围:
- ✅ WCAG 2.1 AA标准合规
- ✅ 键盘导航测试
- ✅ 屏幕阅读器兼容性
- ✅ 颜色对比度验证
- ✅ 表单标签和ARIA属性
测试用例:
test.describe('Accessibility Tests @accessibility', () => {
test('WCAG 2.1 AA合规 - 首页', async ({ page }) => {
await page.goto('/');
const accessibilityScanResults = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa'])
.analyze();
expect(accessibilityScanResults.violations).toEqual([]);
});
test('键盘导航', async ({ page }) => {
await page.goto('/');
await page.keyboard.press('Tab');
await expect(page.locator(':focus')).toBeVisible();
for (let i = 0; i < 10; i++) {
await page.keyboard.press('Tab');
const focusedElement = page.locator(':focus');
await expect(focusedElement).toBeVisible();
}
});
test('颜色对比度', async ({ page }) => {
await page.goto('/');
const contrastResults = await new AxeBuilder({ page })
.withRules(['color-contrast'])
.analyze();
expect(contrastResults.violations).toEqual([]);
});
});
五、迁移计划与实施步骤
5.1 迁移阶段划分
阶段1: 评估与准备(1-2天)
任务清单:
- 分析现有Python测试用例,识别核心测试场景
- 评估现有Playwright测试的完整性和质量
- 确定需要迁移、重构、新增的测试用例
- 制定详细的测试用例清单和优先级
- 准备测试数据和测试环境配置
产出物:
- 测试用例迁移清单(Excel/Markdown)
- 测试覆盖率分析报告
- 风险评估文档
阶段2: 核心测试迁移(3-5天)
任务清单:
- 迁移smoke测试(确保基础功能覆盖)
- 页面可访问性测试
- 导航功能测试
- 关键表单测试
- 迁移regression测试(核心业务流程)
- 联系表单完整测试
- 产品展示测试
- 服务流程测试
- 建立完整的页面对象模型
- 配置测试数据管理
产出物:
- 完整的页面对象模型
- Smoke测试套件
- Regression测试套件
- 测试数据文件
阶段3: 专项测试补充(3-4天)
任务清单:
- 补充performance测试(性能监控)
- 页面加载性能测试
- 资源优化验证
- 性能预算测试
- 补充security测试(安全合规)
- XSS漏洞测试
- 安全头验证
- HTTPS验证
- 补充accessibility测试(无障碍访问)
- WCAG合规测试
- 键盘导航测试
- 颜色对比度测试
- 补充visual regression测试(视觉回归)
- 关键页面截图对比
- 组件视觉测试
产出物:
- Performance测试套件
- Security测试套件
- Accessibility测试套件
- Visual regression测试套件
阶段4: CI/CD集成与清理(2-3天)
任务清单:
- 配置Woodpecker CI流程
- 快速验证流程(每次提交)
- 完整测试流程(PR合并前)
- 定时测试流程(每日构建)
- 建立测试报告和通知机制
- HTML报告生成
- 失败通知配置
- 性能告警配置
- 删除Python测试框架(
e2e-tests/目录) - 更新项目文档
- README更新
- 测试指南编写
- CI/CD文档
产出物:
- Woodpecker CI配置文件
- 测试报告系统
- 更新的项目文档
5.2 迁移优先级矩阵
| 测试类型 | 优先级 | 迁移阶段 | 说明 |
|---|---|---|---|
| 页面可访问性 | 高 | 阶段2 | 核心功能,必须覆盖 |
| 导航功能 | 高 | 阶段2 | 核心功能,必须覆盖 |
| 联系表单 | 高 | 阶段2 | 核心业务,必须覆盖 |
| 表单验证 | 高 | 阶段2 | 核心业务,必须覆盖 |
| 响应式测试 | 中 | 阶段2 | 重要但可后续优化 |
| 性能测试 | 中 | 阶段3 | 专项测试,独立实施 |
| 安全测试 | 中 | 阶段3 | 专项测试,独立实施 |
| 可访问性测试 | 中 | 阶段3 | 专项测试,独立实施 |
| 视觉回归 | 低 | 阶段3 | 可选,根据需求决定 |
5.3 风险控制
风险识别:
- 测试覆盖不足: 迁移过程中可能遗漏测试场景
- 测试不稳定: 新测试可能存在时序问题或环境依赖
- 性能下降: 测试执行时间可能超出预期
- CI流程中断: 配置错误可能导致CI失败
缓解措施:
- 保留Python测试: 在Playwright测试完全覆盖前,保留Python测试作为备份
- 渐进式迁移: 分阶段迁移,每个阶段完成后进行验证
- 并行执行: 在迁移期间,两套框架并行运行,对比测试结果
- 回滚机制: 准备回滚方案,出现问题可快速恢复
六、CI/CD集成方案
6.1 Woodpecker CI流程设计
Level 1: 快速验证(每次提交触发)
# .woodpecker.yml
when:
event: [push, pull_request]
steps:
- name: install-dependencies
image: node:20
commands:
- cd e2e
- npm ci
- name: smoke-tests
image: mcr.microsoft.com/playwright:v1.48.0-jammy
environment:
CI: true
commands:
- cd e2e
- npx playwright test --grep @smoke --project=chromium
when:
status: success
- name: upload-results
image: plugins/s3
settings:
bucket: test-results
source: e2e/test-results/**/*
target: /${CI_BUILD_NUMBER}/
when:
status: [success, failure]
执行时间: < 5分钟
触发条件: 每次代码提交
失败处理: 阻止合并,发送通知
Level 2: 完整测试(PR合并前触发)
when:
event: pull_request
branch: main
steps:
- name: regression-tests
image: mcr.microsoft.com/playwright:v1.48.0-jammy
environment:
CI: true
commands:
- cd e2e
- npx playwright test --grep @regression
# 执行时间:15-30分钟
- name: performance-tests
image: mcr.microsoft.com/playwright:v1.48.0-jammy
commands:
- cd e2e
- npx playwright test --grep @performance
# 执行时间:10-20分钟
- name: generate-report
image: node:20
commands:
- cd e2e
- npx playwright show-report --host 0.0.0.0
执行时间: 25-50分钟
触发条件: PR合并前
失败处理: 阻止合并,生成详细报告
Level 3: 全面测试(定时执行)
when:
event: cron
cron: "0 2 * * *" # 每天凌晨2点
steps:
- name: full-test-suite
image: mcr.microsoft.com/playwright:v1.48.0-jammy
environment:
CI: true
commands:
- cd e2e
- npx playwright test
# 执行完整测试套件
- name: security-tests
commands:
- npx playwright test --grep @security
- name: accessibility-tests
commands:
- npx playwright test --grep @accessibility
- name: generate-trend-report
commands:
- node scripts/generate-trend-report.js
执行时间: 1-2小时
触发条件: 每日凌晨2点
失败处理: 发送通知,生成趋势报告
6.2 测试报告与通知
报告生成
HTML报告:
// playwright.config.ts
reporter: [
['html', {
outputFolder: 'test-results/html-report',
open: 'never'
}],
['json', {
outputFile: 'test-results/results.json'
}],
['junit', {
outputFile: 'test-results/junit.xml'
}],
['list']
]
报告内容:
- 测试执行摘要(通过/失败/跳过)
- 失败测试详情(截图、视频、trace)
- 性能指标趋势
- 测试覆盖率统计
通知机制
失败通知:
- name: notify-failure
image: plugins/slack
settings:
webhook: ${SLACK_WEBHOOK}
channel: testing
template: |
❌ E2E测试失败
构建号: {{build.number}}
分支: {{build.branch}}
提交: {{build.commit}}
失败测试: {{failures}}
报告链接: {{report_url}}
when:
status: failure
性能告警:
// scripts/check-performance-regression.ts
const currentMetrics = await getCurrentMetrics();
const baselineMetrics = await getBaselineMetrics();
if (currentMetrics.loadTime > baselineMetrics.loadTime * 1.2) {
await sendAlert({
type: 'performance_regression',
message: `页面加载时间退化20%: ${currentMetrics.loadTime}ms vs ${baselineMetrics.loadTime}ms`
});
}
6.3 质量门禁
PR合并条件
必须满足:
- ✅ Smoke测试100%通过
- ✅ Regression测试通过率 ≥ 95%
- ✅ 无新增严重缺陷
- ✅ 性能指标在预算范围内
可选条件:
- ⚠️ Performance测试通过率 ≥ 90%
- ⚠️ 测试覆盖率 ≥ 80%
质量指标跟踪
// scripts/quality-metrics.ts
interface QualityMetrics {
smokePassRate: number; // 目标: 100%
regressionPassRate: number; // 目标: ≥ 95%
performanceBudget: number; // 目标: 100%符合预算
testCoverage: number; // 目标: ≥ 80%
avgExecutionTime: number; // 目标: < 30分钟
}
async function checkQualityGate(metrics: QualityMetrics): Promise<boolean> {
return (
metrics.smokePassRate === 100 &&
metrics.regressionPassRate >= 95 &&
metrics.performanceBudget === 100 &&
metrics.testCoverage >= 80
);
}
七、测试数据管理
7.1 测试数据策略
数据源分类:
- 静态测试数据: JSON/YAML文件,存储在
test-data/目录 - 动态测试数据: 运行时生成,使用TestDataGenerator
- 环境特定数据: 通过环境变量配置
7.2 数据文件示例
联系表单测试数据 (test-data/contact-form.json):
{
"valid": {
"name": "张三",
"email": "zhangsan@example.com",
"phone": "13800138000",
"company": "测试公司",
"message": "这是一条测试消息"
},
"invalid": {
"emptyName": {
"name": "",
"email": "test@example.com",
"message": "测试消息",
"expectedError": "请输入姓名"
},
"invalidEmail": {
"name": "测试用户",
"email": "invalid-email",
"message": "测试消息",
"expectedError": "邮箱格式不正确"
}
}
}
7.3 数据生成器
// src/utils/TestDataGenerator.ts
export class TestDataGenerator {
static generateContactForm(overrides = {}) {
return {
name: `测试用户_${Date.now()}`,
email: `test_${Date.now()}@example.com`,
phone: this.generatePhone(),
company: '测试公司',
message: `测试消息_${Date.now()}`,
...overrides
};
}
static generatePhone() {
return `1${Math.floor(Math.random() * 9000000000 + 1000000000)}`;
}
}
八、最佳实践与规范
8.1 测试编写规范
命名规范:
// ✅ 好的命名
test('联系表单 - 提交成功', async ({ page }) => {});
test('联系表单 - 邮箱格式验证', async ({ page }) => {});
// ❌ 不好的命名
test('test1', async ({ page }) => {});
test('表单测试', async ({ page }) => {});
测试结构:
test.describe('功能模块 @tag', () => {
test.beforeEach(async ({ page }) => {
// 前置条件
});
test('测试场景 - 具体描述', async ({ page }) => {
// Arrange: 准备测试数据
const testData = TestDataGenerator.generateContactForm();
// Act: 执行测试操作
await contactPage.fillForm(testData);
await contactPage.submit();
// Assert: 验证结果
await expect(contactPage.successMessage).toBeVisible();
});
test.afterEach(async ({ page }) => {
// 清理工作
});
});
8.2 页面对象模式
BasePage示例:
// src/pages/BasePage.ts
export abstract class BasePage {
constructor(protected page: Page) {}
async goto(path: string = '/') {
await this.page.goto(path);
await this.waitForPageLoad();
}
async waitForPageLoad() {
await this.page.waitForLoadState('networkidle');
}
async screenshot(name: string) {
await this.page.screenshot({
path: `screenshots/${name}.png`,
fullPage: true
});
}
async waitForElement(selector: string, timeout = 30000) {
await this.page.waitForSelector(selector, { timeout });
}
}
ContactPage示例:
// src/pages/ContactPage.ts
export class ContactPage extends BasePage {
readonly nameInput = this.page.locator('input[name="name"]');
readonly emailInput = this.page.locator('input[name="email"]');
readonly messageInput = this.page.locator('textarea[name="message"]');
readonly submitButton = this.page.locator('button[type="submit"]');
readonly successMessage = this.page.locator('.success-message');
readonly nameError = this.page.locator('.error-name');
readonly emailError = this.page.locator('.error-email');
async goto() {
await super.goto('/contact');
}
async fillForm(data: ContactFormData) {
await this.nameInput.fill(data.name);
await this.emailInput.fill(data.email);
if (data.phone) await this.page.locator('input[name="phone"]').fill(data.phone);
if (data.company) await this.page.locator('input[name="company"]').fill(data.company);
await this.messageInput.fill(data.message);
}
async submit() {
await this.submitButton.click();
await this.page.waitForLoadState('networkidle');
}
}
8.3 断言最佳实践
// ✅ 好的断言
await expect(page).toHaveURL(/contact/);
await expect(element).toBeVisible();
await expect(element).toHaveText('预期文本');
await expect(element).toHaveAttribute('href', '/expected-path');
// ❌ 不好的断言
expect(await element.isVisible()).toBe(true);
expect(await element.textContent()).toContain('文本');
九、监控与持续改进
9.1 测试质量指标
关键指标:
- 测试通过率
- 测试覆盖率
- 平均执行时间
- Flaky测试率
- 缺陷逃逸率
9.2 持续改进计划
定期评估:
- 每周回顾测试结果,识别不稳定测试
- 每月分析测试覆盖率,补充缺失场景
- 每季度评估测试策略,优化测试金字塔
优化方向:
- 减少测试执行时间
- 提高测试稳定性
- 增强测试覆盖率
- 改进测试报告
十、总结
核心价值
- 统一技术栈: 降低维护成本,提高开发效率
- 全面覆盖: 业务、性能、安全、可访问性全覆盖
- 金融级质量: 满足金融行业高标准要求
- CI/CD集成: 自动化测试流程,快速反馈
预期收益
- 开发效率: 减少30%的测试维护时间
- 质量保障: 缺陷逃逸率降低50%
- 发布速度: 测试反馈时间缩短60%
- 团队协作: 统一技术栈,降低学习成本
下一步行动
- ✅ 完成设计文档评审
- ⏭️ 开始阶段1:评估与准备
- ⏭️ 按计划推进迁移工作
- ⏭️ 建立CI/CD流程
- ⏭️ 持续优化和改进
文档版本: v1.0
最后更新: 2026-02-28
维护者: 张翔