activity-management.page.ts 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. import { Page, Locator, expect } from '@playwright/test';
  2. export class ActivityManagementPage {
  3. readonly page: Page;
  4. readonly pageTitle: Locator;
  5. readonly createActivityButton: Locator;
  6. readonly searchInput: Locator;
  7. readonly searchButton: Locator;
  8. readonly activityTable: Locator;
  9. readonly editButtons: Locator;
  10. readonly deleteButtons: Locator;
  11. readonly statusToggleButtons: Locator;
  12. readonly pagination: Locator;
  13. readonly typeFilter: Locator;
  14. constructor(page: Page) {
  15. this.page = page;
  16. this.pageTitle = page.locator('[data-testid="activity-management-title"]');
  17. this.createActivityButton = page.locator('[data-testid="create-activity-button"]');
  18. this.searchInput = page.locator('[data-testid="activity-search-input"]');
  19. this.searchButton = page.getByRole('button', { name: '搜索' });
  20. this.activityTable = page.locator('[data-testid="activity-table"]');
  21. this.editButtons = page.locator('[data-testid^="edit-activity-"]');
  22. this.deleteButtons = page.locator('[data-testid^="delete-activity-"]');
  23. this.statusToggleButtons = page.locator('[data-testid^="toggle-activity-"]');
  24. this.pagination = page.locator('[data-slot="pagination"]');
  25. this.typeFilter = page.locator('[data-testid="activity-type-filter"]');
  26. }
  27. async goto() {
  28. // 直接导航到活动管理页面
  29. await this.page.goto('/admin/activities');
  30. // 等待页面完全加载
  31. await this.page.waitForLoadState('domcontentloaded');
  32. // 等待活动管理标题出现
  33. await this.page.waitForSelector('h1:has-text("活动管理")', { state: 'visible', timeout: 15000 });
  34. // 等待表格数据加载完成
  35. await this.page.waitForSelector('table tbody tr', { state: 'visible', timeout: 20000 });
  36. await this.expectToBeVisible();
  37. }
  38. async expectToBeVisible() {
  39. // 等待页面完全加载
  40. await expect(this.pageTitle).toBeVisible({ timeout: 15000 });
  41. await expect(this.createActivityButton).toBeVisible({ timeout: 10000 });
  42. // 等待至少一行活动数据加载完成
  43. await expect(this.activityTable.locator('tbody tr').first()).toBeVisible({ timeout: 20000 });
  44. }
  45. async searchActivities(keyword: string) {
  46. await this.searchInput.fill(keyword);
  47. await this.searchButton.click();
  48. await this.page.waitForLoadState('networkidle');
  49. }
  50. async filterByType(type: '去程活动' | '返程活动') {
  51. await this.typeFilter.click();
  52. await this.page.getByRole('option', { name: type }).click();
  53. await this.page.waitForLoadState('networkidle');
  54. }
  55. async createActivity(activityData: {
  56. name: string;
  57. description?: string;
  58. type: '去程活动' | '返程活动';
  59. startDate: string;
  60. endDate: string;
  61. }) {
  62. await this.createActivityButton.click();
  63. // 填写活动表单
  64. await this.page.getByLabel('活动名称').fill(activityData.name);
  65. if (activityData.description) {
  66. await this.page.getByLabel('活动描述').fill(activityData.description);
  67. }
  68. // 选择活动类型
  69. await this.page.getByLabel('活动类型').click();
  70. await this.page.getByRole('option', { name: activityData.type }).click();
  71. // 填写开始日期和结束日期
  72. await this.page.getByLabel('开始日期').fill(activityData.startDate);
  73. await this.page.getByLabel('结束日期').fill(activityData.endDate);
  74. // 提交表单 - 使用模态框中的创建按钮
  75. await this.page.locator('[role="dialog"]').getByRole('button', { name: '创建活动' }).click();
  76. await this.page.waitForLoadState('networkidle');
  77. // 等待活动创建结果提示
  78. try {
  79. await Promise.race([
  80. this.page.waitForSelector('text=创建成功', { timeout: 10000 }),
  81. this.page.waitForSelector('text=创建失败', { timeout: 10000 })
  82. ]);
  83. // 检查是否有错误提示
  84. const errorVisible = await this.page.locator('text=创建失败').isVisible().catch(() => false);
  85. if (errorVisible) {
  86. return;
  87. }
  88. // 如果是创建成功,刷新页面
  89. await this.page.waitForTimeout(1000);
  90. await this.page.reload();
  91. await this.page.waitForLoadState('networkidle');
  92. await this.expectToBeVisible();
  93. } catch (error) {
  94. // 如果没有提示出现,继续执行
  95. console.log('创建操作没有显示提示信息,继续执行');
  96. await this.page.reload();
  97. await this.page.waitForLoadState('networkidle');
  98. await this.expectToBeVisible();
  99. }
  100. }
  101. async getActivityCount(): Promise<number> {
  102. const rows = await this.activityTable.locator('tbody tr').count();
  103. return rows;
  104. }
  105. async getActivityByName(name: string): Promise<Locator | null> {
  106. const activityRow = this.activityTable.locator('tbody tr').filter({ hasText: name }).first();
  107. return (await activityRow.count()) > 0 ? activityRow : null;
  108. }
  109. async activityExists(name: string): Promise<boolean> {
  110. const activityRow = this.activityTable.locator('tbody tr').filter({ hasText: name }).first();
  111. return (await activityRow.count()) > 0;
  112. }
  113. async editActivity(name: string, updates: {
  114. name?: string;
  115. description?: string;
  116. type?: '去程活动' | '返程活动';
  117. startDate?: string;
  118. endDate?: string;
  119. }) {
  120. const activityRow = await this.getActivityByName(name);
  121. if (!activityRow) throw new Error(`Activity ${name} not found`);
  122. // 编辑按钮是图标按钮
  123. const editButton = activityRow.locator('button').first();
  124. await editButton.waitFor({ state: 'visible', timeout: 10000 });
  125. await editButton.click();
  126. // 等待编辑模态框出现
  127. await this.page.waitForSelector('[role="dialog"]', { state: 'visible', timeout: 10000 });
  128. // 更新字段
  129. if (updates.name) {
  130. await this.page.getByLabel('活动名称').fill(updates.name);
  131. }
  132. if (updates.description) {
  133. await this.page.getByLabel('活动描述').fill(updates.description);
  134. }
  135. if (updates.type) {
  136. await this.page.getByLabel('活动类型').click();
  137. await this.page.getByRole('option', { name: updates.type }).click();
  138. }
  139. if (updates.startDate) {
  140. await this.page.getByLabel('开始日期').fill(updates.startDate);
  141. }
  142. if (updates.endDate) {
  143. await this.page.getByLabel('结束日期').fill(updates.endDate);
  144. }
  145. // 提交更新
  146. await this.page.locator('[role="dialog"]').getByRole('button', { name: '更新活动' }).click();
  147. await this.page.waitForLoadState('networkidle');
  148. // 等待操作完成
  149. await this.page.waitForTimeout(1000);
  150. }
  151. async deleteActivity(name: string) {
  152. const activityRow = await this.getActivityByName(name);
  153. if (!activityRow) throw new Error(`Activity ${name} not found`);
  154. // 删除按钮是图标按钮(第二个按钮是删除)
  155. const deleteButton = activityRow.locator('button').nth(1);
  156. await deleteButton.waitFor({ state: 'visible', timeout: 10000 });
  157. await deleteButton.click();
  158. // 确认删除对话框
  159. await this.page.getByRole('button', { name: '删除' }).click();
  160. // 等待删除操作完成
  161. try {
  162. await Promise.race([
  163. this.page.waitForSelector('text=删除成功', { timeout: 10000 }),
  164. this.page.waitForSelector('text=删除失败', { timeout: 10000 })
  165. ]);
  166. const errorVisible = await this.page.locator('text=删除失败').isVisible().catch(() => false);
  167. if (errorVisible) {
  168. throw new Error('删除操作失败:前端显示删除失败提示');
  169. }
  170. } catch (error) {
  171. console.log('删除操作没有显示提示信息,继续执行');
  172. }
  173. // 刷新页面确认活动是否被删除
  174. await this.page.reload();
  175. await this.page.waitForLoadState('networkidle');
  176. await this.expectToBeVisible();
  177. }
  178. async toggleActivityStatus(name: string) {
  179. const activityRow = await this.getActivityByName(name);
  180. if (!activityRow) throw new Error(`Activity ${name} not found`);
  181. // 状态切换按钮(第三个按钮)
  182. const statusButton = activityRow.locator('button').nth(2);
  183. await statusButton.waitFor({ state: 'visible', timeout: 10000 });
  184. const currentStatus = await statusButton.textContent();
  185. await statusButton.click();
  186. // 确认状态切换对话框
  187. await this.page.getByRole('button', { name: '确认' }).click();
  188. // 等待操作完成
  189. await this.page.waitForLoadState('networkidle');
  190. await this.page.waitForTimeout(1000);
  191. return currentStatus;
  192. }
  193. async expectActivityExists(name: string) {
  194. const exists = await this.activityExists(name);
  195. expect(exists).toBe(true);
  196. }
  197. async expectActivityNotExists(name: string) {
  198. const exists = await this.activityExists(name);
  199. expect(exists).toBe(false);
  200. }
  201. async getActivityStatus(name: string): Promise<string | null> {
  202. const activityRow = await this.getActivityByName(name);
  203. if (!activityRow) return null;
  204. const statusButton = activityRow.locator('button').nth(2);
  205. return await statusButton.textContent();
  206. }
  207. }