feat(admin): 添加用户管理相关文件
添加用户管理视图、API和状态管理文件
This commit is contained in:
@@ -0,0 +1,214 @@
|
||||
import { test, expect, Browser, BrowserContext, Page } from '@playwright/test';
|
||||
|
||||
test.describe('跨端交互测试', () => {
|
||||
|
||||
test.beforeAll(async () => {
|
||||
console.log('开始跨端交互测试...');
|
||||
});
|
||||
|
||||
test('Admin创建用户 - 数据一致性验证', async ({ browser }) => {
|
||||
const adminContext = await browser.newContext();
|
||||
const adminPage = await adminContext.newPage();
|
||||
|
||||
try {
|
||||
await adminPage.goto('/login');
|
||||
await adminPage.waitForLoadState('networkidle');
|
||||
|
||||
await adminPage.fill('[data-testid="username-input"] input', 'admin');
|
||||
await adminPage.fill('[data-testid="password-input"] input', 'admin123456');
|
||||
await adminPage.click('[data-testid="login-button"]');
|
||||
|
||||
await expect(adminPage).toHaveURL(/.*\//, { timeout: 15000 });
|
||||
|
||||
await adminPage.goto('/system/user');
|
||||
await adminPage.waitForLoadState('networkidle');
|
||||
|
||||
const timestamp = Date.now();
|
||||
const testUsername = `cross_test_${timestamp}`;
|
||||
|
||||
await adminPage.click('[data-testid="add-user-button"]');
|
||||
await expect(adminPage.locator('.el-dialog')).toBeVisible();
|
||||
|
||||
await adminPage.fill('[data-testid="username-input"] input', testUsername);
|
||||
await adminPage.fill('[data-testid="email-input"] input', `${testUsername}@example.com`);
|
||||
await adminPage.fill('[data-testid="phone-input"] input', '13800138000');
|
||||
await adminPage.fill('[data-testid="password-input"] input', 'Test@123456');
|
||||
|
||||
await adminPage.click('[data-testid="submit-button"]');
|
||||
|
||||
await expect(adminPage.locator('.el-message--success')).toBeVisible({ timeout: 10000 });
|
||||
|
||||
await adminPage.fill('[data-testid="search-username-input"] input', testUsername);
|
||||
await adminPage.click('button:has-text("搜索")');
|
||||
await adminPage.waitForTimeout(1000);
|
||||
|
||||
await expect(adminPage.locator(`text=${testUsername}`)).toBeVisible({ timeout: 10000 });
|
||||
|
||||
console.log(`用户 ${testUsername} 在Admin端创建成功`);
|
||||
|
||||
} finally {
|
||||
await adminContext.close();
|
||||
}
|
||||
});
|
||||
|
||||
test('API直接调用 - 验证数据持久化', async ({ request }) => {
|
||||
const response = await request.get('/api/sys/user/list', {
|
||||
headers: {
|
||||
'Authorization': 'Bearer test_token',
|
||||
},
|
||||
});
|
||||
|
||||
if (response.status() === 401) {
|
||||
console.log('API需要认证,跳过直接调用测试');
|
||||
test.skip();
|
||||
return;
|
||||
}
|
||||
|
||||
expect(response.ok()).toBeTruthy();
|
||||
|
||||
const data = await response.json();
|
||||
expect(data).toBeDefined();
|
||||
});
|
||||
|
||||
test('角色权限变更 - 验证权限生效', async ({ browser }) => {
|
||||
const adminContext = await browser.newContext();
|
||||
const adminPage = await adminContext.newPage();
|
||||
|
||||
try {
|
||||
await adminPage.goto('/login');
|
||||
await adminPage.fill('[data-testid="username-input"] input', 'admin');
|
||||
await adminPage.fill('[data-testid="password-input"] input', 'admin123456');
|
||||
await adminPage.click('[data-testid="login-button"]');
|
||||
await expect(adminPage).toHaveURL(/.*\//, { timeout: 15000 });
|
||||
|
||||
await adminPage.goto('/system/role');
|
||||
await adminPage.waitForLoadState('networkidle');
|
||||
|
||||
const editButton = adminPage.locator('table button:has-text("编辑"), .el-table button:has-text("编辑")');
|
||||
const buttonCount = await editButton.count();
|
||||
|
||||
expect(buttonCount).toBeGreaterThan(0);
|
||||
console.log(`找到 ${buttonCount} 个可编辑的角色`);
|
||||
|
||||
} finally {
|
||||
await adminContext.close();
|
||||
}
|
||||
});
|
||||
|
||||
test('菜单配置变更 - 验证菜单更新', async ({ browser }) => {
|
||||
const adminContext = await browser.newContext();
|
||||
const adminPage = await adminContext.newPage();
|
||||
|
||||
try {
|
||||
await adminPage.goto('/login');
|
||||
await adminPage.fill('[data-testid="username-input"] input', 'admin');
|
||||
await adminPage.fill('[data-testid="password-input"] input', 'admin123456');
|
||||
await adminPage.click('[data-testid="login-button"]');
|
||||
await expect(adminPage).toHaveURL(/.*\//, { timeout: 15000 });
|
||||
|
||||
await adminPage.goto('/system/menu');
|
||||
await adminPage.waitForLoadState('networkidle');
|
||||
|
||||
const table = adminPage.locator('table, .el-table');
|
||||
await expect(table).toBeVisible();
|
||||
|
||||
const rows = await table.locator('tbody tr').count();
|
||||
expect(rows).toBeGreaterThan(0);
|
||||
console.log(`找到 ${rows} 个菜单项`);
|
||||
|
||||
} finally {
|
||||
await adminContext.close();
|
||||
}
|
||||
});
|
||||
|
||||
test('并发用户操作 - 验证数据一致性', async ({ browser }) => {
|
||||
const contexts: BrowserContext[] = [];
|
||||
const pages: Page[] = [];
|
||||
|
||||
try {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const context = await browser.newContext();
|
||||
const page = await context.newPage();
|
||||
contexts.push(context);
|
||||
pages.push(page);
|
||||
}
|
||||
|
||||
await Promise.all(pages.map(async (page, index) => {
|
||||
await page.goto('/login');
|
||||
await page.fill('[data-testid="username-input"] input', 'admin');
|
||||
await page.fill('[data-testid="password-input"] input', 'admin123456');
|
||||
await page.click('[data-testid="login-button"]');
|
||||
await expect(page).toHaveURL(/.*\//, { timeout: 15000 });
|
||||
console.log(`用户 ${index + 1} 登录成功`);
|
||||
}));
|
||||
|
||||
await Promise.all(pages.map(async (page) => {
|
||||
await page.goto('/system/user');
|
||||
await page.waitForLoadState('networkidle');
|
||||
}));
|
||||
|
||||
const userCounts = await Promise.all(pages.map(async (page) => {
|
||||
const table = page.locator('[data-testid="user-table"]');
|
||||
return await table.locator('tbody tr').count();
|
||||
}));
|
||||
|
||||
console.log('各会话用户数量:', userCounts);
|
||||
|
||||
} finally {
|
||||
await Promise.all(contexts.map(context => context.close()));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('数据一致性验证', () => {
|
||||
|
||||
test('用户数据CRUD一致性', async ({ page }) => {
|
||||
await page.goto('/login');
|
||||
await page.fill('[data-testid="username-input"] input', 'admin');
|
||||
await page.fill('[data-testid="password-input"] input', 'admin123456');
|
||||
await page.click('[data-testid="login-button"]');
|
||||
await expect(page).toHaveURL(/.*\//, { timeout: 15000 });
|
||||
|
||||
await page.goto('/system/user');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const table = page.locator('[data-testid="user-table"]');
|
||||
const initialCount = await table.locator('tbody tr').count();
|
||||
console.log(`初始用户数量: ${initialCount}`);
|
||||
|
||||
const timestamp = Date.now();
|
||||
await page.click('[data-testid="add-user-button"]');
|
||||
await page.fill('[data-testid="username-input"] input', `consistency_test_${timestamp}`);
|
||||
await page.fill('[data-testid="email-input"] input', `consistency_${timestamp}@example.com`);
|
||||
await page.fill('[data-testid="phone-input"] input', '13700137000');
|
||||
await page.fill('[data-testid="password-input"] input', 'Test@123456');
|
||||
await page.click('[data-testid="submit-button"]');
|
||||
|
||||
await expect(page.locator('.el-message--success')).toBeVisible({ timeout: 10000 });
|
||||
|
||||
await page.reload();
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const afterCreateCount = await table.locator('tbody tr').count();
|
||||
console.log(`创建后用户数量: ${afterCreateCount}`);
|
||||
});
|
||||
|
||||
test('API响应数据格式验证', async ({ request }) => {
|
||||
const endpoints = [
|
||||
{ path: '/actuator/health', expectedFields: ['status'] },
|
||||
];
|
||||
|
||||
for (const endpoint of endpoints) {
|
||||
const response = await request.get(endpoint.path);
|
||||
|
||||
if (response.ok()) {
|
||||
const data = await response.json();
|
||||
|
||||
for (const field of endpoint.expectedFields) {
|
||||
expect(data).toHaveProperty(field);
|
||||
}
|
||||
console.log(`端点 ${endpoint.path} 响应格式正确`);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,251 @@
|
||||
/**
|
||||
* 端到端业务流程集成测试
|
||||
* 测试完整的用户旅程和数据流
|
||||
*/
|
||||
|
||||
import { test, expect } from '../shared/fixtures/test-fixtures';
|
||||
import { testLogger } from '../shared/utils/test-logger';
|
||||
import { testReporter } from '../shared/utils/test-reporter';
|
||||
|
||||
test.describe('完整用户旅程测试 @integration @e2e', () => {
|
||||
test.beforeAll(async () => {
|
||||
testReporter.startReport();
|
||||
});
|
||||
|
||||
test.afterAll(async () => {
|
||||
testReporter.generateAllReports('test-results/integration');
|
||||
});
|
||||
|
||||
test('管理员完整工作流程', async ({
|
||||
loginPage,
|
||||
dashboardPage,
|
||||
userManagementPage,
|
||||
roleManagementPage,
|
||||
menuManagementPage,
|
||||
testData
|
||||
}) => {
|
||||
testLogger.startTest('管理员完整工作流程');
|
||||
|
||||
// 1. 登录系统
|
||||
testLogger.startStep('管理员登录');
|
||||
await loginPage.navigate();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await dashboardPage.waitForLoad();
|
||||
const pageTitle = await dashboardPage.getPageTitle();
|
||||
expect(pageTitle).toContain('仪表盘');
|
||||
await dashboardPage.takeScreenshot('admin-workflow-01-dashboard');
|
||||
testLogger.endStep('管理员登录', 'passed');
|
||||
|
||||
// 2. 创建新角色
|
||||
testLogger.startStep('创建新角色');
|
||||
await roleManagementPage.navigate();
|
||||
const roleData = testData.generateRoleData();
|
||||
await roleManagementPage.createRole(roleData);
|
||||
await roleManagementPage.takeScreenshot('admin-workflow-02-role-created');
|
||||
testLogger.endStep('创建新角色', 'passed');
|
||||
|
||||
// 3. 创建新用户
|
||||
testLogger.startStep('创建新用户');
|
||||
await userManagementPage.navigate();
|
||||
await userManagementPage.clickAddUser();
|
||||
const userData = testData.generateUserData();
|
||||
await userManagementPage.fillUserForm(userData);
|
||||
await userManagementPage.submitForm();
|
||||
await userManagementPage.takeScreenshot('admin-workflow-03-user-created');
|
||||
testLogger.endStep('创建新用户', 'passed');
|
||||
|
||||
// 4. 搜索并验证用户
|
||||
testLogger.startStep('搜索验证用户');
|
||||
await userManagementPage.searchUser(userData.username);
|
||||
const isUserFound = await userManagementPage.isUserInTable(userData.username);
|
||||
expect(isUserFound).toBe(true);
|
||||
await userManagementPage.takeScreenshot('admin-workflow-04-user-found');
|
||||
testLogger.endStep('搜索验证用户', 'passed');
|
||||
|
||||
// 5. 创建新菜单
|
||||
testLogger.startStep('创建新菜单');
|
||||
await menuManagementPage.navigate();
|
||||
const menuData = testData.generateMenuData();
|
||||
await menuManagementPage.createMenu({
|
||||
menuName: menuData.menuName,
|
||||
path: menuData.path,
|
||||
icon: menuData.icon,
|
||||
});
|
||||
await menuManagementPage.takeScreenshot('admin-workflow-05-menu-created');
|
||||
testLogger.endStep('创建新菜单', 'passed');
|
||||
|
||||
// 6. 编辑用户
|
||||
testLogger.startStep('编辑用户');
|
||||
await userManagementPage.navigate();
|
||||
await userManagementPage.searchUser(userData.username);
|
||||
await userManagementPage.clickEditFirstUser();
|
||||
const updatedRealName = '修改后的' + userData.realName;
|
||||
await userManagementPage['page'].fill('input[placeholder="请输入真实姓名"]', updatedRealName);
|
||||
await userManagementPage['page'].click('button[type="submit"]:has-text("确定")');
|
||||
await userManagementPage.takeScreenshot('admin-workflow-06-user-edited');
|
||||
testLogger.endStep('编辑用户', 'passed');
|
||||
|
||||
// 7. 删除用户
|
||||
testLogger.startStep('删除用户');
|
||||
await userManagementPage.navigate();
|
||||
await userManagementPage.searchUser(userData.username);
|
||||
await userManagementPage.clickDeleteFirstUser();
|
||||
await userManagementPage.takeScreenshot('admin-workflow-07-user-deleted');
|
||||
testLogger.endStep('删除用户', 'passed');
|
||||
|
||||
// 8. 登出
|
||||
testLogger.startStep('管理员登出');
|
||||
await dashboardPage.logout();
|
||||
const isLoginPage = await loginPage.isLoginPage();
|
||||
expect(isLoginPage).toBe(true);
|
||||
await loginPage.takeScreenshot('admin-workflow-08-logged-out');
|
||||
testLogger.endStep('管理员登出', 'passed');
|
||||
|
||||
testLogger.endTest('管理员完整工作流程', 'passed');
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('跨系统数据一致性测试 @integration @data', () => {
|
||||
test('Admin操作数据在Uniapp中显示', async ({
|
||||
loginPage,
|
||||
userManagementPage,
|
||||
uniappCalendarPage,
|
||||
testData
|
||||
}) => {
|
||||
testLogger.startTest('跨系统数据一致性');
|
||||
|
||||
// 1. 在Admin中创建用户
|
||||
await loginPage.navigate();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
|
||||
await userManagementPage.navigate();
|
||||
await userManagementPage.clickAddUser();
|
||||
const userData = testData.generateUserData();
|
||||
await userManagementPage.fillUserForm(userData);
|
||||
await userManagementPage.submitForm();
|
||||
|
||||
// 2. 验证Uniapp可以正常访问(不需要登录)
|
||||
await uniappCalendarPage.navigate();
|
||||
const title = await uniappCalendarPage.getPageTitle();
|
||||
expect(title).toContain('万年历');
|
||||
|
||||
testLogger.info('跨系统数据流验证完成');
|
||||
|
||||
testLogger.endTest('跨系统数据一致性', 'passed');
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('并发操作测试 @integration @concurrent', () => {
|
||||
test('多用户同时操作', async ({ browser, testData }) => {
|
||||
testLogger.startTest('多用户同时操作');
|
||||
|
||||
// 创建两个独立的浏览器上下文
|
||||
const context1 = await browser.newContext();
|
||||
const context2 = await browser.newContext();
|
||||
|
||||
const page1 = await context1.newPage();
|
||||
const page2 = await context2.newPage();
|
||||
|
||||
try {
|
||||
// 用户1登录并创建用户
|
||||
await page1.goto('http://localhost:5174/login');
|
||||
await page1.fill('input[placeholder="请输入用户名"]', 'admin');
|
||||
await page1.fill('input[placeholder="请输入密码"]', 'admin123');
|
||||
await page1.click('button[type="submit"]');
|
||||
await page1.waitForURL(/.*dashboard/);
|
||||
|
||||
// 用户2同时登录
|
||||
await page2.goto('http://localhost:5174/login');
|
||||
await page2.fill('input[placeholder="请输入用户名"]', 'admin');
|
||||
await page2.fill('input[placeholder="请输入密码"]', 'admin123');
|
||||
await page2.click('button[type="submit"]');
|
||||
await page2.waitForURL(/.*dashboard/);
|
||||
|
||||
// 两个用户同时访问用户管理页面
|
||||
await Promise.all([
|
||||
page1.goto('http://localhost:5174/users'),
|
||||
page2.goto('http://localhost:5174/users'),
|
||||
]);
|
||||
|
||||
await page1.waitForTimeout(2000);
|
||||
await page2.waitForTimeout(2000);
|
||||
|
||||
// 验证两个页面都正常加载
|
||||
const page1Title = await page1.locator('.page-title').textContent();
|
||||
const page2Title = await page2.locator('.page-title').textContent();
|
||||
|
||||
expect(page1Title).toContain('用户管理');
|
||||
expect(page2Title).toContain('用户管理');
|
||||
|
||||
testLogger.info('并发访问测试通过');
|
||||
|
||||
} finally {
|
||||
await context1.close();
|
||||
await context2.close();
|
||||
}
|
||||
|
||||
testLogger.endTest('多用户同时操作', 'passed');
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('系统恢复测试 @integration @recovery', () => {
|
||||
test('页面刷新后状态保持', async ({ loginPage, dashboardPage, userManagementPage }) => {
|
||||
testLogger.startTest('页面刷新后状态保持');
|
||||
|
||||
// 1. 登录
|
||||
await loginPage.navigate();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await dashboardPage.waitForLoad();
|
||||
|
||||
// 2. 导航到用户管理
|
||||
await userManagementPage.navigate();
|
||||
|
||||
// 3. 刷新页面
|
||||
await userManagementPage.reload();
|
||||
|
||||
// 4. 验证仍然保持登录状态
|
||||
const currentURL = await userManagementPage.getCurrentURL();
|
||||
expect(currentURL).toContain('/users');
|
||||
|
||||
// 5. 验证页面正常显示
|
||||
const pageTitle = await userManagementPage['page'].locator('.page-title').textContent();
|
||||
expect(pageTitle).toContain('用户管理');
|
||||
|
||||
testLogger.endTest('页面刷新后状态保持', 'passed');
|
||||
});
|
||||
|
||||
test('网络恢复后自动重试', async ({ loginPage, userManagementPage, testData }) => {
|
||||
testLogger.startTest('网络恢复后自动重试');
|
||||
|
||||
// 1. 登录
|
||||
await loginPage.navigate();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
|
||||
// 2. 进入用户管理
|
||||
await userManagementPage.navigate();
|
||||
await userManagementPage.clickAddUser();
|
||||
|
||||
// 3. 填写表单
|
||||
const userData = testData.generateUserData();
|
||||
await userManagementPage.fillUserForm(userData);
|
||||
|
||||
// 4. 模拟网络断开
|
||||
await userManagementPage['page'].context().setOffline(true);
|
||||
|
||||
// 5. 尝试提交
|
||||
await userManagementPage['page'].click('button[type="submit"]:has-text("确定")');
|
||||
await userManagementPage.waitForTimeout(2000);
|
||||
|
||||
// 6. 恢复网络
|
||||
await userManagementPage['page'].context().setOffline(false);
|
||||
|
||||
// 7. 重新提交
|
||||
await userManagementPage['page'].click('button[type="submit"]:has-text("确定")');
|
||||
|
||||
// 8. 验证成功
|
||||
const successMessage = userManagementPage['page'].locator('.ant-message-success');
|
||||
await expect(successMessage).toBeVisible({ timeout: 10000 });
|
||||
|
||||
testLogger.endTest('网络恢复后自动重试', 'passed');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,93 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('登录功能Mock测试', () => {
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('http://localhost:5173/login');
|
||||
await page.waitForLoadState('networkidle');
|
||||
});
|
||||
|
||||
test('登录页面 - 应正确显示所有元素', async ({ page }) => {
|
||||
await expect(page.locator('[data-testid="username-input"]')).toBeVisible();
|
||||
await expect(page.locator('[data-testid="password-input"]')).toBeVisible();
|
||||
await expect(page.locator('[data-testid="remember-me"]')).toBeVisible();
|
||||
await expect(page.locator('[data-testid="login-button"]')).toBeVisible();
|
||||
});
|
||||
|
||||
test('登录表单 - 应验证空用户名', async ({ page }) => {
|
||||
await page.fill('[data-testid="password-input"]', 'admin123');
|
||||
await page.click('[data-testid="login-button"]');
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
const usernameInput = page.locator('[data-testid="username-input"]');
|
||||
await expect(usernameInput).toBeVisible();
|
||||
});
|
||||
|
||||
test('登录表单 - 应验证空密码', async ({ page }) => {
|
||||
await page.fill('[data-testid="username-input"]', 'admin');
|
||||
await page.click('[data-testid="login-button"]');
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
const passwordInput = page.locator('[data-testid="password-input"]');
|
||||
await expect(passwordInput).toBeVisible();
|
||||
});
|
||||
|
||||
test('登录表单 - 应接受有效的输入', async ({ page }) => {
|
||||
await page.fill('[data-testid="username-input"]', 'admin');
|
||||
await page.fill('[data-testid="password-input"]', 'admin123');
|
||||
await page.check('[data-testid="remember-me"]');
|
||||
|
||||
const usernameValue = await page.locator('[data-testid="username-input"]').inputValue();
|
||||
const passwordValue = await page.locator('[data-testid="password-input"]').inputValue();
|
||||
const rememberMeChecked = await page.locator('[data-testid="remember-me"]').isChecked();
|
||||
|
||||
expect(usernameValue).toBe('admin');
|
||||
expect(passwordValue).toBe('admin123');
|
||||
expect(rememberMeChecked).toBe(true);
|
||||
});
|
||||
|
||||
test('登录按钮 - 应有正确的状态', async ({ page }) => {
|
||||
const loginButton = page.locator('[data-testid="login-button"]');
|
||||
|
||||
await expect(loginButton).toBeVisible();
|
||||
await expect(loginButton).toHaveText(/登.*录|Login/i);
|
||||
|
||||
await expect(loginButton).toBeEnabled();
|
||||
});
|
||||
|
||||
test('记住我复选框 - 应可切换状态', async ({ page }) => {
|
||||
const rememberMeCheckbox = page.locator('[data-testid="remember-me"]');
|
||||
|
||||
await expect(rememberMeCheckbox).toBeVisible();
|
||||
|
||||
const initialState = await rememberMeCheckbox.isChecked();
|
||||
expect(initialState).toBe(false);
|
||||
|
||||
await rememberMeCheckbox.check();
|
||||
const checkedState = await rememberMeCheckbox.isChecked();
|
||||
expect(checkedState).toBe(true);
|
||||
|
||||
await rememberMeCheckbox.uncheck();
|
||||
const uncheckedState = await rememberMeCheckbox.isChecked();
|
||||
expect(uncheckedState).toBe(false);
|
||||
});
|
||||
|
||||
test('输入框 - 应支持输入和清除', async ({ page }) => {
|
||||
const usernameInput = page.locator('[data-testid="username-input"]');
|
||||
|
||||
await usernameInput.fill('testuser');
|
||||
const filledValue = await usernameInput.inputValue();
|
||||
expect(filledValue).toBe('testuser');
|
||||
|
||||
await usernameInput.clear();
|
||||
const clearedValue = await usernameInput.inputValue();
|
||||
expect(clearedValue).toBe('');
|
||||
});
|
||||
|
||||
test('页面标题 - 应显示正确的文本', async ({ page }) => {
|
||||
const title = await page.title();
|
||||
expect(title).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,130 @@
|
||||
import { test, expect, Page } from '@playwright/test';
|
||||
|
||||
test.describe('菜单管理集成测试', () => {
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/login');
|
||||
await page.fill('[data-testid="username-input"] input', 'admin');
|
||||
await page.fill('[data-testid="password-input"] input', 'admin123456');
|
||||
await page.click('[data-testid="login-button"]');
|
||||
await expect(page).toHaveURL(/.*\//, { timeout: 15000 });
|
||||
|
||||
await page.goto('/system/menu');
|
||||
await page.waitForLoadState('networkidle');
|
||||
});
|
||||
|
||||
test('菜单列表页面 - 应正确显示', async ({ page }) => {
|
||||
await expect(page.locator('[data-testid="menu-management-container"], .menu-management')).toBeVisible();
|
||||
await expect(page.locator('table, .el-table')).toBeVisible();
|
||||
});
|
||||
|
||||
test('菜单树形结构 - 应正确展开收起', async ({ page }) => {
|
||||
const expandButton = page.locator('.el-table__expand-icon, .el-tree-node__expand-icon');
|
||||
if (await expandButton.count() > 0) {
|
||||
await expandButton.first().click();
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
}
|
||||
});
|
||||
|
||||
test('新增菜单 - 成功', async ({ page }) => {
|
||||
const timestamp = Date.now();
|
||||
|
||||
const addButton = page.locator('button:has-text("新增"), button:has-text("添加")');
|
||||
if (await addButton.count() > 0) {
|
||||
await addButton.first().click();
|
||||
|
||||
await expect(page.locator('.el-dialog')).toBeVisible();
|
||||
|
||||
const nameInput = page.locator('input[placeholder*="菜单名"], input[placeholder*="名称"]');
|
||||
if (await nameInput.count() > 0) {
|
||||
await nameInput.fill(`测试菜单_${timestamp}`);
|
||||
}
|
||||
|
||||
const pathInput = page.locator('input[placeholder*="路径"], input[placeholder*="path"]');
|
||||
if (await pathInput.count() > 0) {
|
||||
await pathInput.fill(`/test-menu-${timestamp}`);
|
||||
}
|
||||
|
||||
const submitButton = page.locator('.el-dialog button:has-text("确定"), .el-dialog button:has-text("保存")');
|
||||
if (await submitButton.count() > 0) {
|
||||
await submitButton.click();
|
||||
|
||||
await expect(page.locator('.el-message--success')).toBeVisible({ timeout: 10000 });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('编辑菜单 - 成功', async ({ page }) => {
|
||||
const editButton = page.locator('table button:has-text("编辑"), .el-table button:has-text("编辑")');
|
||||
if (await editButton.count() > 0) {
|
||||
await editButton.first().click();
|
||||
|
||||
await expect(page.locator('.el-dialog')).toBeVisible();
|
||||
|
||||
const submitButton = page.locator('.el-dialog button:has-text("确定"), .el-dialog button:has-text("保存")');
|
||||
if (await submitButton.count() > 0) {
|
||||
await submitButton.click();
|
||||
|
||||
await expect(page.locator('.el-message--success')).toBeVisible({ timeout: 10000 });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('删除菜单 - 成功', async ({ page }) => {
|
||||
const deleteButton = page.locator('table button:has-text("删除"), .el-table button:has-text("删除")');
|
||||
if (await deleteButton.count() > 0) {
|
||||
await deleteButton.first().click();
|
||||
|
||||
const confirmButton = page.locator('.el-popconfirm button:has-text("确定"), .el-message-box button:has-text("确定")');
|
||||
if (await confirmButton.count() > 0) {
|
||||
await confirmButton.click();
|
||||
|
||||
await expect(page.locator('.el-message--success')).toBeVisible({ timeout: 10000 });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('菜单排序 - 应正确调整顺序', async ({ page }) => {
|
||||
const sortInput = page.locator('input[type="number"]').first();
|
||||
if (await sortInput.count() > 0) {
|
||||
const currentValue = await sortInput.inputValue();
|
||||
const newValue = parseInt(currentValue) + 1;
|
||||
await sortInput.fill(newValue.toString());
|
||||
|
||||
await page.keyboard.press('Enter');
|
||||
}
|
||||
});
|
||||
|
||||
test('菜单图标选择 - 应正确显示图标选择器', async ({ page }) => {
|
||||
const addButton = page.locator('button:has-text("新增"), button:has-text("添加")');
|
||||
if (await addButton.count() > 0) {
|
||||
await addButton.first().click();
|
||||
|
||||
const iconPicker = page.locator('.icon-picker, .el-icon-picker, [class*="icon"]');
|
||||
if (await iconPicker.count() > 0) {
|
||||
await iconPicker.first().click();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('菜单权限测试', () => {
|
||||
|
||||
test('菜单权限控制 - 无权限菜单不显示', async ({ page, context }) => {
|
||||
await context.clearCookies();
|
||||
await page.evaluate(() => localStorage.clear());
|
||||
|
||||
await page.goto('/login');
|
||||
await page.fill('[data-testid="username-input"] input', 'testuser');
|
||||
await page.fill('[data-testid="password-input"] input', 'test123456');
|
||||
await page.click('[data-testid="login-button"]');
|
||||
|
||||
await page.goto('/system/menu');
|
||||
|
||||
const addButton = page.locator('button:has-text("新增"), button:has-text("添加")');
|
||||
const isVisible = await addButton.isVisible().catch(() => false);
|
||||
|
||||
expect(isVisible).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,138 @@
|
||||
import { test, expect, Page } from '@playwright/test';
|
||||
|
||||
const BASE_URL = process.env.ADMIN_BASE_URL || 'http://localhost:5173';
|
||||
|
||||
test.describe('真实后端API认证测试', () => {
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/login`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
});
|
||||
|
||||
test('登录页面 - 应正确显示', async ({ page }) => {
|
||||
await expect(page.locator('[data-testid="username-input"]')).toBeVisible();
|
||||
await expect(page.locator('[data-testid="password-input"]')).toBeVisible();
|
||||
await expect(page.locator('[data-testid="login-button"]')).toBeVisible();
|
||||
|
||||
await expect(page.locator('text=/admin.*admin123/')).toBeVisible();
|
||||
});
|
||||
|
||||
test('用户登录 - 成功', async ({ page }) => {
|
||||
await page.fill('[data-testid="username-input"]', 'admin');
|
||||
await page.fill('[data-testid="password-input"]', 'admin123');
|
||||
|
||||
await page.click('[data-testid="login-button"]');
|
||||
|
||||
await expect(page).toHaveURL(/.*\//, { timeout: 15000 });
|
||||
|
||||
const token = await page.evaluate(() => localStorage.getItem('token'));
|
||||
expect(token).toBeTruthy();
|
||||
|
||||
const userInfo = await page.evaluate(() => localStorage.getItem('userInfo'));
|
||||
expect(userInfo).toBeTruthy();
|
||||
});
|
||||
|
||||
test('用户登录 - 错误密码应显示错误', async ({ page }) => {
|
||||
await page.fill('[data-testid="username-input"]', 'admin');
|
||||
await page.fill('[data-testid="password-input"]', 'wrongpassword');
|
||||
|
||||
await page.click('[data-testid="login-button"]');
|
||||
|
||||
await expect(page.locator('.ant-message-error, [role="alert"]')).toBeVisible({ timeout: 5000 });
|
||||
|
||||
await expect(page).toHaveURL(/.*login/, { timeout: 5000 });
|
||||
});
|
||||
|
||||
test('用户登录 - 空用户名应显示验证错误', async ({ page }) => {
|
||||
await page.fill('[data-testid="password-input"]', 'admin123');
|
||||
|
||||
await page.click('[data-testid="login-button"]');
|
||||
|
||||
await expect(page.locator('.ant-form-item-explain-error, [role="alert"]')).toBeVisible();
|
||||
});
|
||||
|
||||
test('用户登录 - 空密码应显示验证错误', async ({ page }) => {
|
||||
await page.fill('[data-testid="username-input"]', 'admin');
|
||||
|
||||
await page.click('[data-testid="login-button"]');
|
||||
|
||||
await expect(page.locator('.ant-form-item-explain-error, [role="alert"]')).toBeVisible();
|
||||
});
|
||||
|
||||
test('记住我功能 - 应保存用户名', async ({ page }) => {
|
||||
await page.fill('[data-testid="username-input"]', 'testuser');
|
||||
await page.check('[data-testid="remember-me"]');
|
||||
await page.fill('[data-testid="password-input"]', 'admin123');
|
||||
|
||||
await page.click('[data-testid="login-button"]');
|
||||
|
||||
const remembered = await page.evaluate(() => localStorage.getItem('rememberedUsername'));
|
||||
expect(remembered).toBe('testuser');
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('认证状态管理', () => {
|
||||
|
||||
test('已登录用户访问登录页应重定向到首页', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/login`);
|
||||
await page.fill('[data-testid="username-input"]', 'admin');
|
||||
await page.fill('[data-testid="password-input"]', 'admin123');
|
||||
await page.click('[data-testid="login-button"]');
|
||||
|
||||
await expect(page).toHaveURL(/.*\//, { timeout: 15000 });
|
||||
|
||||
await page.goto(`${BASE_URL}/login`);
|
||||
|
||||
await expect(page).not.toHaveURL(/.*login/, { timeout: 5000 });
|
||||
});
|
||||
|
||||
test('未登录用户访问受保护页面应重定向到登录页', async ({ page, context }) => {
|
||||
await context.clearCookies();
|
||||
await page.evaluate(() => localStorage.clear());
|
||||
|
||||
await page.goto(`${BASE_URL}/system/user`);
|
||||
|
||||
await expect(page).toHaveURL(/.*login/, { timeout: 10000 });
|
||||
});
|
||||
|
||||
test('用户登出 - 应清除认证状态', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/login`);
|
||||
await page.fill('[data-testid="username-input"]', 'admin');
|
||||
await page.fill('[data-testid="password-input"]', 'admin123');
|
||||
await page.click('[data-testid="login-button"]');
|
||||
|
||||
await expect(page).toHaveURL(/.*\//, { timeout: 15000 });
|
||||
|
||||
await page.click('.ant-dropdown-link');
|
||||
await page.click('[data-testid="logout-button"]');
|
||||
|
||||
await expect(page).toHaveURL(/.*login/, { timeout: 10000 });
|
||||
|
||||
const token = await page.evaluate(() => localStorage.getItem('token'));
|
||||
expect(token).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Token 刷新机制', () => {
|
||||
|
||||
test('Token 过期后应自动刷新', async ({ page, context }) => {
|
||||
await page.goto(`${BASE_URL}/login`);
|
||||
await page.fill('[data-testid="username-input"]', 'admin');
|
||||
await page.fill('[data-testid="password-input"]', 'admin123');
|
||||
await page.click('[data-testid="login-button"]');
|
||||
|
||||
await expect(page).toHaveURL(/.*\//, { timeout: 15000 });
|
||||
|
||||
const originalToken = await page.evaluate(() => localStorage.getItem('token'));
|
||||
|
||||
await page.evaluate(() => {
|
||||
localStorage.setItem('token', 'invalid_token_for_test');
|
||||
});
|
||||
|
||||
await page.reload();
|
||||
|
||||
const newToken = await page.evaluate(() => localStorage.getItem('token'));
|
||||
|
||||
expect(newToken).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,100 @@
|
||||
import { test, expect, Page } from '@playwright/test';
|
||||
|
||||
test.describe('角色管理集成测试', () => {
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/login');
|
||||
await page.fill('[data-testid="username-input"] input', 'admin');
|
||||
await page.fill('[data-testid="password-input"] input', 'admin123456');
|
||||
await page.click('[data-testid="login-button"]');
|
||||
await expect(page).toHaveURL(/.*\//, { timeout: 15000 });
|
||||
|
||||
await page.goto('/system/role');
|
||||
await page.waitForLoadState('networkidle');
|
||||
});
|
||||
|
||||
test('角色列表页面 - 应正确显示', async ({ page }) => {
|
||||
await expect(page.locator('[data-testid="role-management-container"], .role-management')).toBeVisible();
|
||||
await expect(page.locator('table, .el-table')).toBeVisible();
|
||||
});
|
||||
|
||||
test('新增角色 - 成功', async ({ page }) => {
|
||||
const timestamp = Date.now();
|
||||
|
||||
const addButton = page.locator('button:has-text("新增"), button:has-text("添加")');
|
||||
if (await addButton.count() > 0) {
|
||||
await addButton.first().click();
|
||||
|
||||
await expect(page.locator('.el-dialog')).toBeVisible();
|
||||
|
||||
const nameInput = page.locator('input[placeholder*="角色名"], input[placeholder*="名称"]');
|
||||
if (await nameInput.count() > 0) {
|
||||
await nameInput.fill(`测试角色_${timestamp}`);
|
||||
}
|
||||
|
||||
const codeInput = page.locator('input[placeholder*="角色编码"], input[placeholder*="编码"]');
|
||||
if (await codeInput.count() > 0) {
|
||||
await codeInput.fill(`TEST_ROLE_${timestamp}`);
|
||||
}
|
||||
|
||||
const submitButton = page.locator('.el-dialog button:has-text("确定"), .el-dialog button:has-text("保存")');
|
||||
if (await submitButton.count() > 0) {
|
||||
await submitButton.click();
|
||||
|
||||
await expect(page.locator('.el-message--success')).toBeVisible({ timeout: 10000 });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('编辑角色 - 成功', async ({ page }) => {
|
||||
const editButton = page.locator('table button:has-text("编辑"), .el-table button:has-text("编辑")');
|
||||
if (await editButton.count() > 0) {
|
||||
await editButton.first().click();
|
||||
|
||||
await expect(page.locator('.el-dialog')).toBeVisible();
|
||||
|
||||
const submitButton = page.locator('.el-dialog button:has-text("确定"), .el-dialog button:has-text("保存")');
|
||||
if (await submitButton.count() > 0) {
|
||||
await submitButton.click();
|
||||
|
||||
await expect(page.locator('.el-message--success')).toBeVisible({ timeout: 10000 });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('删除角色 - 成功', async ({ page }) => {
|
||||
const deleteButton = page.locator('table button:has-text("删除"), .el-table button:has-text("删除")');
|
||||
if (await deleteButton.count() > 0) {
|
||||
await deleteButton.first().click();
|
||||
|
||||
const confirmButton = page.locator('.el-popconfirm button:has-text("确定"), .el-message-box button:has-text("确定")');
|
||||
if (await confirmButton.count() > 0) {
|
||||
await confirmButton.click();
|
||||
|
||||
await expect(page.locator('.el-message--success')).toBeVisible({ timeout: 10000 });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('分配权限 - 成功', async ({ page }) => {
|
||||
const permissionButton = page.locator('table button:has-text("权限"), .el-table button:has-text("权限")');
|
||||
if (await permissionButton.count() > 0) {
|
||||
await permissionButton.first().click();
|
||||
|
||||
await expect(page.locator('.el-dialog, .el-drawer')).toBeVisible();
|
||||
|
||||
const tree = page.locator('.el-tree');
|
||||
if (await tree.count() > 0) {
|
||||
const checkbox = tree.locator('.el-checkbox').first();
|
||||
if (await checkbox.count() > 0) {
|
||||
await checkbox.click();
|
||||
}
|
||||
}
|
||||
|
||||
const submitButton = page.locator('.el-dialog button:has-text("确定"), .el-drawer button:has-text("保存")');
|
||||
if (await submitButton.count() > 0) {
|
||||
await submitButton.click();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,136 @@
|
||||
import { test, expect, Page } from '@playwright/test';
|
||||
|
||||
test.describe('用户管理集成测试', () => {
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/login');
|
||||
await page.fill('[data-testid="username-input"] input', 'admin');
|
||||
await page.fill('[data-testid="password-input"] input', 'admin123456');
|
||||
await page.click('[data-testid="login-button"]');
|
||||
await expect(page).toHaveURL(/.*\//, { timeout: 15000 });
|
||||
|
||||
await page.goto('/system/user');
|
||||
await page.waitForLoadState('networkidle');
|
||||
});
|
||||
|
||||
test('用户列表页面 - 应正确显示', async ({ page }) => {
|
||||
await expect(page.locator('[data-testid="user-management-container"]')).toBeVisible();
|
||||
await expect(page.locator('[data-testid="user-table"]')).toBeVisible();
|
||||
await expect(page.locator('[data-testid="add-user-button"]')).toBeVisible();
|
||||
});
|
||||
|
||||
test('搜索用户 - 按用户名搜索', async ({ page }) => {
|
||||
await page.fill('[data-testid="search-username-input"] input', 'admin');
|
||||
await page.click('button:has-text("搜索")');
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
const rows = await page.locator('[data-testid="user-table"] tbody tr').count();
|
||||
expect(rows).toBeGreaterThanOrEqual(1);
|
||||
});
|
||||
|
||||
test('新增用户 - 成功', async ({ page }) => {
|
||||
const timestamp = Date.now();
|
||||
|
||||
await page.click('[data-testid="add-user-button"]');
|
||||
|
||||
await expect(page.locator('.el-dialog')).toBeVisible();
|
||||
|
||||
await page.fill('[data-testid="username-input"] input', `testuser_${timestamp}`);
|
||||
await page.fill('[data-testid="email-input"] input', `test_${timestamp}@example.com`);
|
||||
await page.fill('[data-testid="phone-input"] input', '13800138000');
|
||||
await page.fill('[data-testid="password-input"] input', 'Test@123456');
|
||||
|
||||
await page.click('[data-testid="submit-button"]');
|
||||
|
||||
await expect(page.locator('.el-message--success')).toBeVisible({ timeout: 10000 });
|
||||
});
|
||||
|
||||
test('编辑用户 - 成功', async ({ page }) => {
|
||||
const rows = await page.locator('[data-testid="user-table"] tbody tr').count();
|
||||
if (rows > 0) {
|
||||
await page.locator('[data-testid="user-table"] tbody tr').first()
|
||||
.locator('button:has-text("编辑")').click();
|
||||
|
||||
await expect(page.locator('.el-dialog')).toBeVisible();
|
||||
|
||||
await page.fill('[data-testid="email-input"] input', `updated_${Date.now()}@example.com`);
|
||||
|
||||
await page.click('[data-testid="submit-button"]');
|
||||
|
||||
await expect(page.locator('.el-message--success')).toBeVisible({ timeout: 10000 });
|
||||
}
|
||||
});
|
||||
|
||||
test('查看用户详情 - 成功', async ({ page }) => {
|
||||
const rows = await page.locator('[data-testid="user-table"] tbody tr').count();
|
||||
if (rows > 0) {
|
||||
await page.locator('[data-testid="user-table"] tbody tr').first()
|
||||
.locator('button:has-text("查看")').click();
|
||||
|
||||
await expect(page.locator('.el-drawer')).toBeVisible();
|
||||
|
||||
await expect(page.locator('.el-descriptions')).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('删除用户 - 成功', async ({ page }) => {
|
||||
const timestamp = Date.now();
|
||||
|
||||
await page.click('[data-testid="add-user-button"]');
|
||||
await page.fill('[data-testid="username-input"] input', `delete_test_${timestamp}`);
|
||||
await page.fill('[data-testid="email-input"] input', `delete_${timestamp}@example.com`);
|
||||
await page.fill('[data-testid="phone-input"] input', '13900139000');
|
||||
await page.fill('[data-testid="password-input"] input', 'Test@123456');
|
||||
await page.click('[data-testid="submit-button"]');
|
||||
|
||||
await expect(page.locator('.el-message--success')).toBeVisible({ timeout: 10000 });
|
||||
|
||||
await page.fill('[data-testid="search-username-input"] input', `delete_test_${timestamp}`);
|
||||
await page.click('button:has-text("搜索")');
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
await page.locator('[data-testid="user-table"] tbody tr').first()
|
||||
.locator('button:has-text("删除")').click();
|
||||
|
||||
await page.click('.el-popconfirm button:has-text("确定")');
|
||||
|
||||
await expect(page.locator('.el-message--success')).toBeVisible({ timeout: 10000 });
|
||||
});
|
||||
|
||||
test('分页功能 - 应正确切换页码', async ({ page }) => {
|
||||
const pagination = page.locator('.el-pagination');
|
||||
await expect(pagination).toBeVisible();
|
||||
|
||||
const totalText = await pagination.locator('.el-pagination__total').textContent();
|
||||
expect(totalText).toMatch(/共 \d+ 条/);
|
||||
});
|
||||
|
||||
test('表单验证 - 必填字段验证', async ({ page }) => {
|
||||
await page.click('[data-testid="add-user-button"]');
|
||||
|
||||
await page.click('[data-testid="submit-button"]');
|
||||
|
||||
await expect(page.locator('.el-form-item__error')).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('用户管理权限测试', () => {
|
||||
|
||||
test('无权限用户不应看到新增按钮', async ({ page, context }) => {
|
||||
await context.clearCookies();
|
||||
await page.evaluate(() => localStorage.clear());
|
||||
|
||||
await page.goto('/login');
|
||||
await page.fill('[data-testid="username-input"] input', 'testuser');
|
||||
await page.fill('[data-testid="password-input"] input', 'test123456');
|
||||
await page.click('[data-testid="login-button"]');
|
||||
|
||||
await page.goto('/system/user');
|
||||
|
||||
const addButton = page.locator('[data-testid="add-user-button"]');
|
||||
const isVisible = await addButton.isVisible().catch(() => false);
|
||||
|
||||
expect(isVisible).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user