# Story 8.1: 创建区域管理 Page Object Status: ready-for-dev ## 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 { } } ``` **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 ``` ### 项目结构笔记 **目标文件位置:** ``` 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**