2
0

order-status.spec.ts 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483
  1. /**
  2. * 订单状态流转 E2E 测试
  3. *
  4. * 测试范围:
  5. * - 激活草稿订单
  6. * - 关闭进行中订单
  7. * - 状态限制验证
  8. * - 状态流转后的 Toast 消息验证
  9. *
  10. * @packageDocumentation
  11. */
  12. import { test, expect, Page } from '../../utils/test-setup';
  13. import { ORDER_STATUS, type OrderStatus, ORDER_STATUS_LABELS } from '../../pages/admin/order-management.page';
  14. /**
  15. * 辅助函数:获取订单列表中第一个订单的名称
  16. *
  17. * @param page - Playwright Page 对象
  18. * @returns 第一个订单的名称,如果没有则返回 null
  19. */
  20. async function getFirstOrderName(page: Page): Promise<string | null> {
  21. const table = page.locator('table tbody tr');
  22. // 等待表格数据加载完成(跳过"加载中"等占位符文本)
  23. await page.waitForTimeout(1000);
  24. const count = await table.count();
  25. if (count === 0) {
  26. return null;
  27. }
  28. // 需要跳过的占位符文本列表
  29. const placeholderTexts = [
  30. '加载中...',
  31. '暂无数据',
  32. '暂无订单数据',
  33. '无数据',
  34. '没有数据'
  35. ];
  36. // 查找第一个有效的订单行(排除占位符)
  37. for (let i = 0; i < count; i++) {
  38. const row = table.nth(i);
  39. const nameCell = row.locator('td').first();
  40. const name = await nameCell.textContent();
  41. const trimmedName = name?.trim() || '';
  42. // 跳过占位符文本
  43. const isPlaceholder = placeholderTexts.some(placeholder => trimmedName.includes(placeholder));
  44. if (trimmedName && !isPlaceholder && !trimmedName.includes('加载')) {
  45. return trimmedName;
  46. }
  47. }
  48. return null;
  49. }
  50. /**
  51. * 辅助函数:在创建订单对话框中选择残疾人
  52. *
  53. * @param page - Playwright Page 对象
  54. * @returns 是否成功选择了残疾人
  55. */
  56. async function selectDisabledPersonForOrder(page: Page): Promise<boolean> {
  57. const selectPersonButton = page.getByRole('button', { name: '选择残疾人' });
  58. await selectPersonButton.click();
  59. await page.waitForSelector('[role="dialog"]', { state: 'visible', timeout: 5000 });
  60. let hasData = false;
  61. try {
  62. const firstCheckbox = page.locator('table tbody tr').first().locator('input[type="checkbox"]').first();
  63. await firstCheckbox.waitFor({ state: 'visible', timeout: 3000 });
  64. await firstCheckbox.check();
  65. console.debug('✓ 已选择第一个残疾人');
  66. hasData = true;
  67. } catch (error) {
  68. console.debug('没有可用的残疾人数据');
  69. hasData = false;
  70. }
  71. if (hasData) {
  72. const confirmButton = page.getByRole('button', { name: /^(确定|确认|选择)$/ });
  73. await confirmButton.click().catch(() => {
  74. console.debug('没有找到确认按钮,尝试关闭对话框');
  75. page.keyboard.press('Escape');
  76. });
  77. } else {
  78. await page.keyboard.press('Escape').catch(() => {
  79. console.debug('无法关闭对话框,可能已经自动关闭');
  80. });
  81. }
  82. await page.waitForTimeout(500);
  83. return hasData;
  84. }
  85. /**
  86. * 辅助函数:创建测试订单
  87. *
  88. * @param page - Playwright Page 对象
  89. * @param orderName - 订单名称
  90. * @param status - 订单状态(默认为草稿)
  91. * @returns 是否成功创建
  92. */
  93. async function createTestOrder(page: Page, orderName: string, status?: OrderStatus): Promise<boolean> {
  94. // 打开创建对话框
  95. const addOrderButton = page.getByTestId('create-order-button');
  96. await addOrderButton.click();
  97. await page.waitForSelector('[role="dialog"]', { state: 'visible', timeout: 5000 });
  98. // 填写必填字段
  99. await page.getByLabel(/订单名称|名称/).fill(orderName);
  100. await page.getByLabel(/预计开始日期|开始日期/).fill('2025-01-15');
  101. // 选择残疾人(必填)
  102. const hasDisabledPerson = await selectDisabledPersonForOrder(page);
  103. if (!hasDisabledPerson) {
  104. await page.keyboard.press('Escape');
  105. return false;
  106. }
  107. // 如果指定了非草稿状态,需要在编辑模式下修改(这里先创建草稿)
  108. // 提交表单
  109. const submitButton = page.getByRole('button', { name: /^(创建|更新|保存)$/ });
  110. await submitButton.click();
  111. // 等待网络请求完成
  112. try {
  113. await page.waitForLoadState('networkidle', { timeout: 5000 });
  114. } catch {
  115. console.debug('networkidle 超时,继续检查 Toast 消息');
  116. }
  117. await page.waitForTimeout(2000);
  118. // 检查成功消息
  119. const successToast = page.locator('[data-sonner-toast][data-type="success"]');
  120. const hasSuccess = await successToast.count() > 0;
  121. // 等待对话框关闭
  122. const dialog = page.locator('[role="dialog"]');
  123. await dialog.waitFor({ state: 'hidden', timeout: 5000 }).catch(() => {
  124. console.debug('对话框关闭超时,可能已经关闭');
  125. });
  126. return hasSuccess;
  127. }
  128. test.describe('订单状态流转测试', () => {
  129. test.describe('激活草稿订单', () => {
  130. let draftOrderName: string;
  131. test.beforeEach(async ({ adminLoginPage, orderManagementPage, testUsers }) => {
  132. // 以管理员身份登录后台
  133. await adminLoginPage.goto();
  134. await adminLoginPage.login(testUsers.admin.username, testUsers.admin.password);
  135. await adminLoginPage.expectLoginSuccess();
  136. await orderManagementPage.goto();
  137. // 创建一个草稿状态的测试订单
  138. const timestamp = Date.now();
  139. draftOrderName = `激活测试_${timestamp}`;
  140. const created = await createTestOrder(orderManagementPage.page, draftOrderName);
  141. if (!created) {
  142. test.skip(true, '没有残疾人数据,无法创建测试订单');
  143. }
  144. // 验证订单出现在列表中
  145. await expect(async () => {
  146. const exists = await orderManagementPage.orderExists(draftOrderName);
  147. expect(exists).toBe(true);
  148. }).toPass({ timeout: 5000 });
  149. // 验证初始状态为草稿
  150. const initialStatus = await orderManagementPage.getOrderStatus(draftOrderName);
  151. console.debug(`✓ 订单初始状态: ${initialStatus ? ORDER_STATUS_LABELS[initialStatus] : '未知'}`);
  152. });
  153. test('应该成功激活草稿订单', async ({ orderManagementPage }) => {
  154. // 执行激活操作
  155. const success = await orderManagementPage.activateOrder(draftOrderName);
  156. // 验证激活成功
  157. expect(success).toBe(true);
  158. // 验证状态从草稿变为进行中
  159. await expect(async () => {
  160. const currentStatus = await orderManagementPage.getOrderStatus(draftOrderName);
  161. expect(currentStatus).toBe(ORDER_STATUS.IN_PROGRESS);
  162. }).toPass({ timeout: 5000 });
  163. });
  164. test('激活后应该显示成功提示', async ({ orderManagementPage }) => {
  165. await orderManagementPage.activateOrder(draftOrderName);
  166. // 验证 Toast 成功消息
  167. const successToast = orderManagementPage.page.locator('[data-sonner-toast][data-type="success"]');
  168. await expect(successToast).toBeVisible();
  169. const message = await successToast.textContent();
  170. expect(message).toBeDefined();
  171. expect(message?.length).toBeGreaterThan(0);
  172. // 验证消息包含激活相关的关键词
  173. expect(message).toMatch(/激活|成功|已激活/);
  174. });
  175. test('激活确认对话框应该正确显示', async ({ orderManagementPage, page }) => {
  176. await orderManagementPage.openActivateDialog(draftOrderName);
  177. // 验证对话框可见
  178. const dialog = page.locator('[role="alertdialog"]');
  179. await expect(dialog).toBeVisible();
  180. // 验证确认按钮存在(支持多种可能的按钮名称)
  181. const confirmButton = page.locator('[role="alertdialog"]').getByRole('button', {
  182. name: /^(确认激活|激活|确定|确认)$/
  183. });
  184. await expect(confirmButton).toBeVisible();
  185. // 验证取消按钮存在
  186. const cancelButton = page.locator('[role="alertdialog"]').getByRole('button', { name: '取消' });
  187. await expect(cancelButton).toBeVisible();
  188. // 取消激活以清理
  189. await page.keyboard.press('Escape');
  190. });
  191. });
  192. test.describe('关闭进行中订单', () => {
  193. let inProgressOrderName: string;
  194. test.beforeEach(async ({ adminLoginPage, orderManagementPage, testUsers }) => {
  195. // 以管理员身份登录后台
  196. await adminLoginPage.goto();
  197. await adminLoginPage.login(testUsers.admin.username, testUsers.admin.password);
  198. await adminLoginPage.expectLoginSuccess();
  199. await orderManagementPage.goto();
  200. // 创建一个草稿订单然后激活为进行中状态
  201. const timestamp = Date.now();
  202. inProgressOrderName = `关闭测试_${timestamp}`;
  203. const created = await createTestOrder(orderManagementPage.page, inProgressOrderName);
  204. if (!created) {
  205. test.skip(true, '没有残疾人数据,无法创建测试订单');
  206. }
  207. // 验证订单出现在列表中
  208. await expect(async () => {
  209. const exists = await orderManagementPage.orderExists(inProgressOrderName);
  210. expect(exists).toBe(true);
  211. }).toPass({ timeout: 5000 });
  212. // 激活订单到进行中状态
  213. await orderManagementPage.activateOrder(inProgressOrderName);
  214. // 验证状态变为进行中
  215. await expect(async () => {
  216. const currentStatus = await orderManagementPage.getOrderStatus(inProgressOrderName);
  217. expect(currentStatus).toBe(ORDER_STATUS.IN_PROGRESS);
  218. }).toPass({ timeout: 5000 });
  219. console.debug(`✓ 订单已激活为进行中状态`);
  220. });
  221. test('应该成功关闭进行中订单', async ({ orderManagementPage }) => {
  222. // 执行关闭操作
  223. const success = await orderManagementPage.closeOrder(inProgressOrderName);
  224. // 验证关闭成功
  225. expect(success).toBe(true);
  226. // 验证状态从进行中变为已完成
  227. await expect(async () => {
  228. const currentStatus = await orderManagementPage.getOrderStatus(inProgressOrderName);
  229. expect(currentStatus).toBe(ORDER_STATUS.COMPLETED);
  230. }).toPass({ timeout: 5000 });
  231. });
  232. test('关闭后应该显示成功提示', async ({ orderManagementPage }) => {
  233. await orderManagementPage.closeOrder(inProgressOrderName);
  234. // 验证 Toast 成功消息
  235. const successToast = orderManagementPage.page.locator('[data-sonner-toast][data-type="success"]');
  236. await expect(successToast).toBeVisible();
  237. const message = await successToast.textContent();
  238. expect(message).toBeDefined();
  239. expect(message?.length).toBeGreaterThan(0);
  240. // 验证消息包含关闭相关的关键词
  241. expect(message).toMatch(/关闭|完成|成功|已关闭|已完成/);
  242. });
  243. test('关闭确认对话框应该正确显示', async ({ orderManagementPage, page }) => {
  244. await orderManagementPage.openCloseDialog(inProgressOrderName);
  245. // 验证对话框可见
  246. const dialog = page.locator('[role="alertdialog"]');
  247. await expect(dialog).toBeVisible();
  248. // 验证确认按钮存在(支持多种可能的按钮名称)
  249. const confirmButton = page.locator('[role="alertdialog"]').getByRole('button', {
  250. name: /^(确认关闭|关闭|确定|确认)$/
  251. });
  252. await expect(confirmButton).toBeVisible();
  253. // 验证取消按钮存在
  254. const cancelButton = page.locator('[role="alertdialog"]').getByRole('button', { name: '取消' });
  255. await expect(cancelButton).toBeVisible();
  256. // 取消关闭以清理
  257. await page.keyboard.press('Escape');
  258. });
  259. });
  260. test.describe('状态限制验证', () => {
  261. let testOrderName: string;
  262. let currentStatus: OrderStatus;
  263. let shouldSkipTests = false;
  264. test.beforeEach(async ({ adminLoginPage, orderManagementPage, testUsers }) => {
  265. // 以管理员身份登录后台
  266. await adminLoginPage.goto();
  267. await adminLoginPage.login(testUsers.admin.username, testUsers.admin.password);
  268. await adminLoginPage.expectLoginSuccess();
  269. await orderManagementPage.goto();
  270. // 尝试使用现有订单
  271. const existingOrder = await getFirstOrderName(orderManagementPage.page);
  272. if (existingOrder) {
  273. testOrderName = existingOrder;
  274. currentStatus = (await orderManagementPage.getOrderStatus(testOrderName)) || ORDER_STATUS.DRAFT;
  275. console.debug(`✓ 使用现有订单: ${testOrderName}, 状态: ${ORDER_STATUS_LABELS[currentStatus]}`);
  276. } else {
  277. // 如果没有现有订单,创建一个
  278. const timestamp = Date.now();
  279. testOrderName = `状态限制测试_${timestamp}`;
  280. const created = await createTestOrder(orderManagementPage.page, testOrderName);
  281. if (!created) {
  282. shouldSkipTests = true;
  283. test.skip(true, '没有残疾人数据,无法创建测试订单');
  284. return;
  285. }
  286. // 验证订单出现在列表中
  287. await expect(async () => {
  288. const exists = await orderManagementPage.orderExists(testOrderName);
  289. expect(exists).toBe(true);
  290. }).toPass({ timeout: 5000 });
  291. currentStatus = ORDER_STATUS.DRAFT;
  292. }
  293. });
  294. test('不能激活非草稿状态的订单', async ({ orderManagementPage }) => {
  295. if (shouldSkipTests) {
  296. test.skip(true, '测试数据不可用');
  297. return;
  298. }
  299. // 如果当前是草稿状态,先激活为进行中
  300. if (currentStatus === ORDER_STATUS.DRAFT) {
  301. await orderManagementPage.activateOrder(testOrderName);
  302. currentStatus = ORDER_STATUS.IN_PROGRESS;
  303. console.debug(`✓ 已激活订单用于测试`);
  304. }
  305. // 对于非草稿状态的订单,激活按钮应该被禁用或不可见
  306. const isActivatedEnabled = await orderManagementPage.isActivateButtonEnabled(testOrderName);
  307. // 根据当前状态验证
  308. if (currentStatus === ORDER_STATUS.IN_PROGRESS ||
  309. currentStatus === ORDER_STATUS.COMPLETED ||
  310. currentStatus === ORDER_STATUS.CONFIRMED) {
  311. // 非草稿状态不应该能激活
  312. expect(isActivatedEnabled).toBe(false);
  313. console.debug(`✓ 状态 ${ORDER_STATUS_LABELS[currentStatus]} 的订单不能激活`);
  314. }
  315. });
  316. test('不能关闭非进行中状态的订单', async ({ orderManagementPage }) => {
  317. if (shouldSkipTests) {
  318. test.skip(true, '测试数据不可用');
  319. return;
  320. }
  321. // 对于非进行中状态的订单,关闭按钮应该被禁用或不可见
  322. const isCloseEnabled = await orderManagementPage.isCloseButtonEnabled(testOrderName);
  323. // 根据当前状态验证
  324. if (currentStatus === ORDER_STATUS.DRAFT ||
  325. currentStatus === ORDER_STATUS.COMPLETED ||
  326. currentStatus === ORDER_STATUS.CONFIRMED) {
  327. // 非进行中状态不应该能关闭
  328. expect(isCloseEnabled).toBe(false);
  329. console.debug(`✓ 状态 ${ORDER_STATUS_LABELS[currentStatus]} 的订单不能关闭`);
  330. }
  331. });
  332. test('已完成状态不能再次操作', async ({ orderManagementPage }) => {
  333. if (shouldSkipTests) {
  334. test.skip(true, '测试数据不可用');
  335. return;
  336. }
  337. // 如果当前不是已完成状态,先操作到已完成
  338. if (currentStatus === ORDER_STATUS.DRAFT) {
  339. await orderManagementPage.activateOrder(testOrderName);
  340. await orderManagementPage.closeOrder(testOrderName);
  341. console.debug(`✓ 已将订单操作到已完成状态`);
  342. } else if (currentStatus === ORDER_STATUS.IN_PROGRESS) {
  343. await orderManagementPage.closeOrder(testOrderName);
  344. console.debug(`✓ 已关闭订单`);
  345. }
  346. // 验证订单状态为已完成
  347. await expect(async () => {
  348. const status = await orderManagementPage.getOrderStatus(testOrderName);
  349. expect(status).toBe(ORDER_STATUS.COMPLETED);
  350. }).toPass({ timeout: 5000 });
  351. // 已完成状态的订单不能激活也不能关闭
  352. const isActivateEnabled = await orderManagementPage.isActivateButtonEnabled(testOrderName);
  353. const isCloseEnabled = await orderManagementPage.isCloseButtonEnabled(testOrderName);
  354. expect(isActivateEnabled).toBe(false);
  355. expect(isCloseEnabled).toBe(false);
  356. console.debug(`✓ 已完成状态的订单不能激活或关闭`);
  357. });
  358. });
  359. test.describe('完整状态流转流程', () => {
  360. test('应该支持草稿→进行中→已完成的完整流转', async ({ adminLoginPage, orderManagementPage, testUsers }) => {
  361. // 以管理员身份登录后台
  362. await adminLoginPage.goto();
  363. await adminLoginPage.login(testUsers.admin.username, testUsers.admin.password);
  364. await adminLoginPage.expectLoginSuccess();
  365. await orderManagementPage.goto();
  366. // 创建草稿订单
  367. const timestamp = Date.now();
  368. const orderName = `完整流转测试_${timestamp}`;
  369. const created = await createTestOrder(orderManagementPage.page, orderName);
  370. if (!created) {
  371. test.skip(true, '没有残疾人数据,无法创建测试订单');
  372. }
  373. // 验证订单出现在列表中
  374. await expect(async () => {
  375. const exists = await orderManagementPage.orderExists(orderName);
  376. expect(exists).toBe(true);
  377. }).toPass({ timeout: 5000 });
  378. // 验证初始状态为草稿
  379. let currentStatus = await orderManagementPage.getOrderStatus(orderName);
  380. expect(currentStatus).toBe(ORDER_STATUS.DRAFT);
  381. console.debug(`✓ 初始状态: ${ORDER_STATUS_LABELS[ORDER_STATUS.DRAFT]}`);
  382. // 激活订单
  383. await orderManagementPage.activateOrder(orderName);
  384. currentStatus = await orderManagementPage.getOrderStatus(orderName);
  385. expect(currentStatus).toBe(ORDER_STATUS.IN_PROGRESS);
  386. console.debug(`✓ 激活后状态: ${ORDER_STATUS_LABELS[ORDER_STATUS.IN_PROGRESS]}`);
  387. // 关闭订单
  388. await orderManagementPage.closeOrder(orderName);
  389. currentStatus = await orderManagementPage.getOrderStatus(orderName);
  390. expect(currentStatus).toBe(ORDER_STATUS.COMPLETED);
  391. console.debug(`✓ 关闭后状态: ${ORDER_STATUS_LABELS[ORDER_STATUS.COMPLETED]}`);
  392. });
  393. });
  394. });