enterprise-mini.page.ts 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. import { TIMEOUTS } from '../../utils/timeouts';
  2. import { Page, Locator } from '@playwright/test';
  3. /**
  4. * 企业小程序 H5 URL
  5. */
  6. const MINI_BASE_URL = process.env.E2E_BASE_URL || 'http://localhost:8080';
  7. const MINI_LOGIN_URL = `${MINI_BASE_URL}/mini`;
  8. /**
  9. * 企业小程序 Page Object
  10. *
  11. * 用于企业小程序 E2E 测试
  12. * H5 页面路径: /mini
  13. *
  14. * 主要功能:
  15. * - 小程序登录(手机号 + 密码)
  16. * - Token 管理
  17. * - 页面导航和验证
  18. *
  19. * @example
  20. * ```typescript
  21. * const miniPage = new EnterpriseMiniPage(page);
  22. * await miniPage.goto();
  23. * await miniPage.login('13800138000', 'password123');
  24. * await miniPage.expectLoginSuccess();
  25. * ```
  26. */
  27. export class EnterpriseMiniPage {
  28. readonly page: Page;
  29. // ===== 页面级选择器 =====
  30. /** 登录页面容器 */
  31. readonly loginPage: Locator;
  32. /** 页面标题 */
  33. readonly pageTitle: Locator;
  34. // ===== 登录表单选择器 =====
  35. /** 手机号输入框 */
  36. readonly phoneInput: Locator;
  37. /** 密码输入框 */
  38. readonly passwordInput: Locator;
  39. /** 登录按钮 */
  40. readonly loginButton: Locator;
  41. // ===== 主页选择器(登录后) =====
  42. /** 用户信息显示区域 */
  43. readonly userInfo: Locator;
  44. constructor(page: Page) {
  45. this.page = page;
  46. // 初始化登录页面选择器(使用 data-testid)
  47. this.loginPage = page.getByTestId('mini-login-page');
  48. this.pageTitle = page.getByTestId('mini-page-title');
  49. // 登录表单选择器
  50. this.phoneInput = page.getByTestId('mini-phone-input');
  51. this.passwordInput = page.getByTestId('mini-password-input');
  52. this.loginButton = page.getByTestId('mini-login-button');
  53. // 主页选择器(登录后可用)
  54. // 用户信息显示 - 根据实际小程序主页结构调整
  55. this.userInfo = page.getByTestId('mini-user-info');
  56. }
  57. // ===== 导航和基础验证 =====
  58. /**
  59. * 导航到企业小程序 H5 登录页面
  60. */
  61. async goto(): Promise<void> {
  62. await this.page.goto(MINI_LOGIN_URL);
  63. // 使用 auto-waiting 机制,等待页面容器可见
  64. await this.expectToBeVisible();
  65. }
  66. /**
  67. * 验证登录页面关键元素可见
  68. */
  69. async expectToBeVisible(): Promise<void> {
  70. // 等待页面容器可见
  71. await this.loginPage.waitFor({ state: 'visible', timeout: TIMEOUTS.PAGE_LOAD });
  72. // 验证页面标题
  73. await this.pageTitle.waitFor({ state: 'visible', timeout: TIMEOUTS.ELEMENT_VISIBLE_SHORT });
  74. }
  75. // ===== 登录功能方法 =====
  76. /**
  77. * 填写手机号
  78. * @param phone 手机号(11位数字)
  79. */
  80. async fillPhone(phone: string): Promise<void> {
  81. await this.phoneInput.fill(phone);
  82. }
  83. /**
  84. * 填写密码
  85. * @param password 密码(6-20位)
  86. */
  87. async fillPassword(password: string): Promise<void> {
  88. await this.passwordInput.fill(password);
  89. }
  90. /**
  91. * 点击登录按钮
  92. */
  93. async clickLoginButton(): Promise<void> {
  94. await this.loginButton.click();
  95. }
  96. /**
  97. * 执行登录操作(完整流程)
  98. * @param phone 手机号
  99. * @param password 密码
  100. */
  101. async login(phone: string, password: string): Promise<void> {
  102. await this.fillPhone(phone);
  103. await this.fillPassword(password);
  104. await this.clickLoginButton();
  105. }
  106. /**
  107. * 验证登录成功
  108. *
  109. * 登录成功后应该跳转到主页或显示用户信息
  110. */
  111. async expectLoginSuccess(): Promise<void> {
  112. // 使用 auto-waiting 机制,等待 URL 变化或用户信息显示
  113. // 小程序登录成功后会跳转到 dashboard 页面
  114. // 等待 URL 变化,使用 Promise.race 实现超时
  115. await this.page.waitForURL(
  116. url => url.pathname.includes('/dashboard') || url.pathname.includes('/pages/yongren/dashboard'),
  117. { timeout: TIMEOUTS.PAGE_LOAD }
  118. ).catch(() => {
  119. // 如果没有跳转,检查是否显示用户信息
  120. // 注意:此验证将在 Story 12.5 E2E 测试中完全实现
  121. // 当前仅提供基础结构
  122. });
  123. }
  124. /**
  125. * 验证登录失败(错误提示显示)
  126. * @param expectedErrorMessage 预期的错误消息(可选)
  127. */
  128. async expectLoginError(expectedErrorMessage?: string): Promise<void> {
  129. // 使用 auto-waiting 机制,等待错误提示显示
  130. // Taro 的 showToast 会显示错误消息
  131. if (expectedErrorMessage) {
  132. // 等待包含错误消息的元素可见
  133. await this.page.getByText(expectedErrorMessage, { exact: false }).waitFor({
  134. state: 'visible',
  135. timeout: TIMEOUTS.TOAST_LONG
  136. });
  137. } else {
  138. // 验证至少有错误提示出现(Toast、Modal 或 Alert)
  139. const toastOrModal = this.page.getByRole('alert').or(
  140. this.page.locator('.taro-toast')
  141. ).or(
  142. this.page.locator('.taro-modal')
  143. );
  144. await toastOrModal.waitFor({ state: 'visible', timeout: TIMEOUTS.TOAST_LONG });
  145. }
  146. }
  147. // ===== Token 管理方法 =====
  148. /**
  149. * 获取当前存储的 token
  150. * @returns token 字符串,如果不存在则返回 null
  151. */
  152. async getToken(): Promise<string | null> {
  153. const token = await this.page.evaluate(() => {
  154. return (
  155. localStorage.getItem('token') ||
  156. localStorage.getItem('auth_token') ||
  157. sessionStorage.getItem('token') ||
  158. sessionStorage.getItem('auth_token') ||
  159. null
  160. );
  161. });
  162. return token;
  163. }
  164. /**
  165. * 设置 token(用于测试前置条件)
  166. * @param token token 字符串
  167. */
  168. async setToken(token: string): Promise<void> {
  169. await this.page.evaluate((t) => {
  170. localStorage.setItem('token', t);
  171. localStorage.setItem('auth_token', t);
  172. }, token);
  173. }
  174. /**
  175. * 清除所有认证相关的存储
  176. */
  177. async clearAuth(): Promise<void> {
  178. await this.page.evaluate(() => {
  179. localStorage.removeItem('token');
  180. localStorage.removeItem('auth_token');
  181. sessionStorage.removeItem('token');
  182. sessionStorage.removeItem('auth_token');
  183. });
  184. }
  185. // ===== 主页元素验证方法 =====
  186. /**
  187. * 验证主页元素可见(登录后)
  188. * 根据实际小程序主页结构调整
  189. */
  190. async expectHomePageVisible(): Promise<void> {
  191. // 使用 auto-waiting 机制,等待主页元素可见
  192. // 注意:此方法将在 Story 12.5 E2E 测试中使用,当前仅提供基础结构
  193. // 根据实际小程序主页的 data-testid 调整
  194. const dashboard = this.page.getByTestId('mini-dashboard');
  195. await dashboard.waitFor({ state: 'visible', timeout: TIMEOUTS.PAGE_LOAD });
  196. }
  197. /**
  198. * 获取用户信息显示的文本
  199. * @returns 用户信息文本
  200. */
  201. async getUserInfoText(): Promise<string | null> {
  202. const userInfo = this.userInfo;
  203. const count = await userInfo.count();
  204. if (count === 0) {
  205. return null;
  206. }
  207. return await userInfo.textContent();
  208. }
  209. }