Files
novalon-manage-system/e2e-tests/e2e.spec.ts
T
张翔 e2ad1331cc feat: 添加测试框架和覆盖率报告功能
feat(测试): 新增Playwright和Vitest测试配置
feat(测试): 添加测试覆盖率报告生成功能
feat(测试): 实现前后端测试脚本集成

fix(测试): 修复测试密码不匹配问题
fix(测试): 修正URL等待策略
fix(测试): 调整错误消息选择器

refactor(测试): 重构测试目录结构
refactor(测试): 优化测试用例组织方式

docs: 更新测试报告文档
docs: 添加测试覆盖率报告模板

ci: 添加Docker测试环境配置
ci: 实现测试自动化脚本

chore: 更新依赖版本
chore: 添加测试相关配置文件
2026-03-25 09:03:37 +08:00

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();
});
});