浏览代码

docs(epic-8): 创建 Story 8.1 - 区域管理 Page Object

创建区域管理 Page Object 的完整 story 文档,为 Epic B(区域管理
业务测试)建立基础。

主要内容包括:
- 完整的用户故事和验收标准(BDD 格式)
- Page Object 设计模式参考(基于 disability-person.page.ts)
- 区域管理功能分析和 DOM 结构探索指南
- 可用的 e2e-test-utils 工具(selectCascade, selectProvinceCity)
- TypeScript 类型定义建议
- 测试隔离策略
- TypeScript + Playwright 陷阱预防

Sprint 状态更新:
- Epic 8: backlog → in-progress
- Story 8-1: backlog → ready-for-dev

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

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
yourname 6 天之前
父节点
当前提交
c62fac99e4

+ 474 - 0
_bmad-output/implementation-artifacts/8-1-region-page-object.md

@@ -0,0 +1,474 @@
+# Story 8.1: 创建区域管理 Page Object
+
+Status: ready-for-dev
+
+<!-- Note: Validation is optional. Run validate-create-story for quality check before dev-story. -->
+
+## Story
+
+作为测试开发者,
+我想要创建区域管理的 Page Object,
+以便组织区域管理相关的页面元素和操作。
+
+## Acceptance Criteria
+
+**Given** Epic 2 的 Page Object 模式已验证
+**When** 创建 `web/tests/e2e/pages/admin/region-management.page.ts`
+**Then** 定义区域列表页面的选择器和操作方法
+**And** 定义添加区域对话框的选择器和操作方法
+**And** 定义编辑区域对话框的选择器和操作方法
+**And** 遵循现有 Page Object 设计模式
+**And** 所有方法有完整的 TypeScript 类型定义
+
+**Given** 区域管理 Page Object 已创建
+**When** 编写区域列表查看测试用例
+**Then** 验证区域列表按预期加载
+**And** 验证区域数据的正确展示(名称、层级、状态等)
+**And** 验证分页功能(如适用)
+**And** 验证搜索功能(如适用)
+**And** 测试在真实浏览器中通过
+
+## Tasks / Subtasks
+
+- [ ] 创建 RegionManagementPage 类基础结构 (AC: #)
+  - [ ] 定义页面选择器(页面标题、新增按钮、表格等)
+  - [ ] 实现构造函数和初始化逻辑
+- [ ] 实现页面导航方法 (AC: #)
+  - [ ] 实现 goto() 方法导航到区域管理页面
+  - [ ] 实现 expectToBeVisible() 验证页面元素可见性
+- [ ] 实现区域列表相关方法 (AC: #)
+  - [ ] 定义列表表格、搜索输入框、搜索按钮等选择器
+  - [ ] 实现 searchByName() 搜索方法
+  - [ ] 实现 regionExists() 验证区域是否存在
+- [ ] 实现添加区域对话框方法 (AC: #)
+  - [ ] 定义添加区域对话框选择器
+  - [ ] 实现 openAddDialog() 打开添加对话框
+  - [ ] 实现 fillRegionForm() 填写区域表单
+  - [ ] 实现 submitForm() 提交表单
+- [ ] 实现编辑区域对话框方法 (AC: #)
+  - [ ] 定义编辑区域对话框选择器
+  - [ ] 实现 openEditDialog() 打开编辑对话框
+  - [ ] 实现区域信息编辑方法
+- [ ] 实现删除区域相关方法 (AC: #)
+  - [ ] 定义删除确认对话框选择器
+  - [ ] 实现 confirmDelete() 确认删除
+  - [ ] 实现 cancelDelete() 取消删除
+- [ ] 添加 TypeScript 类型定义 (AC: #)
+  - [ ] 定义 RegionData 接口
+  - [ ] 定义所有方法的参数和返回值类型
+- [ ] 遵循现有 Page Object 设计模式 (AC: #)
+  - [ ] 参考 disability-person.page.ts 的代码风格
+  - [ ] 使用一致的命名约定
+  - [ ] 使用 e2e-test-utils 工具函数
+
+## Dev Notes
+
+### Epic 8 背景和上下文
+
+**Epic 8: 区域管理 E2E 测试 (Epic B - 业务测试 Epic)**
+
+这是 Epic B(区域管理业务测试)的第一个 Story。Epic 8 的目标是为区域管理功能建立完整的 E2E 测试覆盖,验证省/市/区/街道的添加、编辑、删除和级联选择功能。
+
+**依赖:**
+- Epic 1: ✅ 已完成(Select 工具基础框架)
+- Epic 2: ✅ 已完成(Select 工具在真实 E2E 测试中验证)
+- Epic 3: ✅ 已完成(文件上传工具、级联选择工具)
+
+**业务分组:**
+- Epic A(残疾人管理)- 已完成基础工具验证
+- Epic B(区域管理)- 当前目标
+- Epic C(e2e-test-utils 包维护)- 支持性任务
+
+### Page Object 设计模式参考
+
+基于 `web/tests/e2e/pages/admin/disability-person.page.ts` 的成功经验,区域管理 Page Object 应遵循以下设计模式:
+
+**1. 类结构模式:**
+```typescript
+export class RegionManagementPage {
+  readonly page: Page;
+  // 页面级选择器
+  readonly pageTitle: Locator;
+  readonly addButton: Locator;
+  // ...其他选择器
+
+  constructor(page: Page) {
+    this.page = page;
+    // 初始化所有选择器
+  }
+
+  // 导航方法
+  async goto() { }
+
+  // 表单操作方法
+  async openAddDialog() { }
+  async fillRegionForm(data: RegionData) { }
+  async submitForm() { }
+
+  // 列表操作方法
+  async searchByName(name: string) { }
+  async regionExists(name: string): Promise<boolean> { }
+}
+```
+
+**2. 选择器定义模式:**
+- 使用 `page.getByRole()` 优先(无障碍属性)
+- 使用 `page.getByLabel()` 表单字段
+- 使用 `page.getByText()` 或 `page.locator()` 作为兜底
+- 所有选择器定义为 `readonly Locator` 类型
+
+**3. 方法命名约定:**
+- 导航方法: `goto()`
+- 断言方法: `expectToBeVisible()`, `expectSuccess()`
+- 操作方法: 动词开头,如 `openAddDialog()`, `fillRegionForm()`
+- 查询方法: 返回 boolean,如 `regionExists()`
+
+### 区域管理功能分析
+
+基于现有项目架构和残疾人管理功能的经验,区域管理页面可能包含以下元素:
+
+**页面元素(需要验证实际 DOM 结构):**
+- 页面标题: "区域管理"
+- 新增按钮: "新增区域" 或类似文本
+- 搜索输入框: 占位符可能为 "搜索区域名称"
+- 搜索按钮: "搜索" 或图标按钮
+- 数据表格: 显示区域列表
+  - 列: 区域名称、区域代码、层级(省/市/区/街道)、状态、操作
+
+**表单字段(需要验证实际 DOM 结构):**
+- 区域名称: 文本输入框
+- 区域代码: 文本输入框
+- 区域层级: Radix UI Select(省/市/区/街道)
+- 父级区域: 级联 Radix UI Select(选择上级区域)
+- 状态: Radix UI Select 或 Switch(启用/禁用)
+- 备注: 文本域
+
+**对话框元素(需要验证实际 DOM 结构):**
+- 添加区域对话框
+- 编辑区域对话框
+- 删除确认对话框
+
+### 可用的 e2e-test-utils 工具
+
+根据 `packages/e2e-test-utils/src/index.ts`,以下工具可用于区域管理测试:
+
+```typescript
+// Radix UI Select 工具
+import { selectRadixOption, selectRadixOptionAsync } from '@d8d/e2e-test-utils';
+
+// 省市区级联选择工具(非常适合区域管理)
+import { selectCascade, selectProvinceCity } from '@d8d/e2e-test-utils';
+
+// 文件上传工具(如果区域有图标上传)
+import { uploadFileToField } from '@d8d/e2e-test-utils';
+```
+
+**关键工具 - selectCascade:**
+```typescript
+/**
+ * 级联选择工具
+ * 用于省市区四级级联选择
+ *
+ * @param page - Playwright Page 对象
+ * @param selections - 选择项数组 [{label: '省份', value: '广东省'}, ...]
+ * @param options - 配置选项
+ */
+async function selectCascade(
+  page: Page,
+  selections: Array<{label: string, value: string}>,
+  options?: CascadeSelectOptions
+): Promise<void>
+```
+
+### 项目结构笔记
+
+**目标文件位置:**
+```
+web/tests/e2e/pages/admin/region-management.page.ts
+```
+
+**导入路径:**
+```typescript
+import { Page, Locator } from '@playwright/test';
+import { selectRadixOption, selectCascade } from '@d8d/e2e-test-utils';
+```
+
+**测试文件位置(后续 Story):**
+```
+web/tests/e2e/specs/admin/region-management.spec.ts
+```
+
+### TypeScript 类型定义
+
+推荐定义以下类型:
+
+```typescript
+/**
+ * 区域数据接口
+ */
+export interface RegionData {
+  /** 区域名称 */
+  name: string;
+  /** 区域代码 */
+  code?: string;
+  /** 区域层级(省/市/区/街道) */
+  level: 'province' | 'city' | 'district' | 'street';
+  /** 父级区域名称 */
+  parentRegion?: string;
+  /** 状态 */
+  status?: 'enabled' | 'disabled';
+  /** 备注 */
+  remark?: string;
+}
+
+/**
+ * 表单提交结果
+ */
+export interface FormSubmitResult {
+  success: boolean;
+  message?: string;
+  errorMessage?: string;
+}
+```
+
+### DOM 结构探索步骤
+
+在实现 Page Object 之前,需要探索实际 DOM 结构:
+
+1. **启动开发服务器**(如果未运行):
+   ```bash
+   cd web && pnpm dev
+   ```
+
+2. **导航到区域管理页面**:
+   - URL: `/admin/regions` 或类似路径
+
+3. **使用 Playwright Inspector 或浏览器开发者工具**:
+   - 检查页面元素的 `data-testid`, `role`, `aria-label`
+   - 记录表单字段的选择器策略
+   - 验证 Radix UI Select 的 DOM 结构
+
+4. **记录发现**:
+   - 对话框触发器的选择器
+   - 表单字段的 label 文本
+   - 表格的 CSS 类或结构
+   - Toast 消息的选择器
+
+### 与残疾人管理 Page Object 的主要差异
+
+| 方面 | 残疾人管理 | 区域管理 |
+|------|-----------|----------|
+| 主要实体 | 残疾人个人 | 区域(省市区街道) |
+| 树形结构 | 否 | 是(四级层级) |
+| 级联选择 | 省市区三级 | 可能需要四级 |
+| 图片上传 | 身份证、残疾证照片 | 可能无或区域图标 |
+| 动态列表 | 银行卡、备注、回访 | 可能无 |
+| 删除约束 | 较少 | 有子级区域不能删除 |
+
+### 测试隔离策略
+
+为支持未来的并行执行(Epic 9),考虑测试数据隔离:
+
+```typescript
+/**
+ * 生成唯一区域名称(用于测试隔离)
+ */
+function generateUniqueRegionName(prefix: string = '测试区域'): string {
+  const timestamp = Date.now();
+  const random = Math.floor(Math.random() * 1000);
+  return `${prefix}_${timestamp}_${random}`;
+}
+```
+
+### 常见陷阱和注意事项
+
+**基于 Architecture.md 的 TypeScript + Playwright 陷阱:**
+
+1. **DOM 结构假设必须验证 ⚠️**
+   - 不能基于理想模型开发选择器
+   - 必须在真实组件上验证 DOM 结构
+   - Radix UI Select 的 DOM 可能随版本变化
+
+2. **选择器策略优先级:**
+   - `data-testid` → `aria-label` + role → text content
+   - 推荐在组件上添加 `data-testid`
+
+3. **精确文本匹配:**
+   - 使用 `:text-is()` 而非 `:has-text()`
+   - 避免部分匹配导致误选
+
+4. **超时配置:**
+   - 使用 `DEFAULT_TIMEOUTS` 常量
+   - 网络空闲等待使用用户自定义超时值
+
+5. **避免使用 page.evaluate():**
+   - 优先使用 Playwright API
+   - 使用 `element.textContent()` 而非 `page.evaluate()`
+
+## Dev Agent Record
+
+### Agent Model Used
+
+Claude Opus 4 (claude-opus-4-5-20251101)
+
+### Debug Log References
+
+### Completion Notes List
+
+### File List
+
+## Project Context Reference
+
+### 关键项目规则摘要
+
+**技术栈:**
+- Playwright 1.55.0 - E2E 测试框架
+- TypeScript 5.9.3 - 严格模式
+- @d8d/e2e-test-utils - 内部测试工具包
+
+**测试命令:**
+```bash
+# 运行所有 E2E 测试
+pnpm test:e2e:chromium
+
+# 运行单个测试文件
+pnpm test:e2e:chromium region-management
+
+# 快速失败模式(调试时使用)
+timeout 60 pnpm test:e2e:chromium region-management
+```
+
+**包管理:**
+- 使用 pnpm(版本 10.18.3)
+- 内部包使用 workspace 协议: `@d8d/e2e-test-utils@workspace:*`
+
+**命名约定:**
+- 文件名: kebab-case (如: `region-management.page.ts`)
+- 类名: PascalCase (如: `RegionManagementPage`)
+- 函数/变量: camelCase (如: `goto()`, `searchByName()`)
+
+### 必须遵循的架构决策
+
+**来自 Architecture.md 的关键决策:**
+
+1. **选择器策略(混合策略优先级):**
+   - `data-testid` - 最高优先级
+   - `aria-label` + role - 无障碍标准
+   - Text content + role - 兜底方案
+
+2. **错误处理策略:**
+   - 使用 `E2ETestError` 类(来自 e2e-test-utils)
+   - 包含完整 ErrorContext
+
+3. **类型系统:**
+   - 所有方法必须有完整的 TypeScript 类型定义
+   - 禁止使用 `any` 类型
+
+4. **测试基础设施:**
+   - 测试文件位置: `web/tests/e2e/specs/admin/`
+   - Page Object 位置: `web/tests/e2e/pages/admin/`
+   - Fixtures 位置: `web/tests/e2e/fixtures/`
+
+### TypeScript + Playwright 陷阱预防
+
+**来自 Architecture.md "TypeScript + Playwright 常见陷阱" 部分:**
+
+⚠️ **DOM 结构假设必须验证**
+- 不能基于理想模型开发选择器
+- 必须在真实组件上验证 DOM 结构
+- 单元测试无法发现真实 DOM 问题
+
+✅ **正确做法:**
+```typescript
+// 使用 Playwright API 而非 page.evaluate()
+const text = await element.textContent();
+
+// 使用精确文本匹配
+page.locator(`.option:text-is("广东省")`)
+
+// 使用 data-testid 作为首选
+page.getByTestId('region-name-input')
+```
+
+❌ **避免:**
+```typescript
+// 避免使用 page.evaluate()
+const text = await page.evaluate(el => el.textContent, element);
+
+// 避免部分文本匹配
+page.locator(`.option:has-text("广东省")`)
+```
+
+### 代码质量检查清单
+
+基于 Architecture.md 的实现检查清单:
+
+**代码质量:**
+- [ ] 所有导出方法都有完整的 JSDoc
+- [ ] 内部方法使用 `@internal` 标记
+- [ ] 错误处理提供友好消息
+
+**选择器策略:**
+- [ ] 优先使用 `data-testid` 或 `getByRole()`
+- [ ] 文本选择器使用精确匹配
+- [ ] DOM 结构基于真实组件验证
+
+**配置和超时:**
+- [ ] 超时值使用合理配置
+- [ ] 网络操作使用 `waitForLoadState('networkidle')`
+
+**DOM 操作:**
+- [ ] 避免使用 `page.evaluate()`
+- [ ] 优先使用 Playwright API
+
+### 参考文档位置
+
+| 文档 | 路径 |
+|------|------|
+| PRD | `_bmad-output/planning-artifacts/prd.md` |
+| Architecture | `_bmad-output/planning-artifacts/architecture.md` |
+| Epics | `_bmad-output/planning-artifacts/epics.md` |
+| Project Context | `_bmad-output/project-context.md` |
+| 参考Page Object | `web/tests/e2e/pages/admin/disability-person.page.ts` |
+| e2e-test-utils | `packages/e2e-test-utils/src/index.ts` |
+
+### 相关 Epic 和 Story
+
+**前置 Epic:**
+- Epic 1: ✅ 完成 - Select 工具基础框架
+- Epic 2: ✅ 完成 - Select 工具在真实 E2E 测试中验证
+- Epic 3: ✅ 完成 - 文件上传工具、级联选择工具
+
+**当前 Epic (Epic 8):**
+- Story 8.1: 📝 当前 - 创建区域管理 Page Object
+- Story 8.2: ⏳ 待开始 - 编写区域列表查看测试
+- Story 8.3: ⏳ 待开始 - 编写添加区域测试
+- Story 8.4: ⏳ 待开始 - 编写编辑区域测试
+- Story 8.5: ⏳ 待开始 - 编写删除区域测试
+- Story 8.6: ⏳ 待开始 - 编写级联选择完整流程测试
+
+**后续 Epic:**
+- Epic 9: 🔄 进行中 - 残疾人管理完整 E2E 测试覆盖
+
+## Completion Status
+
+**Story ID:** 8.1
+**Story Key:** 8-1-region-page-object
+**Epic:** Epic 8 - 区域管理 E2E 测试 (Epic B)
+**Status:** ready-for-dev
+
+**交付物:**
+- [x] Story 文档创建完成
+- [ ] RegionManagementPage 类实现
+- [ ] TypeScript 类型定义
+- [ ] DOM 结构探索和验证
+- [ ] 代码审查和测试
+
+**下一步操作:**
+1. 运行 `/bmad:bmm:workflows:dev-story` 开始实现
+2. 探索区域管理页面的实际 DOM 结构
+3. 实现 RegionManagementPage 类
+4. 编写基础测试验证 Page Object 可用性
+
+**Ultimate context engine analysis completed - comprehensive developer guide created**
+

+ 3 - 3
_bmad-output/implementation-artifacts/sprint-status.yaml

@@ -110,8 +110,8 @@ development_status:
   # 业务分组: Epic B(业务测试 Epic)
   # 范围: 省/市/区/街道的添加、编辑、删除和级联选择功能
   # 依赖: Epic 9 完成(确保测试隔离和并行执行策略已验证)
-  epic-8: backlog
-  8-1-region-page-object: backlog        # 创建区域管理 Page Object
+  epic-8: in-progress
+  8-1-region-page-object: ready-for-dev        # 创建区域管理 Page Object
   8-2-region-list-test: backlog          # 编写区域列表查看测试
   8-3-add-region-test: backlog           # 编写添加区域测试
   8-4-edit-region-test: backlog          # 编写编辑区域测试
@@ -129,7 +129,7 @@ development_status:
   # 优先级: HIGH - 阻塞 Epic B(区域管理测试)
   # 详情参见: _bmad-output/implementation-artifacts/epic-9-plan.md
   epic-9: in-progress
-  9-1-photo-upload-tests: ready-for-dev  # 照片上传功能完整测试(真实上传、多文件、格式验证)
+  9-1-photo-upload-tests: in-progress       # 照片上传功能完整测试(真实上传、多文件、格式验证)
   9-2-bankcard-tests: backlog            # 银行卡管理功能测试(添加、编辑、删除)
   9-3-note-tests: backlog                # 备注管理功能测试(添加、修改、删除)
   9-4-visit-tests: backlog               # 回访记录管理测试(创建、查看、编辑)