Files
everything-is-suitable/everything-is-suitable-test/e2e/password-validator-e2e.spec.ts
T
张翔 08ea5fbe98 feat(admin): 添加用户管理相关文件
添加用户管理视图、API和状态管理文件
2026-03-28 14:37:29 +08:00

367 lines
12 KiB
TypeScript

/**
* 密码验证器 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<PasswordValidationResult> {
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<string> {
return this.page.locator('[data-testid="strength-text"]').textContent() || '';
}
/**
* 检查是否有错误提示
*/
async hasErrors(): Promise<boolean> {
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();
});
});