user-management.page.ts 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. import { TIMEOUTS } from '../../utils/timeouts';
  2. import { Page, Locator, expect } from '@playwright/test';
  3. export class UserManagementPage {
  4. readonly page: Page;
  5. readonly pageTitle: Locator;
  6. readonly createUserButton: Locator;
  7. readonly searchInput: Locator;
  8. readonly searchButton: Locator;
  9. readonly userTable: Locator;
  10. readonly editButtons: Locator;
  11. readonly deleteButtons: Locator;
  12. readonly pagination: Locator;
  13. constructor(page: Page) {
  14. this.page = page;
  15. this.pageTitle = page.getByRole('heading', { name: '用户管理' });
  16. this.createUserButton = page.getByRole('button', { name: '创建用户' });
  17. this.searchInput = page.getByPlaceholder('搜索用户名、昵称或邮箱...');
  18. this.searchButton = page.getByRole('button', { name: '搜索' });
  19. this.userTable = page.locator('table');
  20. this.editButtons = page.locator('button').filter({ hasText: '编辑' });
  21. this.deleteButtons = page.locator('button').filter({ hasText: '删除' });
  22. this.pagination = page.locator('[data-slot="pagination"]');
  23. }
  24. async goto() {
  25. // 直接导航到用户管理页面
  26. await this.page.goto('/admin/users');
  27. // 等待页面完全加载 - 使用更可靠的等待条件
  28. // 先等待domcontentloaded,然后等待表格数据加载
  29. await this.page.waitForLoadState('domcontentloaded');
  30. // 等待用户表格出现,使用更具体的等待条件
  31. await this.page.waitForSelector('h1:has-text("用户管理")', { state: 'visible', timeout: TIMEOUTS.PAGE_LOAD });
  32. // 等待表格数据加载完成,而不是等待所有网络请求
  33. await this.page.waitForSelector('table tbody tr', { state: 'visible', timeout: TIMEOUTS.PAGE_LOAD_LONG });
  34. await this.expectToBeVisible();
  35. }
  36. async expectToBeVisible() {
  37. // 等待页面完全加载,使用更精确的选择器
  38. await expect(this.pageTitle).toBeVisible({ timeout: TIMEOUTS.PAGE_LOAD });
  39. await expect(this.createUserButton).toBeVisible({ timeout: TIMEOUTS.TABLE_LOAD });
  40. // 等待至少一行用户数据加载完成
  41. await expect(this.userTable.locator('tbody tr').first()).toBeVisible({ timeout: TIMEOUTS.PAGE_LOAD_LONG });
  42. }
  43. async searchUsers(keyword: string) {
  44. await this.searchInput.fill(keyword);
  45. await this.searchButton.click();
  46. await this.page.waitForLoadState('networkidle');
  47. }
  48. async createUser(userData: {
  49. username: string;
  50. password: string;
  51. nickname?: string;
  52. email?: string;
  53. phone?: string;
  54. name?: string;
  55. }) {
  56. await this.createUserButton.click();
  57. // 填写用户表单
  58. await this.page.getByLabel('用户名').fill(userData.username);
  59. await this.page.getByLabel('密码').fill(userData.password);
  60. if (userData.nickname) {
  61. await this.page.getByLabel('昵称').fill(userData.nickname);
  62. }
  63. if (userData.email) {
  64. await this.page.getByLabel('邮箱').fill(userData.email);
  65. }
  66. if (userData.phone) {
  67. await this.page.getByLabel('手机号').fill(userData.phone);
  68. }
  69. if (userData.name) {
  70. await this.page.getByLabel('真实姓名').fill(userData.name);
  71. }
  72. // 提交表单 - 使用模态框中的创建按钮
  73. await this.page.locator('[role="dialog"]').getByRole('button', { name: '创建用户' }).click();
  74. await this.page.waitForLoadState('networkidle');
  75. // 等待用户创建结果提示 - 成功或失败
  76. try {
  77. await Promise.race([
  78. this.page.waitForSelector('text=创建成功', { timeout: TIMEOUTS.TABLE_LOAD }),
  79. this.page.waitForSelector('text=创建失败', { timeout: TIMEOUTS.TABLE_LOAD })
  80. ]);
  81. // 检查是否有错误提示
  82. const errorVisible = await this.page.locator('text=创建失败').isVisible().catch(() => false);
  83. if (errorVisible) {
  84. // 如果是创建失败,不需要刷新页面
  85. return;
  86. }
  87. // 如果是创建成功,刷新页面
  88. await this.page.waitForTimeout(TIMEOUTS.LONG);
  89. await this.page.reload();
  90. await this.page.waitForLoadState('networkidle');
  91. await this.expectToBeVisible();
  92. } catch (error) {
  93. // 如果没有提示出现,继续执行
  94. console.log('创建操作没有显示提示信息,继续执行');
  95. await this.page.reload();
  96. await this.page.waitForLoadState('networkidle');
  97. await this.expectToBeVisible();
  98. }
  99. }
  100. async getUserCount(): Promise<number> {
  101. const rows = await this.userTable.locator('tbody tr').count();
  102. return rows;
  103. }
  104. async getUserByUsername(username: string): Promise<Locator | null> {
  105. const userRow = this.userTable.locator('tbody tr').filter({ hasText: username }).first();
  106. return (await userRow.count()) > 0 ? userRow : null;
  107. }
  108. async userExists(username: string): Promise<boolean> {
  109. const userRow = this.userTable.locator('tbody tr').filter({ hasText: username }).first();
  110. return (await userRow.count()) > 0;
  111. }
  112. async editUser(username: string, updates: {
  113. nickname?: string;
  114. email?: string;
  115. phone?: string;
  116. name?: string;
  117. }) {
  118. const userRow = await this.getUserByUsername(username);
  119. if (!userRow) throw new Error(`User ${username} not found`);
  120. // 编辑按钮是图标按钮,使用按钮定位(第一个按钮是编辑,第二个是删除)
  121. const editButton = userRow.locator('button').first();
  122. await editButton.waitFor({ state: 'visible', timeout: TIMEOUTS.TABLE_LOAD });
  123. await editButton.click();
  124. // 等待编辑模态框出现
  125. await this.page.waitForSelector('[role="dialog"]', { state: 'visible', timeout: TIMEOUTS.TABLE_LOAD });
  126. // 更新字段
  127. if (updates.nickname) {
  128. await this.page.getByLabel('昵称').fill(updates.nickname);
  129. }
  130. if (updates.email) {
  131. await this.page.getByLabel('邮箱').fill(updates.email);
  132. }
  133. if (updates.phone) {
  134. await this.page.getByLabel('手机号').fill(updates.phone);
  135. }
  136. if (updates.name) {
  137. await this.page.getByLabel('真实姓名').fill(updates.name);
  138. }
  139. // 提交更新
  140. await this.page.locator('[role="dialog"]').getByRole('button', { name: '更新用户' }).click();
  141. await this.page.waitForLoadState('networkidle');
  142. // 等待操作完成
  143. await this.page.waitForTimeout(TIMEOUTS.LONG);
  144. }
  145. async deleteUser(username: string) {
  146. const userRow = await this.getUserByUsername(username);
  147. if (!userRow) throw new Error(`User ${username} not found`);
  148. // 删除按钮是图标按钮,使用按钮定位(第二个按钮是删除)
  149. const deleteButton = userRow.locator('button').nth(1);
  150. await deleteButton.waitFor({ state: 'visible', timeout: TIMEOUTS.TABLE_LOAD });
  151. await deleteButton.click();
  152. // 确认删除对话框
  153. await this.page.getByRole('button', { name: '删除' }).click();
  154. // 等待删除操作完成 - 等待成功提示或错误提示
  155. try {
  156. // 等待成功提示或错误提示出现
  157. await Promise.race([
  158. this.page.waitForSelector('text=删除成功', { timeout: TIMEOUTS.TABLE_LOAD }),
  159. this.page.waitForSelector('text=删除失败', { timeout: TIMEOUTS.TABLE_LOAD })
  160. ]);
  161. // 检查是否有错误提示
  162. const errorVisible = await this.page.locator('text=删除失败').isVisible().catch(() => false);
  163. if (errorVisible) {
  164. throw new Error('删除操作失败:前端显示删除失败提示');
  165. }
  166. } catch (error) {
  167. // 如果没有提示出现,继续执行(可能是静默删除)
  168. console.log('删除操作没有显示提示信息,继续执行');
  169. }
  170. // 刷新页面确认用户是否被删除
  171. await this.page.reload();
  172. await this.page.waitForLoadState('networkidle');
  173. await this.expectToBeVisible();
  174. }
  175. async expectUserExists(username: string) {
  176. const exists = await this.userExists(username);
  177. expect(exists).toBe(true);
  178. }
  179. async expectUserNotExists(username: string) {
  180. const exists = await this.userExists(username);
  181. expect(exists).toBe(false);
  182. }
  183. }