Przeglądaj źródła

docs(epic-9): 创建 Story 9.2 - 银行卡管理功能测试

为银行卡管理功能创建完整的 E2E 测试 story 文档,包含:
- 添加、编辑、删除银行卡的测试场景
- 多张银行卡管理和默认银行卡设置测试
- Page Object 方法设计(addBankCard, editBankCard, deleteBankCard)
- 从 Story 9.1 学到的经验和最佳实践

Generated with [Claude Code](https://claude.com/claude-code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
yourname 1 tydzień temu
rodzic
commit
b739b962cc

+ 523 - 0
_bmad-output/implementation-artifacts/9-2-bankcard-tests.md

@@ -0,0 +1,523 @@
+# Story 9.2: 银行卡管理功能测试
+
+Status: ready-for-dev
+
+<!-- Note: Validation is optional. Run validate-create-story for quality check before dev-story. -->
+
+## Story
+
+作为测试开发者,
+我想要编写银行卡管理功能的测试,
+以便验证银行卡的添加、编辑、删除功能。
+
+## Acceptance Criteria
+
+**Given** 残疾人管理 Page Object 已存在
+**When** 编写银行卡管理测试
+**Then** 包含以下测试场景:
+
+1. **添加银行卡**
+   - 添加单张银行卡
+   - 验证银行卡信息保存正确
+   - 验证银行卡列表显示
+
+2. **编辑银行卡**
+   - 修改银行卡信息
+   - 验证修改后数据更新
+
+3. **删除银行卡**
+   - 删除单张银行卡
+   - 验证删除后列表更新
+
+4. **多张银行卡管理**
+   - 添加多张银行卡
+   - 验证列表顺序
+   - 验证默认银行卡设置
+
+## Tasks / Subtasks
+
+- [ ] **Task 1: 分析银行卡管理功能的 DOM 结构** (AC: #1, #2, #3, #4)
+  - [ ] Subtask 1.1: 在残疾人管理页面中定位银行卡管理区域
+  - [ ] Subtask 1.2: 分析添加银行卡按钮和表单结构
+  - [ ] Subtask 1.3: 分析银行卡列表展示结构
+  - [ ] Subtask 1.4: 分析编辑和删除按钮的选择器
+
+- [ ] **Task 2: 创建银行卡测试文件** (AC: #1, #2, #3, #4)
+  - [ ] Subtask 2.1: 创建 `web/tests/e2e/specs/admin/disability-person-bankcard.spec.ts`
+  - [ ] Subtask 2.2: 编写添加银行卡测试
+  - [ ] Subtask 2.3: 编写编辑银行卡测试
+  - [ ] Subtask 2.4: 编写删除银行卡测试
+  - [ ] Subtask 2.5: 编写多张银行卡管理测试
+
+- [ ] **Task 3: 更新 Page Object** (AC: #1, #2, #3, #4)
+  - [ ] Subtask 3.1: 添加银行卡管理相关方法到 `DisabilityPersonManagementPage`
+  - [ ] Subtask 3.2: 实现 `addBankCard()` 方法
+  - [ ] Subtask 3.3: 实现 `editBankCard()` 方法
+  - [ ] Subtask 3.4: 实现 `deleteBankCard()` 方法
+  - [ ] Subtask 3.5: 实现 `getBankCardList()` 方法用于验证
+
+- [ ] **Task 4: 运行测试并验证通过** (AC: #1, #2, #3, #4)
+  - [ ] Subtask 4.1: 使用 `pnpm test:e2e:chromium disability-person-bankcard` 运行测试
+  - [ ] Subtask 4.2: 修复发现的问题
+  - [ ] Subtask 4.3: 验证所有测试通过
+
+## Dev Notes
+
+### Epic 9 背景与目标
+
+**Epic 9: 残疾人管理完整 E2E 测试覆盖(含并行隔离)**
+
+为残疾人管理功能编写完整的、真正验证业务功能的 E2E 测试,并确保测试可以与未来的区域管理测试并行运行。
+
+**Epic 9 的 Story 依赖关系:**
+- Story 9.1:照片上传功能测试 ✅ Done
+- **Story 9.2(本故事)**:银行卡管理功能测试
+- Story 9.3:备注管理功能测试
+- Story 9.4:回访记录管理测试
+- Story 9.5:完整流程测试(CRUD)
+- Story 9.6:测试隔离与并行执行验证
+- Story 9.7:稳定性验证(10次连续运行)
+
+### 业务功能分析
+
+**银行卡管理功能概述:**
+
+残疾人管理系统中,每个残疾人可以关联多张银行卡,用于管理残疾人的工资发放、补贴发放等信息。
+
+**典型功能流程:**
+1. 在残疾人详情页或编辑对话框中找到银行卡管理区域
+2. 点击"添加银行卡"按钮打开添加表单
+3. 填写银行卡信息(银行名称、卡号、持卡人等)
+4. 保存后银行卡显示在列表中
+5. 可以编辑或删除已添加的银行卡
+6. 可以设置默认银行卡
+
+### 技术规范
+
+#### 现有 Page Object 结构
+
+**当前 Page Object 位置:** `web/tests/e2e/pages/admin/disability-person.page.ts`
+
+**从 Story 9.1 学到的模式:**
+- 使用 `data-testid` 选择器最稳定
+- 在对话框内操作时,使用 `form.getByLabel()` 限制范围
+- 表单提交使用 `form.handleSubmit()` 并配合 `console.debug` 调试验证错误
+
+#### 测试文件结构
+
+```
+web/tests/e2e/
+├── specs/
+│   └── admin/
+│       └── disability-person-bankcard.spec.ts  # 本测试文件(需创建)
+└── pages/
+    └── admin/
+        └── disability-person.page.ts  # Page Object(需扩展)
+```
+
+#### 测试用例设计
+
+**测试文件模板:**
+
+```typescript
+// web/tests/e2e/specs/admin/disability-person-bankcard.spec.ts
+import { test, expect } from '@playwright/test';
+import { DisabilityPersonManagementPage } from '../../pages/admin/disability-person.page';
+
+test.describe('残疾人管理 - 银行卡管理功能', () => {
+  let pageObject: DisabilityPersonManagementPage;
+  const TIMESTAMP = Date.now();
+  const UNIQUE_ID = `test_bankcard_${TIMESTAMP}`;
+
+  test.beforeEach(async ({ page }) => {
+    pageObject = new DisabilityPersonManagementPage(page);
+    await pageObject.goto();
+    // 打开创建对话框或编辑已有残疾人的对话框
+    await pageObject.openCreateDialog();
+    // 先填写基本信息才能添加银行卡
+    await pageObject.fillBasicInfo({
+      name: UNIQUE_ID,
+      idCard: `110101199001011234`, // 测试身份证号
+    });
+  });
+
+  test('应该成功添加单张银行卡', async ({ page }) => {
+    const bankCardData = {
+      bankName: '中国工商银行',
+      cardNumber: '6222021234567890123',
+      cardHolder: UNIQUE_ID,
+      isDefault: true,
+    };
+
+    await pageObject.addBankCard(bankCardData);
+
+    // 验证银行卡出现在列表中
+    const bankCardList = await pageObject.getBankCardList();
+    expect(bankCardList).toHaveLength(1);
+    expect(bankCardList[0]).toContain(bankCardData.bankName);
+    expect(bankCardList[0]).toContain(bankCardData.cardNumber.slice(-4)); // 验证后4位
+  });
+
+  test('应该成功编辑银行卡信息', async ({ page }) => {
+    // 先添加一张银行卡
+    const originalData = {
+      bankName: '中国工商银行',
+      cardNumber: '6222021234567890123',
+      cardHolder: UNIQUE_ID,
+    };
+    await pageObject.addBankCard(originalData);
+
+    // 编辑银行卡
+    const updatedData = {
+      bankName: '中国建设银行',
+      cardNumber: '6227001234567890123',
+      cardHolder: UNIQUE_ID,
+    };
+    await pageObject.editBankCard(0, updatedData);
+
+    // 验证更新后的信息
+    const bankCardList = await pageObject.getBankCardList();
+    expect(bankCardList[0]).toContain(updatedData.bankName);
+  });
+
+  test('应该成功删除银行卡', async ({ page }) => {
+    // 先添加一张银行卡
+    const bankCardData = {
+      bankName: '中国工商银行',
+      cardNumber: '6222021234567890123',
+      cardHolder: UNIQUE_ID,
+    };
+    await pageObject.addBankCard(bankCardData);
+
+    // 验证银行卡存在
+    let bankCardList = await pageObject.getBankCardList();
+    expect(bankCardList).toHaveLength(1);
+
+    // 删除银行卡
+    await pageObject.deleteBankCard(0);
+
+    // 验证银行卡已被删除
+    bankCardList = await pageObject.getBankCardList();
+    expect(bankCardList).toHaveLength(0);
+  });
+
+  test('应该支持添加多张银行卡', async ({ page }) => {
+    // 添加多张银行卡
+    await pageObject.addBankCard({
+      bankName: '中国工商银行',
+      cardNumber: '6222021234567890123',
+      cardHolder: UNIQUE_ID,
+    });
+    await pageObject.addBankCard({
+      bankName: '中国建设银行',
+      cardNumber: '6227001234567890123',
+      cardHolder: UNIQUE_ID,
+    });
+    await pageObject.addBankCard({
+      bankName: '中国农业银行',
+      cardNumber: '6228481234567890123',
+      cardHolder: UNIQUE_ID,
+    });
+
+    // 验证所有银行卡都显示
+    const bankCardList = await pageObject.getBankCardList();
+    expect(bankCardList).toHaveLength(3);
+
+    // 验证列表顺序(后添加的在前面还是后面,取决于业务逻辑)
+  });
+
+  test('应该能够设置默认银行卡', async ({ page }) => {
+    // 添加两张银行卡
+    await pageObject.addBankCard({
+      bankName: '中国工商银行',
+      cardNumber: '6222021234567890123',
+      cardHolder: UNIQUE_ID,
+      isDefault: true,
+    });
+    await pageObject.addBankCard({
+      bankName: '中国建设银行',
+      cardNumber: '6227001234567890123',
+      cardHolder: UNIQUE_ID,
+      isDefault: false,
+    });
+
+    // 验证默认银行卡标记
+    const defaultCard = await pageObject.getDefaultBankCard();
+    expect(defaultCard).toContain('中国工商银行');
+    expect(defaultCard).toContain('默认');
+  });
+});
+```
+
+### Page Object 方法设计
+
+**需要在 `DisabilityPersonManagementPage` 中添加的方法:**
+
+```typescript
+/**
+ * 添加银行卡
+ * @param bankCardData 银行卡数据
+ */
+async addBankCard(bankCardData: {
+  bankName: string;
+  cardNumber: string;
+  cardHolder: string;
+  isDefault?: boolean;
+}): Promise<void> {
+  // 1. 点击"添加银行卡"按钮
+  // 2. 等待银行卡表单对话框打开
+  // 3. 填写银行卡信息
+  // 4. 如果 isDefault 为 true,设置默认银行卡
+  // 5. 点击保存按钮
+  // 6. 等待对话框关闭
+}
+
+/**
+ * 编辑银行卡
+ * @param index 银行卡索引(第几张,从0开始)
+ * @param bankCardData 更新的银行卡数据
+ */
+async editBankCard(
+  index: number,
+  bankCardData: {
+    bankName?: string;
+    cardNumber?: string;
+    cardHolder?: string;
+    isDefault?: boolean;
+  }
+): Promise<void> {
+  // 1. 找到指定索引的银行卡的编辑按钮
+  // 2. 点击编辑按钮
+  // 3. 等待编辑对话框打开
+  // 4. 更新银行卡信息
+  // 5. 点击保存按钮
+  // 6. 等待对话框关闭
+}
+
+/**
+ * 删除银行卡
+ * @param index 银行卡索引(第几张,从0开始)
+ */
+async deleteBankCard(index: number): Promise<void> {
+  // 1. 找到指定索引的银行卡的删除按钮
+  // 2. 点击删除按钮
+  // 3. 等待确认对话框
+  // 4. 点击确认按钮
+  // 5. 等待删除完成
+}
+
+/**
+ * 获取银行卡列表
+ * @returns 银行卡信息数组
+ */
+async getBankCardList(): Promise<string[]> {
+  // 1. 定位银行卡列表容器
+  // 2. 获取所有银行卡项的文本内容
+  // 3. 返回银行卡信息数组
+  return []; // 实现时返回真实数据
+}
+
+/**
+ * 获取默认银行卡信息
+ * @returns 默认银行卡的文本内容
+ */
+async getDefaultBankCard(): Promise<string> {
+  // 1. 找到带有"默认"标记的银行卡
+  // 2. 返回其文本内容
+  return ''; // 实现时返回真实数据
+}
+```
+
+### 选择器策略
+
+**参考 Story 9.1 的经验:**
+
+1. **添加银行卡按钮:** 使用 `page.getByRole('button', { name: /添加.*银行卡/ })`
+2. **银行卡表单:** 在对话框内使用 `form.getByLabel()` 限制范围
+3. **银行卡列表项:** 使用 `data-testid` 或 `role="listitem"` 选择器
+4. **编辑/删除按钮:** 每个银行卡项内,使用索引或文本定位
+
+**示例:**
+```typescript
+// 点击编辑按钮(第1张银行卡的编辑按钮)
+const bankCards = page.locator('[data-testid="bankcard-item"]');
+await bankCards.nth(0).getByRole('button', { name: /编辑/ }).click();
+
+// 或使用更具体的选择器
+await page.locator('[data-testid="bankcard-item-0"] [data-testid="edit-button"]').click();
+```
+
+### 数据隔离策略
+
+**参考 Story 9.1 的数据隔离模式:**
+
+```typescript
+test.beforeEach(async ({ page }) => {
+  // 使用时间戳确保数据唯一
+  const timestamp = Date.now();
+  const uniqueId = `test_bankcard_${timestamp}`;
+
+  pageObject = new DisabilityPersonManagementPage(page);
+  await pageObject.goto();
+  await pageObject.openCreateDialog();
+});
+
+test.afterEach(async ({ page }) => {
+  // 清理测试数据(根据实际业务逻辑实现)
+  // 选项1: 使用测试账号 + 软删除
+  // 选项2: API 删除创建的数据
+  // 选项3: 数据库事务回滚
+});
+```
+
+### 表单验证调试
+
+**参考 Story 9.1 的调试经验:**
+
+如果表单提交失败,在表单 `onsubmit` 的第二个参数中加 `console.debug`:
+
+```typescript
+form.handleSubmit(handleSubmit, (errors) => console.debug('表单验证错误:', errors))
+```
+
+### 项目结构说明
+
+**测试目录组织:**
+- **specs/**: 测试用例文件
+  - 按功能模块组织
+  - `.spec.ts` 后缀(Playwright 约定)
+- **pages/**: Page Object 封装
+  - 页面元素和操作方法
+  - 复用性强
+
+**无冲突检测:**
+- 新增测试文件,不影响现有代码
+- Page Object 扩展是增强,非破坏性修改
+
+### Previous Story Intelligence
+
+**Story 9.1 (照片上传功能测试) 的关键经验:**
+
+1. **表单操作范围控制**
+   - 问题:`page.getByLabel()` 会匹配到对话框外的搜索框
+   - 解决:使用 `form.getByLabel()` 限制查找范围在表单内
+
+2. **按钮文本获取时机**
+   - 问题:点击后获取文本会超时
+   - 解决:在点击前获取按钮文本
+
+3. **预览元素验证**
+   - 问题:预览图片不在 photoCard 内部
+   - 解决:使用多种方式查找 + 容错逻辑
+
+4. **硬编码路径问题**
+   - 问题:使用绝对路径导致测试不可移植
+   - 解决:使用 `join(FIXTURES_IMAGES_DIR, fileName)` 相对路径
+
+5. **测试数据唯一性**
+   - 问题:数据冲突导致测试不稳定
+   - 解决:使用 `Math.random()` 或 `Date.now()` 生成更大范围的随机值
+
+6. **超时常量定义**
+   - 问题:硬编码超时值不一致
+   - 解决:定义 `TIMEOUTS` 常量统一管理
+
+### Git Intelligence Summary
+
+**Recent Commits (from git log):**
+- `732dc7f` - 完成 Story 9.1: 照片上传功能完整测试
+- `582a8b3` - 完成 Story 8.2: 区域列表查看测试(代码审查)
+- `2e64dd6` - 完成 Story 10.1: 创建订单管理 Page Object
+
+**Code Patterns Observed:**
+- 测试文件命名:`disability-person-{feature}.spec.ts`
+- Page Object 方法命名:动词+名词(如 `addBankCard`, `editBankCard`)
+- 使用 `describe` 组织测试套件,使用 `beforeEach` 初始化
+
+### TypeScript + Playwright 陷阱(关键)
+
+基于架构文档的陷阱章节(architecture.md 第 533-657 行):
+
+**陷阱 1: DOM 结构假设必须验证** ⚠️
+- 银行卡管理功能的 DOM 结构需要在实际页面中验证
+- 使用 `data-testid` 选择器(最稳定)
+- 避免依赖动态类名或不稳定的选择器
+
+**陷阱 2: 精确文本匹配**
+- 使用 `:text-is()` 进行精确文本匹配,而非 `:has-text()`
+- 示例:`page.getByRole('button', { name: /添加/ })` 可能匹配多个按钮
+
+**陷阱 3: 网络空闲等待**
+- 银行卡添加/编辑后可能需要等待网络请求完成
+- 考虑使用 `waitForLoadState('networkidle')` 或 `waitForSelector()`
+
+**陷阱 4: 避免使用 page.evaluate()**
+- 使用 Playwright API 而非 `page.evaluate()` 获取元素内容
+- 示例:使用 `element.textContent()` 而非 `page.evaluate(el => el.textContent, element)`
+
+### References
+
+**源文档引用:**
+- [Source: _bmad-output/planning-artifacts/epics.md#Epic-9-Story-9.2] - 完整业务需求
+- [Source: _bmad-output/planning-artifacts/architecture.md#Testing-Configuration] - 三层测试策略
+- [Source: docs/standards/e2e-radix-testing.md] - E2E 测试标准
+
+**前置 Story 参考:**
+- [Source: _bmad-output/implementation-artifacts/9-1-photo-upload-tests.md] - 照片上传测试实现经验
+
+**相关组件源码:**
+- [Source: web/tests/e2e/pages/admin/disability-person.page.ts] - 现有 Page Object
+- [Source: allin-packages/disability-person-management-ui] - 银行卡管理 UI 组件(需验证 DOM 结构)
+
+### Project Structure Notes
+
+**Monorepo 结构对齐:**
+- 测试位于 `web/tests/e2e/` 目录
+- 使用 pnpm workspace 协议引用 `@d8d/e2e-test-utils`
+- 与现有 Page Object 模式保持一致
+
+**文件组织:**
+- 新建测试 specs 文件:`web/tests/e2e/specs/admin/disability-person-bankcard.spec.ts`
+- 扩展现有 Page Object:`web/tests/e2e/pages/admin/disability-person.page.ts`
+
+**遵循的项目标准:**
+- 文件命名:`.spec.ts` 后缀(Playwright 测试)
+- 测试目录:`specs/` 分离,`pages/` Page Object
+- 使用 `@d8d/e2e-test-utils` 工具函数(如需要)
+- 遵循 `docs/standards/e2e-radix-testing.md` 标准
+
+## Dev Agent Record
+
+### Agent Model Used
+
+Claude Opus 4 (claude-opus-4-5-20251101)
+
+### Debug Log References
+
+### Completion Notes List
+
+1. ✅ 加载并分析 Epic 9 Story 9.2 需求(从 epics.md)
+2. ✅ 加载并分析架构文档(architecture.md)
+3. ✅ 分析前置 Story 9.1 的实现经验和问题
+4. ✅ 创建完整的 Story 9.2 文档,包含:
+   - Story 和验收标准
+   - 详细的任务分解
+   - Epic 9 背景和目标
+   - 业务功能分析
+   - 技术规范(Page Object 方法、选择器策略)
+   - 完整的测试用例模板
+   - Previous Story Intelligence(从 Story 9.1 学到的经验)
+   - Git Intelligence Summary
+   - TypeScript + Playwright 陷阱警告
+   - 完整的参考文档列表
+
+### File List
+
+**创建的文件:**
+- `_bmad-output/implementation-artifacts/9-2-bankcard-tests.md` - 本 story 文档
+
+**需要创建的文件(待开发时):**
+- `web/tests/e2e/specs/admin/disability-person-bankcard.spec.ts` - 银行卡测试文件
+
+**需要修改的文件(待开发时):**
+- `web/tests/e2e/pages/admin/disability-person.page.ts` - 添加银行卡管理相关方法