person-add-sync.spec.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. import { TIMEOUTS } from '../../utils/timeouts';
  2. import { test, expect } from '../../utils/test-setup';
  3. import { AdminLoginPage } from '../../pages/admin/login.page';
  4. import { TalentMiniPage } from '../../pages/mini/talent-mini.page';
  5. /**
  6. * 跨端数据同步 E2E 测试 - 人员添加
  7. *
  8. * 测试目标:验证后台添加残疾人到订单后,人才小程序能否正确显示关联的订单
  9. *
  10. * 测试流程:
  11. * 1. 后台操作:登录 → 打开订单详情 → 添加残疾人 → 验证添加成功
  12. * 2. 小程序验证:登录 → 导航到"我的订单" → 验证订单显示
  13. *
  14. * 测试要点:
  15. * - 使用两个独立的 browser context(后台和小程序)
  16. * - 记录数据同步时间
  17. * - 使用 data-testid 选择器
  18. * - 验证两步确认流程(选择残疾人 → 确认添加)
  19. *
  20. * 关键发现(来自 Task 0 Playwright MCP 探索):
  21. * 1. 添加人员需要两步确认:
  22. * - 第一步:选择残疾人 → "确认选择"
  23. * - 第二步:待添加人员列表 → "确认添加"
  24. * 2. 默认薪资为 5000
  25. * 3. 入职日期自动设置为当天
  26. * 4. 已绑定人员的复选框自动禁用
  27. */
  28. // 测试常量
  29. const TEST_SYNC_TIMEOUT = 10000; // 数据同步等待时间(ms)- AC 要求 ≤ 10 秒
  30. const TEST_POLL_INTERVAL = 500; // 轮询检查间隔(ms)
  31. const TEST_ORDER_ID = 724; // 测试订单 ID(使用已存在的订单)
  32. const TEST_ORDER_NAME = 'Epic13验证测试_1768403960000_Story13.2已编辑'; // 测试订单名称
  33. // 人才小程序登录凭据(需要与添加的残疾人关联)
  34. const TALENT_LOGIN_PHONE = '13800119311'; // 测试残疾人_1768346764677_11_9311 的手机号
  35. const TALENT_LOGIN_PASSWORD = 'password123'; // 默认测试密码
  36. /**
  37. * 后台登录辅助函数
  38. */
  39. async function loginAdmin(page: any, testUsers: any) {
  40. const adminLoginPage = new AdminLoginPage(page);
  41. await adminLoginPage.goto();
  42. await adminLoginPage.page.getByPlaceholder('请输入用户名').fill(testUsers.admin.username);
  43. await adminLoginPage.page.getByPlaceholder('请输入密码').fill(testUsers.admin.password);
  44. await adminLoginPage.page.getByRole('button', { name: '登录' }).click();
  45. await adminLoginPage.page.waitForURL('**/admin/dashboard', { timeout: TIMEOUTS.PAGE_LOAD });
  46. console.debug('[后台] 登录成功');
  47. }
  48. /**
  49. * 人才小程序登录辅助函数
  50. */
  51. async function loginTalentMini(page: any) {
  52. const talentMiniPage = new TalentMiniPage(page);
  53. await talentMiniPage.goto();
  54. await talentMiniPage.login(TALENT_LOGIN_PHONE, TALENT_LOGIN_PASSWORD);
  55. await talentMiniPage.expectLoginSuccess();
  56. console.debug('[人才小程序] 登录成功');
  57. }
  58. // 测试状态管理(使用闭包替代 process.env)
  59. interface TestState {
  60. personId: number | null;
  61. personName: string | null;
  62. syncTime: number | null;
  63. }
  64. const testState: TestState = {
  65. personId: null,
  66. personName: null,
  67. syncTime: null,
  68. };
  69. test.describe('跨端数据同步测试 - 后台添加人员到人才小程序', () => {
  70. // 在所有测试后清理测试数据
  71. test.afterAll(async () => {
  72. // 清理测试状态
  73. testState.personId = null;
  74. testState.personName = null;
  75. testState.syncTime = null;
  76. console.debug('[清理] 测试数据已清理');
  77. });
  78. test.describe.serial('后台添加人员', () => {
  79. test('应该成功打开订单详情并添加残疾人', async ({ page: adminPage, testUsers }) => {
  80. // 记录开始时间
  81. const startTime = Date.now();
  82. // 1. 后台登录(使用辅助函数)
  83. await loginAdmin(adminPage, testUsers);
  84. // 2. 导航到订单管理页面
  85. await adminPage.goto('/admin/orders');
  86. await adminPage.waitForSelector('table tbody tr', { state: 'visible', timeout: TIMEOUTS.PAGE_LOAD });
  87. console.debug('[后台] 导航到订单管理页面');
  88. // 3. 打开订单详情对话框
  89. // 点击订单的"打开菜单"按钮
  90. await adminPage.getByTestId(`order-menu-trigger-${TEST_ORDER_ID}`).click();
  91. await adminPage.waitForTimeout(TIMEOUTS.VERY_SHORT);
  92. console.debug(`[后台] 打开订单 ${TEST_ORDER_ID} 的菜单`);
  93. // 点击"查看详情"菜单项
  94. await adminPage.getByTestId(`view-order-detail-button-${TEST_ORDER_ID}`).click();
  95. await adminPage.waitForSelector('[role="dialog"]', { state: 'visible', timeout: TIMEOUTS.DIALOG });
  96. console.debug('[后台] 打开订单详情对话框');
  97. // 4. 点击"添加人员"按钮
  98. await adminPage.getByTestId('order-detail-card-add-persons-button').click();
  99. await adminPage.waitForSelector('text=选择残疾人', { state: 'visible', timeout: TIMEOUTS.DIALOG });
  100. console.debug('[后台] 打开选择残疾人对话框');
  101. // 5. 选择残疾人
  102. // 使用测试残疾人_1768346764677_11_9311(ID: 1239)
  103. // 注意:已绑定人员的复选框会被禁用
  104. const testPersonId = 1239; // 测试残疾人_1768346764677_11_9311 的 ID
  105. await adminPage.getByTestId(`person-checkbox-${testPersonId}`).click();
  106. console.debug(`[后台] 选择残疾人 ID: ${testPersonId}`);
  107. // 6. 点击"确认选择"按钮
  108. // 人员进入"待添加人员列表"
  109. await adminPage.getByTestId('confirm-batch-button').click();
  110. await adminPage.waitForTimeout(TIMEOUTS.MEDIUM);
  111. console.debug('[后台] 确认选择残疾人');
  112. // 7. 验证待添加人员列表显示
  113. // 检查是否有"待添加人员列表"或"确认添加"按钮
  114. const confirmAddButton = adminPage.getByTestId('confirm-add-persons-button');
  115. await expect(confirmAddButton).toBeVisible();
  116. console.debug('[后台] 待添加人员列表已显示');
  117. // 8. 点击"确认添加"按钮完成绑定
  118. await confirmAddButton.click();
  119. await adminPage.waitForTimeout(TIMEOUTS.LONG);
  120. // 9. 验证 Toast 通知
  121. const successToast = adminPage.locator('[data-sonner-toast][data-type="success"]');
  122. await expect(successToast).toBeVisible({ timeout: TIMEOUTS.VERY_LONG });
  123. const toastMessage = await successToast.textContent();
  124. console.debug(`[后台] Toast 消息: ${toastMessage}`);
  125. expect(toastMessage).toContain('批量添加人员成功');
  126. // 10. 验证绑定人员列表更新
  127. // 检查订单详情对话框中的绑定人员列表
  128. // 应该能看到新添加的人员
  129. const personRow = adminPage.locator('table tbody tr').filter({ hasText: '测试残疾人_1768346764677_11_9311' });
  130. await expect(personRow).toBeVisible();
  131. console.debug('[后台] 人员已成功添加到绑定人员列表');
  132. // 记录测试数据
  133. testState.personId = testPersonId;
  134. testState.personName = '测试残疾人_1768346764677_11_9311';
  135. const endTime = Date.now();
  136. const duration = endTime - startTime;
  137. console.debug(`[后台] 添加人员完成,耗时: ${duration}ms`);
  138. });
  139. });
  140. test.describe.serial('人才小程序验证', () => {
  141. test('应该在小程序中显示关联的订单', async ({ page: talentMiniPage }) => {
  142. // 1. 人才小程序登录
  143. await loginTalentMini(talentMiniPage);
  144. // 2. 记录同步开始时间
  145. const syncStartTime = Date.now();
  146. // 3. 导航到"我的订单"页面
  147. // TODO: 实现 TalentMiniPage 的 navigateToMyOrders 方法
  148. // await talentMiniPage.navigateToMyOrders();
  149. console.debug('[人才小程序] 导航到我的订单');
  150. // 4. 验证订单显示(使用轮询等待)
  151. // 由于数据同步可能有延迟,使用轮询检查
  152. let orderFound = false;
  153. const maxWaitTime = TEST_SYNC_TIMEOUT;
  154. const pollInterval = TEST_POLL_INTERVAL;
  155. const pollStartTime = Date.now();
  156. while (Date.now() - pollStartTime < maxWaitTime) {
  157. // TODO: 实现 TalentMiniPage 的 getMyOrders 方法
  158. // const orders = await talentMiniPage.getMyOrders();
  159. // orderFound = orders.some(order => order.name === TEST_ORDER_NAME);
  160. // if (orderFound) break;
  161. await talentMiniPage.waitForTimeout(pollInterval);
  162. }
  163. const syncEndTime = Date.now();
  164. testState.syncTime = syncEndTime - syncStartTime;
  165. // 5. 验证订单存在
  166. expect(orderFound, `订单 "${TEST_ORDER_NAME}" 应该在人才小程序中显示`).toBe(true);
  167. console.debug(`[人才小程序] 订单已同步,耗时: ${testState.syncTime}ms`);
  168. // 6. 验证同步时间符合要求
  169. expect(testState.syncTime).toBeLessThanOrEqual(TEST_SYNC_TIMEOUT);
  170. console.debug(`[验证] 数据同步时间 ${testState.syncTime}ms 符合要求(≤ ${TEST_SYNC_TIMEOUT}ms)`);
  171. });
  172. test('应该在小程序订单详情中显示完整信息', async ({ page: talentMiniPage }) => {
  173. // 前置条件:已登录
  174. await loginTalentMini(talentMiniPage);
  175. // 1. 导航到"我的订单"并找到测试订单
  176. // TODO: 实现订单查找逻辑
  177. // 2. 点击订单详情
  178. // TODO: 实现点击订单详情逻辑
  179. // 3. 验证订单信息完整性
  180. // 验证订单名称、公司、状态等字段
  181. console.debug('[人才小程序] 验证订单详情信息');
  182. });
  183. });
  184. test.describe.serial('数据同步时效性验证', () => {
  185. test('应该在合理时间内完成数据同步(≤ 10 秒)', async ({ page: adminPage, page: talentMiniPage, testUsers }) => {
  186. // 此测试专门验证数据同步时效性
  187. // AC6: 数据应在 5 秒内同步,最多等待 10 秒
  188. console.debug('[验证] 数据同步时效性测试');
  189. console.debug(`[验证] 要求: ≤ ${TEST_SYNC_TIMEOUT}ms`);
  190. console.debug(`[验证] 实际: ${testState.syncTime}ms`);
  191. console.debug(`[验证] 轮询间隔: ${TEST_POLL_INTERVAL}ms`);
  192. if (testState.syncTime !== null) {
  193. expect(testState.syncTime).toBeLessThanOrEqual(TEST_SYNC_TIMEOUT);
  194. console.debug(`[验证] ✅ 数据同步时间符合要求`);
  195. } else {
  196. console.warn('[验证] ⚠️ 未记录到同步时间,请检查测试执行');
  197. }
  198. });
  199. });
  200. test.describe.serial('多人员添加同步验证', () => {
  201. test('应该支持批量添加多个残疾人', async ({ page: adminPage, testUsers }) => {
  202. // AC3: 多人员添加同步验证
  203. // 此测试验证添加多个残疾人后小程序的同步情况
  204. // 1. 后台登录
  205. await loginAdmin(adminPage, testUsers);
  206. // 2. 打开订单详情
  207. await adminPage.goto('/admin/orders');
  208. await adminPage.getByTestId(`order-menu-trigger-${TEST_ORDER_ID}`).click();
  209. await adminPage.waitForTimeout(TIMEOUTS.VERY_SHORT);
  210. await adminPage.getByTestId(`view-order-detail-button-${TEST_ORDER_ID}`).click();
  211. await adminPage.waitForSelector('[role="dialog"]', { state: 'visible', timeout: TIMEOUTS.DIALOG });
  212. // 3. 点击"添加人员"按钮
  213. await adminPage.getByTestId('order-detail-card-add-persons-button').click();
  214. await adminPage.waitForSelector('text=选择残疾人', { state: 'visible', timeout: TIMEOUTS.DIALOG });
  215. // 4. 选择多个残疾人
  216. // TODO: 实现多选逻辑
  217. console.debug('[后台] 多人员添加测试 - TODO: 实现多选逻辑');
  218. });
  219. });
  220. });
  221. /**
  222. * 待实现的方法(TalentMiniPage 扩展):
  223. *
  224. * // 导航到"我的订单"页面
  225. * async navigateToMyOrders(): Promise<void>
  226. *
  227. * // 获取订单列表
  228. * async getMyOrders(): Promise<OrderData[]>
  229. *
  230. * // 等待订单出现(带超时)
  231. * async waitForOrderToAppear(orderName: string, timeout?: number): Promise<boolean>
  232. *
  233. * // 打开订单详情
  234. * async openOrderDetail(orderName: string): Promise<void>
  235. *
  236. * // 获取订单详情信息
  237. * async getOrderDetail(): Promise<OrderDetailData>
  238. *
  239. * 待实现的数据类型:
  240. * interface OrderData {
  241. * id: number;
  242. * name: string;
  243. * companyName: string;
  244. * status: string;
  245. * // ...
  246. * }
  247. *
  248. * interface OrderDetailData {
  249. * id: number;
  250. * name: string;
  251. * platform: string;
  252. * company: string;
  253. * status: string;
  254. * expectedPersons: number;
  255. * actualPersons: number;
  256. * // ...
  257. * }
  258. */