e2ad1331cc
feat(测试): 新增Playwright和Vitest测试配置 feat(测试): 添加测试覆盖率报告生成功能 feat(测试): 实现前后端测试脚本集成 fix(测试): 修复测试密码不匹配问题 fix(测试): 修正URL等待策略 fix(测试): 调整错误消息选择器 refactor(测试): 重构测试目录结构 refactor(测试): 优化测试用例组织方式 docs: 更新测试报告文档 docs: 添加测试覆盖率报告模板 ci: 添加Docker测试环境配置 ci: 实现测试自动化脚本 chore: 更新依赖版本 chore: 添加测试相关配置文件
408 lines
15 KiB
TypeScript
408 lines
15 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
import { LoginPage } from './pages/LoginPage';
|
|
import { DashboardPage } from './pages/DashboardPage';
|
|
import { TestHelper } from './utils/testHelper';
|
|
|
|
test.describe('认证异常场景测试', () => {
|
|
let loginPage: LoginPage;
|
|
let dashboardPage: DashboardPage;
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
loginPage = new LoginPage(page);
|
|
dashboardPage = new DashboardPage(page);
|
|
await loginPage.goto();
|
|
});
|
|
|
|
test.afterEach(async ({ page }) => {
|
|
await TestHelper.clearAllStorage(page);
|
|
});
|
|
|
|
test('登录失败 - 用户名为空', async ({ page }) => {
|
|
await test.step('尝试使用空用户名登录', async () => {
|
|
await loginPage.usernameInput.fill('');
|
|
await loginPage.passwordInput.fill('admin123');
|
|
await loginPage.loginButton.click();
|
|
});
|
|
|
|
await test.step('验证错误提示', async () => {
|
|
await TestHelper.waitForElementVisible(page, '.el-form-item__error');
|
|
const errorMessage = await TestHelper.getElementText(page, '.el-form-item__error');
|
|
expect(errorMessage).toBeTruthy();
|
|
});
|
|
});
|
|
|
|
test('登录失败 - 密码为空', async ({ page }) => {
|
|
await test.step('尝试使用空密码登录', async () => {
|
|
await loginPage.usernameInput.fill('admin');
|
|
await loginPage.passwordInput.fill('');
|
|
await loginPage.loginButton.click();
|
|
});
|
|
|
|
await test.step('验证错误提示', async () => {
|
|
await TestHelper.waitForElementVisible(page, '.el-form-item__error');
|
|
const errorMessage = await TestHelper.getElementText(page, '.el-form-item__error');
|
|
expect(errorMessage).toBeTruthy();
|
|
});
|
|
});
|
|
|
|
test('登录失败 - 用户名和密码都为空', async ({ page }) => {
|
|
await test.step('尝试使用空用户名和密码登录', async () => {
|
|
await loginPage.usernameInput.fill('');
|
|
await loginPage.passwordInput.fill('');
|
|
await loginPage.loginButton.click();
|
|
});
|
|
|
|
await test.step('验证错误提示', async () => {
|
|
const errorMessages = await page.locator('.el-form-item__error').all();
|
|
expect(errorMessages.length).toBeGreaterThan(0);
|
|
});
|
|
});
|
|
|
|
test('登录失败 - 用户名不存在', async ({ page }) => {
|
|
await test.step('尝试使用不存在的用户名登录', async () => {
|
|
await loginPage.usernameInput.fill('nonexistentuser123456');
|
|
await loginPage.passwordInput.fill('admin123');
|
|
await loginPage.loginButton.click();
|
|
});
|
|
|
|
await test.step('验证错误消息', async () => {
|
|
await TestHelper.waitForErrorMessage(page);
|
|
const errorMessage = await TestHelper.getElementText(page, '.el-message__content');
|
|
expect(errorMessage).toContain('用户名或密码错误');
|
|
});
|
|
});
|
|
|
|
test('登录失败 - 密码错误', async ({ page }) => {
|
|
await test.step('尝试使用错误的密码登录', async () => {
|
|
await loginPage.usernameInput.fill('admin');
|
|
await loginPage.passwordInput.fill('wrongpassword');
|
|
await loginPage.loginButton.click();
|
|
});
|
|
|
|
await test.step('验证错误消息', async () => {
|
|
await TestHelper.waitForErrorMessage(page);
|
|
const errorMessage = await TestHelper.getElementText(page, '.el-message__content');
|
|
expect(errorMessage).toContain('用户名或密码错误');
|
|
});
|
|
});
|
|
|
|
test('登录失败 - 账户被锁定', async ({ page }) => {
|
|
await test.step('连续多次登录失败', async () => {
|
|
for (let i = 0; i < 5; i++) {
|
|
await loginPage.usernameInput.fill('admin');
|
|
await loginPage.passwordInput.fill('wrongpassword');
|
|
await loginPage.loginButton.click();
|
|
await page.waitForTimeout(1000);
|
|
|
|
await loginPage.usernameInput.fill('');
|
|
await loginPage.passwordInput.fill('');
|
|
}
|
|
});
|
|
|
|
await test.step('验证账户锁定提示', async () => {
|
|
await loginPage.usernameInput.fill('admin');
|
|
await loginPage.passwordInput.fill('admin123');
|
|
await loginPage.loginButton.click();
|
|
|
|
await TestHelper.waitForErrorMessage(page);
|
|
const errorMessage = await TestHelper.getElementText(page, '.el-message__content');
|
|
expect(errorMessage).toContain('账户已被锁定');
|
|
});
|
|
});
|
|
|
|
test('登录失败 - 账户被禁用', async ({ page, request }) => {
|
|
await test.step('禁用admin账户', async () => {
|
|
await request.put('http://localhost:8084/api/users/admin/status', {
|
|
data: { status: '0' }
|
|
});
|
|
});
|
|
|
|
await test.step('尝试使用被禁用的账户登录', async () => {
|
|
await loginPage.usernameInput.fill('admin');
|
|
await loginPage.passwordInput.fill('admin123');
|
|
await loginPage.loginButton.click();
|
|
});
|
|
|
|
await test.step('验证账户禁用提示', async () => {
|
|
await TestHelper.waitForErrorMessage(page);
|
|
const errorMessage = await TestHelper.getElementText(page, '.el-message__content');
|
|
expect(errorMessage).toContain('账户已被禁用');
|
|
});
|
|
|
|
await test.step('恢复admin账户状态', async () => {
|
|
await request.put('http://localhost:8084/api/users/admin/status', {
|
|
data: { status: '1' }
|
|
});
|
|
});
|
|
});
|
|
|
|
test('登录失败 - Token过期', async ({ page }) => {
|
|
await test.step('正常登录', async () => {
|
|
await loginPage.usernameInput.fill('admin');
|
|
await loginPage.passwordInput.fill('admin123');
|
|
await loginPage.loginButton.click();
|
|
await TestHelper.waitForUrl(page, /.*dashboard/);
|
|
});
|
|
|
|
await test.step('设置过期的Token', async () => {
|
|
await TestHelper.setLocalStorage(page, 'token', 'expired_token_123456');
|
|
await TestHelper.setLocalStorage(page, 'token_expires', '0');
|
|
});
|
|
|
|
await test.step('刷新页面验证Token过期', async () => {
|
|
await page.reload();
|
|
await TestHelper.waitForPageLoad(page);
|
|
});
|
|
|
|
await test.step('验证自动跳转到登录页面', async () => {
|
|
await TestHelper.waitForUrl(page, /.*login/);
|
|
await expect(page).toHaveURL(/.*login/);
|
|
});
|
|
});
|
|
|
|
test('登录失败 - 无效的Token格式', async ({ page }) => {
|
|
await test.step('设置无效的Token', async () => {
|
|
await TestHelper.setLocalStorage(page, 'token', 'invalid_token_format');
|
|
});
|
|
|
|
await test.step('尝试访问需要认证的页面', async () => {
|
|
await page.goto('/users');
|
|
await TestHelper.waitForPageLoad(page);
|
|
});
|
|
|
|
await test.step('验证自动跳转到登录页面', async () => {
|
|
await TestHelper.waitForUrl(page, /.*login/);
|
|
await expect(page).toHaveURL(/.*login/);
|
|
});
|
|
});
|
|
|
|
test('登出失败 - Token已失效', async ({ page }) => {
|
|
await test.step('正常登录', async () => {
|
|
await loginPage.usernameInput.fill('admin');
|
|
await loginPage.passwordInput.fill('admin123');
|
|
await loginPage.loginButton.click();
|
|
await TestHelper.waitForUrl(page, /.*dashboard/);
|
|
});
|
|
|
|
await test.step('清除Token', async () => {
|
|
await TestHelper.clearLocalStorage(page);
|
|
});
|
|
|
|
await test.step('尝试登出', async () => {
|
|
const avatar = page.locator('.el-avatar');
|
|
if (await avatar.count() > 0) {
|
|
await avatar.click();
|
|
await TestHelper.waitForElementVisible(page, '.el-dropdown-menu');
|
|
|
|
const logoutButton = page.locator('.el-dropdown-menu').getByText('退出登录');
|
|
if (await logoutButton.count() > 0) {
|
|
await logoutButton.click();
|
|
}
|
|
}
|
|
});
|
|
|
|
await test.step('验证跳转到登录页面', async () => {
|
|
await TestHelper.waitForUrl(page, /.*login/);
|
|
await expect(page).toHaveURL(/.*login/);
|
|
});
|
|
});
|
|
|
|
test('登录成功 - 记住我功能', async ({ page }) => {
|
|
await test.step('启用记住我功能并登录', async () => {
|
|
await loginPage.usernameInput.fill('admin');
|
|
await loginPage.passwordInput.fill('admin123');
|
|
|
|
const rememberMeCheckbox = page.locator('.remember-me-checkbox');
|
|
if (await rememberMeCheckbox.count() > 0) {
|
|
await rememberMeCheckbox.check();
|
|
}
|
|
|
|
await loginPage.loginButton.click();
|
|
await TestHelper.waitForUrl(page, /.*dashboard/);
|
|
});
|
|
|
|
await test.step('验证Token持久化', async () => {
|
|
const token = await TestHelper.getLocalStorage(page, 'token');
|
|
expect(token).toBeTruthy();
|
|
|
|
const rememberMe = await TestHelper.getLocalStorage(page, 'remember_me');
|
|
expect(rememberMe).toBe('true');
|
|
});
|
|
});
|
|
|
|
test('登录成功 - 自动填充上次登录用户名', async ({ page }) => {
|
|
await test.step('首次登录', async () => {
|
|
await loginPage.usernameInput.fill('testuser');
|
|
await loginPage.passwordInput.fill('testpassword');
|
|
await loginPage.loginButton.click();
|
|
await TestHelper.waitForUrl(page, /.*dashboard/);
|
|
});
|
|
|
|
await test.step('登出', async () => {
|
|
await loginPage.logout();
|
|
await TestHelper.waitForUrl(page, /.*login/);
|
|
});
|
|
|
|
await test.step('验证自动填充上次登录用户名', async () => {
|
|
const usernameInput = page.locator('input[placeholder*="用户名"]');
|
|
const usernameValue = await usernameInput.inputValue();
|
|
expect(usernameValue).toBe('testuser');
|
|
});
|
|
});
|
|
|
|
test('登录失败 - SQL注入攻击', async ({ page }) => {
|
|
await test.step('尝试SQL注入攻击', async () => {
|
|
const sqlInjection = "' OR '1'='1";
|
|
await loginPage.usernameInput.fill(sqlInjection);
|
|
await loginPage.passwordInput.fill('admin123');
|
|
await loginPage.loginButton.click();
|
|
});
|
|
|
|
await test.step('验证登录失败', async () => {
|
|
await TestHelper.waitForErrorMessage(page);
|
|
const currentUrl = page.url();
|
|
expect(currentUrl).toContain('/login');
|
|
});
|
|
});
|
|
|
|
test('登录失败 - XSS攻击', async ({ page }) => {
|
|
await test.step('尝试XSS攻击', async () => {
|
|
const xssAttack = '<script>alert("XSS")</script>';
|
|
await loginPage.usernameInput.fill(xssAttack);
|
|
await loginPage.passwordInput.fill('admin123');
|
|
await loginPage.loginButton.click();
|
|
});
|
|
|
|
await test.step('验证XSS被过滤', async () => {
|
|
await TestHelper.waitForErrorMessage(page);
|
|
const currentUrl = page.url();
|
|
expect(currentUrl).toContain('/login');
|
|
|
|
const usernameInput = page.locator('input[placeholder*="用户名"]');
|
|
const usernameValue = await usernameInput.inputValue();
|
|
expect(usernameValue).not.toContain('<script>');
|
|
});
|
|
});
|
|
|
|
test('登录失败 - 暴力破解防护', async ({ page }) => {
|
|
await test.step('快速连续登录失败', async () => {
|
|
const loginAttempts = 10;
|
|
for (let i = 0; i < loginAttempts; i++) {
|
|
await loginPage.usernameInput.fill('admin');
|
|
await loginPage.passwordInput.fill(`wrongpassword${i}`);
|
|
await loginPage.loginButton.click();
|
|
await page.waitForTimeout(500);
|
|
|
|
await loginPage.usernameInput.fill('');
|
|
await loginPage.passwordInput.fill('');
|
|
}
|
|
});
|
|
|
|
await test.step('验证账户被临时锁定', async () => {
|
|
await loginPage.usernameInput.fill('admin');
|
|
await loginPage.passwordInput.fill('admin123');
|
|
await loginPage.loginButton.click();
|
|
|
|
await TestHelper.waitForErrorMessage(page);
|
|
const errorMessage = await TestHelper.getElementText(page, '.el-message__content');
|
|
expect(errorMessage).toContain('登录尝试次数过多');
|
|
});
|
|
});
|
|
|
|
test('登录失败 - 网络错误', async ({ page }) => {
|
|
await test.step('模拟网络错误', async () => {
|
|
await page.route('**/api/auth/login', route => route.abort('failed'));
|
|
});
|
|
|
|
await test.step('尝试登录', async () => {
|
|
await loginPage.usernameInput.fill('admin');
|
|
await loginPage.passwordInput.fill('admin123');
|
|
await loginPage.loginButton.click();
|
|
});
|
|
|
|
await test.step('验证网络错误提示', async () => {
|
|
await TestHelper.waitForErrorMessage(page);
|
|
const errorMessage = await TestHelper.getElementText(page, '.el-message__content');
|
|
expect(errorMessage).toContain('网络连接失败');
|
|
});
|
|
});
|
|
|
|
test('登录失败 - 服务器错误', async ({ page }) => {
|
|
await test.step('模拟服务器错误', async () => {
|
|
await page.route('**/api/auth/login', route => {
|
|
route.fulfill({
|
|
status: 500,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({ message: 'Internal Server Error' })
|
|
});
|
|
});
|
|
});
|
|
|
|
await test.step('尝试登录', async () => {
|
|
await loginPage.usernameInput.fill('admin');
|
|
await loginPage.passwordInput.fill('admin123');
|
|
await loginPage.loginButton.click();
|
|
});
|
|
|
|
await test.step('验证服务器错误提示', async () => {
|
|
await TestHelper.waitForErrorMessage(page);
|
|
const errorMessage = await TestHelper.getElementText(page, '.el-message__content');
|
|
expect(errorMessage).toContain('服务器错误');
|
|
});
|
|
});
|
|
|
|
test('登录成功 - 验证重定向保护', async ({ page }) => {
|
|
await test.step('访问受保护页面', async () => {
|
|
await page.goto('/users');
|
|
await TestHelper.waitForPageLoad(page);
|
|
});
|
|
|
|
await test.step('验证重定向到登录页面', async () => {
|
|
await TestHelper.waitForUrl(page, /.*login/);
|
|
await expect(page).toHaveURL(/.*login/);
|
|
});
|
|
|
|
await test.step('登录后验证重定向回原页面', async () => {
|
|
await loginPage.usernameInput.fill('admin');
|
|
await loginPage.passwordInput.fill('admin123');
|
|
await loginPage.loginButton.click();
|
|
|
|
await TestHelper.waitForUrl(page, /.*users/);
|
|
await expect(page).toHaveURL(/.*users/);
|
|
});
|
|
});
|
|
|
|
test('登录成功 - 验证会话管理', async ({ page, context }) => {
|
|
await test.step('正常登录', async () => {
|
|
await loginPage.usernameInput.fill('admin');
|
|
await loginPage.passwordInput.fill('admin123');
|
|
await loginPage.loginButton.click();
|
|
await TestHelper.waitForUrl(page, /.*dashboard/);
|
|
});
|
|
|
|
await test.step('验证Session Cookie存在', async () => {
|
|
const cookies = await context.cookies();
|
|
const sessionCookie = cookies.find(c => c.name === 'SESSION' || c.name === 'JSESSIONID');
|
|
expect(sessionCookie).toBeDefined();
|
|
});
|
|
|
|
await test.step('验证Token存储在localStorage', async () => {
|
|
const token = await TestHelper.getLocalStorage(page, 'token');
|
|
expect(token).toBeTruthy();
|
|
});
|
|
});
|
|
|
|
test('登录失败 - 验证CSRF保护', async ({ page }) => {
|
|
await test.step('检查CSRF Token', async () => {
|
|
const csrfToken = page.locator('input[name="csrf_token"]');
|
|
const hasCsrfToken = await csrfToken.count() > 0;
|
|
|
|
if (hasCsrfToken) {
|
|
const csrfValue = await csrfToken.inputValue();
|
|
expect(csrfValue).toBeTruthy();
|
|
expect(csrfValue.length).toBeGreaterThan(10);
|
|
}
|
|
});
|
|
});
|
|
});
|