Parcourir la source

docs(epic-13): 创建 Story 13.3 并更新 Story 13.2 探索结果

- 创建 Story 13.3: 后台添加人员 → 人才小程序验证
- 更新 Story 13.2 Playwright MCP 探索结果(发现小程序列表缓存问题)
- 更新 sprint-status: Story 13.3 状态为 ready-for-dev
- 添加 order-edit-sync.spec.ts 测试文件

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 il y a 3 jours
Parent
commit
c380467fe3

+ 62 - 23
_bmad-output/implementation-artifacts/13-2-order-edit-sync.md

@@ -304,47 +304,86 @@ Story 13.6: 首页看板数据联动专项测试
 
 ### Playwright MCP 探索结果(任务 0 完成后更新)
 
-> **注意**: 本部分需要在任务 0 完成后填充。以下是预期探索的内容模板。
+> **完成时间**: 2026-01-14
+> **探索方法**: 使用 Playwright MCP 工具手动验证完整测试流程
 
 **测试目标**: 验证后台编辑订单后,企业小程序能否正确显示更新后的订单信息
 
-**测试结果**: (待任务 0 完成后填写
+**测试结果**: ✅ 成功(有重要发现
 
 **数据同步时效性**:
-- 实际同步时间: (待任务 0 完成后填写
+- 实际同步时间: < 1秒(详情页 API 请求立即返回最新数据
 - 要求: ≤ 10 秒
-- 结论: (待任务 0 完成后填写)
+- 结论: 远超预期,数据同步速度非常快
 
 **后台选择器(已验证可用)**:
-| 功能 | data-testid |
+| 功能 | 选择器/方法 |
 |------|-------------|
-| 编辑按钮 | (待任务 0 完成后填写) |
-| 订单名称输入框 | (待任务 0 完成后填写) |
-| 预计开始日期 | (待任务 0 完成后填写) |
-| 平台选择器 | `platform-selector-edit` |
-| 公司选择器 | `company-selector-edit` |
-| 保存提交按钮 | (待任务 0 完成后填写) |
+| 订单菜单触发器 | `order-menu-trigger-{orderId}` (例如: `order-menu-trigger-721`) |
+| 编辑按钮 | `edit-order-button-{orderId}` (例如: `edit-order-button-721`) |
+| 订单名称输入框 | `getByRole('textbox', { name: '订单名称' })` |
+| 预计开始日期 | `getByRole('textbox', { name: '预计开始日期' })` |
+| 平台选择器 | 与创建相同(待确认 data-testid) |
+| 公司选择器 | 与创建相同(待确认 data-testid) |
+| 订单状态选择器 | `getByRole('combobox', { name: '订单状态' })` |
+| 工作状态选择器 | `getByRole('combobox', { name: '工作状态' })` |
+| 更新按钮 | `order-update-submit-button` |
+| 成功提示 | `getByRole('listitem').filter({ hasText: '订单更新成功' })` |
 
 **小程序选择器(已验证可用)**:
-| 功能 | data-testid |
+| 功能 | 选择器/方法 |
 |------|-------------|
 | 手机号输入框 | `mini-phone-input` |
 | 登录按钮 | `mini-login-button` |
-| 订单列表项 | (待任务 0 完成后填写) |
-| 订单详情 | (待任务 0 完成后填写) |
+| 订单列表项 | 通过文本内容定位订单名称 |
+| 查看详情按钮 | `getByText('查看详情')` |
+| 订单名称(详情页) | 通过文本内容验证 |
 
 **测试流程(已验证)**:
-1. 后台登录 → 导航到订单管理 → 点击已有订单的编辑按钮
-2. 修改订单信息(名称、日期等)→ 提交保存
-3. 验证后台列表显示更新后的信息
-4. 小程序登录 → 导航到订单列表 → 验证订单信息已更新
-5. 点击订单详情 → 验证详情页信息完整
-
-**关键发现(任务 0 完成后更新)**:
-- (待任务 0 完成后填写)
+1. **后台编辑流程**:
+   - 登录后台 → 导航到订单管理 (`/admin/orders`)
+   - 点击订单行的"打开菜单"按钮 → 菜单展开
+   - 点击"编辑"菜单项 → 编辑对话框打开
+   - 修改订单信息(名称、日期等)
+   - 点击"更新"按钮 → 对话框关闭,显示"订单更新成功"提示
+   - 后台列表立即显示更新后的订单名称
+
+2. **小程序验证流程**:
+   - 小程序登录 (`13800001111` / `password123`)
+   - 导航到订单列表 → **列表显示缓存数据(旧名称)**
+   - 点击"查看详情" → **详情页显示最新数据(新名称)** ✅
+   - 返回列表 → 列表仍然显示旧数据
+
+**关键发现(2026-01-14 探索)**:
+
+1. **⚠️ 重要发现:小程序列表页缓存问题**
+   - 后台编辑订单后,小程序**列表页显示缓存数据**(旧订单名称)
+   - 小程序**详情页显示最新数据**(新订单名称)✅
+   - **原因**: 列表页可能使用本地缓存或未在返回时刷新数据
+   - **影响**: E2E 测试不能只验证列表页,需要验证详情页或实现列表刷新机制
+
+2. **数据同步验证策略**:
+   - **方案 A(推荐)**: 点击订单详情验证编辑后数据(详情页 API 返回最新数据)
+   - **方案 B**: 实现下拉刷新或重新导航到列表页
+   - **方案 C**: 等待并验证列表自动更新(当前不可行)
+
+3. **公司关联确认(与 Story 13.1 一致)**:
+   - 测试用户 `13800001111` 关联公司: "测试公司_1768346782396"
+   - 测试必须选择正确公司的订单,否则小程序看不到数据
+
+4. **编辑订单选择器模式**:
+   - 菜单触发器: `order-menu-trigger-{orderId}`
+   - 编辑按钮: `edit-order-button-{orderId}`
+   - 更新按钮: `order-update-submit-button`
 
 **待解决问题**:
-- (待任务 0 完成后填写)
+- **小程序列表缓存**: 小程序列表页未在返回时刷新数据,需要实现刷新机制或在测试中验证详情页
+
+**建议的 E2E 测试验证点**:
+1. 后台编辑成功(验证 Toast 提示)
+2. 后台列表显示更新(验证列表项文本)
+3. 小程序详情页显示更新(点击详情验证订单名称)✅ **主要验证点**
+4. 数据同步时效性(详情页加载时间 < 1秒)
 
 ### Previous Story Intelligence: Story 13.1
 

+ 563 - 0
_bmad-output/implementation-artifacts/13-3-person-add-sync.md

@@ -0,0 +1,563 @@
+# Story 13.3: 后台添加人员 → 人才小程序验证
+
+Status: ready-for-dev
+
+<!-- Note: Validation is optional. Run validate-create-story for quality check before dev-story. -->
+
+## Story
+
+作为测试开发者,
+我想要验证后台添加残疾人到订单后人才小程序的数据同步,
+以便确保跨端人员关联的正确性和时效性。
+
+## Acceptance Criteria
+
+### AC1: 后台添加人员到订单流程
+**Given** 后台订单管理和残疾人管理功能已完成(Epic 9、Epic 10)
+**When** 测试者在管理后台为订单添加残疾人
+**Then** 测试应验证以下功能:
+- 使用 OrderManagementPage 打开订单的人员管理对话框
+- 从残疾人列表中选择一个或多个残疾人
+- 设置人员的入职日期和薪资(如需要)
+- 验证人员添加成功,订单的人员列表显示新添加的人员
+- 获取添加的人员 ID 和关键信息
+
+### AC2: 人才小程序数据同步验证
+**Given** 管理后台已为订单添加残疾人
+**When** 人才用户登录小程序
+**Then** 测试应验证以下功能:
+- 使用 TalentMiniPage 登录人才小程序
+- 验证与该残疾人关联的订单显示在"我的订单"列表中
+- 验证订单信息完整(订单名称、公司、状态等)
+- 验证数据同步在合理时间内完成(≤ 10 秒)
+
+### AC3: 多人员添加同步验证
+**Given** 订单支持添加多个残疾人
+**When** 执行批量添加人员操作
+**Then** 测试应验证以下场景:
+- 为订单添加多个残疾人后验证小程序同步
+- 验证每个残疾人都能在小程序中看到关联的订单
+- 验证人员的工作状态显示正确
+
+### AC4: 多 Page 对象管理
+**Given** 测试需要同时操作后台和小程序
+**When** 执行跨端测试
+**Then** 测试应满足以下要求:
+- 使用独立的 Page 对象管理后台和人才小程序
+- 正确管理多个 browser context 或 page
+- 确保测试之间不会相互干扰
+
+### AC5: 测试数据隔离和清理
+**Given** 跨端测试涉及多个系统
+**When** 执行测试
+**Then** 测试应遵循以下策略:
+- 测试前置条件:创建测试订单和测试残疾人(用于关联)
+- 添加人员操作不影响其他订单和残疾人数据
+- 测试后可选择清理或恢复测试数据(移除人员关联或删除测试数据)
+- 验证清理后小程序不再显示该订单
+
+### AC6: 数据同步时效性验证
+**Given** 网络延迟和系统负载
+**When** 添加人员后查询小程序
+**Then** 测试应验证以下场景:
+- 在正常情况下,数据应在 5 秒内同步
+- 支持轮询等待机制(最多等待 10 秒)
+- 验证超时情况下的错误处理
+
+### AC7: 代码质量标准
+**Given** 遵循项目测试规范
+**When** 编写测试代码
+**Then** 代码应符合以下标准:
+- 使用 TIMEOUTS 常量定义超时
+- 使用 data-testid 选择器(优先级高于文本选择器)
+- 测试文件命名:`person-add-sync.spec.ts`
+- 完整的测试描述和注释
+- TypeScript 类型安全
+- 通过 `pnpm typecheck` 类型检查
+
+## Tasks / Subtasks
+
+> **测试开发流程(增强的 TDD + 持续验证)**: 本 Story 采用 Playwright MCP 持续验证的测试开发流程。
+>
+> **核心原则**:
+> 1. **即时验证**: 在开发过程中立即使用 Playwright MCP 验证,不等到专门的 E2E Story
+> 2. **持续反馈**: 每完成一个功能模块立即验证,快速发现问题
+> 3. **减少返工**: 早期发现问题可以减少后期返工成本
+>
+> **Playwright MCP 使用时机**:
+> - ✅ 任务 0: 探索页面结构、验证流程可行性
+> - ✅ 任务 1-3: 每写一个测试就用 MCP 验证选择器、页面结构
+> - ✅ 任务 4-5: 每实现一个功能就用 MCP 验证是否正常工作
+> - ✅ 任务 6: 重构后用 MCP 确保功能未破坏
+>
+> **任务 0 必须在任务 1-6 之前完成**,它为后续任务提供验证过的选择器和基础模式。
+
+### 阶段 1: EXPLORE - Playwright MCP 探索(RED 之前)
+
+- [ ] **任务 0: Playwright MCP 探索验证**
+  - [ ] 0.1 启动子代理使用 Playwright MCP 手动验证完整测试流程
+    - 后台:创建订单 → 打开人员管理对话框 → 选择残疾人 → 添加人员
+    - 小程序:登录 → 导航到"我的订单" → 验证订单显示
+  - [ ] 0.2 记录验证的选择器(优先 data-testid,避免文本选择器)
+  - [ ] 0.3 记录交互模式(打开对话框、选择残疾人、设置信息、保存、等待)
+  - [ ] 0.4 记录数据流(人员关联 API 调用、请求/响应格式、同步时间)
+  - [ ] 0.5 立即修复发现的应用层 bug(如果有)
+  - [ ] 0.6 生成测试代码骨架(基于验证的流程)
+  - [ ] 0.7 将探索结果更新到本文档 Dev Notes 的"Playwright MCP 探索结果"部分
+
+### 阶段 2: RED - 编写测试(基于任务 0 的探索结果)
+
+- [ ] 任务 1: 创建跨端测试文件和基础设施 (AC: #4, #7)
+  - [ ] 1.1 基于任务 0 的探索结果创建 `web/tests/e2e/specs/cross-platform/person-add-sync.spec.ts`
+  - [ ] 1.2 配置测试 fixtures(使用任务 0 验证过的选择器)
+  - [ ] 1.3 添加测试前置条件(创建测试订单和测试残疾人用于关联)
+
+- [ ] 任务 2: 实现后台添加人员测试 (AC: #1)
+  - [ ] 2.1 使用任务 0 验证的选择器编写"后台添加人员成功"测试
+  - [ ] 2.2 验证人员添加后订单的人员列表显示新人员
+  - [ ] 2.3 获取并存储添加的人员数据(人员 ID、姓名等)
+
+- [ ] 任务 3: 实现人才小程序验证测试 (AC: #2)
+  - [ ] 3.1 使用任务 0 验证的选择器编写"人才小程序显示关联订单"测试
+  - [ ] 3.2 验证订单信息完整性(订单名称、公司、状态)
+  - [ ] 3.3 实现数据同步等待机制(基于任务 0 实测的同步时间)
+
+- [ ] 任务 4: 实现多人员添加同步测试 (AC: #3)
+  - [ ] 4.1 编写测试验证添加多个残疾人后小程序同步
+  - [ ] 4.2 编写测试验证每个残疾人都能在小程序看到订单
+  - [ ] 4.3 编写测试验证人员工作状态显示正确
+
+### 阶段 3: GREEN - 实现代码(让测试通过)
+
+- [ ] 任务 5: 实现测试数据清理策略 (AC: #5)
+  - [ ] 5.1 添加测试前置钩子创建测试订单和残疾人
+  - [ ] 5.2 添加 afterEach 钩子清理或恢复测试数据(移除人员关联)
+  - [ ] 5.3 验证清理后小程序不再显示该订单
+
+- [ ] 任务 6: 实现数据同步时效性验证 (AC: #6)
+  - [ ] 6.1 实现轮询等待机制
+  - [ ] 6.2 验证正常同步时间(基于任务 0 的实测数据,应 ≤ 5 秒)
+  - [ ] 6.3 验证超时处理(最长 10 秒)
+
+### 阶段 4: REFACTOR - 优化代码质量
+
+- [ ] 任务 7: 验证代码质量 (AC: #7)
+  - [ ] 7.1 运行 `pnpm typecheck` 验证类型检查
+  - [ ] 7.2 运行测试确保所有测试通过
+  - [ ] 7.3 验证使用 data-testid 选择器(任务 0 已确认)
+
+## Dev Notes
+
+### Playwright MCP 持续验证策略
+
+> **核心理念**: Playwright MCP 不应该只在任务0使用一次,而应该作为整个开发过程中的**持续验证工具**。
+
+参考 Story 13.1 和 13.2 的详细说明,核心要点:
+- **即时验证**: 每写一个测试就用 MCP 验证选择器、页面结构
+- **持续反馈**: 每实现一个功能就用 MCP 验证是否正常工作
+- **减少返工**: 早期发现问题可以减少后期返工成本
+
+**开发循环模式**:
+```
+编写/修改代码 → 立即用 Playwright MCP 验证 → 发现问题?
+  ├── 是 → 修复代码 → 重新验证
+  └── 否 → 继续下一步
+```
+
+### Epic 13 背景和依赖
+
+**Epic 13: 跨端数据同步测试 (Epic E)**
+
+- **目标**: 验证后台操作后小程序端的数据同步,覆盖完整的业务流程
+- **业务分组**: Epic E(跨端数据同步测试)
+- **背景**: 真实用户旅程跨越管理后台和小程序,需要验证数据同步的正确性和时效性
+- **依赖**:
+  - Epic 10: ✅ 已完成(订单管理 E2E 测试)
+  - Epic 12: ✅ 已完成(小程序登录测试)
+
+**Epic 13 Story 依赖关系:**
+```
+Story 13.1: 后台创建订单 → 企业小程序验证 ✅ 已完成
+Story 13.2: 后台编辑订单 → 企业小程序验证 🔄 进行中
+Story 13.3: 后台添加人员 → 人才小程序验证 ← 当前 Story
+Story 13.4: 后台更新状态 → 双小程序验证
+Story 13.5: 跨端测试稳定性验证
+Story 13.6: 首页看板数据联动专项测试 ✅ 已完成
+```
+
+### Playwright MCP 探索结果(任务 0 完成后更新)
+
+> **完成时间**: 待完成
+> **探索方法**: 使用 Playwright MCP 工具手动验证完整测试流程
+
+**测试目标**: 验证后台添加残疾人到订单后,人才小程序能否正确显示关联的订单
+
+**测试结果**: 待验证
+
+**数据同步时效性**:
+- 实际同步时间: 待测试
+- 要求: ≤ 10 秒
+
+**后台选择器(待验证)**:
+| 功能 | 选择器/方法 |
+|------|-------------|
+| 订单人员管理按钮 | 待验证 |
+| 添加人员对话框触发器 | 待验证 |
+| 残疾人选择器 | 待验证 |
+| 入职日期输入框 | 待验证 |
+| 薪资输入框 | 待验证 |
+| 添加/保存按钮 | 待验证 |
+
+**小程序选择器(待验证)**:
+| 功能 | 选择器/方法 |
+|------|-------------|
+| 手机号输入框 | 待验证 |
+| 登录按钮 | 待验证 |
+| 我的订单列表 | 待验证 |
+| 订单项 | 待验证 |
+| 订单详情 | 待验证 |
+
+**测试流程(待验证)**:
+1. **后台添加人员流程**:
+   - 登录后台 → 导航到订单管理
+   - 打开订单详情或人员管理对话框
+   - 选择残疾人 → 设置入职信息 → 保存
+
+2. **小程序验证流程**:
+   - 人才小程序登录
+   - 导航到"我的订单"
+   - 验证关联订单显示
+
+### Previous Story Intelligence: Story 13.1 & 13.2
+
+从 Story 13.1 和 13.2 中学习到的关键经验:
+
+**Playwright MCP 探索经验**:
+- 必须先验证完整流程再编写测试,避免反复调试选择器
+- 优先使用 `data-testid` 选择器,避免文本选择器
+- 记录实际数据同步时间,而非使用假设值
+- 发现小程序列表页可能使用缓存,详情页显示最新数据
+
+**关键发现(Story 13.1 探索结果)**:
+1. **公司关联问题**: 小程序用户关联的公司必须与后台选择的公司一致
+2. **对话框检测**: 使用 `text=选择残疾人` 检测对话框标题比 `[role="dialog"]` 更可靠
+3. **数据同步时间**: 实际同步时间 < 1秒,远超 ≤ 10 秒的要求
+
+**关键发现(Story 13.2 探索结果)**:
+1. **小程序列表缓存问题**: 小程序列表页显示缓存数据,详情页显示最新数据
+   - **影响**: E2E 测试不能只验证列表页,需要验证详情页或实现列表刷新机制
+   - **推荐方案**: 点击订单详情验证编辑后数据(详情页 API 返回最新数据)
+
+### Epic 10 关键经验(订单人员管理)
+
+从已完成的 Epic 10 Story 10.9 中学习到的订单人员管理模式:
+
+**OrderManagementPage 人员管理方法**:
+```typescript
+// 打开订单人员管理对话框
+async openPersonManagementDialog(orderName: string): Promise<void>
+
+// 添加残疾人到订单
+async addPersonToOrder(personData: {
+  personId: number;
+  joinDate?: string;
+  salary?: number;
+}): Promise<FormSubmitResult>
+
+// 获取订单的人员列表
+async getOrderPersons(orderName: string): Promise<PersonData[]>
+```
+
+**人员数据结构**:
+```typescript
+interface PersonData {
+  id: number;
+  name: string;
+  idCard: string;
+  joinDate?: string;
+  salary?: number;
+  workStatus?: string;
+}
+```
+
+### Epic 12 关键经验(人才小程序)
+
+从已完成的 Epic 12 Story 12.7 中学习到的模式:
+
+**TalentMiniPage 可用方法**:
+```typescript
+// 页面导航
+async goto(): Promise<void>
+async expectToBeVisible(): Promise<void>
+
+// 登录方法
+async login(phone: string, password: string): Promise<void>
+
+// Token 管理
+async getToken(): Promise<string | null>
+async setToken(token: string): Promise<void>
+async clearAuth(): Promise<void>
+
+// 我的订单列表(待实现/扩展)
+async getMyOrders(): Promise<OrderData[]>
+async waitForOrderToAppear(orderName: string, timeout?: number): Promise<boolean>
+```
+
+**人才小程序登录流程**:
+```typescript
+await talentMiniPage.goto();
+await talentMiniPage.login(
+  '13800138000',  // 人才用户手机号
+  'password123'
+);
+await talentMiniPage.expectLoginSuccess();
+```
+
+### 多 Page 对象管理策略
+
+参考 Story 13.1 的多 Page 对象管理模式:
+
+**方案 1: 使用多个 Browser Context(推荐)**
+```typescript
+import { test as base } from '@playwright/test';
+
+type CrossPlatformFixtures = {
+  adminPage: Page;
+  talentMiniPage: Page;
+  orderManagementPage: OrderManagementPage;
+  talentMini: TalentMiniPage;
+};
+
+export const test = base.extend<CrossPlatformFixtures>({
+  adminPage: async ({ browser }, use) => {
+    const context = await browser.newContext();
+    const page = await context.newPage();
+    await use(page);
+    await context.close();
+  },
+  talentMiniPage: async ({ browser }, use) => {
+    const context = await browser.newContext();
+    const page = await context.newPage();
+    await use(page);
+    await context.close();
+  },
+  orderManagementPage: async ({ adminPage }, use) => {
+    const page = new OrderManagementPage(adminPage);
+    await use(page);
+  },
+  talentMini: async ({ talentMiniPage }, use) => {
+    const page = new TalentMiniPage(talentMiniPage);
+    await use(page);
+  },
+});
+```
+
+### 数据同步等待策略
+
+**轮询等待模式(推荐)**:
+```typescript
+async waitForOrderToAppear(
+  orderName: string,
+  timeout: number = 10000
+): Promise<boolean> {
+  const startTime = Date.now();
+  while (Date.now() - startTime < timeout) {
+    const orders = await this.getMyOrders();
+    if (orders.some(o => o.name === orderName)) {
+      return true;
+    }
+    await this.page.waitForTimeout(500); // 每 500ms 检查一次
+  }
+  return false;
+}
+```
+
+### 测试数据准备策略
+
+**前置条件**:
+1. 需要测试平台数据(使用 Story 11.2 创建的平台)
+2. 需要测试公司数据(使用 Story 11.5 创建的公司)
+3. 需要测试订单数据(测试前置步骤创建)
+4. 需要测试残疾人数据(用于添加到订单)
+
+**重要**: 添加人员测试需要:
+1. 测试残疾人必须与人才小程序用户关联(同一人)
+2. 测试订单必须已经创建
+3. 人才小程序用户必须能够看到该残疾人关联的订单
+
+**测试数据唯一性**:
+```typescript
+const timestamp = Date.now();
+const personData = {
+  name: `跨端测试残疾人_${timestamp}`,
+  idCard: generateTestIdCard(timestamp),
+  // ...
+};
+```
+
+### 测试数据清理策略
+
+**清理方法**:
+```typescript
+test.afterEach(async ({ orderManagementPage, orderId, personId }) => {
+  // 方案1: 移除人员关联
+  await orderManagementPage.removePersonFromOrder(orderId, personId);
+
+  // 方案2: 删除测试订单(如不需要保留)
+  await orderManagementPage.deleteOrder(orderId);
+});
+```
+
+**验证清理成功**:
+```typescript
+test('清理后小程序不显示订单', async ({ talentMini, orderName }) => {
+  const orders = await talentMini.getMyOrders();
+  expect(orders.some(o => o.name === orderName)).toBe(false);
+});
+```
+
+### 人才小程序"我的订单"页面(待实现/扩展)
+
+**预期页面元素**:
+- 我的订单列表容器
+- 订单项(订单名称、公司、状态、日期)
+- 订单详情按钮
+
+**如页面未实现,需要添加 data-testid**:
+```typescript
+private readonly selectors = {
+  myOrdersList: '[data-testid="talent-my-orders-list"]',
+  orderItem: '[data-testid="talent-order-item"]',
+  orderName: '[data-testid="talent-order-name"]',
+  orderCompany: '[data-testid="talent-order-company"]',
+  orderStatus: '[data-testid="talent-order-status"]',
+};
+```
+
+### 项目结构
+
+**新建文件**:
+- `web/tests/e2e/specs/cross-platform/person-add-sync.spec.ts`
+
+**需要扩展的文件**:
+- `web/tests/e2e/pages/mini/talent-mini.page.ts`(扩展"我的订单"方法)
+- `web/tests/e2e/pages/admin/order-management.page.ts`(扩展人员管理方法,如需要)
+
+**相关参考文件**:
+- `web/tests/e2e/pages/admin/order-management.page.ts` (Epic 10) - 订单人员管理
+- `web/tests/e2e/pages/mini/talent-mini.page.ts` (Epic 12) - 人才小程序
+- `web/tests/e2e/specs/admin/order-person.spec.ts` (Epic 10) - 人员关联测试
+- `web/tests/e2e/specs/cross-platform/order-create-sync.spec.ts` (Story 13.1) - 跨端测试模式
+- `web/tests/e2e/specs/cross-platform/order-edit-sync.spec.ts` (Story 13.2) - 跨端编辑测试
+
+### 选择器策略
+
+**优先级(遵循项目标准)**:
+1. `data-testid` 属性(最高优先级)
+2. ARIA 属性 + role
+3. 文本内容(最低优先级,避免使用)
+
+**后台人员管理选择器(参考 Epic 10)**:
+- 人员管理按钮: `manage-persons-button-{orderId}` 或订单详情页
+- 添加人员按钮: `add-person-button`
+- 残疾人选择器: 参考创建订单的选择器
+
+### TypeScript 类型定义
+
+**跨端测试数据类型**:
+```typescript
+interface CrossPlatformPersonData {
+  /** 残疾人 ID */
+  personId: number;
+  /** 残疾人姓名 */
+  personName: string;
+  /** 身份证号 */
+  idCard: string;
+  /** 入职日期 */
+  joinDate?: string;
+  /** 薪资 */
+  salary?: number;
+}
+
+interface PersonSyncVerificationResult {
+  /** 同步是否成功 */
+  synced: boolean;
+  /** 同步耗时(毫秒) */
+  syncTime: number;
+  /** 后台订单数据 */
+  adminOrder: OrderData;
+  /** 小程序订单数据 */
+  miniOrder?: OrderData;
+  /** 残疾人信息 */
+  person: CrossPlatformPersonData;
+}
+```
+
+### 测试超时配置
+
+**使用 TIMEOUTS 常量**:
+```typescript
+import { TIMEOUTS } from '../../utils/timeouts';
+
+// 数据同步等待时间
+const SYNC_TIMEOUT = TIMEOUTS.networkIdle; // 10000ms
+
+// 轮询检查间隔
+const POLL_INTERVAL = 500;
+```
+
+### 调试技巧
+
+**跨端测试调试**:
+1. 使用 `page.screenshot()` 在关键步骤截图
+2. 使用 `console.debug()` 输出人员关联信息
+3. 分别记录后台和小程序的操作时间
+
+**同步问题调试**:
+- 检查网络请求(使用 Playwright 的 network 监听)
+- 检查人员关联 API 请求和响应
+- 验证残疾人与人才小程序用户的关联关系
+
+### 参考文档
+
+**架构文档**:
+- `_bmad-output/planning-artifacts/epics.md#Epic 13`
+- `_bmad-output/project-context.md`
+- `docs/standards/e2e-radix-testing.md`
+
+**相关 Story 文档**:
+- `13-1-order-create-sync.md` - Story 13.1 完整文档(Playwright MCP 探索结果、测试模式)
+- `13-2-order-edit-sync.md` - Story 13.2 完整文档(小程序缓存问题、验证策略)
+- `10-9-order-person-tests.md` (订单人员关联测试)
+- `12-6-talent-mini-page-object.md` (人才小程序 Page Object)
+- `12-7-talent-mini-login.md` (人才小程序登录测试)
+
+## Dev Agent Record
+
+### Agent Model Used
+
+_Created by create-story workflow_
+
+### Debug Log References
+
+_Implementation phase - no debug yet_
+
+### Completion Notes List
+
+_Story created - ready for development_
+
+### File List
+
+**新建文件**:
+- `_bmad-output/implementation-artifacts/13-3-person-add-sync.md` - 本 Story 文档
+
+**待创建文件**(开发阶段):
+- `web/tests/e2e/specs/cross-platform/person-add-sync.spec.ts` - 跨端人员添加同步测试
+
+## Change Log
+
+- 2026-01-14: Story 13.3 创建完成
+  - 后台添加人员 → 人才小程序验证需求
+  - 多 Page 对象管理策略
+  - 数据同步等待策略
+  - 状态:ready-for-dev

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

@@ -212,7 +212,7 @@ development_status:
   epic-13: in-progress
   13-1-order-create-sync: done   # 后台创建订单 → 企业小程序验证 ✅ 完成 (2026-01-14) - 跨端数据同步测试、Playwright MCP 探索、DisabledPersonSelector 组件修复
   13-2-order-edit-sync: in-progress       # 后台编辑订单 → 企业小程序验证
-  13-3-person-add-sync: backlog            # 后台添加人员 → 人才小程序验证
+  13-3-person-add-sync: ready-for-dev            # 后台添加人员 → 人才小程序验证
   13-4-work-status-sync: backlog           # 后台更新状态 → 双小程序验证
   13-5-cross-platform-stability: backlog   # 跨端测试稳定性验证
   13-6-dashboard-sync: done       # 首页看板数据联动专项测试 ✅ 完成 (2026-01-14) - 所有4个测试通过

+ 243 - 0
web/tests/e2e/specs/cross-platform/order-edit-sync.spec.ts

@@ -0,0 +1,243 @@
+import { TIMEOUTS } from '../../utils/timeouts';
+import { test, expect } from '../../utils/test-setup';
+import { AdminLoginPage } from '../../pages/admin/login.page';
+import { EnterpriseMiniPage } from '../../pages/mini/enterprise-mini.page';
+
+/**
+ * 跨端数据同步 E2E 测试 - 编辑订单
+ *
+ * 测试目标:验证后台编辑订单后,企业小程序能否正确显示更新后的订单信息
+ *
+ * 测试流程:
+ * 1. 后台操作:登录 → 导航到订单管理 → 编辑已有订单 → 验证编辑成功
+ * 2. 小程序验证:登录 → 导航到订单列表 → 点击订单详情 → 验证订单信息已更新
+ *
+ * 重要发现(来自 Playwright MCP 探索):
+ * - 小程序列表页显示缓存数据(编辑后不自动刷新)
+ * - 小程序详情页显示最新数据(API 返回最新数据)
+ * - 因此 E2E 测试需要通过详情页验证数据同步
+ */
+
+// 测试常量
+const TEST_COMPANY_NAME = '测试公司_1768346782396'; // 测试公司名称
+const MINI_LOGIN_PHONE = '13800001111'; // 小程序登录手机号
+const MINI_LOGIN_PASSWORD = 'password123'; // 小程序登录密码
+
+/**
+ * 后台登录辅助函数
+ */
+async function loginAdmin(page: any, testUsers: any) {
+  const adminLoginPage = new AdminLoginPage(page);
+  await adminLoginPage.goto();
+  await adminLoginPage.page.getByPlaceholder('请输入用户名').fill(testUsers.admin.username);
+  await adminLoginPage.page.getByPlaceholder('请输入密码').fill(testUsers.admin.password);
+  await adminLoginPage.page.getByRole('button', { name: '登录' }).click();
+  await adminLoginPage.page.waitForURL('**/admin/dashboard', { timeout: TIMEOUTS.PAGE_LOAD });
+  console.debug('[后台] 登录成功');
+}
+
+/**
+ * 企业小程序登录辅助函数
+ */
+async function loginMini(page: any) {
+  const miniPage = new EnterpriseMiniPage(page);
+  await miniPage.goto();
+  await miniPage.login(MINI_LOGIN_PHONE, MINI_LOGIN_PASSWORD);
+  await miniPage.expectLoginSuccess();
+  console.debug('[小程序] 登录成功');
+}
+
+test.describe('跨端数据同步测试 - 后台编辑订单到企业小程序', () => {
+  test.describe.configure({ mode: 'serial' });
+
+  test('完整的跨端编辑同步测试:后台编辑订单 → 小程序详情验证', async ({ page: adminPage, page: miniPage, testUsers }) => {
+    // ==================== 后台编辑订单 ====================
+    console.debug('=== 阶段 1: 后台编辑订单 ===');
+
+    // 1. 后台登录
+    await loginAdmin(adminPage, testUsers);
+
+    // 2. 导航到订单管理页面
+    await adminPage.goto('/admin/orders');
+    await adminPage.waitForSelector('table tbody tr', { state: 'visible', timeout: TIMEOUTS.PAGE_LOAD });
+    console.debug('[后台] 导航到订单管理页面');
+
+    // 3. 查找属于测试公司的未编辑订单(用于编辑)
+    const testCompanyOrders = adminPage.locator('table tbody tr').filter({
+      hasText: TEST_COMPANY_NAME
+    });
+
+    // 获取订单总数并遍历查找未编辑的订单
+    const orderCount = await testCompanyOrders.count();
+    let originalOrderName: string | null = null;
+
+    for (let i = 0; i < orderCount; i++) {
+      const nameCell = await testCompanyOrders.nth(i).locator('td').first();
+      const name = (await nameCell.textContent())?.trim() || '';
+      // 跳过已被编辑的订单(名称包含 "已编辑" 或 "_edit")
+      if (!name.includes('已编辑') && !name.includes('_edit')) {
+        originalOrderName = name;
+        break;
+      }
+    }
+
+    if (!originalOrderName) {
+      test.skip();
+      console.debug('[测试] 所有测试订单都已被编辑过,跳过此测试');
+      return;
+    }
+
+    console.debug(`[后台] 找到测试订单: ${originalOrderName}`);
+
+    // 4. 打开编辑对话框
+    const orderRow = adminPage.locator('table tbody tr').filter({ hasText: originalOrderName });
+    const menuButton = orderRow.getByRole('button', { name: '打开菜单' });
+    await menuButton.click();
+    console.debug('[后台] 点击订单菜单按钮');
+
+    // 等待菜单出现并点击"编辑"选项
+    const editOption = adminPage.getByRole('menuitem', { name: '编辑' });
+    await editOption.waitFor({ state: 'visible', timeout: TIMEOUTS.ELEMENT_VISIBLE_SHORT });
+    await editOption.click();
+    console.debug('[后台] 点击编辑菜单项');
+
+    // 等待编辑对话框出现
+    await adminPage.waitForSelector('[role="dialog"]', { state: 'visible', timeout: TIMEOUTS.DIALOG });
+    console.debug('[后台] 编辑对话框已打开');
+
+    // 5. 修改订单名称(使用较短的后缀以避免 UI 截断)
+    const editedOrderName = `${originalOrderName}_编辑`;
+    await adminPage.getByRole('textbox', { name: '订单名称' }).fill(editedOrderName);
+    console.debug(`[后台] 修改订单名称为: ${editedOrderName}`);
+
+    // 6. 提交编辑表单
+    await adminPage.getByTestId('order-update-submit-button').click();
+    await adminPage.waitForLoadState('domcontentloaded');
+    await adminPage.waitForTimeout(TIMEOUTS.LONG);
+    console.debug('[后台] 提交编辑表单');
+
+    // 7. 验证编辑成功的 Toast(确认编辑操作成功)
+    const successToast = adminPage.locator('[data-sonner-toast][data-type="success"]');
+    const toastCount = await successToast.count();
+    expect(toastCount).toBeGreaterThan(0);
+    console.debug('[后台] 订单编辑成功(Toast 已显示)');
+
+    // 8. 可选:验证列表中更新(通过搜索,但可能有分页或延迟问题)
+    // 由于核心测试目标是小程序数据同步,列表验证跳过
+    // Toast 已经确认编辑成功,继续进行小程序验证
+
+    // ==================== 小程序验证编辑后的订单 ====================
+    console.debug('=== 阶段 2: 小程序验证编辑后的订单 ===');
+
+    // 1. 小程序登录
+    await loginMini(miniPage);
+
+    // 2. 导航到订单列表页面
+    await miniPage.getByText('订单', { exact: true }).click();
+    await miniPage.waitForLoadState('domcontentloaded');
+    console.debug('[小程序] 导航到订单列表页面');
+
+    // 3. 等待订单列表加载
+    await miniPage.waitForTimeout(TIMEOUTS.LONG);
+
+    // 4. 查找编辑后的订单(通过原订单名称,因为列表可能有缓存)
+    const orderExistsByOriginal = await miniPage.locator('text=' + originalOrderName).count() > 0;
+    const orderExistsByEdited = await miniPage.locator('text=' + editedOrderName).count() > 0;
+
+    console.debug(`[小程序] 列表中 - 原名称存在: ${orderExistsByOriginal}, 编辑后名称存在: ${orderExistsByEdited}`);
+
+    // 5. 点击查看详情(详情页会获取最新数据)
+    // 查找包含原订单名称的行,然后点击其"查看详情"按钮
+    const orderRowInMini = miniPage.getByText(originalOrderName);
+    const detailButton = orderRowInMini.locator('..').getByText('查看详情');
+
+    // 记录详情导航开始时间
+    const detailStartTime = Date.now();
+
+    await detailButton.click();
+    await miniPage.waitForURL(/\/detail/, { timeout: TIMEOUTS.PAGE_LOAD });
+    console.debug('[小程序] 打开订单详情');
+
+    // 6. 验证订单详情页显示编辑后的订单名称
+    // 详情页会从 API 获取最新数据,所以应该显示编辑后的名称
+    const editedOrderNameElement = miniPage.getByText(editedOrderName);
+    await expect(editedOrderNameElement).toBeVisible();
+    console.debug(`[小程序] 订单详情显示编辑后的名称: ${editedOrderName}`);
+
+    // 7. 记录数据同步完成时间
+    const detailEndTime = Date.now();
+    const detailSyncTime = detailEndTime - detailStartTime;
+    console.debug(`[小程序] 详情页加载并验证完成,耗时: ${detailSyncTime}ms`);
+
+    // 8. 验证数据同步时效性(应 ≤ 10 秒)
+    expect(detailSyncTime).toBeLessThanOrEqual(10000);
+    console.debug(`[小程序] 数据同步时效性验证通过: ${detailSyncTime}ms ≤ 10000ms`);
+  });
+
+  test('后台编辑订单基本信息(名称和日期)', async ({ page: adminPage, testUsers }) => {
+    // 这个测试验证编辑订单基本信息的功能
+    await loginAdmin(adminPage, testUsers);
+    await adminPage.goto('/admin/orders');
+
+    // 查找测试公司的订单
+    const testCompanyOrders = adminPage.locator('table tbody tr').filter({
+      hasText: TEST_COMPANY_NAME
+    });
+
+    // 跳过已被编辑的订单(名称包含 "_编辑")
+    const validOrders = testCompanyOrders.filter(async (_, index) => {
+      const nameCell = await testCompanyOrders.nth(index).locator('td').first();
+      const name = (await nameCell.textContent())?.trim() || '';
+      return !name.includes('_编辑') && !name.includes('_edit');
+    });
+
+    const firstValidOrder = await validOrders.first();
+    const orderName = (await firstValidOrder.locator('td').first().textContent())?.trim();
+
+    if (!orderName || orderName.includes('_编辑')) {
+      test.skip();
+      console.debug('[测试] 所有测试订单都已被编辑过,跳过此测试');
+      return;
+    }
+
+    console.debug(`[测试] 编辑订单: ${orderName}`);
+
+    // 打开编辑对话框
+    const orderRow = adminPage.locator('table tbody tr').filter({ hasText: orderName });
+    await orderRow.getByRole('button', { name: '打开菜单' }).click();
+
+    const editOption = adminPage.getByRole('menuitem', { name: '编辑' });
+    await editOption.waitFor({ state: 'visible', timeout: TIMEOUTS.ELEMENT_VISIBLE_SHORT });
+    await editOption.click();
+
+    await adminPage.waitForSelector('[role="dialog"]', { state: 'visible', timeout: TIMEOUTS.DIALOG });
+
+    // 修改订单名称
+    const newOrderName = `${orderName}_info_edit`;
+    await adminPage.getByRole('textbox', { name: '订单名称' }).fill(newOrderName);
+
+    // 修改预计开始日期
+    const newStartDate = '2026-02-01';
+    await adminPage.getByRole('textbox', { name: '预计开始日期' }).fill(newStartDate);
+
+    // 提交编辑
+    await adminPage.getByTestId('order-update-submit-button').click();
+    await adminPage.waitForLoadState('domcontentloaded');
+    await adminPage.waitForTimeout(TIMEOUTS.LONG);
+
+    // 验证成功
+    const successToast = adminPage.locator('[data-sonner-toast][data-type="success"]');
+    expect(await successToast.count()).toBeGreaterThan(0);
+    console.debug('[测试] 订单编辑成功(Toast 已显示)');
+
+    // 验证列表显示更新(使用搜索)
+    await adminPage.getByTestId('search-order-name-input').fill(newOrderName);
+    await adminPage.getByRole('button', { name: '搜索' }).click();
+    await adminPage.waitForTimeout(TIMEOUTS.LONG);
+
+    const updatedOrderExists = adminPage.locator('table tbody tr').filter({ hasText: newOrderName }).count() > 0;
+    expect(updatedOrderExists).toBe(true);
+
+    console.debug(`[测试] 编辑订单基本信息成功: ${orderName} -> ${newOrderName}`);
+  });
+});