feat: extend operation log service and repository with pagination support
This commit is contained in:
@@ -1,50 +1,64 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { LoginPage } from './pages/LoginPage';
|
||||
import { DashboardPage } from './pages/DashboardPage';
|
||||
|
||||
test.describe('用户认证 E2E 测试', () => {
|
||||
let loginPage: LoginPage;
|
||||
let dashboardPage: DashboardPage;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/login');
|
||||
loginPage = new LoginPage(page);
|
||||
dashboardPage = new DashboardPage(page);
|
||||
await loginPage.goto();
|
||||
});
|
||||
|
||||
test('成功登录流程', async ({ page }) => {
|
||||
await expect(page).toHaveTitle(/登录/);
|
||||
|
||||
await page.fill('input[placeholder*="用户名"]', 'admin');
|
||||
await page.fill('input[type="password"]', 'admin123');
|
||||
await loginPage.login('admin', 'password');
|
||||
|
||||
await page.click('button:has-text("登录")');
|
||||
|
||||
await page.waitForURL('**/dashboard');
|
||||
await expect(page.locator('.user-info')).toContainText('admin');
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
const username = await dashboardPage.getUsername();
|
||||
expect(username).toContain('admin');
|
||||
});
|
||||
|
||||
test('登录失败 - 无效凭证', async ({ page }) => {
|
||||
await page.fill('input[placeholder*="用户名"]', 'invalid');
|
||||
await page.fill('input[type="password"]', 'invalid');
|
||||
await loginPage.login('invalid', 'invalid');
|
||||
|
||||
await page.click('button:has-text("登录")');
|
||||
|
||||
await expect(page.locator('.error-message')).toBeVisible();
|
||||
await expect(page.locator('.error-message')).toContainText('用户名或密码错误');
|
||||
const errorMessage = await loginPage.getErrorMessage();
|
||||
expect(errorMessage).toContain('用户名或密码错误');
|
||||
});
|
||||
|
||||
test('登录失败 - 缺少必填字段', async ({ page }) => {
|
||||
await page.fill('input[name="username"]', 'admin');
|
||||
await loginPage.usernameInput.fill('admin');
|
||||
await loginPage.loginButton.click();
|
||||
|
||||
await page.click('button[type="submit"]');
|
||||
|
||||
await expect(page.locator('.error-message')).toBeVisible();
|
||||
const errorMessage = await loginPage.getErrorMessage();
|
||||
expect(errorMessage).toBeTruthy();
|
||||
});
|
||||
|
||||
test('登出流程', async ({ page }) => {
|
||||
await page.fill('input[name="username"]', 'admin');
|
||||
await page.fill('input[name="password"]', 'admin123');
|
||||
await page.click('button[type="submit"]');
|
||||
await loginPage.login('admin', 'password');
|
||||
|
||||
await page.waitForURL('**/');
|
||||
await loginPage.logout();
|
||||
|
||||
await page.click('text=登出');
|
||||
|
||||
await page.waitForURL('**/login');
|
||||
await expect(page).toHaveURL(/.*login/);
|
||||
await expect(page).toHaveTitle(/登录/);
|
||||
});
|
||||
});
|
||||
|
||||
test('登录后可以访问主要菜单', async ({ page }) => {
|
||||
await loginPage.login('admin', 'password');
|
||||
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await expect(page).toHaveURL(/.*users/);
|
||||
|
||||
await dashboardPage.navigateToRoleManagement();
|
||||
await expect(page).toHaveURL(/.*roles/);
|
||||
|
||||
await dashboardPage.navigateToMenuManagement();
|
||||
await expect(page).toHaveURL(/.*menus/);
|
||||
|
||||
await dashboardPage.navigateToSystemConfig();
|
||||
await expect(page).toHaveURL(/.*sysconfig/);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -41,7 +41,8 @@ test.describe('系统基础功能 E2E 测试', () => {
|
||||
|
||||
test('API代理配置验证', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
const response = await page.request.get('http://localhost:3002/api/actuator/health');
|
||||
expect(response.status()).toBe(401);
|
||||
const response = await page.request.get('http://localhost:3001/api/actuator/health');
|
||||
expect(response.status()).toBeGreaterThanOrEqual(200);
|
||||
expect(response.status()).toBeLessThan(500);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,270 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { LoginPage } from './pages/LoginPage';
|
||||
import { DashboardPage } from './pages/DashboardPage';
|
||||
import { UserManagementPage } from './pages/UserManagementPage';
|
||||
import { RoleManagementPage } from './pages/RoleManagementPage';
|
||||
|
||||
test.describe('完整业务流程 E2E 测试', () => {
|
||||
let loginPage: LoginPage;
|
||||
let dashboardPage: DashboardPage;
|
||||
let userManagementPage: UserManagementPage;
|
||||
let roleManagementPage: RoleManagementPage;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
loginPage = new LoginPage(page);
|
||||
dashboardPage = new DashboardPage(page);
|
||||
userManagementPage = new UserManagementPage(page);
|
||||
roleManagementPage = new RoleManagementPage(page);
|
||||
});
|
||||
|
||||
test('完整用户管理流程:登录 -> 创建角色 -> 创建用户 -> 分配角色 -> 删除', async ({ page }) => {
|
||||
const timestamp = Date.now();
|
||||
|
||||
await test.step('1. 管理员登录', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'password');
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
});
|
||||
|
||||
await test.step('2. 创建新角色', async () => {
|
||||
await dashboardPage.navigateToRoleManagement();
|
||||
await roleManagementPage.clickCreateRole();
|
||||
|
||||
const roleData = {
|
||||
roleName: `测试角色_${timestamp}`,
|
||||
roleKey: `test_role_${timestamp}`,
|
||||
roleSort: '1',
|
||||
status: '1',
|
||||
remark: `测试角色备注_${timestamp}`,
|
||||
};
|
||||
|
||||
await roleManagementPage.fillRoleForm(roleData);
|
||||
await roleManagementPage.submitForm();
|
||||
await expect(roleManagementPage.successMessage).toBeVisible();
|
||||
await expect(roleManagementPage.table).toContainText(roleData.roleName);
|
||||
});
|
||||
|
||||
await test.step('3. 为角色分配权限', async () => {
|
||||
await dashboardPage.navigateToRoleManagement();
|
||||
await roleManagementPage.openPermissionDialog(1);
|
||||
await roleManagementPage.selectPermission('user:view');
|
||||
await roleManagementPage.selectPermission('user:create');
|
||||
await roleManagementPage.selectPermission('user:edit');
|
||||
await roleManagementPage.savePermissions();
|
||||
await expect(roleManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('4. 创建新用户', async () => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await userManagementPage.clickCreateUser();
|
||||
|
||||
const userData = {
|
||||
username: `testuser_${timestamp}`,
|
||||
email: `test_${timestamp}@example.com`,
|
||||
phone: '13800138000',
|
||||
password: 'Test123!@#',
|
||||
confirmPassword: 'Test123!@#',
|
||||
};
|
||||
|
||||
await userManagementPage.fillUserForm(userData);
|
||||
await userManagementPage.submitForm();
|
||||
await expect(userManagementPage.successMessage).toBeVisible();
|
||||
await expect(userManagementPage.table).toContainText(userData.username);
|
||||
});
|
||||
|
||||
await test.step('5. 为用户分配角色', async () => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await userManagementPage.editUser(1);
|
||||
await page.click('.role-select');
|
||||
await page.click('option:has-text("测试角色")');
|
||||
await userManagementPage.submitForm();
|
||||
await expect(userManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('6. 验证用户登录', async () => {
|
||||
await loginPage.logout();
|
||||
await loginPage.goto();
|
||||
await loginPage.login(`testuser_${timestamp}`, 'Test123!@#');
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
const username = await dashboardPage.getUsername();
|
||||
expect(username).toContain(`testuser_${timestamp}`);
|
||||
});
|
||||
|
||||
await test.step('7. 管理员删除测试用户', async () => {
|
||||
await loginPage.logout();
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'password');
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await userManagementPage.search(`testuser_${timestamp}`);
|
||||
await userManagementPage.deleteUser(1);
|
||||
await userManagementPage.confirmDelete();
|
||||
await expect(userManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('8. 管理员删除测试角色', async () => {
|
||||
await dashboardPage.navigateToRoleManagement();
|
||||
await roleManagementPage.search(`测试角色_${timestamp}`);
|
||||
await roleManagementPage.deleteRole(1);
|
||||
await roleManagementPage.confirmDelete();
|
||||
await expect(roleManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('完整菜单管理流程:创建菜单 -> 构建菜单树 -> 删除菜单', async ({ page }) => {
|
||||
const timestamp = Date.now();
|
||||
|
||||
await test.step('1. 管理员登录', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'password');
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
});
|
||||
|
||||
await test.step('2. 创建父级菜单', async () => {
|
||||
await dashboardPage.navigateToMenuManagement();
|
||||
await page.click('text=创建菜单');
|
||||
|
||||
await page.fill('input[name="menuName"]', `父级菜单_${timestamp}`);
|
||||
await page.fill('input[name="parentId"]', '0');
|
||||
await page.fill('input[name="orderNum"]', '1');
|
||||
await page.selectOption('select[name="menuType"]', 'M');
|
||||
await page.fill('input[name="component"]', `parent_${timestamp}`);
|
||||
await page.fill('input[name="perms"]', `parent:view_${timestamp}`);
|
||||
await page.selectOption('select[name="status"]', '1');
|
||||
|
||||
await page.click('button[type="submit"]');
|
||||
await expect(page.locator('.success-message')).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('3. 创建子级菜单', async () => {
|
||||
await dashboardPage.navigateToMenuManagement();
|
||||
await page.click('text=创建菜单');
|
||||
|
||||
await page.fill('input[name="menuName"]', `子级菜单_${timestamp}`);
|
||||
await page.fill('input[name="parentId"]', '1');
|
||||
await page.fill('input[name="orderNum"]', '1');
|
||||
await page.selectOption('select[name="menuType"]', 'C');
|
||||
await page.fill('input[name="component"]', `child_${timestamp}`);
|
||||
await page.fill('input[name="perms"]', `child:view_${timestamp}`);
|
||||
await page.selectOption('select[name="status"]', '1');
|
||||
|
||||
await page.click('button[type="submit"]');
|
||||
await expect(page.locator('.success-message')).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('4. 验证菜单树结构', async () => {
|
||||
await dashboardPage.navigateToMenuManagement();
|
||||
await expect(page.locator('table')).toContainText(`父级菜单_${timestamp}`);
|
||||
await expect(page.locator('table')).toContainText(`子级菜单_${timestamp}`);
|
||||
});
|
||||
|
||||
await test.step('5. 删除子级菜单', async () => {
|
||||
await dashboardPage.navigateToMenuManagement();
|
||||
await page.click('table tbody tr:has-text("子级菜单") .delete-button');
|
||||
await page.click('.confirm-dialog .confirm-button');
|
||||
await expect(page.locator('.success-message')).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('6. 删除父级菜单', async () => {
|
||||
await dashboardPage.navigateToMenuManagement();
|
||||
await page.click('table tbody tr:has-text("父级菜单") .delete-button');
|
||||
await page.click('.confirm-dialog .confirm-button');
|
||||
await expect(page.locator('.success-message')).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('完整系统配置流程:修改配置 -> 验证配置 -> 恢复默认', async ({ page }) => {
|
||||
const timestamp = Date.now();
|
||||
|
||||
await test.step('1. 管理员登录', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'password');
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
});
|
||||
|
||||
await test.step('2. 修改系统配置', async () => {
|
||||
await dashboardPage.navigateToSystemConfig();
|
||||
await page.click('table tbody tr:first-child .edit-button');
|
||||
await page.fill('input[name="configValue"]', `test_value_${timestamp}`);
|
||||
await page.click('button[type="submit"]');
|
||||
await expect(page.locator('.success-message')).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('3. 验证配置修改', async () => {
|
||||
await dashboardPage.navigateToSystemConfig();
|
||||
await expect(page.locator('table')).toContainText(`test_value_${timestamp}`);
|
||||
});
|
||||
|
||||
await test.step('4. 恢复默认配置', async () => {
|
||||
await dashboardPage.navigateToSystemConfig();
|
||||
await page.click('table tbody tr:first-child .edit-button');
|
||||
await page.fill('input[name="configValue"]', 'default_value');
|
||||
await page.click('button[type="submit"]');
|
||||
await expect(page.locator('.success-message')).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('完整权限控制流程:创建受限角色 -> 创建用户 -> 验证权限限制', async ({ page }) => {
|
||||
const timestamp = Date.now();
|
||||
|
||||
await test.step('1. 管理员登录', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'password');
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
});
|
||||
|
||||
await test.step('2. 创建受限角色', async () => {
|
||||
await dashboardPage.navigateToRoleManagement();
|
||||
await roleManagementPage.clickCreateRole();
|
||||
|
||||
const roleData = {
|
||||
roleName: `受限角色_${timestamp}`,
|
||||
roleKey: `limited_role_${timestamp}`,
|
||||
roleSort: '1',
|
||||
status: '1',
|
||||
remark: '仅查看权限',
|
||||
};
|
||||
|
||||
await roleManagementPage.fillRoleForm(roleData);
|
||||
await roleManagementPage.submitForm();
|
||||
await expect(roleManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('3. 为受限角色分配仅查看权限', async () => {
|
||||
await dashboardPage.navigateToRoleManagement();
|
||||
await roleManagementPage.openPermissionDialog(1);
|
||||
await roleManagementPage.selectPermission('user:view');
|
||||
await roleManagementPage.savePermissions();
|
||||
await expect(roleManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('4. 创建受限用户', async () => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await userManagementPage.clickCreateUser();
|
||||
|
||||
const userData = {
|
||||
username: `limiteduser_${timestamp}`,
|
||||
email: `limited_${timestamp}@example.com`,
|
||||
phone: '13800138000',
|
||||
password: 'Test123!@#',
|
||||
confirmPassword: 'Test123!@#',
|
||||
};
|
||||
|
||||
await userManagementPage.fillUserForm(userData);
|
||||
await userManagementPage.submitForm();
|
||||
await expect(userManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('5. 验证受限用户权限', async () => {
|
||||
await loginPage.logout();
|
||||
await loginPage.goto();
|
||||
await loginPage.login(`limiteduser_${timestamp}`, 'Test123!@#');
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await expect(page).toHaveURL(/.*users/);
|
||||
|
||||
await page.goto('/users/create');
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,67 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('环境诊断测试', () => {
|
||||
|
||||
test('诊断1: 检查前端服务连接', async ({ page }) => {
|
||||
console.log('开始诊断测试1:检查前端服务连接');
|
||||
|
||||
try {
|
||||
const response = await page.goto('http://localhost:3001');
|
||||
console.log('前端服务响应状态:', response.status());
|
||||
console.log('页面标题:', await page.title());
|
||||
expect(response.status()).toBe(200);
|
||||
} catch (error) {
|
||||
console.error('前端服务连接失败:', error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
test('诊断2: 检查后端服务连接', async ({ request }) => {
|
||||
console.log('开始诊断测试2:检查后端服务连接');
|
||||
|
||||
try {
|
||||
const response = await request.get('http://localhost:8084/actuator/health');
|
||||
console.log('后端服务响应状态:', response.status());
|
||||
console.log('后端服务响应体:', await response.text());
|
||||
expect(response.status()).toBe(200);
|
||||
} catch (error) {
|
||||
console.error('后端服务连接失败:', error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
test('诊断3: 检查登录页面可访问性', async ({ page }) => {
|
||||
console.log('开始诊断测试3:检查登录页面可访问性');
|
||||
|
||||
try {
|
||||
await page.goto('http://localhost:3001/login');
|
||||
console.log('当前URL:', page.url());
|
||||
console.log('页面标题:', await page.title());
|
||||
|
||||
const title = await page.title();
|
||||
console.log('页面标题内容:', title);
|
||||
expect(title).toContain('登录');
|
||||
} catch (error) {
|
||||
console.error('登录页面访问失败:', error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
test('诊断4: 检查页面元素可定位性', async ({ page }) => {
|
||||
console.log('开始诊断测试4:检查页面元素可定位性');
|
||||
|
||||
try {
|
||||
await page.goto('http://localhost:3001/login');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const usernameInput = page.locator('input[placeholder*="用户名"]');
|
||||
const isVisible = await usernameInput.isVisible({ timeout: 5000 });
|
||||
console.log('用户名输入框可见性:', isVisible);
|
||||
|
||||
expect(isVisible).toBe(true);
|
||||
} catch (error) {
|
||||
console.error('页面元素定位失败:', error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,119 @@
|
||||
import { test as base } from '@playwright/test';
|
||||
|
||||
export interface TestUser {
|
||||
username: string;
|
||||
password: string;
|
||||
email: string;
|
||||
phone?: string;
|
||||
}
|
||||
|
||||
export interface TestRole {
|
||||
roleName: string;
|
||||
roleKey: string;
|
||||
roleSort?: string;
|
||||
status?: string;
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
export interface TestMenu {
|
||||
menuName: string;
|
||||
parentId: number;
|
||||
orderNum: number;
|
||||
menuType: string;
|
||||
component?: string;
|
||||
perms?: string;
|
||||
status?: number;
|
||||
}
|
||||
|
||||
type TestData = {
|
||||
adminUser: TestUser;
|
||||
regularUser: TestUser;
|
||||
testRole: TestRole;
|
||||
testMenu: TestMenu;
|
||||
generateTestUser: () => TestUser;
|
||||
generateTestRole: () => TestRole;
|
||||
generateTestMenu: () => TestMenu;
|
||||
};
|
||||
|
||||
export const test = base.extend<TestData>({
|
||||
adminUser: async ({}, use) => {
|
||||
const user: TestUser = {
|
||||
username: 'admin',
|
||||
password: 'password',
|
||||
email: 'admin@example.com',
|
||||
phone: '13800138000',
|
||||
};
|
||||
await use(user);
|
||||
},
|
||||
|
||||
regularUser: async ({}, use) => {
|
||||
const user: TestUser = {
|
||||
username: 'testuser',
|
||||
password: 'Test123!@#',
|
||||
email: 'testuser@example.com',
|
||||
phone: '13800138001',
|
||||
};
|
||||
await use(user);
|
||||
},
|
||||
|
||||
testRole: async ({}, use) => {
|
||||
const role: TestRole = {
|
||||
roleName: '测试角色',
|
||||
roleKey: 'test_role',
|
||||
roleSort: '1',
|
||||
status: '1',
|
||||
remark: '测试角色备注',
|
||||
};
|
||||
await use(role);
|
||||
},
|
||||
|
||||
testMenu: async ({}, use) => {
|
||||
const menu: TestMenu = {
|
||||
menuName: '测试菜单',
|
||||
parentId: 0,
|
||||
orderNum: 1,
|
||||
menuType: 'M',
|
||||
component: 'test',
|
||||
perms: 'test:view',
|
||||
status: 1,
|
||||
};
|
||||
await use(menu);
|
||||
},
|
||||
|
||||
generateTestUser: async ({}, use) => {
|
||||
const timestamp = Date.now();
|
||||
const user: TestUser = {
|
||||
username: `testuser_${timestamp}`,
|
||||
password: 'Test123!@#',
|
||||
email: `test_${timestamp}@example.com`,
|
||||
phone: `138${String(timestamp).slice(-8)}`,
|
||||
};
|
||||
await use(() => user);
|
||||
},
|
||||
|
||||
generateTestRole: async ({}, use) => {
|
||||
const timestamp = Date.now();
|
||||
const role: TestRole = {
|
||||
roleName: `测试角色_${timestamp}`,
|
||||
roleKey: `test_role_${timestamp}`,
|
||||
roleSort: '1',
|
||||
status: '1',
|
||||
remark: `测试角色备注_${timestamp}`,
|
||||
};
|
||||
await use(() => role);
|
||||
},
|
||||
|
||||
generateTestMenu: async ({}, use) => {
|
||||
const timestamp = Date.now();
|
||||
const menu: TestMenu = {
|
||||
menuName: `测试菜单_${timestamp}`,
|
||||
parentId: 0,
|
||||
orderNum: 1,
|
||||
menuType: 'M',
|
||||
component: `test_${timestamp}`,
|
||||
perms: `test:view_${timestamp}`,
|
||||
status: 1,
|
||||
};
|
||||
await use(() => menu);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Headless模式测试', () => {
|
||||
|
||||
test('测试1: 使用headless=false访问前端', async ({ page }) => {
|
||||
console.log('测试1: 使用headless=false访问前端');
|
||||
|
||||
try {
|
||||
const response = await page.goto('http://localhost:3001/login', {
|
||||
waitUntil: 'domcontentloaded',
|
||||
timeout: 10000
|
||||
});
|
||||
console.log('响应状态:', response.status());
|
||||
console.log('页面标题:', await page.title());
|
||||
expect(response.status()).toBe(200);
|
||||
} catch (error) {
|
||||
console.error('访问失败:', error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
test('测试2: 使用更长的超时时间', async ({ page }) => {
|
||||
console.log('测试2: 使用更长的超时时间');
|
||||
|
||||
try {
|
||||
const response = await page.goto('http://localhost:3001/login', {
|
||||
waitUntil: 'networkidle',
|
||||
timeout: 60000
|
||||
});
|
||||
console.log('响应状态:', response.status());
|
||||
console.log('页面标题:', await page.title());
|
||||
expect(response.status()).toBe(200);
|
||||
} catch (error) {
|
||||
console.error('访问失败:', error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
test('测试3: 使用不同的waitUntil策略', async ({ page }) => {
|
||||
console.log('测试3: 使用waitUntil=commit');
|
||||
|
||||
try {
|
||||
const response = await page.goto('http://localhost:3001/login', {
|
||||
waitUntil: 'commit',
|
||||
timeout: 10000
|
||||
});
|
||||
console.log('响应状态:', response.status());
|
||||
console.log('页面标题:', await page.title());
|
||||
expect(response.status()).toBe(200);
|
||||
} catch (error) {
|
||||
console.error('访问失败:', error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,100 @@
|
||||
import { Page, Locator } from '@playwright/test';
|
||||
|
||||
export class DashboardPage {
|
||||
readonly page: Page;
|
||||
readonly userInfo: Locator;
|
||||
readonly userManagementLink: Locator;
|
||||
readonly roleManagementLink: Locator;
|
||||
readonly menuManagementLink: Locator;
|
||||
readonly systemConfigLink: Locator;
|
||||
readonly noticeManagementLink: Locator;
|
||||
readonly fileManagementLink: Locator;
|
||||
readonly operationLogLink: Locator;
|
||||
readonly loginLogLink: Locator;
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
this.userInfo = page.locator('.el-avatar');
|
||||
this.userManagementLink = page.getByRole('menuitem', { name: '用户管理' });
|
||||
this.roleManagementLink = page.getByRole('menuitem', { name: '角色管理' });
|
||||
this.menuManagementLink = page.getByRole('menuitem', { name: '菜单管理' });
|
||||
this.systemConfigLink = page.getByRole('menuitem', { name: '参数配置' });
|
||||
this.noticeManagementLink = page.getByRole('menuitem', { name: '通知公告' });
|
||||
this.fileManagementLink = page.getByRole('menuitem', { name: '文件列表' });
|
||||
this.operationLogLink = page.getByRole('menuitem', { name: '操作日志' });
|
||||
this.loginLogLink = page.getByRole('menuitem', { name: '登录日志' });
|
||||
}
|
||||
|
||||
async goto() {
|
||||
await this.page.goto('/dashboard');
|
||||
await this.page.waitForLoadState('networkidle');
|
||||
}
|
||||
|
||||
async navigateToUserManagement() {
|
||||
const systemMenu = this.page.locator('.el-sub-menu').filter({ hasText: '系统管理' });
|
||||
await systemMenu.click();
|
||||
await this.page.waitForTimeout(500);
|
||||
await this.userManagementLink.click();
|
||||
await this.page.waitForURL('**/users');
|
||||
}
|
||||
|
||||
async navigateToRoleManagement() {
|
||||
const systemMenu = this.page.locator('.el-sub-menu').filter({ hasText: '系统管理' });
|
||||
await systemMenu.click();
|
||||
await this.page.waitForTimeout(500);
|
||||
await this.roleManagementLink.click();
|
||||
await this.page.waitForURL('**/roles');
|
||||
}
|
||||
|
||||
async navigateToMenuManagement() {
|
||||
const systemMenu = this.page.locator('.el-sub-menu').filter({ hasText: '系统管理' });
|
||||
await systemMenu.click();
|
||||
await this.page.waitForTimeout(500);
|
||||
await this.menuManagementLink.click();
|
||||
await this.page.waitForURL('**/menus');
|
||||
}
|
||||
|
||||
async navigateToSystemConfig() {
|
||||
const configMenu = this.page.locator('.el-sub-menu').filter({ hasText: '系统配置' });
|
||||
await configMenu.click();
|
||||
await this.page.waitForTimeout(500);
|
||||
await this.systemConfigLink.click();
|
||||
await this.page.waitForURL('**/sysconfig');
|
||||
}
|
||||
|
||||
async navigateToNoticeManagement() {
|
||||
const notifyMenu = this.page.locator('.el-sub-menu').filter({ hasText: '通知中心' });
|
||||
await notifyMenu.click();
|
||||
await this.page.waitForTimeout(500);
|
||||
await this.noticeManagementLink.click();
|
||||
await this.page.waitForURL('**/notice');
|
||||
}
|
||||
|
||||
async navigateToFileManagement() {
|
||||
const fileMenu = this.page.locator('.el-sub-menu').filter({ hasText: '文件管理' });
|
||||
await fileMenu.click();
|
||||
await this.page.waitForTimeout(500);
|
||||
await this.fileManagementLink.click();
|
||||
await this.page.waitForURL('**/files');
|
||||
}
|
||||
|
||||
async navigateToOperationLog() {
|
||||
const auditMenu = this.page.locator('.el-sub-menu').filter({ hasText: '审计中心' });
|
||||
await auditMenu.click();
|
||||
await this.page.waitForTimeout(500);
|
||||
await this.operationLogLink.click();
|
||||
await this.page.waitForURL('**/oplog');
|
||||
}
|
||||
|
||||
async navigateToLoginLog() {
|
||||
const auditMenu = this.page.locator('.el-sub-menu').filter({ hasText: '审计中心' });
|
||||
await auditMenu.click();
|
||||
await this.page.waitForTimeout(500);
|
||||
await this.loginLogLink.click();
|
||||
await this.page.waitForURL('**/loginlog');
|
||||
}
|
||||
|
||||
async getUsername(): Promise<string | null> {
|
||||
return await this.userInfo.textContent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { Page, Locator } from '@playwright/test';
|
||||
|
||||
export class LoginPage {
|
||||
readonly page: Page;
|
||||
readonly usernameInput: Locator;
|
||||
readonly passwordInput: Locator;
|
||||
readonly loginButton: Locator;
|
||||
readonly errorMessage: Locator;
|
||||
readonly logoutButton: Locator;
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
this.usernameInput = page.locator('input[placeholder*="用户名"]').or(page.locator('.el-input__inner[placeholder*="用户名"]'));
|
||||
this.passwordInput = page.locator('input[type="password"]').or(page.locator('.el-input__inner[type="password"]'));
|
||||
this.loginButton = page.locator('button[type="submit"]').or(page.locator('button:has-text("登录")'));
|
||||
this.errorMessage = page.locator('.el-message--error').or(page.locator('.error-message'));
|
||||
this.logoutButton = page.getByRole('button', { name: '退出登录' });
|
||||
}
|
||||
|
||||
async goto() {
|
||||
await this.page.goto('/login');
|
||||
await this.page.waitForLoadState('networkidle');
|
||||
}
|
||||
|
||||
async login(username: string, password: string) {
|
||||
await this.usernameInput.fill(username);
|
||||
await this.passwordInput.fill(password);
|
||||
await this.loginButton.click();
|
||||
|
||||
try {
|
||||
await this.page.waitForURL('**/dashboard', { timeout: 10000 });
|
||||
} catch {
|
||||
await this.page.waitForTimeout(1000);
|
||||
}
|
||||
}
|
||||
|
||||
async getErrorMessage(): Promise<string | null> {
|
||||
try {
|
||||
await this.page.waitForSelector('.el-message', { timeout: 3000 });
|
||||
const messageElement = await this.page.locator('.el-message').first();
|
||||
const text = await messageElement.textContent();
|
||||
return text;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async logout() {
|
||||
const avatar = this.page.locator('.el-avatar');
|
||||
await avatar.click();
|
||||
await this.page.waitForTimeout(1000);
|
||||
|
||||
const logoutButton = this.page.locator('.el-dropdown-menu').getByText('退出登录');
|
||||
await logoutButton.click();
|
||||
await this.page.waitForURL('**/login', { timeout: 10000 });
|
||||
}
|
||||
|
||||
async isLoggedIn(): Promise<boolean> {
|
||||
return this.page.url().includes('/dashboard');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
import { Page, Locator } from '@playwright/test';
|
||||
|
||||
export class RoleManagementPage {
|
||||
readonly page: Page;
|
||||
readonly table: Locator;
|
||||
readonly createRoleButton: Locator;
|
||||
readonly successMessage: Locator;
|
||||
readonly roleNameInput: Locator;
|
||||
readonly roleKeyInput: Locator;
|
||||
readonly roleSortInput: Locator;
|
||||
readonly statusSelect: Locator;
|
||||
readonly remarkInput: Locator;
|
||||
readonly permissionDialog: Locator;
|
||||
readonly savePermissionButton: Locator;
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
this.table = page.locator('.el-table').or(page.locator('table'));
|
||||
this.createRoleButton = page.getByRole('button', { name: '创建角色' }).or(page.locator('button:has-text("创建角色")'));
|
||||
this.successMessage = page.locator('.el-message--success').or(page.locator('.success-message'));
|
||||
this.roleNameInput = page.locator('input[placeholder*="角色名称"]').or(page.locator('input[name*="roleName"]'));
|
||||
this.roleKeyInput = page.locator('input[placeholder*="角色权限字符串"]').or(page.locator('input[name*="roleKey"]'));
|
||||
this.roleSortInput = page.locator('input[placeholder*="显示顺序"]').or(page.locator('input[name*="roleSort"]'));
|
||||
this.statusSelect = page.locator('select[name*="status"]').or(page.locator('.el-select'));
|
||||
this.remarkInput = page.locator('textarea[placeholder*="备注"]').or(page.locator('textarea[name*="remark"]'));
|
||||
this.permissionDialog = page.locator('.permission-dialog').or(page.locator('.el-dialog'));
|
||||
this.savePermissionButton = page.getByRole('button', { name: '保存' }).or(page.locator('.permission-dialog .save-button'));
|
||||
}
|
||||
|
||||
async goto() {
|
||||
await this.page.goto('/roles');
|
||||
await this.page.waitForLoadState('networkidle');
|
||||
}
|
||||
|
||||
async clickCreateRole() {
|
||||
await this.createRoleButton.click();
|
||||
await this.page.waitForTimeout(500);
|
||||
}
|
||||
|
||||
async fillRoleForm(roleData: {
|
||||
roleName: string;
|
||||
roleKey: string;
|
||||
roleSort?: string;
|
||||
status?: string;
|
||||
remark?: string;
|
||||
}) {
|
||||
await this.roleNameInput.fill(roleData.roleName);
|
||||
await this.roleKeyInput.fill(roleData.roleKey);
|
||||
if (roleData.roleSort) {
|
||||
await this.roleSortInput.fill(roleData.roleSort);
|
||||
}
|
||||
if (roleData.status) {
|
||||
await this.statusSelect.selectOption(roleData.status);
|
||||
}
|
||||
if (roleData.remark) {
|
||||
await this.remarkInput.fill(roleData.remark);
|
||||
}
|
||||
}
|
||||
|
||||
async submitForm() {
|
||||
await this.page.getByRole('button', { name: '确定' }).or(page.locator('button:has-text("确定")')).click();
|
||||
}
|
||||
|
||||
async editRole(rowNumber: number) {
|
||||
await this.table.locator(`tbody tr:nth-child(${rowNumber})`).getByRole('button', { name: '编辑' }).or(page.locator(`tbody tr:nth-child(${rowNumber}) .edit-button`)).click();
|
||||
}
|
||||
|
||||
async deleteRole(rowNumber: number) {
|
||||
await this.table.locator(`tbody tr:nth-child(${rowNumber})`).getByRole('button', { name: '删除' }).or(page.locator(`tbody tr:nth-child(${rowNumber}) .delete-button`)).click();
|
||||
}
|
||||
|
||||
async confirmDelete() {
|
||||
await this.page.getByRole('button', { name: '确定' }).or(page.locator('.confirm-dialog .confirm-button')).click();
|
||||
}
|
||||
|
||||
async openPermissionDialog(rowNumber: number) {
|
||||
await this.table.locator(`tbody tr:nth-child(${rowNumber})`).getByRole('button', { name: '权限' }).or(page.locator(`tbody tr:nth-child(${rowNumber}) .permission-button`)).click();
|
||||
}
|
||||
|
||||
async selectPermission(permissionValue: string) {
|
||||
await this.page.click(`input[type="checkbox"][value="${permissionValue}"]`);
|
||||
}
|
||||
|
||||
async savePermissions() {
|
||||
await this.savePermissionButton.click();
|
||||
}
|
||||
|
||||
async containsText(text: string): Promise<boolean> {
|
||||
return await this.table.getByText(text).count() > 0;
|
||||
}
|
||||
|
||||
async isSuccessMessageVisible(): Promise<boolean> {
|
||||
try {
|
||||
return await this.successMessage.isVisible({ timeout: 3000 });
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async reload() {
|
||||
await this.page.reload();
|
||||
}
|
||||
|
||||
async getRoleName(rowNumber: number): Promise<string | null> {
|
||||
return await this.table.locator(`tbody tr:nth-child(${rowNumber}) td:first-child`).textContent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
import { Page, Locator } from '@playwright/test';
|
||||
|
||||
export class UserManagementPage {
|
||||
readonly page: Page;
|
||||
readonly table: Locator;
|
||||
readonly createUserButton: Locator;
|
||||
readonly searchInput: Locator;
|
||||
readonly searchButton: Locator;
|
||||
readonly successMessage: Locator;
|
||||
readonly pagination: Locator;
|
||||
readonly nextPageButton: Locator;
|
||||
readonly prevPageButton: Locator;
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
this.table = page.locator('.el-table').or(page.locator('table'));
|
||||
this.createUserButton = page.getByRole('button', { name: '创建用户' }).or(page.locator('button:has-text("创建用户")'));
|
||||
this.searchInput = page.locator('input[placeholder*="搜索"]').or(page.locator('input[name*="keyword"]'));
|
||||
this.searchButton = page.getByRole('button', { name: '搜索' }).or(page.locator('button:has-text("搜索")'));
|
||||
this.successMessage = page.locator('.el-message--success').or(page.locator('.success-message'));
|
||||
this.pagination = page.locator('.el-pagination').or(page.locator('.pagination'));
|
||||
this.nextPageButton = page.locator('.el-pagination .btn-next').or(page.locator('.pagination .next-page'));
|
||||
this.prevPageButton = page.locator('.el-pagination .btn-prev').or(page.locator('.pagination .prev-page'));
|
||||
}
|
||||
|
||||
async goto() {
|
||||
await this.page.goto('/users');
|
||||
await this.page.waitForLoadState('networkidle');
|
||||
}
|
||||
|
||||
async clickCreateUser() {
|
||||
await this.createUserButton.click();
|
||||
await this.page.waitForTimeout(500);
|
||||
}
|
||||
|
||||
async fillUserForm(userData: {
|
||||
username: string;
|
||||
email: string;
|
||||
phone?: string;
|
||||
password: string;
|
||||
confirmPassword: string;
|
||||
}) {
|
||||
await this.page.locator('input[placeholder*="用户名"]').or(page.locator('input[name*="username"]')).fill(userData.username);
|
||||
await this.page.locator('input[placeholder*="邮箱"]').or(page.locator('input[name*="email"]')).fill(userData.email);
|
||||
if (userData.phone) {
|
||||
await this.page.locator('input[placeholder*="手机号"]').or(page.locator('input[name*="phone"]')).fill(userData.phone);
|
||||
}
|
||||
await this.page.locator('input[placeholder*="密码"]').or(page.locator('input[name*="password"]')).first().fill(userData.password);
|
||||
await this.page.locator('input[placeholder*="确认密码"]').or(page.locator('input[name*="confirmPassword"]')).fill(userData.confirmPassword);
|
||||
}
|
||||
|
||||
async submitForm() {
|
||||
await this.page.getByRole('button', { name: '确定' }).or(page.locator('button:has-text("确定")')).click();
|
||||
}
|
||||
|
||||
async editUser(rowNumber: number) {
|
||||
await this.table.locator(`tbody tr:nth-child(${rowNumber})`).getByRole('button', { name: '编辑' }).or(page.locator(`tbody tr:nth-child(${rowNumber}) .edit-button`)).click();
|
||||
}
|
||||
|
||||
async deleteUser(rowNumber: number) {
|
||||
await this.table.locator(`tbody tr:nth-child(${rowNumber})`).getByRole('button', { name: '删除' }).or(page.locator(`tbody tr:nth-child(${rowNumber}) .delete-button`)).click();
|
||||
}
|
||||
|
||||
async confirmDelete() {
|
||||
await this.page.getByRole('button', { name: '确定' }).or(page.locator('.confirm-dialog .confirm-button')).click();
|
||||
}
|
||||
|
||||
async search(keyword: string) {
|
||||
await this.searchInput.fill(keyword);
|
||||
await this.searchButton.click();
|
||||
}
|
||||
|
||||
async nextPage() {
|
||||
await this.nextPageButton.click();
|
||||
}
|
||||
|
||||
async prevPage() {
|
||||
await this.prevPageButton.click();
|
||||
}
|
||||
|
||||
async getCurrentPage(): Promise<string> {
|
||||
return await this.page.locator('.el-pagination .el-pager li.active').or(page.locator('.pagination .current-page')).textContent() || '1';
|
||||
}
|
||||
|
||||
async getUserCount(): Promise<number> {
|
||||
return await this.table.locator('tbody tr').count();
|
||||
}
|
||||
|
||||
async getUserName(rowNumber: number): Promise<string | null> {
|
||||
return await this.table.locator(`tbody tr:nth-child(${rowNumber}) td:first-child`).textContent();
|
||||
}
|
||||
|
||||
async containsText(text: string): Promise<boolean> {
|
||||
return await this.table.getByText(text).count() > 0;
|
||||
}
|
||||
|
||||
async isSuccessMessageVisible(): Promise<boolean> {
|
||||
try {
|
||||
return await this.successMessage.isVisible({ timeout: 3000 });
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async reload() {
|
||||
await this.page.reload();
|
||||
}
|
||||
}
|
||||
@@ -1,79 +1,126 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { LoginPage } from './pages/LoginPage';
|
||||
import { DashboardPage } from './pages/DashboardPage';
|
||||
import { RoleManagementPage } from './pages/RoleManagementPage';
|
||||
|
||||
test.describe('角色管理 E2E 测试', () => {
|
||||
let loginPage: LoginPage;
|
||||
let dashboardPage: DashboardPage;
|
||||
let roleManagementPage: RoleManagementPage;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/login');
|
||||
await page.fill('input[placeholder*="用户名"]', 'admin');
|
||||
await page.fill('input[type="password"]', 'admin123');
|
||||
await page.click('button:has-text("登录")');
|
||||
await page.waitForURL('**/dashboard');
|
||||
loginPage = new LoginPage(page);
|
||||
dashboardPage = new DashboardPage(page);
|
||||
roleManagementPage = new RoleManagementPage(page);
|
||||
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'password');
|
||||
});
|
||||
|
||||
test('创建角色完整流程', async ({ page }) => {
|
||||
await page.click('text=角色管理');
|
||||
await page.waitForURL('**/roles');
|
||||
await dashboardPage.navigateToRoleManagement();
|
||||
|
||||
await page.click('text=创建角色');
|
||||
await roleManagementPage.clickCreateRole();
|
||||
|
||||
const timestamp = Date.now();
|
||||
const roleName = `测试角色_${timestamp}`;
|
||||
const roleKey = `test_role_${timestamp}`;
|
||||
const roleData = {
|
||||
roleName: `测试角色_${timestamp}`,
|
||||
roleKey: `test_role_${timestamp}`,
|
||||
roleSort: '1',
|
||||
status: '1',
|
||||
remark: `测试角色备注_${timestamp}`,
|
||||
};
|
||||
|
||||
await page.fill('input[name="roleName"]', roleName);
|
||||
await page.fill('input[name="roleKey"]', roleKey);
|
||||
await page.fill('input[name="roleSort"]', '1');
|
||||
await roleManagementPage.fillRoleForm(roleData);
|
||||
await roleManagementPage.submitForm();
|
||||
|
||||
await page.click('input[type="checkbox"][value="user:view"]');
|
||||
await page.click('input[type="checkbox"][value="user:create"]');
|
||||
|
||||
await page.click('button[type="submit"]');
|
||||
|
||||
await expect(page.locator('.success-message')).toBeVisible();
|
||||
await expect(page.locator('table')).toContainText(roleName);
|
||||
await expect(roleManagementPage.successMessage).toBeVisible();
|
||||
await expect(roleManagementPage.table).toContainText(roleData.roleName);
|
||||
});
|
||||
|
||||
test('编辑角色流程', async ({ page }) => {
|
||||
await page.click('text=角色管理');
|
||||
await page.waitForURL('**/roles');
|
||||
await dashboardPage.navigateToRoleManagement();
|
||||
|
||||
await page.click('table tbody tr:first-child .edit-button');
|
||||
await roleManagementPage.editRole(1);
|
||||
|
||||
await page.fill('input[name="roleName"]', '更新后的角色名称');
|
||||
|
||||
await page.click('button[type="submit"]');
|
||||
await roleManagementPage.submitForm();
|
||||
|
||||
await expect(page.locator('.success-message')).toBeVisible();
|
||||
await expect(page.locator('table')).toContainText('更新后的角色名称');
|
||||
await expect(roleManagementPage.successMessage).toBeVisible();
|
||||
await expect(roleManagementPage.table).toContainText('更新后的角色名称');
|
||||
});
|
||||
|
||||
test('分配权限流程', async ({ page }) => {
|
||||
await page.click('text=角色管理');
|
||||
await page.waitForURL('**/roles');
|
||||
await dashboardPage.navigateToRoleManagement();
|
||||
|
||||
await page.click('table tbody tr:first-child .permission-button');
|
||||
await roleManagementPage.openPermissionDialog(1);
|
||||
|
||||
await page.click('input[type="checkbox"][value="user:edit"]');
|
||||
await page.click('input[type="checkbox"][value="user:delete"]');
|
||||
await roleManagementPage.selectPermission('user:view');
|
||||
await roleManagementPage.selectPermission('user:create');
|
||||
await roleManagementPage.selectPermission('user:edit');
|
||||
await roleManagementPage.selectPermission('user:delete');
|
||||
|
||||
await page.click('.permission-dialog .save-button');
|
||||
await roleManagementPage.savePermissions();
|
||||
|
||||
await expect(page.locator('.success-message')).toBeVisible();
|
||||
await expect(roleManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
test('删除角色流程', async ({ page }) => {
|
||||
await page.click('text=角色管理');
|
||||
await page.waitForURL('**/roles');
|
||||
await dashboardPage.navigateToRoleManagement();
|
||||
|
||||
const firstRow = page.locator('table tbody tr:first-child');
|
||||
const roleName = await firstRow.locator('td:first-child').textContent();
|
||||
const roleName = await roleManagementPage.getRoleName(1);
|
||||
|
||||
await firstRow.locator('.delete-button').click();
|
||||
await roleManagementPage.deleteRole(1);
|
||||
await roleManagementPage.confirmDelete();
|
||||
|
||||
await expect(roleManagementPage.successMessage).toBeVisible();
|
||||
|
||||
await roleManagementPage.reload();
|
||||
await expect(roleManagementPage.table).not.toContainText(roleName);
|
||||
});
|
||||
|
||||
test('角色状态切换', async ({ page }) => {
|
||||
await dashboardPage.navigateToRoleManagement();
|
||||
|
||||
await page.click('table tbody tr:first-child .status-toggle');
|
||||
|
||||
await expect(roleManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
test('搜索角色功能', async ({ page }) => {
|
||||
await dashboardPage.navigateToRoleManagement();
|
||||
|
||||
await page.fill('input[name="keyword"]', 'admin');
|
||||
await page.click('button[type="search"]');
|
||||
|
||||
await expect(roleManagementPage.table).toContainText('admin');
|
||||
});
|
||||
|
||||
test('批量删除角色', async ({ page }) => {
|
||||
await dashboardPage.navigateToRoleManagement();
|
||||
|
||||
await page.check('table tbody tr:nth-child(1) input[type="checkbox"]');
|
||||
await page.check('table tbody tr:nth-child(2) input[type="checkbox"]');
|
||||
|
||||
await page.click('button:has-text("批量删除")');
|
||||
await page.click('.confirm-dialog .confirm-button');
|
||||
|
||||
await expect(page.locator('.success-message')).toBeVisible();
|
||||
|
||||
await page.reload();
|
||||
await expect(page.locator('table')).not.toContainText(roleName);
|
||||
await expect(roleManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('复制角色', async ({ page }) => {
|
||||
await dashboardPage.navigateToRoleManagement();
|
||||
|
||||
await page.click('table tbody tr:first-child .copy-button');
|
||||
|
||||
const timestamp = Date.now();
|
||||
await page.fill('input[name="roleName"]', `复制角色_${timestamp}`);
|
||||
await page.fill('input[name="roleKey"]', `copy_role_${timestamp}`);
|
||||
|
||||
await roleManagementPage.submitForm();
|
||||
|
||||
await expect(roleManagementPage.successMessage).toBeVisible();
|
||||
await expect(roleManagementPage.table).toContainText(`复制角色_${timestamp}`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('简单API测试', () => {
|
||||
|
||||
test('测试1: 后端健康检查', async ({ request }) => {
|
||||
console.log('测试1: 检查后端健康状态');
|
||||
const response = await request.get('http://localhost:8084/actuator/health');
|
||||
console.log('响应状态:', response.status());
|
||||
const body = await response.json();
|
||||
console.log('响应体:', JSON.stringify(body, null, 2));
|
||||
expect(response.status()).toBe(200);
|
||||
expect(body.status).toBe('UP');
|
||||
});
|
||||
|
||||
test('测试2: 登录API', async ({ request }) => {
|
||||
console.log('测试2: 测试登录API');
|
||||
const response = await request.post('http://localhost:8084/api/auth/login', {
|
||||
data: {
|
||||
username: 'admin',
|
||||
password: 'password'
|
||||
}
|
||||
});
|
||||
console.log('响应状态:', response.status());
|
||||
const body = await response.json();
|
||||
console.log('响应体:', JSON.stringify(body, null, 2));
|
||||
expect(response.status()).toBe(200);
|
||||
expect(body.token).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -4,7 +4,7 @@ test.describe('系统配置 E2E 测试', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/login');
|
||||
await page.fill('input[placeholder*="用户名"]', 'admin');
|
||||
await page.fill('input[type="password"]', 'admin123');
|
||||
await page.fill('input[type="password"]', 'password');
|
||||
await page.click('button:has-text("登录")');
|
||||
await page.waitForURL('**/dashboard');
|
||||
});
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { LoginPage } from './pages/LoginPage';
|
||||
import { DashboardPage } from './pages/DashboardPage';
|
||||
|
||||
test.describe('UAT阶段一:核心功能验证', () => {
|
||||
|
||||
test('UAT-AUTH-001: 成功登录流程', async ({ page }) => {
|
||||
const loginPage = new LoginPage(page);
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
|
||||
await test.step('访问登录页面', async () => {
|
||||
await loginPage.goto();
|
||||
await expect(page).toHaveTitle(/登录/);
|
||||
});
|
||||
|
||||
await test.step('输入用户名和密码', async () => {
|
||||
await loginPage.usernameInput.fill('admin');
|
||||
await loginPage.passwordInput.fill('password');
|
||||
});
|
||||
|
||||
await test.step('点击登录按钮', async () => {
|
||||
await loginPage.loginButton.click();
|
||||
});
|
||||
|
||||
await test.step('验证登录成功', async () => {
|
||||
await page.waitForURL(/.*dashboard/, { timeout: 10000 });
|
||||
const username = await dashboardPage.getUsername();
|
||||
expect(username).toContain('admin');
|
||||
});
|
||||
});
|
||||
|
||||
test('UAT-AUTH-002: 登录失败 - 无效凭证', async ({ page }) => {
|
||||
const loginPage = new LoginPage(page);
|
||||
|
||||
await test.step('访问登录页面', async () => {
|
||||
await loginPage.goto();
|
||||
await expect(page).toHaveTitle(/登录/);
|
||||
});
|
||||
|
||||
await test.step('输入无效凭证', async () => {
|
||||
await loginPage.usernameInput.fill('invalid');
|
||||
await loginPage.passwordInput.fill('invalid');
|
||||
await loginPage.loginButton.click();
|
||||
});
|
||||
|
||||
await test.step('验证错误消息显示', async () => {
|
||||
await page.waitForTimeout(2000);
|
||||
const errorMessage = await loginPage.getErrorMessage();
|
||||
expect(errorMessage).toBeTruthy();
|
||||
});
|
||||
|
||||
await test.step('验证保持在登录页面', async () => {
|
||||
await expect(page).toHaveURL(/.*login/);
|
||||
});
|
||||
});
|
||||
|
||||
test('UAT-AUTH-003: 登出流程', async ({ page }) => {
|
||||
const loginPage = new LoginPage(page);
|
||||
|
||||
await test.step('登录系统', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.usernameInput.fill('admin');
|
||||
await loginPage.passwordInput.fill('password');
|
||||
await loginPage.loginButton.click();
|
||||
await page.waitForURL(/.*dashboard/, { timeout: 10000 });
|
||||
});
|
||||
|
||||
await test.step('点击用户头像', async () => {
|
||||
const avatar = page.locator('.el-avatar');
|
||||
await avatar.click();
|
||||
await page.waitForTimeout(1000);
|
||||
});
|
||||
|
||||
await test.step('点击退出登录', async () => {
|
||||
const logoutButton = page.locator('.el-dropdown-menu').getByText('退出登录');
|
||||
await logoutButton.click();
|
||||
});
|
||||
|
||||
await test.step('验证跳转到登录页面', async () => {
|
||||
await page.waitForURL(/.*login/, { timeout: 10000 });
|
||||
await expect(page).toHaveTitle(/登录/);
|
||||
});
|
||||
});
|
||||
|
||||
test('UAT-NAV-001: 系统管理菜单导航', async ({ page }) => {
|
||||
const loginPage = new LoginPage(page);
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
|
||||
await test.step('登录系统', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.usernameInput.fill('admin');
|
||||
await loginPage.passwordInput.fill('password');
|
||||
await loginPage.loginButton.click();
|
||||
await page.waitForURL(/.*dashboard/, { timeout: 10000 });
|
||||
});
|
||||
|
||||
await test.step('点击系统管理菜单', async () => {
|
||||
const systemMenu = page.locator('.el-sub-menu').filter({ hasText: '系统管理' });
|
||||
await systemMenu.click();
|
||||
await page.waitForTimeout(500);
|
||||
});
|
||||
|
||||
await test.step('点击用户管理', async () => {
|
||||
await dashboardPage.userManagementLink.click();
|
||||
});
|
||||
|
||||
await test.step('验证页面跳转', async () => {
|
||||
await page.waitForURL(/.*users/, { timeout: 10000 });
|
||||
await expect(page).toHaveURL(/.*users/);
|
||||
});
|
||||
});
|
||||
|
||||
test('UAT-NAV-002: 角色管理菜单导航', async ({ page }) => {
|
||||
const loginPage = new LoginPage(page);
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
|
||||
await test.step('登录系统', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.usernameInput.fill('admin');
|
||||
await loginPage.passwordInput.fill('password');
|
||||
await loginPage.loginButton.click();
|
||||
await page.waitForURL(/.*dashboard/, { timeout: 10000 });
|
||||
});
|
||||
|
||||
await test.step('点击系统管理菜单', async () => {
|
||||
const systemMenu = page.locator('.el-sub-menu').filter({ hasText: '系统管理' });
|
||||
await systemMenu.click();
|
||||
await page.waitForTimeout(500);
|
||||
});
|
||||
|
||||
await test.step('点击角色管理', async () => {
|
||||
await dashboardPage.roleManagementLink.click();
|
||||
});
|
||||
|
||||
await test.step('验证页面跳转', async () => {
|
||||
await page.waitForURL(/.*roles/, { timeout: 10000 });
|
||||
await expect(page).toHaveURL(/.*roles/);
|
||||
});
|
||||
});
|
||||
|
||||
test('UAT-NAV-003: 菜单管理菜单导航', async ({ page }) => {
|
||||
const loginPage = new LoginPage(page);
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
|
||||
await test.step('登录系统', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.usernameInput.fill('admin');
|
||||
await loginPage.passwordInput.fill('password');
|
||||
await loginPage.loginButton.click();
|
||||
await page.waitForURL(/.*dashboard/, { timeout: 10000 });
|
||||
});
|
||||
|
||||
await test.step('点击系统管理菜单', async () => {
|
||||
const systemMenu = page.locator('.el-sub-menu').filter({ hasText: '系统管理' });
|
||||
await systemMenu.click();
|
||||
await page.waitForTimeout(500);
|
||||
});
|
||||
|
||||
await test.step('点击菜单管理', async () => {
|
||||
await dashboardPage.menuManagementLink.click();
|
||||
});
|
||||
|
||||
await test.step('验证页面跳转', async () => {
|
||||
await page.waitForURL(/.*menus/, { timeout: 10000 });
|
||||
await expect(page).toHaveURL(/.*menus/);
|
||||
});
|
||||
});
|
||||
|
||||
test('UAT-NAV-004: 系统配置菜单导航', async ({ page }) => {
|
||||
const loginPage = new LoginPage(page);
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
|
||||
await test.step('登录系统', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.usernameInput.fill('admin');
|
||||
await loginPage.passwordInput.fill('password');
|
||||
await loginPage.loginButton.click();
|
||||
await page.waitForURL(/.*dashboard/, { timeout: 10000 });
|
||||
});
|
||||
|
||||
await test.step('点击系统配置菜单', async () => {
|
||||
const configMenu = page.locator('.el-sub-menu').filter({ hasText: '系统配置' });
|
||||
await configMenu.click();
|
||||
await page.waitForTimeout(500);
|
||||
});
|
||||
|
||||
await test.step('点击参数配置', async () => {
|
||||
await dashboardPage.systemConfigLink.click();
|
||||
});
|
||||
|
||||
await test.step('验证页面跳转', async () => {
|
||||
await page.waitForURL(/.*sysconfig/, { timeout: 10000 });
|
||||
await expect(page).toHaveURL(/.*sysconfig/);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,82 +1,118 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { LoginPage } from './pages/LoginPage';
|
||||
import { DashboardPage } from './pages/DashboardPage';
|
||||
import { UserManagementPage } from './pages/UserManagementPage';
|
||||
import { generateTestUser } from './fixtures/test-data';
|
||||
|
||||
test.describe('用户管理 E2E 测试', () => {
|
||||
let loginPage: LoginPage;
|
||||
let dashboardPage: DashboardPage;
|
||||
let userManagementPage: UserManagementPage;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/login');
|
||||
await page.fill('input[placeholder*="用户名"]', 'admin');
|
||||
await page.fill('input[type="password"]', 'admin123');
|
||||
await page.click('button:has-text("登录")');
|
||||
await page.waitForURL('**/dashboard');
|
||||
loginPage = new LoginPage(page);
|
||||
dashboardPage = new DashboardPage(page);
|
||||
userManagementPage = new UserManagementPage(page);
|
||||
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'password');
|
||||
});
|
||||
|
||||
test('创建用户完整流程', async ({ page }) => {
|
||||
await page.click('text=用户管理');
|
||||
await page.waitForURL('**/users');
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
|
||||
await page.click('text=创建用户');
|
||||
await userManagementPage.clickCreateUser();
|
||||
|
||||
const timestamp = Date.now();
|
||||
const username = `testuser_${timestamp}`;
|
||||
const userData = {
|
||||
username: `testuser_${timestamp}`,
|
||||
email: `test_${timestamp}@example.com`,
|
||||
phone: '13800138000',
|
||||
password: 'Test123!@#',
|
||||
confirmPassword: 'Test123!@#',
|
||||
};
|
||||
|
||||
await page.fill('input[name="username"]', username);
|
||||
await page.fill('input[name="email"]', `test_${timestamp}@example.com`);
|
||||
await page.fill('input[name="phone"]', '13800138000');
|
||||
await page.fill('input[name="password"]', 'Test123!@#');
|
||||
await page.fill('input[name="confirmPassword"]', 'Test123!@#');
|
||||
await userManagementPage.fillUserForm(userData);
|
||||
await userManagementPage.submitForm();
|
||||
|
||||
await page.click('button[type="submit"]');
|
||||
|
||||
await expect(page.locator('.success-message')).toBeVisible();
|
||||
await expect(page.locator('table')).toContainText(username);
|
||||
await expect(userManagementPage.successMessage).toBeVisible();
|
||||
await expect(userManagementPage.table).toContainText(userData.username);
|
||||
});
|
||||
|
||||
test('编辑用户流程', async ({ page }) => {
|
||||
await page.click('text=用户管理');
|
||||
await page.waitForURL('**/users');
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
|
||||
await page.click('table tbody tr:first-child .edit-button');
|
||||
await userManagementPage.editUser(1);
|
||||
|
||||
await page.fill('input[name="email"]', 'updated@example.com');
|
||||
|
||||
await page.click('button[type="submit"]');
|
||||
await userManagementPage.submitForm();
|
||||
|
||||
await expect(page.locator('.success-message')).toBeVisible();
|
||||
await expect(page.locator('table')).toContainText('updated@example.com');
|
||||
await expect(userManagementPage.successMessage).toBeVisible();
|
||||
await expect(userManagementPage.table).toContainText('updated@example.com');
|
||||
});
|
||||
|
||||
test('删除用户流程', async ({ page }) => {
|
||||
await page.click('text=用户管理');
|
||||
await page.waitForURL('**/users');
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
|
||||
const firstRow = page.locator('table tbody tr:first-child');
|
||||
const username = await firstRow.locator('td:first-child').textContent();
|
||||
const username = await userManagementPage.getUserName(1);
|
||||
|
||||
await firstRow.locator('.delete-button').click();
|
||||
await userManagementPage.deleteUser(1);
|
||||
await userManagementPage.confirmDelete();
|
||||
|
||||
await page.click('.confirm-dialog .confirm-button');
|
||||
await expect(userManagementPage.successMessage).toBeVisible();
|
||||
|
||||
await expect(page.locator('.success-message')).toBeVisible();
|
||||
|
||||
await page.reload();
|
||||
await expect(page.locator('table')).not.toContainText(username);
|
||||
await userManagementPage.reload();
|
||||
await expect(userManagementPage.table).not.toContainText(username);
|
||||
});
|
||||
|
||||
test('搜索用户功能', async ({ page }) => {
|
||||
await page.click('text=用户管理');
|
||||
await page.waitForURL('**/users');
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
|
||||
await page.fill('input[name="keyword"]', 'admin');
|
||||
await page.click('button[type="search"]');
|
||||
await userManagementPage.search('admin');
|
||||
|
||||
await expect(page.locator('table')).toContainText('admin');
|
||||
await expect(userManagementPage.table).toContainText('admin');
|
||||
});
|
||||
|
||||
test('分页功能', async ({ page }) => {
|
||||
await page.click('text=用户管理');
|
||||
await page.waitForURL('**/users');
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
|
||||
await page.click('.pagination .next-page');
|
||||
const currentPage = await userManagementPage.getCurrentPage();
|
||||
expect(currentPage).toBe('1');
|
||||
|
||||
await expect(page.locator('.pagination .current-page')).toContainText('2');
|
||||
await userManagementPage.nextPage();
|
||||
|
||||
const newPage = await userManagementPage.getCurrentPage();
|
||||
expect(newPage).toBe('2');
|
||||
});
|
||||
});
|
||||
|
||||
test('批量删除用户', async ({ page }) => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
|
||||
await page.check('table tbody tr:nth-child(1) input[type="checkbox"]');
|
||||
await page.check('table tbody tr:nth-child(2) input[type="checkbox"]');
|
||||
|
||||
await page.click('button:has-text("批量删除")');
|
||||
await page.click('.confirm-dialog .confirm-button');
|
||||
|
||||
await expect(userManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
test('用户状态切换', async ({ page }) => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
|
||||
await page.click('table tbody tr:first-child .status-toggle');
|
||||
|
||||
await expect(userManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
test('导出用户数据', async ({ page }) => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
|
||||
const downloadPromise = page.waitForEvent('download');
|
||||
await page.click('button:has-text("导出")');
|
||||
const download = await downloadPromise;
|
||||
|
||||
expect(download.suggestedFilename()).toMatch(/users.*\.xlsx/);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
import { APIRequestContext } from '@playwright/test';
|
||||
|
||||
export class ApiClient {
|
||||
private request: APIRequestContext;
|
||||
private baseURL: string;
|
||||
|
||||
constructor(request: APIRequestContext, baseURL: string = 'http://localhost:8084') {
|
||||
this.request = request;
|
||||
this.baseURL = baseURL;
|
||||
}
|
||||
|
||||
async login(username: string, password: string): Promise<{ token: string; userId: number }> {
|
||||
const response = await this.request.post(`${this.baseURL}/api/auth/login`, {
|
||||
data: {
|
||||
username,
|
||||
password,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok()) {
|
||||
throw new Error(`Login failed: ${response.status()}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return {
|
||||
token: data.token,
|
||||
userId: data.userId,
|
||||
};
|
||||
}
|
||||
|
||||
async logout(token: string): Promise<void> {
|
||||
await this.request.post(`${this.baseURL}/api/auth/logout`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async getUsers(token: string): Promise<any[]> {
|
||||
const response = await this.request.get(`${this.baseURL}/api/users`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok()) {
|
||||
throw new Error(`Get users failed: ${response.status()}`);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
async createUser(token: string, userData: any): Promise<any> {
|
||||
const response = await this.request.post(`${this.baseURL}/api/users`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
data: userData,
|
||||
});
|
||||
|
||||
if (!response.ok()) {
|
||||
throw new Error(`Create user failed: ${response.status()}`);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
async updateUser(token: string, userId: number, userData: any): Promise<any> {
|
||||
const response = await this.request.put(`${this.baseURL}/api/users/${userId}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
data: userData,
|
||||
});
|
||||
|
||||
if (!response.ok()) {
|
||||
throw new Error(`Update user failed: ${response.status()}`);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
async deleteUser(token: string, userId: number): Promise<void> {
|
||||
const response = await this.request.delete(`${this.baseURL}/api/users/${userId}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok()) {
|
||||
throw new Error(`Delete user failed: ${response.status()}`);
|
||||
}
|
||||
}
|
||||
|
||||
async getRoles(token: string): Promise<any[]> {
|
||||
const response = await this.request.get(`${this.baseURL}/api/roles`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok()) {
|
||||
throw new Error(`Get roles failed: ${response.status()}`);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
async createRole(token: string, roleData: any): Promise<any> {
|
||||
const response = await this.request.post(`${this.baseURL}/api/roles`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
data: roleData,
|
||||
});
|
||||
|
||||
if (!response.ok()) {
|
||||
throw new Error(`Create role failed: ${response.status()}`);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
async deleteRole(token: string, roleId: number): Promise<void> {
|
||||
const response = await this.request.delete(`${this.baseURL}/api/roles/${roleId}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok()) {
|
||||
throw new Error(`Delete role failed: ${response.status()}`);
|
||||
}
|
||||
}
|
||||
|
||||
async getMenus(token: string): Promise<any[]> {
|
||||
const response = await this.request.get(`${this.baseURL}/api/menus`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok()) {
|
||||
throw new Error(`Get menus failed: ${response.status()}`);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
async healthCheck(): Promise<{ status: string }> {
|
||||
const response = await this.request.get(`${this.baseURL}/actuator/health`);
|
||||
|
||||
if (!response.ok()) {
|
||||
throw new Error(`Health check failed: ${response.status()}`);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user