order-edit-sync.spec.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. import { TIMEOUTS } from '../../utils/timeouts';
  2. import { test, expect } from '../../utils/test-setup';
  3. import { AdminLoginPage } from '../../pages/admin/login.page';
  4. import { EnterpriseMiniPage } from '../../pages/mini/enterprise-mini.page';
  5. /**
  6. * 跨端数据同步 E2E 测试 - 编辑订单
  7. *
  8. * 测试目标:验证后台编辑订单后,企业小程序能否正确显示更新后的订单信息
  9. *
  10. * 测试流程:
  11. * 1. 后台操作:登录 → 导航到订单管理 → 编辑已有订单 → 验证编辑成功
  12. * 2. 小程序验证:登录 → 导航到订单列表 → 点击订单详情 → 验证订单信息已更新
  13. *
  14. * 重要发现(来自 Playwright MCP 探索):
  15. * - 小程序列表页显示缓存数据(编辑后不自动刷新)
  16. * - 小程序详情页显示最新数据(API 返回最新数据)
  17. * - 因此 E2E 测试需要通过详情页验证数据同步
  18. */
  19. // 测试常量
  20. const TEST_COMPANY_NAME = '测试公司_1768346782396'; // 测试公司名称
  21. const MINI_LOGIN_PHONE = '13800001111'; // 小程序登录手机号
  22. const MINI_LOGIN_PASSWORD = 'password123'; // 小程序登录密码
  23. /**
  24. * 后台登录辅助函数
  25. */
  26. async function loginAdmin(page: any, testUsers: any) {
  27. const adminLoginPage = new AdminLoginPage(page);
  28. await adminLoginPage.goto();
  29. await adminLoginPage.page.getByPlaceholder('请输入用户名').fill(testUsers.admin.username);
  30. await adminLoginPage.page.getByPlaceholder('请输入密码').fill(testUsers.admin.password);
  31. await adminLoginPage.page.getByRole('button', { name: '登录' }).click();
  32. await adminLoginPage.page.waitForURL('**/admin/dashboard', { timeout: TIMEOUTS.PAGE_LOAD });
  33. console.debug('[后台] 登录成功');
  34. }
  35. /**
  36. * 企业小程序登录辅助函数
  37. */
  38. async function loginMini(page: any) {
  39. const miniPage = new EnterpriseMiniPage(page);
  40. await miniPage.goto();
  41. await miniPage.login(MINI_LOGIN_PHONE, MINI_LOGIN_PASSWORD);
  42. await miniPage.expectLoginSuccess();
  43. console.debug('[小程序] 登录成功');
  44. }
  45. test.describe('跨端数据同步测试 - 后台编辑订单到企业小程序', () => {
  46. test.describe.configure({ mode: 'serial' });
  47. test('完整的跨端编辑同步测试:后台编辑订单 → 小程序详情验证', async ({ page: adminPage, page: miniPage, testUsers }) => {
  48. // ==================== 后台编辑订单 ====================
  49. console.debug('=== 阶段 1: 后台编辑订单 ===');
  50. // 1. 后台登录
  51. await loginAdmin(adminPage, testUsers);
  52. // 2. 导航到订单管理页面
  53. await adminPage.goto('/admin/orders');
  54. await adminPage.waitForSelector('table tbody tr', { state: 'visible', timeout: TIMEOUTS.PAGE_LOAD });
  55. console.debug('[后台] 导航到订单管理页面');
  56. // 3. 查找属于测试公司的未编辑订单(用于编辑)
  57. const testCompanyOrders = adminPage.locator('table tbody tr').filter({
  58. hasText: TEST_COMPANY_NAME
  59. });
  60. // 获取订单总数并遍历查找未编辑的订单
  61. const orderCount = await testCompanyOrders.count();
  62. let originalOrderName: string | null = null;
  63. for (let i = 0; i < orderCount; i++) {
  64. const nameCell = await testCompanyOrders.nth(i).locator('td').first();
  65. const name = (await nameCell.textContent())?.trim() || '';
  66. // 跳过已被编辑的订单(名称包含 "已编辑" 或 "_edit")
  67. if (!name.includes('已编辑') && !name.includes('_edit')) {
  68. originalOrderName = name;
  69. break;
  70. }
  71. }
  72. if (!originalOrderName) {
  73. test.skip();
  74. console.debug('[测试] 所有测试订单都已被编辑过,跳过此测试');
  75. return;
  76. }
  77. console.debug(`[后台] 找到测试订单: ${originalOrderName}`);
  78. // 4. 打开编辑对话框
  79. const orderRow = adminPage.locator('table tbody tr').filter({ hasText: originalOrderName });
  80. const menuButton = orderRow.getByRole('button', { name: '打开菜单' });
  81. await menuButton.click();
  82. console.debug('[后台] 点击订单菜单按钮');
  83. // 等待菜单出现并点击"编辑"选项
  84. const editOption = adminPage.getByRole('menuitem', { name: '编辑' });
  85. await editOption.waitFor({ state: 'visible', timeout: TIMEOUTS.ELEMENT_VISIBLE_SHORT });
  86. await editOption.click();
  87. console.debug('[后台] 点击编辑菜单项');
  88. // 等待编辑对话框出现
  89. await adminPage.waitForSelector('[role="dialog"]', { state: 'visible', timeout: TIMEOUTS.DIALOG });
  90. console.debug('[后台] 编辑对话框已打开');
  91. // 5. 修改订单名称(使用较短的后缀以避免 UI 截断)
  92. const editedOrderName = `${originalOrderName}_编辑`;
  93. await adminPage.getByRole('textbox', { name: '订单名称' }).fill(editedOrderName);
  94. console.debug(`[后台] 修改订单名称为: ${editedOrderName}`);
  95. // 6. 提交编辑表单
  96. await adminPage.getByTestId('order-update-submit-button').click();
  97. await adminPage.waitForLoadState('domcontentloaded');
  98. await adminPage.waitForTimeout(TIMEOUTS.LONG);
  99. console.debug('[后台] 提交编辑表单');
  100. // 7. 验证编辑成功的 Toast(确认编辑操作成功)
  101. const successToast = adminPage.locator('[data-sonner-toast][data-type="success"]');
  102. const toastCount = await successToast.count();
  103. expect(toastCount).toBeGreaterThan(0);
  104. console.debug('[后台] 订单编辑成功(Toast 已显示)');
  105. // 8. 可选:验证列表中更新(通过搜索,但可能有分页或延迟问题)
  106. // 由于核心测试目标是小程序数据同步,列表验证跳过
  107. // Toast 已经确认编辑成功,继续进行小程序验证
  108. // ==================== 小程序验证编辑后的订单 ====================
  109. console.debug('=== 阶段 2: 小程序验证编辑后的订单 ===');
  110. // 1. 小程序登录
  111. await loginMini(miniPage);
  112. // 2. 导航到订单列表页面
  113. await miniPage.getByText('订单', { exact: true }).click();
  114. await miniPage.waitForLoadState('domcontentloaded');
  115. console.debug('[小程序] 导航到订单列表页面');
  116. // 3. 等待订单列表加载
  117. await miniPage.waitForTimeout(TIMEOUTS.LONG);
  118. // 4. 查找编辑后的订单(通过原订单名称,因为列表可能有缓存)
  119. const orderExistsByOriginal = await miniPage.locator('text=' + originalOrderName).count() > 0;
  120. const orderExistsByEdited = await miniPage.locator('text=' + editedOrderName).count() > 0;
  121. console.debug(`[小程序] 列表中 - 原名称存在: ${orderExistsByOriginal}, 编辑后名称存在: ${orderExistsByEdited}`);
  122. // 5. 点击查看详情(详情页会获取最新数据)
  123. // 查找包含原订单名称的行,然后点击其"查看详情"按钮
  124. const orderRowInMini = miniPage.getByText(originalOrderName);
  125. const detailButton = orderRowInMini.locator('..').getByText('查看详情');
  126. // 记录详情导航开始时间
  127. const detailStartTime = Date.now();
  128. await detailButton.click();
  129. await miniPage.waitForURL(/\/detail/, { timeout: TIMEOUTS.PAGE_LOAD });
  130. console.debug('[小程序] 打开订单详情');
  131. // 6. 验证订单详情页显示编辑后的订单名称
  132. // 详情页会从 API 获取最新数据,所以应该显示编辑后的名称
  133. const editedOrderNameElement = miniPage.getByText(editedOrderName);
  134. await expect(editedOrderNameElement).toBeVisible();
  135. console.debug(`[小程序] 订单详情显示编辑后的名称: ${editedOrderName}`);
  136. // 7. 记录数据同步完成时间
  137. const detailEndTime = Date.now();
  138. const detailSyncTime = detailEndTime - detailStartTime;
  139. console.debug(`[小程序] 详情页加载并验证完成,耗时: ${detailSyncTime}ms`);
  140. // 8. 验证数据同步时效性(应 ≤ 10 秒)
  141. expect(detailSyncTime).toBeLessThanOrEqual(10000);
  142. console.debug(`[小程序] 数据同步时效性验证通过: ${detailSyncTime}ms ≤ 10000ms`);
  143. });
  144. test('后台编辑订单基本信息(名称和日期)', async ({ page: adminPage, testUsers }) => {
  145. // 这个测试验证编辑订单基本信息的功能
  146. await loginAdmin(adminPage, testUsers);
  147. await adminPage.goto('/admin/orders');
  148. // 查找测试公司的订单
  149. const testCompanyOrders = adminPage.locator('table tbody tr').filter({
  150. hasText: TEST_COMPANY_NAME
  151. });
  152. // 跳过已被编辑的订单(名称包含 "_编辑")
  153. const validOrders = testCompanyOrders.filter(async (_, index) => {
  154. const nameCell = await testCompanyOrders.nth(index).locator('td').first();
  155. const name = (await nameCell.textContent())?.trim() || '';
  156. return !name.includes('_编辑') && !name.includes('_edit');
  157. });
  158. const firstValidOrder = await validOrders.first();
  159. const orderName = (await firstValidOrder.locator('td').first().textContent())?.trim();
  160. if (!orderName || orderName.includes('_编辑')) {
  161. test.skip();
  162. console.debug('[测试] 所有测试订单都已被编辑过,跳过此测试');
  163. return;
  164. }
  165. console.debug(`[测试] 编辑订单: ${orderName}`);
  166. // 打开编辑对话框
  167. const orderRow = adminPage.locator('table tbody tr').filter({ hasText: orderName });
  168. await orderRow.getByRole('button', { name: '打开菜单' }).click();
  169. const editOption = adminPage.getByRole('menuitem', { name: '编辑' });
  170. await editOption.waitFor({ state: 'visible', timeout: TIMEOUTS.ELEMENT_VISIBLE_SHORT });
  171. await editOption.click();
  172. await adminPage.waitForSelector('[role="dialog"]', { state: 'visible', timeout: TIMEOUTS.DIALOG });
  173. // 修改订单名称
  174. const newOrderName = `${orderName}_info_edit`;
  175. await adminPage.getByRole('textbox', { name: '订单名称' }).fill(newOrderName);
  176. // 修改预计开始日期
  177. const newStartDate = '2026-02-01';
  178. await adminPage.getByRole('textbox', { name: '预计开始日期' }).fill(newStartDate);
  179. // 提交编辑
  180. await adminPage.getByTestId('order-update-submit-button').click();
  181. await adminPage.waitForLoadState('domcontentloaded');
  182. await adminPage.waitForTimeout(TIMEOUTS.LONG);
  183. // 验证成功
  184. const successToast = adminPage.locator('[data-sonner-toast][data-type="success"]');
  185. expect(await successToast.count()).toBeGreaterThan(0);
  186. console.debug('[测试] 订单编辑成功(Toast 已显示)');
  187. // 验证列表显示更新(使用搜索)
  188. await adminPage.getByTestId('search-order-name-input').fill(newOrderName);
  189. await adminPage.getByRole('button', { name: '搜索' }).click();
  190. await adminPage.waitForTimeout(TIMEOUTS.LONG);
  191. const updatedOrderExists = adminPage.locator('table tbody tr').filter({ hasText: newOrderName }).count() > 0;
  192. expect(updatedOrderExists).toBe(true);
  193. console.debug(`[测试] 编辑订单基本信息成功: ${orderName} -> ${newOrderName}`);
  194. });
  195. });