test: E2E 测试用例更新与新增

- 更新 Page Object 模型适配新字段名
- 新增 UAT 测试套件与 journey 测试用例
- 优化测试辅助工具与数据工厂
- 更新 playwright 认证状态
This commit is contained in:
张翔
2026-05-06 14:17:51 +08:00
parent 0b246b3e24
commit bd21e2d1f7
47 changed files with 1764 additions and 1226 deletions
@@ -0,0 +1,65 @@
import { test, expect } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';
test.describe('UAT: 认证功能验收', () => {
test('UAT-AUTH-01: 有效凭据登录成功', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('admin', 'Test@123');
await expect(page).toHaveURL(/.*dashboard/);
});
test('UAT-AUTH-02: 无效密码登录失败', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.loginAndExpectError('admin', 'wrong_password');
await expect(page).toHaveURL(/.*login/);
});
test('UAT-AUTH-03: 空用户名提交被阻止', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.passwordInput.fill('somepassword');
await loginPage.loginButton.click();
await expect(page.locator('.ant-form-item-explain-error').first()).toBeVisible({ timeout: 5000 });
});
test('UAT-AUTH-04: 空密码提交被阻止', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.usernameInput.fill('admin');
await loginPage.loginButton.click();
await expect(page.locator('.ant-form-item-explain-error').first()).toBeVisible({ timeout: 5000 });
});
test('UAT-AUTH-05: 登出后 Token 被清除', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('admin', 'Test@123');
await expect(page).toHaveURL(/.*dashboard/);
const tokenBefore = await page.evaluate(() => localStorage.getItem('token'));
expect(tokenBefore).not.toBeNull();
await loginPage.logout();
const tokenAfter = await page.evaluate(() => localStorage.getItem('token'));
expect(tokenAfter).toBeNull();
});
test('UAT-AUTH-06: 未认证访问受保护路由重定向', async ({ page }) => {
await page.goto('/users');
await page.waitForTimeout(2000);
await expect(page).toHaveURL(/.*login/, { timeout: 10000 });
});
test('UAT-AUTH-07: 登录页面 UI 元素完整性', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await expect(loginPage.usernameInput).toBeVisible();
await expect(loginPage.passwordInput).toBeVisible();
await expect(loginPage.loginButton).toBeVisible();
await expect(page.locator('input[placeholder="用户名"]')).toBeVisible();
await expect(page.locator('input[placeholder="密码"]')).toBeVisible();
});
});
@@ -0,0 +1,180 @@
import { test, expect } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';
import { UserManagementPage } from '../pages/UserManagementPage';
import { SystemConfigPage } from '../pages/SystemConfigPage';
import { DictionaryManagementPage } from '../pages/DictionaryManagementPage';
import { NotificationPage } from '../pages/NotificationPage';
test.describe('UAT: 异常处理与边界条件', () => {
test('UAT-ERR-01: 用户管理 - 重复用户名处理', async ({ page }) => {
const loginPage = new LoginPage(page);
const userPage = new UserManagementPage(page);
await loginPage.goto();
await loginPage.login('admin', 'admin123');
await userPage.goto();
await test.step('尝试创建已存在的用户名', async () => {
await userPage.clickCreateUser();
await userPage.fillUserForm({
username: 'admin',
password: 'Test@123456',
nickname: '重复用户',
email: 'duplicate@test.com',
});
await userPage.submitForm();
});
await test.step('验证错误提示或表单验证', async () => {
await page.waitForTimeout(2000);
const hasError = (await page.locator('.ant-message-error').count()) > 0 ||
(await page.locator('.ant-form-item-explain-error').count()) > 0;
expect(hasError).toBe(true);
});
});
test('UAT-ERR-02: 用户管理 - 邮箱格式验证', async ({ page }) => {
const loginPage = new LoginPage(page);
const userPage = new UserManagementPage(page);
await loginPage.goto();
await loginPage.login('admin', 'admin123');
await userPage.goto();
await userPage.clickCreateUser();
await test.step('输入无效邮箱格式', async () => {
const timestamp = Date.now();
await userPage.fillUserForm({
username: `err_user_${timestamp}`,
password: 'Test@123456',
nickname: '邮箱测试',
email: 'invalid-email',
});
});
await test.step('验证邮箱格式错误提示', async () => {
await userPage.submitForm();
await page.waitForTimeout(1000);
const hasValidationError = (await page.locator('.ant-form-item-explain-error').count()) > 0;
expect(hasValidationError).toBe(true);
});
});
test('UAT-ERR-03: 系统配置 - 空配置键验证', async ({ page }) => {
const loginPage = new LoginPage(page);
const configPage = new SystemConfigPage(page);
await loginPage.goto();
await loginPage.login('admin', 'admin123');
await configPage.goto();
await test.step('点击新增配置但不填写', async () => {
await configPage.addButton.click();
await page.waitForTimeout(500);
const modal = page.locator('.ant-modal').filter({ hasText: /新增配置/ });
await modal.getByRole('button', { name: '确 定' }).click();
});
await test.step('验证表单验证错误', async () => {
await page.waitForTimeout(1000);
const hasError = (await page.locator('.ant-form-item-explain-error').count()) > 0;
expect(hasError).toBe(true);
});
});
test('UAT-ERR-04: 字典管理 - 空字典名称验证', async ({ page }) => {
const loginPage = new LoginPage(page);
const dictPage = new DictionaryManagementPage(page);
await loginPage.goto();
await loginPage.login('admin', 'admin123');
await dictPage.goto();
await test.step('点击新增字典类型但不填写', async () => {
await dictPage.addTypeButton.click();
await page.waitForTimeout(500);
const modal = page.locator('.ant-modal').filter({ hasText: /新增字典类型/ });
await modal.getByRole('button', { name: '确 定' }).click();
});
await test.step('验证表单验证错误', async () => {
await page.waitForTimeout(1000);
const hasError = (await page.locator('.ant-form-item-explain-error').count()) > 0;
expect(hasError).toBe(true);
});
});
test('UAT-ERR-05: 通知管理 - 空标题验证', async ({ page }) => {
const loginPage = new LoginPage(page);
const notifyPage = new NotificationPage(page);
await loginPage.goto();
await loginPage.login('admin', 'admin123');
await notifyPage.goto();
await test.step('点击新增通知但不填写标题', async () => {
await notifyPage.addButton.click();
await page.waitForTimeout(500);
const modal = page.locator('.ant-modal').filter({ hasText: /新增通知/ });
await modal.locator('.ant-form-item').filter({ hasText: '内容' }).locator('textarea').fill('测试内容');
await modal.getByRole('button', { name: '确 定' }).click();
});
await test.step('验证表单验证错误', async () => {
await page.waitForTimeout(1000);
const hasError = (await page.locator('.ant-form-item-explain-error').count()) > 0;
expect(hasError).toBe(true);
});
});
test('UAT-ERR-06: 删除确认弹窗 - 取消操作', async ({ page }) => {
const loginPage = new LoginPage(page);
const userPage = new UserManagementPage(page);
await loginPage.goto();
await loginPage.login('admin', 'admin123');
await userPage.goto();
await userPage.waitForTableReady();
const countBefore = await userPage.getUserCount();
if (countBefore > 0) {
await test.step('点击删除按钮', async () => {
const row = userPage.table.locator('tbody tr').first();
await row.locator('.ant-btn').filter({ has: page.locator('.anticon-delete') }).click();
await page.waitForTimeout(300);
});
await test.step('点击取消', async () => {
const popconfirm = page.locator('.ant-popconfirm');
await popconfirm.getByRole('button', { name: '取 消' }).click();
await page.waitForTimeout(500);
});
await test.step('验证数据未被删除', async () => {
const countAfter = await userPage.getUserCount();
expect(countAfter).toBe(countBefore);
});
}
});
test('UAT-ERR-07: 模态框关闭 - 点击取消按钮', async ({ page }) => {
const loginPage = new LoginPage(page);
const userPage = new UserManagementPage(page);
await loginPage.goto();
await loginPage.login('admin', 'admin123');
await userPage.goto();
await userPage.clickCreateUser();
await test.step('点击取消按钮关闭模态框', async () => {
const modal = page.locator('.ant-modal').filter({ hasText: /新增用户/ });
await modal.getByRole('button', { name: '取 消' }).click();
await page.waitForTimeout(500);
});
await test.step('验证模态框已关闭', async () => {
const modalVisible = await page.locator('.ant-modal:visible').count();
expect(modalVisible).toBe(0);
});
});
});
@@ -0,0 +1,73 @@
import { test, expect } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';
import { DashboardPage } from '../pages/DashboardPage';
test.describe('UAT: 权限边界与访问控制', () => {
test('UAT-PERM-01: 管理员可访问所有菜单', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('admin', 'Test@123');
await expect(page).toHaveURL(/.*dashboard/);
const menuItems = page.locator('.ant-menu-submenu-title');
const count = await menuItems.count();
expect(count).toBeGreaterThan(0);
});
test('UAT-PERM-02: 未登录用户无法访问任何功能页面', async ({ page }) => {
const protectedRoutes = ['/users', '/roles', '/menus', '/dict', '/sys/config', '/oplog', '/loginlog', '/exceptionlog'];
for (const route of protectedRoutes) {
await page.goto(route);
await page.waitForTimeout(2000);
await expect(page).toHaveURL(/.*login/, { timeout: 5000 });
}
});
test('UAT-PERM-03: 登出后无法回退访问受保护页面', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('admin', 'Test@123');
await expect(page).toHaveURL(/.*dashboard/);
await loginPage.logout();
await expect(page).toHaveURL(/.*login/);
await page.goBack();
await page.waitForTimeout(2000);
await expect(page).toHaveURL(/.*login/, { timeout: 5000 });
});
test('UAT-PERM-04: 侧边栏菜单折叠与展开', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('admin', 'Test@123');
const collapseButton = page.locator('.ant-layout-sider-trigger, [class*="sider-trigger"]').first();
if (await collapseButton.isVisible()) {
await collapseButton.click();
await page.waitForTimeout(500);
const sider = page.locator('.ant-layout-sider-collapsed');
const isCollapsed = await sider.count() > 0;
expect(typeof isCollapsed).toBe('boolean');
await collapseButton.click();
await page.waitForTimeout(500);
}
});
test('UAT-PERM-05: 仪表盘统计卡片可见', async ({ page }) => {
const loginPage = new LoginPage(page);
const dashboardPage = new DashboardPage(page);
await loginPage.goto();
await loginPage.login('admin', 'Test@123');
await expect(page).toHaveURL(/.*dashboard/);
await expect(page.locator('.ant-statistic').first()).toBeVisible({ timeout: 15000 });
});
});
@@ -0,0 +1,123 @@
import { test, expect } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';
import { UserManagementPage } from '../pages/UserManagementPage';
import { RoleManagementPage } from '../pages/RoleManagementPage';
import { MenuManagementPage } from '../pages/MenuManagementPage';
test.describe('UAT: 系统管理功能验收', () => {
const timestamp = Date.now();
test('UAT-SYS-01: 用户管理 - 新增用户表单验证', async ({ page }) => {
const loginPage = new LoginPage(page);
const userPage = new UserManagementPage(page);
await loginPage.goto();
await loginPage.login('admin', 'Test@123');
await userPage.goto();
await userPage.clickCreateUser();
await test.step('空表单提交应显示验证错误', async () => {
await userPage.submitForm();
await expect(page.locator('.ant-form-item-explain-error').first()).toBeVisible({ timeout: 5000 });
});
});
test('UAT-SYS-02: 用户管理 - 完整 CRUD', async ({ page }) => {
const loginPage = new LoginPage(page);
const userPage = new UserManagementPage(page);
const username = `uat_user_${timestamp}`;
await loginPage.goto();
await loginPage.login('admin', 'Test@123');
await test.step('创建用户', async () => {
await userPage.goto();
await userPage.clickCreateUser();
await userPage.fillUserForm({
username,
password: 'Uat@123456',
nickname: `UAT用户_${timestamp}`,
email: `uat_${timestamp}@test.com`,
phone: '13900139000',
});
await userPage.submitForm();
const success = await userPage.waitForSuccessMessage();
expect(success).toBe(true);
});
await test.step('验证用户出现在列表中', async () => {
await userPage.goto();
await userPage.waitForTableReady();
const exists = await userPage.containsText(username);
expect(exists).toBe(true);
});
await test.step('编辑用户', async () => {
await userPage.editUser(1);
const modal = page.locator('.ant-modal').filter({ hasText: /编辑用户/ });
const nicknameInput = modal.locator('.ant-form-item').filter({ hasText: '昵称' }).locator('input');
await nicknameInput.clear();
await nicknameInput.fill(`UAT修改_${timestamp}`);
await userPage.submitForm();
});
});
test('UAT-SYS-03: 角色管理 - 新增角色', async ({ page }) => {
const loginPage = new LoginPage(page);
const rolePage = new RoleManagementPage(page);
const roleName = `UAT角色_${timestamp}`;
const roleKey = `uat_role_${timestamp}`;
await loginPage.goto();
await loginPage.login('admin', 'Test@123');
await test.step('创建角色', async () => {
await rolePage.goto();
await rolePage.clickCreateRole();
await rolePage.fillRoleForm({
roleName,
roleKey,
roleSort: 50,
status: 'ACTIVE',
});
await rolePage.submitForm();
});
await test.step('验证角色出现在列表中', async () => {
await rolePage.goto();
const exists = await rolePage.containsText(roleName);
expect(exists).toBe(true);
});
});
test('UAT-SYS-04: 菜单管理 - 新增菜单', async ({ page }) => {
const loginPage = new LoginPage(page);
const menuPage = new MenuManagementPage(page);
const menuName = `UAT菜单_${timestamp}`;
await loginPage.goto();
await loginPage.login('admin', 'Test@123');
await test.step('创建菜单', async () => {
await menuPage.goto();
await menuPage.clickCreateMenu();
await menuPage.fillMenuForm({
name: menuName,
type: 'menu',
path: `/uat-test-${timestamp}`,
icon: 'setting',
component: `uat/Test_${timestamp}`,
permission: `uat:test_${timestamp}`,
sort: 50,
status: 'ACTIVE',
});
await menuPage.submitForm();
});
await test.step('验证菜单出现在列表中', async () => {
await menuPage.goto();
const exists = await menuPage.containsText(menuName);
expect(exists).toBe(true);
});
});
});
@@ -0,0 +1,97 @@
import { test, expect } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';
import { DashboardPage } from '../pages/DashboardPage';
test.describe('UAT: UI 一致性与响应性验收', () => {
test('UAT-UI-01: 登录页面布局一致性', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await expect(page.locator('input[placeholder="用户名"]')).toBeVisible();
await expect(page.locator('input[placeholder="密码"]')).toBeVisible();
await expect(page.locator('button:has-text("登录")')).toBeVisible();
const bodyWidth = await page.evaluate(() => document.body.offsetWidth);
expect(bodyWidth).toBeGreaterThan(0);
});
test('UAT-UI-02: 仪表盘布局一致性', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('admin', 'admin123');
await expect(page.locator('.ant-layout')).toBeVisible();
await expect(page.locator('.ant-layout-sider')).toBeVisible({ timeout: 10000 });
await expect(page.locator('.ant-layout-header')).toBeVisible({ timeout: 10000 });
await expect(page.locator('.ant-layout-content')).toBeVisible({ timeout: 10000 });
});
test('UAT-UI-03: 表格分页功能', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('admin', 'admin123');
await page.goto('/users');
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
const pagination = page.locator('.ant-pagination');
if (await pagination.isVisible()) {
await expect(pagination).toBeVisible();
const nextButton = pagination.locator('.ant-pagination-next');
if (await nextButton.isEnabled()) {
await nextButton.click();
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
}
}
});
test('UAT-UI-04: 面包屑导航', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('admin', 'admin123');
await page.goto('/users');
await page.waitForLoadState('networkidle');
const breadcrumb = page.locator('.ant-breadcrumb');
if (await breadcrumb.isVisible()) {
await expect(breadcrumb).toBeVisible();
}
});
test('UAT-UI-05: 全局消息提示样式', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('admin', 'admin123');
await page.goto('/users');
await page.waitForLoadState('networkidle');
const table = page.locator('.ant-table').first();
if (await table.isVisible()) {
await expect(table).toBeVisible();
}
});
test('UAT-UI-06: 页面标题一致性', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('admin', 'admin123');
const routes = [
{ path: '/dashboard', title: /仪表盘|Dashboard/ },
{ path: '/users', title: /用户/ },
{ path: '/roles', title: /角色/ },
];
for (const route of routes) {
await page.goto(route.path);
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
const pageTitle = await page.title();
expect(pageTitle.length).toBeGreaterThan(0);
}
});
});