order-management.page.ts 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726
  1. import { Page, Locator } from '@playwright/test';
  2. import { selectRadixOption, selectRadixOptionAsync } from '@d8d/e2e-test-utils';
  3. /**
  4. * 订单状态常量
  5. */
  6. export const ORDER_STATUS = {
  7. DRAFT: 'draft',
  8. CONFIRMED: 'confirmed',
  9. IN_PROGRESS: 'in_progress',
  10. COMPLETED: 'completed',
  11. } as const;
  12. /**
  13. * 订单状态类型
  14. */
  15. export type OrderStatus = typeof ORDER_STATUS[keyof typeof ORDER_STATUS];
  16. /**
  17. * 订单状态显示名称映射
  18. */
  19. export const ORDER_STATUS_LABELS: Record<OrderStatus, string> = {
  20. draft: '草稿',
  21. confirmed: '已确认',
  22. in_progress: '进行中',
  23. completed: '已完成',
  24. } as const;
  25. /**
  26. * 工作状态常量
  27. */
  28. export const WORK_STATUS = {
  29. NOT_EMPLOYED: 'not_employed',
  30. PENDING: 'pending',
  31. EMPLOYED: 'employed',
  32. RESIGNED: 'resigned',
  33. } as const;
  34. /**
  35. * 工作状态类型
  36. */
  37. export type WorkStatus = typeof WORK_STATUS[keyof typeof WORK_STATUS];
  38. /**
  39. * 工作状态显示名称映射
  40. */
  41. export const WORK_STATUS_LABELS: Record<WorkStatus, string> = {
  42. not_employed: '未就业',
  43. pending: '待就业',
  44. employed: '已就业',
  45. resigned: '已离职',
  46. } as const;
  47. /**
  48. * 订单数据接口
  49. */
  50. export interface OrderData {
  51. /** 订单名称 */
  52. name: string;
  53. /** 预计开始日期 */
  54. expectedStartDate?: string;
  55. /** 平台ID */
  56. platformId?: number;
  57. /** 平台名称 */
  58. platformName?: string;
  59. /** 公司ID */
  60. companyId?: number;
  61. /** 公司名称 */
  62. companyName?: string;
  63. /** 渠道ID */
  64. channelId?: number;
  65. /** 渠道名称 */
  66. channelName?: string;
  67. /** 订单状态 */
  68. status?: OrderStatus;
  69. /** 工作状态 */
  70. workStatus?: WorkStatus;
  71. }
  72. /**
  73. * 订单人员数据接口
  74. */
  75. export interface OrderPersonData {
  76. /** 残疾人ID */
  77. disabledPersonId: number;
  78. /** 残疾人姓名 */
  79. disabledPersonName?: string;
  80. /** 入职日期 */
  81. hireDate?: string;
  82. /** 薪资 */
  83. salary?: number;
  84. /** 工作状态 */
  85. workStatus?: WorkStatus;
  86. /** 实际入职日期 */
  87. actualHireDate?: string;
  88. /** 离职日期 */
  89. resignDate?: string;
  90. }
  91. /**
  92. * 网络响应数据接口
  93. */
  94. export interface NetworkResponse {
  95. /** 请求URL */
  96. url: string;
  97. /** 请求方法 */
  98. method: string;
  99. /** 响应状态码 */
  100. status: number;
  101. /** 是否成功 */
  102. ok: boolean;
  103. /** 响应头 */
  104. responseHeaders: Record<string, string>;
  105. /** 响应体 */
  106. responseBody: unknown;
  107. }
  108. /**
  109. * 表单提交结果接口
  110. */
  111. export interface FormSubmitResult {
  112. /** 提交是否成功 */
  113. success: boolean;
  114. /** 是否有错误 */
  115. hasError: boolean;
  116. /** 是否有成功消息 */
  117. hasSuccess: boolean;
  118. /** 错误消息 */
  119. errorMessage?: string;
  120. /** 成功消息 */
  121. successMessage?: string;
  122. /** 网络响应列表 */
  123. responses?: NetworkResponse[];
  124. }
  125. /**
  126. * 订单管理 Page Object
  127. *
  128. * 用于订单管理功能的 E2E 测试
  129. * 页面路径: /admin/orders(待确认)
  130. *
  131. * @example
  132. * ```typescript
  133. * const orderPage = new OrderManagementPage(page);
  134. * await orderPage.goto();
  135. * await orderPage.createOrder({ name: '测试订单' });
  136. * ```
  137. */
  138. export class OrderManagementPage {
  139. readonly page: Page;
  140. // ===== 页面级选择器 =====
  141. /** 页面标题 */
  142. readonly pageTitle: Locator;
  143. /** 新增订单按钮 */
  144. readonly addOrderButton: Locator;
  145. /** 订单列表表格 */
  146. readonly orderTable: Locator;
  147. /** 搜索输入框 */
  148. readonly searchInput: Locator;
  149. /** 搜索按钮 */
  150. readonly searchButton: Locator;
  151. constructor(page: Page) {
  152. this.page = page;
  153. // 初始化页面级选择器
  154. this.pageTitle = page.getByText('订单管理', { exact: true });
  155. this.addOrderButton = page.getByRole('button', { name: '新增订单', exact: true });
  156. this.orderTable = page.locator('table');
  157. this.searchInput = page.getByPlaceholder('搜索订单名称');
  158. this.searchButton = page.getByRole('button', { name: '搜索' });
  159. }
  160. // ===== 导航和基础验证 =====
  161. /**
  162. * 导航到订单管理页面
  163. */
  164. async goto() {
  165. await this.page.goto('/admin/orders');
  166. await this.page.waitForLoadState('domcontentloaded');
  167. // 等待页面标题出现
  168. await this.pageTitle.waitFor({ state: 'visible', timeout: 15000 });
  169. // 等待表格数据加载
  170. await this.page.waitForSelector('table tbody tr', { state: 'visible', timeout: 20000 });
  171. await this.expectToBeVisible();
  172. }
  173. /**
  174. * 验证页面关键元素可见
  175. */
  176. async expectToBeVisible() {
  177. await this.pageTitle.waitFor({ state: 'visible', timeout: 15000 });
  178. await this.addOrderButton.waitFor({ state: 'visible', timeout: 10000 });
  179. }
  180. // ===== 搜索和筛选功能 =====
  181. /**
  182. * 按订单名称搜索
  183. * @param name 订单名称
  184. */
  185. async searchByName(name: string) {
  186. await this.searchInput.fill(name);
  187. await this.searchButton.click();
  188. await this.page.waitForLoadState('networkidle');
  189. await this.page.waitForTimeout(1000);
  190. }
  191. /**
  192. * 打开高级筛选对话框
  193. */
  194. async openFilterDialog() {
  195. const filterButton = this.page.getByRole('button', { name: /筛选|高级筛选/ });
  196. await filterButton.click();
  197. await this.page.waitForSelector('[role="dialog"]', { state: 'visible', timeout: 5000 });
  198. }
  199. /**
  200. * 设置筛选条件
  201. * @param filters 筛选条件
  202. */
  203. async setFilters(filters: {
  204. status?: OrderStatus;
  205. workStatus?: WorkStatus;
  206. platformId?: number;
  207. platformName?: string;
  208. companyId?: number;
  209. companyName?: string;
  210. channelId?: number;
  211. channelName?: string;
  212. dateRange?: { start?: string; end?: string };
  213. }) {
  214. // 订单状态筛选
  215. if (filters.status || filters.workStatus) {
  216. const statusFilter = this.page.getByLabel(/订单状态|状态/);
  217. await statusFilter.click();
  218. const statusLabel = filters.status
  219. ? ORDER_STATUS_LABELS[filters.status]
  220. : undefined;
  221. if (statusLabel) {
  222. await this.page.getByRole('option', { name: statusLabel }).click();
  223. }
  224. }
  225. // 平台筛选
  226. if (filters.platformName) {
  227. await selectRadixOption(this.page, '平台', filters.platformName);
  228. }
  229. // 公司筛选
  230. if (filters.companyName) {
  231. await selectRadixOption(this.page, '公司', filters.companyName);
  232. }
  233. // 渠道筛选
  234. if (filters.channelName) {
  235. await selectRadixOption(this.page, '渠道', filters.channelName);
  236. }
  237. // 日期范围筛选
  238. if (filters.dateRange) {
  239. if (filters.dateRange.start) {
  240. const startDateInput = this.page.getByLabel(/开始日期|起始日期/);
  241. await startDateInput.fill(filters.dateRange.start);
  242. }
  243. if (filters.dateRange.end) {
  244. const endDateInput = this.page.getByLabel(/结束日期|截止日期/);
  245. await endDateInput.fill(filters.dateRange.end);
  246. }
  247. }
  248. }
  249. /**
  250. * 应用筛选条件
  251. */
  252. async applyFilters() {
  253. const applyButton = this.page.getByRole('button', { name: /应用|确定|筛选/ });
  254. await applyButton.click();
  255. await this.page.waitForLoadState('networkidle');
  256. await this.page.waitForTimeout(1000);
  257. }
  258. /**
  259. * 清空筛选条件
  260. */
  261. async clearFilters() {
  262. const clearButton = this.page.getByRole('button', { name: /重置|清空/ });
  263. await clearButton.click();
  264. await this.page.waitForTimeout(500);
  265. }
  266. // ===== 订单 CRUD 操作 =====
  267. /**
  268. * 打开创建订单对话框
  269. */
  270. async openCreateDialog() {
  271. await this.addOrderButton.click();
  272. await this.page.waitForSelector('[role="dialog"]', { state: 'visible', timeout: 5000 });
  273. }
  274. /**
  275. * 打开编辑订单对话框
  276. * @param orderName 订单名称
  277. */
  278. async openEditDialog(orderName: string) {
  279. // 找到订单行并点击编辑按钮
  280. const orderRow = this.orderTable.locator('tbody tr').filter({ hasText: orderName });
  281. const editButton = orderRow.getByRole('button', { name: '编辑' });
  282. await editButton.click();
  283. await this.page.waitForSelector('[role="dialog"]', { state: 'visible', timeout: 5000 });
  284. }
  285. /**
  286. * 打开删除确认对话框
  287. * @param orderName 订单名称
  288. */
  289. async openDeleteDialog(orderName: string) {
  290. // 找到订单行并点击删除按钮
  291. const orderRow = this.orderTable.locator('tbody tr').filter({ hasText: orderName });
  292. const deleteButton = orderRow.getByRole('button', { name: '删除' });
  293. await deleteButton.click();
  294. await this.page.waitForSelector('[role="alertdialog"]', { state: 'visible', timeout: 5000 });
  295. }
  296. /**
  297. * 填写订单表单
  298. * @param data 订单数据
  299. */
  300. async fillOrderForm(data: OrderData) {
  301. // 等待表单出现
  302. await this.page.waitForSelector('form', { state: 'visible', timeout: 5000 });
  303. // 填写订单名称
  304. if (data.name) {
  305. await this.page.getByLabel(/订单名称|名称/).fill(data.name);
  306. }
  307. // 填写预计开始日期
  308. if (data.expectedStartDate) {
  309. const dateInput = this.page.getByLabel(/预计开始日期|开始日期/);
  310. await dateInput.fill(data.expectedStartDate);
  311. }
  312. // 选择平台
  313. if (data.platformName) {
  314. await selectRadixOption(this.page, '平台', data.platformName);
  315. }
  316. // 选择公司
  317. if (data.companyName) {
  318. await selectRadixOption(this.page, '公司', data.companyName);
  319. }
  320. // 选择渠道
  321. if (data.channelName) {
  322. await selectRadixOption(this.page, '渠道', data.channelName);
  323. }
  324. // 选择订单状态(如果是编辑模式)
  325. if (data.status) {
  326. const statusLabel = ORDER_STATUS_LABELS[data.status];
  327. await selectRadixOption(this.page, '订单状态', statusLabel);
  328. }
  329. // 选择工作状态(如果是编辑模式)
  330. if (data.workStatus) {
  331. const workStatusLabel = WORK_STATUS_LABELS[data.workStatus];
  332. await selectRadixOption(this.page, '工作状态', workStatusLabel);
  333. }
  334. }
  335. /**
  336. * 提交表单
  337. * @returns 表单提交结果
  338. */
  339. async submitForm(): Promise<FormSubmitResult> {
  340. // 收集网络响应
  341. const responses: NetworkResponse[] = [];
  342. // 监听所有网络请求
  343. const responseHandler = async (response: Response) => {
  344. const url = response.url();
  345. // 监听订单管理相关的 API 请求
  346. if (url.includes('/orders') || url.includes('order')) {
  347. const requestBody = response.request()?.postData();
  348. const responseBody = await response.text().catch(() => '');
  349. let jsonBody = null;
  350. try {
  351. jsonBody = JSON.parse(responseBody);
  352. } catch {
  353. // 不是 JSON 响应
  354. }
  355. responses.push({
  356. url,
  357. method: response.request()?.method() ?? 'UNKNOWN',
  358. status: response.status(),
  359. ok: response.ok(),
  360. responseHeaders: await response.allHeaders().catch(() => ({})),
  361. responseBody: jsonBody || responseBody,
  362. });
  363. }
  364. };
  365. this.page.on('response', responseHandler);
  366. // 点击提交按钮(创建或更新)
  367. const submitButton = this.page.getByRole('button', { name: /^(创建|更新|保存)$/ });
  368. await submitButton.click();
  369. // 等待网络请求完成
  370. await this.page.waitForLoadState('networkidle', { timeout: 10000 });
  371. // 移除监听器
  372. this.page.off('response', responseHandler);
  373. // 等待 Toast 消息显示
  374. await this.page.waitForTimeout(2000);
  375. // 检查 Toast 消息
  376. const errorToast = this.page.locator('[data-sonner-toast][data-type="error"]');
  377. const successToast = this.page.locator('[data-sonner-toast][data-type="success"]');
  378. const hasError = await errorToast.count() > 0;
  379. const hasSuccess = await successToast.count() > 0;
  380. let errorMessage: string | null = null;
  381. let successMessage: string | null = null;
  382. if (hasError) {
  383. errorMessage = await errorToast.first().textContent();
  384. }
  385. if (hasSuccess) {
  386. successMessage = await successToast.first().textContent();
  387. }
  388. return {
  389. success: hasSuccess || (!hasError && !hasSuccess),
  390. hasError,
  391. hasSuccess,
  392. errorMessage: errorMessage ?? undefined,
  393. successMessage: successMessage ?? undefined,
  394. responses,
  395. };
  396. }
  397. /**
  398. * 取消对话框
  399. */
  400. async cancelDialog() {
  401. const cancelButton = this.page.getByRole('button', { name: '取消' });
  402. await cancelButton.click();
  403. await this.waitForDialogClosed();
  404. }
  405. /**
  406. * 等待对话框关闭
  407. */
  408. async waitForDialogClosed() {
  409. const dialog = this.page.locator('[role="dialog"]');
  410. await dialog.waitFor({ state: 'hidden', timeout: 5000 }).catch(() => {});
  411. await this.page.waitForTimeout(500);
  412. }
  413. /**
  414. * 确认删除操作
  415. */
  416. async confirmDelete() {
  417. const confirmButton = this.page.getByRole('button', { name: /^确认删除$/ });
  418. await confirmButton.click();
  419. // 等待确认对话框关闭和网络请求完成
  420. await this.page.waitForSelector('[role="alertdialog"]', { state: 'hidden', timeout: 5000 }).catch(() => {});
  421. await this.page.waitForLoadState('networkidle', { timeout: 10000 });
  422. await this.page.waitForTimeout(1000);
  423. }
  424. /**
  425. * 取消删除操作
  426. */
  427. async cancelDelete() {
  428. const cancelButton = this.page.getByRole('button', { name: '取消' }).and(
  429. this.page.locator('[role="alertdialog"]')
  430. );
  431. await cancelButton.click();
  432. await this.page.waitForSelector('[role="alertdialog"]', { state: 'hidden', timeout: 5000 }).catch(() => {});
  433. }
  434. /**
  435. * 验证订单是否存在
  436. * @param orderName 订单名称
  437. * @returns 订单是否存在
  438. */
  439. async orderExists(orderName: string): Promise<boolean> {
  440. const orderRow = this.orderTable.locator('tbody tr').filter({ hasText: orderName });
  441. return (await orderRow.count()) > 0;
  442. }
  443. // ===== 订单详情 =====
  444. /**
  445. * 打开订单详情对话框
  446. * @param orderName 订单名称
  447. */
  448. async openDetailDialog(orderName: string) {
  449. // 找到订单行并点击查看详情按钮
  450. const orderRow = this.orderTable.locator('tbody tr').filter({ hasText: orderName });
  451. const detailButton = orderRow.getByRole('button', { name: /详情|查看/ });
  452. await detailButton.click();
  453. await this.page.waitForSelector('[role="dialog"]', { state: 'visible', timeout: 5000 });
  454. }
  455. /**
  456. * 获取订单详情中的基本信息
  457. * @returns 订单基本信息
  458. */
  459. async getOrderDetailInfo(): Promise<{
  460. name?: string;
  461. status?: string;
  462. workStatus?: string;
  463. expectedStartDate?: string;
  464. platform?: string;
  465. company?: string;
  466. channel?: string;
  467. }> {
  468. const result: Record<string, string> = {};
  469. // 订单名称
  470. const nameElement = this.page.locator('[role="dialog"]').getByText(/订单名称/);
  471. if (await nameElement.count() > 0) {
  472. result.name = await nameElement.textContent();
  473. }
  474. // 订单状态
  475. const statusElement = this.page.locator('[role="dialog"]').getByText(/订单状态/);
  476. if (await statusElement.count() > 0) {
  477. result.status = await statusElement.textContent();
  478. }
  479. // 工作状态
  480. const workStatusElement = this.page.locator('[role="dialog"]').getByText(/工作状态/);
  481. if (await workStatusElement.count() > 0) {
  482. result.workStatus = await workStatusElement.textContent();
  483. }
  484. return result;
  485. }
  486. // ===== 人员关联管理 =====
  487. /**
  488. * 打开人员管理对话框
  489. * @param orderName 订单名称(如果在订单列表页)
  490. */
  491. async openPersonManagementDialog(orderName?: string) {
  492. // 如果提供了订单名称,先找到对应的订单行
  493. if (orderName) {
  494. const orderRow = this.orderTable.locator('tbody tr').filter({ hasText: orderName });
  495. const personButton = orderRow.getByRole('button', { name: /人员|员工/ });
  496. await personButton.click();
  497. } else {
  498. // 如果在详情页,直接点击人员管理按钮
  499. const personButton = this.page.getByRole('button', { name: /人员管理|添加人员/ });
  500. await personButton.click();
  501. }
  502. await this.page.waitForSelector('[role="dialog"]', { state: 'visible', timeout: 5000 });
  503. }
  504. /**
  505. * 添加人员到订单
  506. * @param personData 人员数据
  507. */
  508. async addPersonToOrder(personData: OrderPersonData) {
  509. // 点击添加人员按钮
  510. const addButton = this.page.getByRole('button', { name: /添加人员|新增人员/ });
  511. await addButton.click();
  512. await this.page.waitForTimeout(300);
  513. // 选择残疾人
  514. if (personData.disabledPersonName) {
  515. await selectRadixOption(this.page, '残疾人|选择残疾人', personData.disabledPersonName);
  516. }
  517. // 填写入职日期
  518. if (personData.hireDate) {
  519. const hireDateInput = this.page.getByLabel(/入职日期/);
  520. await hireDateInput.fill(personData.hireDate);
  521. }
  522. // 填写薪资
  523. if (personData.salary !== undefined) {
  524. const salaryInput = this.page.getByLabel(/薪资|工资/);
  525. await salaryInput.fill(String(personData.salary));
  526. }
  527. // 选择工作状态
  528. if (personData.workStatus) {
  529. const workStatusLabel = WORK_STATUS_LABELS[personData.workStatus];
  530. await selectRadixOption(this.page, '工作状态', workStatusLabel);
  531. }
  532. // 提交
  533. const submitButton = this.page.getByRole('button', { name: /^(添加|确定|保存)$/ });
  534. await submitButton.click();
  535. await this.page.waitForLoadState('networkidle');
  536. await this.page.waitForTimeout(1000);
  537. }
  538. /**
  539. * 修改人员工作状态
  540. * @param personName 人员姓名
  541. * @param newStatus 新的工作状态
  542. */
  543. async updatePersonWorkStatus(personName: string, newStatus: WorkStatus) {
  544. // 找到人员行
  545. const personRow = this.page.locator('[role="dialog"]').locator('table tbody tr').filter({ hasText: personName });
  546. // 点击编辑工作状态按钮
  547. const editButton = personRow.getByRole('button', { name: /编辑|修改/ });
  548. await editButton.click();
  549. await this.page.waitForTimeout(300);
  550. // 选择新的工作状态
  551. const workStatusLabel = WORK_STATUS_LABELS[newStatus];
  552. await selectRadixOption(this.page, '工作状态', workStatusLabel);
  553. // 提交
  554. const submitButton = this.page.getByRole('button', { name: /^(更新|保存|确定)$/ });
  555. await submitButton.click();
  556. await this.page.waitForLoadState('networkidle');
  557. await this.page.waitForTimeout(1000);
  558. }
  559. // ===== 附件管理 =====
  560. /**
  561. * 打开添加附件对话框
  562. */
  563. async openAddAttachmentDialog() {
  564. const attachmentButton = this.page.getByRole('button', { name: /添加附件|上传附件/ });
  565. await attachmentButton.click();
  566. await this.page.waitForSelector('[role="dialog"]', { state: 'visible', timeout: 5000 });
  567. }
  568. /**
  569. * 上传附件
  570. * @param personName 人员姓名
  571. * @param fileName 文件名
  572. * @param mimeType 文件类型(默认为 image/jpeg)
  573. */
  574. async uploadAttachment(personName: string, fileName: string, mimeType: string = 'image/jpeg') {
  575. // 选择订单人员
  576. const personSelect = this.page.getByLabel(/选择人员|订单人员/);
  577. await personSelect.click();
  578. await this.page.getByRole('option', { name: personName }).click();
  579. // 查找文件上传输入框
  580. const fileInput = this.page.locator('input[type="file"]');
  581. await fileInput.setInputFiles({
  582. name: fileName,
  583. mimeType,
  584. buffer: Buffer.from(`fake ${fileName} content`),
  585. });
  586. // 等待上传处理
  587. await this.page.waitForTimeout(500);
  588. // 提交
  589. const submitButton = this.page.getByRole('button', { name: /^(上传|确定|保存)$/ });
  590. await submitButton.click();
  591. await this.page.waitForLoadState('networkidle');
  592. await this.page.waitForTimeout(1000);
  593. }
  594. // ===== 高级操作 =====
  595. /**
  596. * 创建订单(完整流程)
  597. * @param data 订单数据
  598. * @returns 表单提交结果
  599. */
  600. async createOrder(data: OrderData): Promise<FormSubmitResult> {
  601. await this.openCreateDialog();
  602. await this.fillOrderForm(data);
  603. const result = await this.submitForm();
  604. await this.waitForDialogClosed();
  605. return result;
  606. }
  607. /**
  608. * 编辑订单(完整流程)
  609. * @param orderName 订单名称
  610. * @param data 更新的订单数据
  611. * @returns 表单提交结果
  612. */
  613. async editOrder(orderName: string, data: OrderData): Promise<FormSubmitResult> {
  614. await this.openEditDialog(orderName);
  615. await this.fillOrderForm(data);
  616. const result = await this.submitForm();
  617. await this.waitForDialogClosed();
  618. return result;
  619. }
  620. /**
  621. * 删除订单(完整流程)
  622. * @param orderName 订单名称
  623. * @returns 是否成功删除
  624. */
  625. async deleteOrder(orderName: string): Promise<boolean> {
  626. await this.openDeleteDialog(orderName);
  627. await this.confirmDelete();
  628. // 等待并检查 Toast 消息
  629. await this.page.waitForTimeout(1000);
  630. const successToast = this.page.locator('[data-sonner-toast][data-type="success"]');
  631. const hasSuccess = await successToast.count() > 0;
  632. return hasSuccess;
  633. }
  634. }