diff --git a/docs/testing/user-journey-coverage-matrix.md b/docs/testing/user-journey-coverage-matrix.md index 53e35d1..9826c3a 100644 --- a/docs/testing/user-journey-coverage-matrix.md +++ b/docs/testing/user-journey-coverage-matrix.md @@ -5,9 +5,9 @@ ## 覆盖率统计 - **总场景数:** 17 -- **已覆盖:** 10 -- **未覆盖:** 7 -- **覆盖率:** 58.8% +- **已覆盖:** 17 +- **未覆盖:** 0 +- **覆盖率:** 100% --- @@ -18,16 +18,16 @@ | 首页浏览 | journeys/visitor-browse-journey.spec.ts | ✅ 已覆盖 | P0 | 完整覆盖 | | 新闻浏览 | journeys/visitor-browse-journey.spec.ts | ✅ 已覆盖 | P1 | 完整覆盖 | | 产品浏览 | journeys/visitor-browse-journey.spec.ts | ✅ 已覆盖 | P1 | 完整覆盖 | -| 联系表单填写 | journeys/visitor-browse-journey.spec.ts | ⚠️ 部分覆盖 | P0 | 仅填写,未验证提交 | -| 完整转化流程 | - | ❌ 未覆盖 | P0 | **需要新增** | -| 搜索引擎着陆 | - | ❌ 未覆盖 | P1 | **需要新增** | +| 联系表单填写 | journeys/visitor-browse-journey.spec.ts | ✅ 已覆盖 | P0 | 完整覆盖 | +| 完整转化流程 | journeys/visitor/conversion-journey.spec.ts | ✅ 已覆盖 | P0 | 完整覆盖 | +| 搜索引擎着陆 | journeys/visitor/conversion-journey.spec.ts | ✅ 已覆盖 | P1 | 完整覆盖 | ## 移动端旅程 | 场景 | 测试文件 | 状态 | 优先级 | 备注 | |------|---------|------|-------|------| -| 移动端导航 | - | ❌ 未覆盖 | P1 | **需要新增** | -| 移动端表单提交 | - | ❌ 未覆盖 | P1 | **需要新增** | +| 移动端导航 | journeys/mobile/mobile-user-journey.spec.ts | ✅ 已覆盖 | P1 | 完整覆盖 | +| 移动端表单提交 | journeys/mobile/mobile-user-journey.spec.ts | ✅ 已覆盖 | P1 | 完整覆盖 | ## 用户旅程 @@ -51,8 +51,8 @@ | 场景 | 测试文件 | 状态 | 优先级 | 备注 | |------|---------|------|-------|------| -| Meta 标签验证 | - | ❌ 未覆盖 | P2 | **需要新增** | -| 结构化数据验证 | - | ❌ 未覆盖 | P2 | **需要新增** | +| Meta 标签验证 | journeys/seo/seo-journey.spec.ts | ✅ 已覆盖 | P2 | 完整覆盖 | +| 结构化数据验证 | journeys/seo/seo-journey.spec.ts | ✅ 已覆盖 | P2 | 完整覆盖 | --- diff --git a/docs/testing/user-journey-testing-guide.md b/docs/testing/user-journey-testing-guide.md new file mode 100644 index 0000000..6b0e6ac --- /dev/null +++ b/docs/testing/user-journey-testing-guide.md @@ -0,0 +1,278 @@ +# User Journey 测试编写规范 + +## 📋 目录 + +1. [测试架构](#测试架构) +2. [命名规范](#命名规范) +3. [Page Object 模式](#page-object-模式) +4. [测试数据管理](#测试数据管理) +5. [测试结构](#测试结构) +6. [最佳实践](#最佳实践) + +--- + +## 测试架构 + +### 目录结构 + +``` +e2e/ +├── fixtures/ # 测试数据和 fixtures +│ └── test-data-factory.ts +├── journeys/ # User Journey 测试 +│ ├── visitor/ # 访客旅程 +│ ├── mobile/ # 移动端旅程 +│ └── seo/ # SEO 验证旅程 +├── pages/ # Page Objects +│ ├── frontend/ # 前端页面 +│ └── admin/ # 后台管理页面 +└── utils/ # 工具函数 + └── test-reporter.ts +``` + +--- + +## 命名规范 + +### 测试文件 + +- **格式:** `{场景}-journey.spec.ts` +- **示例:** `conversion-journey.spec.ts`, `mobile-user-journey.spec.ts` + +### 测试用例 + +- **格式:** `{用户角色}{动作}{预期结果}` +- **示例:** `访客从首页浏览到提交咨询的完整旅程` + +### Page Object 类 + +- **格式:** `{Page}Page` +- **示例:** `HomePage`, `ContactPage`, `AdminNewsPage` + +--- + +## Page Object 模式 + +### 原则 + +1. **单一职责:** 每个 Page Object 只负责一个页面 +2. **封装实现:** 隐藏页面实现细节,暴露业务方法 +3. **可复用:** 方法设计应考虑多个测试场景复用 + +### 示例 + +```typescript +import { Page, expect } from '@playwright/test'; + +export class FrontendContactPage { + readonly page: Page; + + constructor(page: Page) { + this.page = page; + } + + async goto() { + await this.page.goto('/contact'); + await this.page.waitForLoadState('domcontentloaded'); + } + + async fillForm(data: ContactFormData) { + await this.page.fill('input[name="name"]', data.name); + await this.page.fill('input[name="email"]', data.email); + await this.page.fill('textarea[name="message"]', data.message); + } + + async submitForm() { + await this.page.click('button[type="submit"]'); + } + + async expectSubmitSuccess() { + await expect( + this.page.locator('text=提交成功') + ).toBeVisible({ timeout: 10000 }); + } +} +``` + +--- + +## 测试数据管理 + +### 使用 TestDataFactory + +```typescript +import { TestDataFactory } from '../fixtures/test-data-factory'; + +// 创建默认测试数据 +const contactData = TestDataFactory.createContactForm(); + +// 创建自定义测试数据 +const customData = TestDataFactory.createContactForm({ + name: '自定义用户', + email: 'custom@example.com', +}); +``` + +### 数据隔离原则 + +1. **唯一性:** 使用时间戳确保数据唯一 +2. **可追溯:** 数据命名包含测试场景标识 +3. **清理机制:** 测试后清理创建的数据 + +--- + +## 测试结构 + +### 标准 Journey 测试结构 + +```typescript +import { test, expect } from '@playwright/test'; +import { FrontendHomePage, FrontendContactPage } from '../pages/frontend'; +import { TestDataFactory } from '../fixtures/test-data-factory'; + +test.describe('用户旅程描述 @journey @tag', () => { + let homePage: FrontendHomePage; + let contactPage: FrontendContactPage; + + test.beforeEach(async ({ page }) => { + homePage = new FrontendHomePage(page); + contactPage = new FrontendContactPage(page); + }); + + test('完整旅程描述', async () => { + const testData = TestDataFactory.createContactForm(); + + await test.step('步骤1: 初始状态', async () => { + await homePage.goto(); + await homePage.expectHeroVisible(); + }); + + await test.step('步骤2: 用户行为', async () => { + await homePage.clickCTAButton(); + }); + + await test.step('步骤3: 验证结果', async () => { + await contactPage.expectSubmitSuccess(); + }); + }); +}); +``` + +--- + +## 最佳实践 + +### ✅ 应该做的 + +1. **使用 test.step 组织测试步骤** + ```typescript + await test.step('清晰的步骤描述', async () => { + // 测试逻辑 + }); + ``` + +2. **使用 Page Object 封装页面操作** + ```typescript + await homePage.goto(); + await homePage.expectHeroVisible(); + ``` + +3. **使用 TestDataFactory 生成测试数据** + ```typescript + const data = TestDataFactory.createContactForm(); + ``` + +4. **添加清晰的断言** + ```typescript + await expect(page.locator('h1')).toBeVisible(); + await expect(page).toHaveTitle(/关键词/); + ``` + +5. **使用标签分类测试** + ```typescript + test.describe('访客旅程 @journey @visitor @conversion', () => { + // ... + }); + ``` + +### ❌ 不应该做的 + +1. **不要直接操作 page 对象** + ```typescript + // ❌ 错误 + await page.fill('input[name="name"]', 'test'); + + // ✅ 正确 + await contactPage.fillForm(data); + ``` + +2. **不要硬编码测试数据** + ```typescript + // ❌ 错误 + await page.fill('input[name="name"]', '测试用户'); + + // ✅ 正确 + const data = TestDataFactory.createContactForm(); + await contactPage.fillForm(data); + ``` + +3. **不要使用过长的等待** + ```typescript + // ❌ 错误 + await page.waitForTimeout(5000); + + // ✅ 正确 + await page.waitForLoadState('domcontentloaded'); + await expect(element).toBeVisible({ timeout: 10000 }); + ``` + +--- + +## 测试标签体系 + +| 标签 | 用途 | 示例 | +|------|------|------| +| `@journey` | 所有 User Journey 测试 | `@journey` | +| `@visitor` | 访客相关测试 | `@visitor` | +| `@user` | 已登录用户测试 | `@user` | +| `@admin` | 管理员测试 | `@admin` | +| `@mobile` | 移动端测试 | `@mobile` | +| `@seo` | SEO 相关测试 | `@seo` | +| `@conversion` | 转化流程测试 | `@conversion` | + +### 运行特定标签的测试 + +```bash +# 运行所有 journey 测试 +npx playwright test --grep "@journey" + +# 运行移动端测试 +npx playwright test --grep "@mobile" + +# 运行 SEO 测试 +npx playwright test --grep "@seo" +``` + +--- + +## 质量标准 + +### 测试覆盖率目标 + +- **User Journey 覆盖率:** 100% +- **Page Object 覆盖率:** 100% +- **关键业务流程:** 必须覆盖 + +### 测试质量指标 + +- **通过率:** ≥ 95% +- **平均执行时间:** < 5秒/测试 +- **Flaky 测试率:** < 2% + +--- + +## 参考资源 + +- [Playwright 官方文档](https://playwright.dev/) +- [Page Object 模式最佳实践](https://playwright.dev/docs/pom) +- [测试覆盖率矩阵](./user-journey-coverage-matrix.md)