/** * 密码验证器 E2E 测试 * * 测试真实用户场景: * 1. 用户注册时密码验证 * 2. 用户修改密码时的验证 * 3. 管理员重置用户密码 * * @tags @password @security @e2e @tdd */ import { test, expect, Page } from '@playwright/test'; import { TestLogger } from './core/test-logger.js'; interface PasswordValidationResult { isValid: boolean; errors: string[]; warnings: string[]; suggestions: string[]; strength: 'weak' | 'medium' | 'strong'; score: number; } /** * 密码验证器页面对象 */ class PasswordValidatorPage { private page: Page; private logger: TestLogger; constructor(page: Page, logger: TestLogger) { this.page = page; this.logger = logger; } /** * 导航到密码验证页面 */ async navigate() { this.logger.info('导航到密码验证页面'); await this.page.goto('http://localhost:5174/demo/password-validator'); await this.page.waitForLoadState('networkidle'); } /** * 输入密码 */ async enterPassword(password: string) { this.logger.info(`输入密码: ${'*'.repeat(password.length)}`); await this.page.fill('[data-testid="password-input"]', password); } /** * 输入确认密码 */ async enterConfirmPassword(password: string) { this.logger.info(`输入确认密码: ${'*'.repeat(password.length)}`); await this.page.fill('[data-testid="confirm-password-input"]', password); } /** * 点击验证按钮 */ async clickValidate() { this.logger.info('点击验证按钮'); await this.page.click('[data-testid="validate-button"]'); } /** * 获取验证结果 */ async getValidationResult(): Promise { this.logger.info('获取验证结果'); const isValid = await this.page.locator('[data-testid="validation-success"]').isVisible().catch(() => false); const strength = await this.page.locator('[data-testid="strength-indicator"]').getAttribute('data-strength') as 'weak' | 'medium' | 'strong'; const scoreText = await this.page.locator('[data-testid="score-value"]').textContent() || '0'; const errors = await this.page.locator('[data-testid="error-list"] li').allTextContents(); const warnings = await this.page.locator('[data-testid="warning-list"] li').allTextContents(); const suggestions = await this.page.locator('[data-testid="suggestion-list"] li').allTextContents(); return { isValid, errors: errors.filter(e => e.trim()), warnings: warnings.filter(w => w.trim()), suggestions: suggestions.filter(s => s.trim()), strength, score: parseInt(scoreText, 10) }; } /** * 获取密码强度文本 */ async getStrengthText(): Promise { return this.page.locator('[data-testid="strength-text"]').textContent() || ''; } /** * 检查是否有错误提示 */ async hasErrors(): Promise { const errorCount = await this.page.locator('[data-testid="error-list"] li').count(); return errorCount > 0; } /** * 清除表单 */ async clearForm() { this.logger.info('清除表单'); await this.page.fill('[data-testid="password-input"]', ''); await this.page.fill('[data-testid="confirm-password-input"]', ''); } } test.describe('E2E: 密码验证器 - 用户注册场景', () => { let page: PasswordValidatorPage; let logger: TestLogger; test.beforeEach(async ({ page: p }) => { logger = new TestLogger(); page = new PasswordValidatorPage(p, logger); await page.navigate(); }); test('用户应该能够使用强密码注册 @smoke @critical', async () => { // Given: 用户在注册页面 await expect(page['page'].locator('[data-testid="password-validator-form"]')).toBeVisible(); // When: 输入强密码 const strongPassword = 'MyS3cur3P@ssw0rd!'; await page.enterPassword(strongPassword); await page.enterConfirmPassword(strongPassword); await page.clickValidate(); // Then: 应该验证通过 const result = await page.getValidationResult(); expect(result.isValid).toBe(true); expect(result.strength).toBe('strong'); expect(result.score).toBeGreaterThanOrEqual(80); expect(result.errors).toHaveLength(0); }); test('应该拒绝弱密码并显示具体错误 @regression', async () => { // Given: 用户输入弱密码 const weakPassword = '123456'; await page.enterPassword(weakPassword); await page.clickValidate(); // Then: 应该显示多个错误 const result = await page.getValidationResult(); expect(result.isValid).toBe(false); expect(result.strength).toBe('weak'); expect(result.errors.length).toBeGreaterThan(0); expect(result.errors).toContain('密码长度至少为8位'); }); test('应该实时显示密码强度 @smoke', async () => { // When: 输入不同强度的密码 const testCases = [ { password: 'abc', expectedStrength: 'weak' }, { password: 'Abcdef1!', expectedStrength: 'medium' }, { password: 'MyS3cur3P@ssw0rd!2024', expectedStrength: 'strong' } ]; for (const testCase of testCases) { await page.clearForm(); await page.enterPassword(testCase.password); // Then: 应该显示对应的强度 const result = await page.getValidationResult(); expect(result.strength).toBe(testCase.expectedStrength); } }); test('应该检测密码不匹配 @regression', async () => { // Given: 用户输入不同的密码 await page.enterPassword('MyS3cur3P@ssw0rd!'); await page.enterConfirmPassword('DifferentP@ssw0rd!'); await page.clickValidate(); // Then: 应该显示不匹配错误 const result = await page.getValidationResult(); expect(result.isValid).toBe(false); expect(result.errors).toContain('两次输入的密码不一致'); }); test('应该提供密码改进建议 @regression', async () => { // Given: 用户输入需要改进的密码 await page.enterPassword('password'); await page.clickValidate(); // Then: 应该显示改进建议 const result = await page.getValidationResult(); expect(result.suggestions.length).toBeGreaterThan(0); expect(result.suggestions).toContain('添加大写字母'); expect(result.suggestions).toContain('添加数字'); expect(result.suggestions).toContain('添加特殊字符'); }); }); test.describe('E2E: 密码验证器 - 边界条件测试', () => { let page: PasswordValidatorPage; let logger: TestLogger; test.beforeEach(async ({ page: p }) => { logger = new TestLogger(); page = new PasswordValidatorPage(p, logger); await page.navigate(); }); test('应该接受最小长度(8位)的密码 @boundary', async () => { const minLengthPassword = 'Abcdef1!'; await page.enterPassword(minLengthPassword); await page.enterConfirmPassword(minLengthPassword); await page.clickValidate(); const result = await page.getValidationResult(); expect(result.isValid).toBe(true); }); test('应该拒绝小于最小长度的密码 @boundary', async () => { const shortPassword = 'Abcdef1'; await page.enterPassword(shortPassword); await page.clickValidate(); const result = await page.getValidationResult(); expect(result.isValid).toBe(false); expect(result.errors).toContain('密码长度至少为8位'); }); test('应该接受最大长度(128位)的密码 @boundary', async () => { const maxLengthPassword = 'A1!a' + 'b'.repeat(124); await page.enterPassword(maxLengthPassword); await page.enterConfirmPassword(maxLengthPassword); await page.clickValidate(); const result = await page.getValidationResult(); expect(result.isValid).toBe(true); }); test('应该拒绝超过最大长度的密码 @boundary', async () => { const tooLongPassword = 'A1!a' + 'b'.repeat(125); await page.enterPassword(tooLongPassword); await page.clickValidate(); const result = await page.getValidationResult(); expect(result.isValid).toBe(false); expect(result.errors).toContain('密码长度不能超过128位'); }); test('应该拒绝空密码 @boundary', async () => { await page.enterPassword(''); await page.clickValidate(); const result = await page.getValidationResult(); expect(result.isValid).toBe(false); expect(result.errors.length).toBeGreaterThan(0); }); }); test.describe('E2E: 密码验证器 - 安全测试', () => { let page: PasswordValidatorPage; let logger: TestLogger; test.beforeEach(async ({ page: p }) => { logger = new TestLogger(); page = new PasswordValidatorPage(p, logger); await page.navigate(); }); test('应该检测常见弱密码 @security', async () => { const commonPasswords = ['12345678', 'password', 'qwerty123', 'admin123']; for (const password of commonPasswords) { await page.clearForm(); await page.enterPassword(password); await page.clickValidate(); const result = await page.getValidationResult(); expect(result.isValid).toBe(false); expect(result.errors).toContain('密码过于常见,请使用更复杂的密码'); } }); test('应该检测连续字符 @security', async () => { const sequentialPassword = 'Abcdef1!'; await page.enterPassword(sequentialPassword); await page.clickValidate(); const result = await page.getValidationResult(); expect(result.warnings).toContain('密码包含连续字符,建议避免'); }); test('应该检测重复字符 @security', async () => { const repeatedPassword = 'AAAbbb1!'; await page.enterPassword(repeatedPassword); await page.clickValidate(); const result = await page.getValidationResult(); expect(result.warnings).toContain('密码包含重复字符,建议避免'); }); test('应该拒绝纯数字密码 @security', async () => { const numericPassword = '12345678'; await page.enterPassword(numericPassword); await page.clickValidate(); const result = await page.getValidationResult(); expect(result.isValid).toBe(false); expect(result.errors).toContain('密码必须包含至少一个大写字母'); expect(result.errors).toContain('密码必须包含至少一个小写字母'); expect(result.errors).toContain('密码必须包含至少一个特殊字符'); }); test('应该拒绝纯字母密码 @security', async () => { const alphaPassword = 'abcdefgh'; await page.enterPassword(alphaPassword); await page.clickValidate(); const result = await page.getValidationResult(); expect(result.isValid).toBe(false); expect(result.errors).toContain('密码必须包含至少一个大写字母'); expect(result.errors).toContain('密码必须包含至少一个数字'); expect(result.errors).toContain('密码必须包含至少一个特殊字符'); }); }); test.describe('E2E: 密码验证器 - 响应式测试', () => { let logger: TestLogger; test.beforeEach(async () => { logger = new TestLogger(); }); test('应该在桌面端正常显示 @responsive', async ({ page }) => { await page.setViewportSize({ width: 1920, height: 1080 }); const passwordPage = new PasswordValidatorPage(page, logger); await passwordPage.navigate(); await expect(page.locator('[data-testid="password-validator-form"]')).toBeVisible(); await expect(page.locator('[data-testid="password-input"]')).toBeVisible(); await expect(page.locator('[data-testid="strength-indicator"]')).toBeVisible(); }); test('应该在平板端正常显示 @responsive', async ({ page }) => { await page.setViewportSize({ width: 768, height: 1024 }); const passwordPage = new PasswordValidatorPage(page, logger); await passwordPage.navigate(); await expect(page.locator('[data-testid="password-validator-form"]')).toBeVisible(); }); test('应该在手机端正常显示 @responsive', async ({ page }) => { await page.setViewportSize({ width: 375, height: 667 }); const passwordPage = new PasswordValidatorPage(page, logger); await passwordPage.navigate(); await expect(page.locator('[data-testid="password-validator-form"]')).toBeVisible(); }); });