e2ad1331cc
feat(测试): 新增Playwright和Vitest测试配置 feat(测试): 添加测试覆盖率报告生成功能 feat(测试): 实现前后端测试脚本集成 fix(测试): 修复测试密码不匹配问题 fix(测试): 修正URL等待策略 fix(测试): 调整错误消息选择器 refactor(测试): 重构测试目录结构 refactor(测试): 优化测试用例组织方式 docs: 更新测试报告文档 docs: 添加测试覆盖率报告模板 ci: 添加Docker测试环境配置 ci: 实现测试自动化脚本 chore: 更新依赖版本 chore: 添加测试相关配置文件
513 lines
17 KiB
TypeScript
513 lines
17 KiB
TypeScript
import { test, expect, Page } from '@playwright/test';
|
|
|
|
// 测试数据
|
|
const TEST_USERS = {
|
|
admin: {
|
|
username: 'admin',
|
|
password: 'admin123',
|
|
expectedRole: '超级管理员'
|
|
},
|
|
testUser: {
|
|
username: 'test_user',
|
|
password: 'test123',
|
|
expectedRole: '测试普通用户'
|
|
},
|
|
testAdmin: {
|
|
username: 'test_admin',
|
|
password: 'test123',
|
|
expectedRole: '测试管理员'
|
|
}
|
|
};
|
|
|
|
const BASE_URL = 'http://localhost:3003';
|
|
const API_BASE_URL = 'http://localhost:8084';
|
|
|
|
// 测试辅助函数
|
|
async function login(page: Page, username: string, password: string) {
|
|
await page.goto(`${BASE_URL}/login`);
|
|
await page.fill('input[placeholder="请输入用户名"]', username);
|
|
await page.fill('input[placeholder="请输入密码"]', password);
|
|
await page.click('button:has-text("登录")');
|
|
await page.waitForURL(`${BASE_URL}/dashboard`);
|
|
}
|
|
|
|
async function waitForAPIResponse(page: Page, urlPattern: string) {
|
|
return page.waitForResponse(response =>
|
|
response.url().includes(urlPattern) && response.status() === 200
|
|
);
|
|
}
|
|
|
|
// TC-001: 完整登录流程
|
|
test.describe('TC-001: 完整登录流程', () => {
|
|
test('管理员登录成功并验证登录日志', async ({ page }) => {
|
|
await login(page, TEST_USERS.admin.username, TEST_USERS.admin.password);
|
|
|
|
// 验证登录成功,跳转到首页
|
|
await expect(page).toHaveURL(`${BASE_URL}/dashboard`);
|
|
// 验证Dashboard页面加载成功
|
|
await expect(page.locator('text=用户总数')).toBeVisible();
|
|
await expect(page.locator('text=角色总数')).toBeVisible();
|
|
|
|
// 验证登录日志记录
|
|
const loginLogResponse = await page.evaluate(async (apiBaseUrl) => {
|
|
const response = await fetch(`${apiBaseUrl}/api/auth/login-logs`);
|
|
return response.json();
|
|
}, API_BASE_URL);
|
|
|
|
expect(loginLogResponse.data).toBeDefined();
|
|
expect(loginLogResponse.data.length).toBeGreaterThan(0);
|
|
|
|
// 验证最新的登录日志
|
|
const latestLog = loginLogResponse.data[0];
|
|
expect(latestLog.username).toBe(TEST_USERS.admin.username);
|
|
expect(latestLog.browser).toContain('Chrome');
|
|
expect(latestLog.os).toContain('Mac OS X');
|
|
});
|
|
|
|
test('普通用户登录成功', async ({ page }) => {
|
|
await login(page, TEST_USERS.testUser.username, TEST_USERS.testUser.password);
|
|
|
|
await expect(page).toHaveURL(`${BASE_URL}/dashboard`);
|
|
await expect(page.locator('text=用户总数')).toBeVisible();
|
|
});
|
|
|
|
test('登录失败 - 错误密码', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/login`);
|
|
await page.fill('input[placeholder="请输入用户名"]', TEST_USERS.admin.username);
|
|
await page.fill('input[placeholder="请输入密码"]', 'wrongpassword');
|
|
await page.click('button:has-text("登录")');
|
|
|
|
await expect(page.locator('text=用户名或密码错误')).toBeVisible();
|
|
});
|
|
|
|
test('登录失败 - 空用户名', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/login`);
|
|
await page.fill('input[placeholder="请输入密码"]', TEST_USERS.admin.password);
|
|
await page.click('button:has-text("登录")');
|
|
|
|
await expect(page.locator('text=请输入用户名')).toBeVisible();
|
|
});
|
|
|
|
test('登录失败 - 空密码', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/login`);
|
|
await page.fill('input[placeholder="请输入用户名"]', TEST_USERS.admin.username);
|
|
await page.click('button:has-text("登录")');
|
|
|
|
await expect(page.locator('text=请输入密码')).toBeVisible();
|
|
});
|
|
});
|
|
|
|
// TC-002: 角色管理完整流程
|
|
test.describe('TC-002: 角色管理完整流程', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await login(page, TEST_USERS.admin.username, TEST_USERS.admin.password);
|
|
});
|
|
|
|
test('查看角色列表 - 验证字段映射', async ({ page }) => {
|
|
await page.click('text=系统管理');
|
|
await page.click('text=角色管理');
|
|
|
|
// 等待角色列表加载
|
|
await page.waitForSelector('table');
|
|
|
|
// 验证角色列表显示正确的字段
|
|
await expect(page.locator('th:has-text("角色名称")')).toBeVisible();
|
|
await expect(page.locator('th:has-text("角色标识")')).toBeVisible();
|
|
await expect(page.locator('th:has-text("显示顺序")')).toBeVisible();
|
|
await expect(page.locator('th:has-text("状态")')).toBeVisible();
|
|
|
|
// 验证角色数据
|
|
const roles = await page.evaluate(async () => {
|
|
const response = await fetch(`${API_BASE_URL}/api/roles`);
|
|
const data = await response.json();
|
|
return data.data;
|
|
});
|
|
|
|
expect(roles).toBeDefined();
|
|
expect(roles.length).toBeGreaterThan(0);
|
|
|
|
// 验证字段映射正确性
|
|
const firstRole = roles[0];
|
|
expect(firstRole.roleName).toBeDefined();
|
|
expect(firstRole.roleKey).toBeDefined();
|
|
expect(firstRole.roleSort).toBeDefined();
|
|
expect(firstRole.status).toBeDefined();
|
|
|
|
// 验证不包含旧字段
|
|
expect(firstRole.name).toBeUndefined();
|
|
expect(firstRole.code).toBeUndefined();
|
|
expect(firstRole.description).toBeUndefined();
|
|
});
|
|
|
|
test('创建新角色', async ({ page }) => {
|
|
await page.click('text=系统管理');
|
|
await page.click('text=角色管理');
|
|
|
|
// 点击新建按钮
|
|
await page.click('button:has-text("新建")');
|
|
|
|
// 填写角色信息
|
|
const newRoleName = `测试角色_${Date.now()}`;
|
|
await page.fill('input[placeholder="角色名称"]', newRoleName);
|
|
await page.fill('input[placeholder="角色标识"]', `test_role_${Date.now()}`);
|
|
await page.fill('input[placeholder="显示顺序"]', '10');
|
|
|
|
// 提交表单
|
|
await page.click('button:has-text("确定")');
|
|
|
|
// 验证创建成功
|
|
await expect(page.locator('text=创建成功')).toBeVisible();
|
|
|
|
// 验证角色出现在列表中
|
|
await expect(page.locator(`text=${newRoleName}`)).toBeVisible();
|
|
});
|
|
|
|
test('编辑角色', async ({ page }) => {
|
|
await page.click('text=系统管理');
|
|
await page.click('text=角色管理');
|
|
|
|
// 等待列表加载
|
|
await page.waitForSelector('table');
|
|
|
|
// 点击第一个编辑按钮
|
|
const editButtons = await page.locator('button:has-text("编辑")').all();
|
|
await editButtons[0].click();
|
|
|
|
// 修改角色名称
|
|
const updatedRoleName = `更新角色_${Date.now()}`;
|
|
await page.fill('input[placeholder="角色名称"]', updatedRoleName);
|
|
|
|
// 提交修改
|
|
await page.click('button:has-text("确定")');
|
|
|
|
// 验证更新成功
|
|
await expect(page.locator('text=更新成功')).toBeVisible();
|
|
await expect(page.locator(`text=${updatedRoleName}`)).toBeVisible();
|
|
});
|
|
|
|
test('删除角色', async ({ page }) => {
|
|
await page.click('text=系统管理');
|
|
await page.click('text=角色管理');
|
|
|
|
// 等待列表加载
|
|
await page.waitForSelector('table');
|
|
|
|
// 点击删除按钮
|
|
const deleteButtons = await page.locator('button:has-text("删除")').all();
|
|
page.on('dialog', dialog => dialog.accept());
|
|
await deleteButtons[0].click();
|
|
|
|
// 验证删除成功
|
|
await expect(page.locator('text=删除成功')).toBeVisible();
|
|
});
|
|
});
|
|
|
|
// TC-003: 菜单管理数据验证
|
|
test.describe('TC-003: 菜单管理数据验证', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await login(page, TEST_USERS.admin.username, TEST_USERS.admin.password);
|
|
});
|
|
|
|
test('查看菜单树结构', async ({ page }) => {
|
|
await page.click('text=系统管理');
|
|
await page.click('text=菜单管理');
|
|
|
|
// 等待菜单树加载
|
|
await page.waitForSelector('.el-tree');
|
|
|
|
// 验证一级菜单
|
|
await expect(page.locator('text=系统管理')).toBeVisible();
|
|
await expect(page.locator('text=审计日志')).toBeVisible();
|
|
await expect(page.locator('text=系统监控')).toBeVisible();
|
|
|
|
// 验证二级菜单
|
|
await expect(page.locator('text=用户管理')).toBeVisible();
|
|
await expect(page.locator('text=角色管理')).toBeVisible();
|
|
await expect(page.locator('text=菜单管理')).toBeVisible();
|
|
await expect(page.locator('text=登录日志')).toBeVisible();
|
|
});
|
|
|
|
test('验证菜单字段映射', async ({ page }) => {
|
|
// 直接调用API验证字段
|
|
const menus = await page.evaluate(async () => {
|
|
const response = await fetch(`${API_BASE_URL}/api/menus`);
|
|
const data = await response.json();
|
|
return data.data;
|
|
});
|
|
|
|
expect(menus).toBeDefined();
|
|
expect(menus.length).toBeGreaterThan(0);
|
|
|
|
// 验证字段映射正确性
|
|
const firstMenu = menus[0];
|
|
expect(firstMenu.menuName).toBeDefined();
|
|
expect(firstMenu.menuType).toBeDefined();
|
|
expect(firstMenu.orderNum).toBeDefined();
|
|
expect(firstMenu.component).toBeDefined();
|
|
expect(firstMenu.perms).toBeDefined();
|
|
});
|
|
|
|
test('空数据处理', async ({ page }) => {
|
|
// 模拟空数据场景
|
|
await page.evaluate(async () => {
|
|
// 清空菜单数据(仅用于测试)
|
|
await fetch(`${API_BASE_URL}/api/menus/clear`, { method: 'DELETE' });
|
|
});
|
|
|
|
await page.click('text=系统管理');
|
|
await page.click('text=菜单管理');
|
|
|
|
// 验证显示空状态提示
|
|
await expect(page.locator('text=暂无数据')).toBeVisible();
|
|
});
|
|
});
|
|
|
|
// TC-004: 前后端字段映射一致性
|
|
test.describe('TC-004: 前后端字段映射一致性', () => {
|
|
test('验证角色API字段映射', async ({ page }) => {
|
|
const roles = await page.evaluate(async () => {
|
|
const response = await fetch(`${API_BASE_URL}/api/roles`);
|
|
const data = await response.json();
|
|
return data.data;
|
|
});
|
|
|
|
roles.forEach((role: any) => {
|
|
expect(role.roleName).toBeDefined();
|
|
expect(role.roleKey).toBeDefined();
|
|
expect(role.roleSort).toBeDefined();
|
|
expect(role.status).toBeDefined();
|
|
|
|
// 验证不包含旧字段
|
|
expect(role.name).toBeUndefined();
|
|
expect(role.code).toBeUndefined();
|
|
expect(role.description).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
test('验证菜单API字段映射', async ({ page }) => {
|
|
const menus = await page.evaluate(async () => {
|
|
const response = await fetch(`${API_BASE_URL}/api/menus`);
|
|
const data = await response.json();
|
|
return data.data;
|
|
});
|
|
|
|
menus.forEach((menu: any) => {
|
|
expect(menu.menuName).toBeDefined();
|
|
expect(menu.menuType).toBeDefined();
|
|
expect(menu.orderNum).toBeDefined();
|
|
expect(menu.component).toBeDefined();
|
|
expect(menu.perms).toBeDefined();
|
|
});
|
|
});
|
|
|
|
test('验证用户API字段映射', async ({ page }) => {
|
|
const users = await page.evaluate(async () => {
|
|
const response = await fetch(`${API_BASE_URL}/api/users`);
|
|
const data = await response.json();
|
|
return data.data;
|
|
});
|
|
|
|
users.forEach((user: any) => {
|
|
expect(user.username).toBeDefined();
|
|
expect(user.email).toBeDefined();
|
|
expect(user.status).toBeDefined();
|
|
});
|
|
});
|
|
});
|
|
|
|
// TC-005: RBAC权限验证
|
|
test.describe('TC-005: RBAC权限验证', () => {
|
|
test('管理员访问所有功能', async ({ page }) => {
|
|
await login(page, TEST_USERS.admin.username, TEST_USERS.admin.password);
|
|
|
|
// 验证管理员能访问所有菜单
|
|
await expect(page.locator('text=系统管理')).toBeVisible();
|
|
await expect(page.locator('text=审计日志')).toBeVisible();
|
|
await expect(page.locator('text=系统监控')).toBeVisible();
|
|
|
|
// 尝试访问各个功能
|
|
await page.click('text=系统管理');
|
|
await page.click('text=用户管理');
|
|
await expect(page).toHaveURL(`${BASE_URL}/system/user`);
|
|
|
|
await page.click('text=系统管理');
|
|
await page.click('text=角色管理');
|
|
await expect(page).toHaveURL(`${BASE_URL}/system/role`);
|
|
|
|
await page.click('text=审计日志');
|
|
await page.click('text=登录日志');
|
|
await expect(page).toHaveURL(`${BASE_URL}/audit/loginlog`);
|
|
});
|
|
|
|
test('普通用户权限限制', async ({ page }) => {
|
|
await login(page, TEST_USERS.testUser.username, TEST_USERS.testUser.password);
|
|
|
|
// 验证普通用户只能看到授权的菜单
|
|
await expect(page.locator('text=系统管理')).toBeVisible();
|
|
await expect(page.locator('text=用户管理')).toBeVisible();
|
|
|
|
// 尝试访问未授权功能
|
|
await page.goto(`${BASE_URL}/system/role`);
|
|
|
|
// 验证被拒绝访问
|
|
await expect(page.locator('text=权限不足')).toBeVisible();
|
|
});
|
|
|
|
test('未授权访问返回403', async ({ page }) => {
|
|
// 直接调用未授权API
|
|
const response = await page.evaluate(async () => {
|
|
try {
|
|
const response = await fetch(`${API_BASE_URL}/api/roles`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': 'Bearer invalid_token'
|
|
},
|
|
body: JSON.stringify({
|
|
roleName: 'Test Role',
|
|
roleKey: 'test_role',
|
|
roleSort: 1
|
|
})
|
|
});
|
|
return { status: response.status };
|
|
} catch (error) {
|
|
return { status: 0 };
|
|
}
|
|
});
|
|
|
|
expect(response.status).toBe(403);
|
|
});
|
|
});
|
|
|
|
// TC-006: 空数据处理
|
|
test.describe('TC-006: 空数据处理', () => {
|
|
test('角色列表空状态', async ({ page }) => {
|
|
await login(page, TEST_USERS.admin.username, TEST_USERS.admin.password);
|
|
|
|
// 清空角色数据
|
|
await page.evaluate(async () => {
|
|
await fetch(`${API_BASE_URL}/api/roles/clear`, { method: 'DELETE' });
|
|
});
|
|
|
|
await page.click('text=系统管理');
|
|
await page.click('text=角色管理');
|
|
|
|
// 验证空状态提示
|
|
await expect(page.locator('text=暂无角色数据')).toBeVisible();
|
|
});
|
|
|
|
test('菜单列表空状态', async ({ page }) => {
|
|
await login(page, TEST_USERS.admin.username, TEST_USERS.admin.password);
|
|
|
|
// 清空菜单数据
|
|
await page.evaluate(async () => {
|
|
await fetch(`${API_BASE_URL}/api/menus/clear`, { method: 'DELETE' });
|
|
});
|
|
|
|
await page.click('text=系统管理');
|
|
await page.click('text=菜单管理');
|
|
|
|
// 验证空状态提示
|
|
await expect(page.locator('text=暂无菜单数据')).toBeVisible();
|
|
});
|
|
});
|
|
|
|
// TC-007: 异常输入处理
|
|
test.describe('TC-007: 异常输入处理', () => {
|
|
test('创建角色 - 重复的roleKey', async ({ page }) => {
|
|
await login(page, TEST_USERS.admin.username, TEST_USERS.admin.password);
|
|
|
|
await page.click('text=系统管理');
|
|
await page.click('text=角色管理');
|
|
await page.click('button:has-text("新建")');
|
|
|
|
// 使用已存在的roleKey
|
|
await page.fill('input[placeholder="角色名称"]', '重复角色');
|
|
await page.fill('input[placeholder="角色标识"]', 'admin'); // 已存在的roleKey
|
|
await page.fill('input[placeholder="显示顺序"]', '10');
|
|
|
|
await page.click('button:has-text("确定")');
|
|
|
|
// 验证错误提示
|
|
await expect(page.locator('text=角色标识已存在')).toBeVisible();
|
|
});
|
|
|
|
test('创建菜单 - 无效的menuType', async ({ page }) => {
|
|
await login(page, TEST_USERS.admin.username, TEST_USERS.admin.password);
|
|
|
|
await page.click('text=系统管理');
|
|
await page.click('text=菜单管理');
|
|
await page.click('button:has-text("新建")');
|
|
|
|
// 选择无效的菜单类型
|
|
await page.fill('input[placeholder="菜单名称"]', '测试菜单');
|
|
await page.selectOption('select[placeholder="菜单类型"]', 'X'); // 无效值
|
|
|
|
await page.click('button:has-text("确定")');
|
|
|
|
// 验证表单验证
|
|
await expect(page.locator('text=请选择有效的菜单类型')).toBeVisible();
|
|
});
|
|
|
|
test('超长字符串输入', async ({ page }) => {
|
|
await login(page, TEST_USERS.admin.username, TEST_USERS.admin.password);
|
|
|
|
await page.click('text=系统管理');
|
|
await page.click('text=角色管理');
|
|
await page.click('button:has-text("新建")');
|
|
|
|
// 输入超长字符串
|
|
const longString = 'A'.repeat(1000);
|
|
await page.fill('input[placeholder="角色名称"]', longString);
|
|
|
|
await page.click('button:has-text("确定")');
|
|
|
|
// 验证长度限制
|
|
await expect(page.locator('text=角色名称长度不能超过50个字符')).toBeVisible();
|
|
});
|
|
});
|
|
|
|
// TC-008: 并发操作测试
|
|
test.describe('TC-008: 并发操作测试', () => {
|
|
test('多用户同时编辑角色', async ({ browser }) => {
|
|
const context1 = await browser.newContext();
|
|
const context2 = await browser.newContext();
|
|
|
|
const page1 = await context1.newPage();
|
|
const page2 = await context2.newPage();
|
|
|
|
// 用户1登录并开始编辑
|
|
await login(page1, TEST_USERS.admin.username, TEST_USERS.admin.password);
|
|
await page1.click('text=系统管理');
|
|
await page1.click('text=角色管理');
|
|
const editButtons1 = await page1.locator('button:has-text("编辑")').all();
|
|
await editButtons1[0].click();
|
|
|
|
// 用户2登录并尝试编辑同一角色
|
|
await login(page2, TEST_USERS.testAdmin.username, TEST_USERS.testAdmin.password);
|
|
await page2.click('text=系统管理');
|
|
await page2.click('text=角色管理');
|
|
const editButtons2 = await page2.locator('button:has-text("编辑")').all();
|
|
await editButtons2[0].click();
|
|
|
|
// 用户1提交修改
|
|
await page1.fill('input[placeholder="角色名称"]', '并发测试角色1');
|
|
await page1.click('button:has-text("确定")');
|
|
await expect(page1.locator('text=更新成功')).toBeVisible();
|
|
|
|
// 用户2提交修改
|
|
await page2.fill('input[placeholder="角色名称"]', '并发测试角色2');
|
|
await page2.click('button:has-text("确定")');
|
|
|
|
// 验证系统处理并发请求
|
|
const updateSuccess = page2.locator('text=更新成功');
|
|
const dataModified = page2.locator('text=数据已被修改');
|
|
await Promise.race([
|
|
updateSuccess.waitFor({ state: 'visible' }),
|
|
dataModified.waitFor({ state: 'visible' })
|
|
]);
|
|
|
|
await context1.close();
|
|
await context2.close();
|
|
});
|
|
}); |