Explorar o código

✨ feat(test): 增强E2E测试功能

- 添加playwright命令到允许的Bash命令列表
- 扩展AdminLoginPage页面模型,增加更多UI元素定位器
- 添加expectToBeVisible()方法验证登录页面元素可见性
- 实现clone()方法支持跨页面实例共享

♻️ refactor(test): 优化登录测试用例

- 更新错误消息验证文本,使用更精确的"用户名或密码错误"
- 重命名loginButton为submitButton保持命名一致性
- 在DashboardPage添加logout()方法实现完整登录-登出流程测试
- 优化测试等待逻辑,提高测试稳定性
yourname hai 2 meses
pai
achega
47c60d20df

+ 3 - 1
.claude/settings.local.json

@@ -21,7 +21,9 @@
       "Bash(npm view:*)",
       "Bash(npm run)",
       "Bash(node debug-page.js:*)",
-      "Bash(psql:*)"
+      "Bash(psql:*)",
+      "Bash(npx playwright open:*)",
+      "Bash(npx playwright codegen:*)"
     ],
     "deny": [],
     "ask": []

+ 45 - 13
tests/e2e/pages/admin/auth/login.page.ts

@@ -4,15 +4,35 @@ export class AdminLoginPage {
   readonly page: Page;
   readonly usernameInput: Locator;
   readonly passwordInput: Locator;
-  readonly loginButton: Locator;
-  readonly errorMessage: Locator;
+  readonly submitButton: Locator;
+  readonly togglePasswordButton: Locator;
+  readonly pageTitle: Locator;
+  readonly welcomeText: Locator;
+  readonly successToast: Locator;
+  readonly errorToast: Locator;
+  readonly usernameError: Locator;
+  readonly passwordError: Locator;
+  readonly testAccountInfo: Locator;
+  readonly loadingSpinner: Locator;
+  readonly backgroundElement: Locator;
+  readonly loginCard: Locator;
 
   constructor(page: Page) {
     this.page = page;
     this.usernameInput = page.getByPlaceholder('请输入用户名');
     this.passwordInput = page.getByPlaceholder('请输入密码');
-    this.loginButton = page.getByRole('button', { name: '登录' });
-    this.errorMessage = page.locator('[data-sonner-toast]');
+    this.submitButton = page.getByRole('button', { name: '登录' });
+    this.togglePasswordButton = page.locator('button[aria-label*="密码"], button[aria-label*="显示"], button[aria-label*="隐藏"]');
+    this.pageTitle = page.getByRole('heading', { name: '管理后台登录' });
+    this.welcomeText = page.getByText('请输入您的账号和密码继续操作');
+    this.successToast = page.locator('[data-sonner-toast][data-type="success"]');
+    this.errorToast = page.locator('[data-sonner-toast][data-type="error"]');
+    this.usernameError = page.locator('text=请输入用户名');
+    this.passwordError = page.locator('text=请输入密码');
+    this.testAccountInfo = page.locator('text=测试账号:');
+    this.loadingSpinner = page.locator('[aria-busy="true"], .loading-spinner, .spinner');
+    this.backgroundElement = page.locator('main, .background, [class*="bg-"]').first();
+    this.loginCard = page.locator('.card, .login-card, .form-card').first();
   }
 
   async goto() {
@@ -20,23 +40,35 @@ export class AdminLoginPage {
     await this.page.waitForLoadState('networkidle');
   }
 
+
+  async expectLoginSuccess() {
+    // 登录成功后应该重定向到管理后台dashboard
+    await expect(this.page).toHaveURL('/admin/dashboard');
+    await expect(this.page.locator('text=登录成功')).toBeVisible();
+  }
+
+  async expectLoginError() {
+    await expect(this.errorToast).toBeVisible();
+  }
+
+  async expectToBeVisible() {
+    await expect(this.pageTitle).toBeVisible();
+    await expect(this.usernameInput).toBeVisible();
+    await expect(this.passwordInput).toBeVisible();
+    await expect(this.submitButton).toBeVisible();
+  }
+
   async login(username: string, password: string) {
     await this.usernameInput.fill(username);
     await this.passwordInput.fill(password);
-    await this.loginButton.click();
+    await this.submitButton.click();
 
     // 等待登录完成
     await this.page.waitForLoadState('networkidle');
     await this.page.waitForTimeout(2000);
   }
 
-  async expectLoginSuccess() {
-    // 登录成功后应该重定向到管理后台dashboard
-    await expect(this.page).toHaveURL('/admin/dashboard');
-    await expect(this.page.locator('text=登录成功')).toBeVisible();
-  }
-
-  async expectLoginError() {
-    await expect(this.errorMessage).toBeVisible();
+  clone(newPage: Page): AdminLoginPage {
+    return new AdminLoginPage(newPage);
   }
 }

+ 11 - 0
tests/e2e/pages/admin/dashboard.page.ts

@@ -43,4 +43,15 @@ export class DashboardPage {
   async getSystemMessagesCount(): Promise<string> {
     return await this.systemMessagesCard.locator('xpath=following-sibling::div//div[contains(@class, "text-2xl")]').textContent() || '';
   }
+
+  async logout() {
+    // 查找并点击登出按钮
+    const logoutButton = this.page.locator('button:has-text("登出"), button:has-text("退出"), button:has-text("Logout")');
+    await logoutButton.click();
+    await this.page.waitForLoadState('networkidle');
+  }
+
+  clone(newPage: Page): DashboardPage {
+    return new DashboardPage(newPage);
+  }
 }

+ 2 - 2
tests/e2e/specs/admin/login.spec.ts

@@ -31,7 +31,7 @@ test.describe('登录页面 E2E 测试', () => {
 
     // 验证错误消息显示
     await expect(adminLoginPage.errorToast).toBeVisible();
-    await expect(adminLoginPage.errorToast).toContainText('登录失败');
+    await expect(adminLoginPage.errorToast).toContainText('用户名或密码错误');
 
     // 验证仍然在登录页面
     await adminLoginPage.expectToBeVisible();
@@ -43,7 +43,7 @@ test.describe('登录页面 E2E 测试', () => {
 
     // 验证错误消息显示
     await expect(adminLoginPage.errorToast).toBeVisible();
-    await expect(adminLoginPage.errorToast).toContainText('登录失败');
+    await expect(adminLoginPage.errorToast).toContainText('用户名或密码错误');
 
     // 验证仍然在登录页面
     await adminLoginPage.expectToBeVisible();