From e8f51309e5ccaaa89b2d154a477c24b74ac5849b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=BF=94?= Date: Wed, 6 May 2026 14:17:51 +0800 Subject: [PATCH] =?UTF-8?q?test:=20E2E=20=E6=B5=8B=E8=AF=95=E7=94=A8?= =?UTF-8?q?=E4=BE=8B=E6=9B=B4=E6=96=B0=E4=B8=8E=E6=96=B0=E5=A2=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 更新 Page Object 模型适配新字段名 - 新增 UAT 测试套件与 journey 测试用例 - 优化测试辅助工具与数据工厂 - 更新 playwright 认证状态 --- novalon-manage-web/e2e/auth-test.spec.ts | 8 +- novalon-manage-web/e2e/basic-ui-test.spec.ts | 2 +- .../e2e/config-management.spec.ts | 10 +- .../e2e/dict-management.spec.ts | 10 +- .../e2e/helpers/TestDataManager.ts | 12 +- .../e2e/helpers/TestStabilityHelper.ts | 12 +- novalon-manage-web/e2e/helpers/auth.ts | 39 +-- .../journeys/admin-complete-workflow.spec.ts | 50 +-- .../e2e/journeys/audit-log-viewing.spec.ts | 82 +++++ .../e2e/journeys/audit-workflow.spec.ts | 22 +- .../journeys/config-dict-management.spec.ts | 80 +++++ .../e2e/journeys/config-workflow.spec.ts | 6 +- .../e2e/journeys/dict-workflow.spec.ts | 6 +- .../dictionary-complete-workflow.spec.ts | 28 +- .../journeys/exception-log-workflow.spec.ts | 2 +- .../journeys/file-management-workflow.spec.ts | 12 +- .../journeys/login-dashboard-logout.spec.ts | 81 +++++ .../e2e/journeys/menu-management.spec.ts | 58 ++++ .../e2e/journeys/notice-workflow.spec.ts | 6 +- .../journeys/notify-file-management.spec.ts | 65 ++++ .../system-config-complete-workflow.spec.ts | 28 +- .../journeys/user-permission-boundary.spec.ts | 14 +- .../e2e/journeys/user-role-management.spec.ts | 134 ++++++++ .../e2e/menu-management.spec.ts | 10 +- novalon-manage-web/e2e/pages/DashboardPage.ts | 75 ++--- .../e2e/pages/DictionaryManagementPage.ts | 152 ++++----- .../e2e/pages/ExceptionLogPage.ts | 88 +----- .../e2e/pages/FileManagementPage.ts | 129 +++----- novalon-manage-web/e2e/pages/LoginLogPage.ts | 58 ++-- novalon-manage-web/e2e/pages/LoginPage.ts | 98 ++---- .../e2e/pages/MenuManagementPage.ts | 178 +++++------ .../e2e/pages/NotificationPage.ts | 113 ++++--- .../e2e/pages/OperationLogPage.ts | 58 ++-- .../e2e/pages/RoleManagementPage.ts | 258 +++++----------- .../e2e/pages/SystemConfigPage.ts | 120 ++++---- .../e2e/pages/UserManagementPage.ts | 290 ++++-------------- .../e2e/smoke/login-logout.spec.ts | 2 +- .../e2e/uat/auth-acceptance.spec.ts | 65 ++++ .../e2e/uat/error-handling.spec.ts | 180 +++++++++++ .../e2e/uat/permission-boundary.spec.ts | 73 +++++ .../e2e/uat/system-management.spec.ts | 123 ++++++++ .../e2e/uat/ui-consistency.spec.ts | 97 ++++++ .../e2e/utils/TestDataCleanup.ts | 24 +- .../e2e/utils/TestDataFactory.ts | 4 +- novalon-manage-web/e2e/utils/TestHelpers.ts | 14 +- novalon-manage-web/e2e/utils/testHelper.ts | 6 +- novalon-manage-web/playwright/.auth/user.json | 8 +- 47 files changed, 1764 insertions(+), 1226 deletions(-) create mode 100644 novalon-manage-web/e2e/journeys/audit-log-viewing.spec.ts create mode 100644 novalon-manage-web/e2e/journeys/config-dict-management.spec.ts create mode 100644 novalon-manage-web/e2e/journeys/login-dashboard-logout.spec.ts create mode 100644 novalon-manage-web/e2e/journeys/menu-management.spec.ts create mode 100644 novalon-manage-web/e2e/journeys/notify-file-management.spec.ts create mode 100644 novalon-manage-web/e2e/journeys/user-role-management.spec.ts create mode 100644 novalon-manage-web/e2e/uat/auth-acceptance.spec.ts create mode 100644 novalon-manage-web/e2e/uat/error-handling.spec.ts create mode 100644 novalon-manage-web/e2e/uat/permission-boundary.spec.ts create mode 100644 novalon-manage-web/e2e/uat/system-management.spec.ts create mode 100644 novalon-manage-web/e2e/uat/ui-consistency.spec.ts diff --git a/novalon-manage-web/e2e/auth-test.spec.ts b/novalon-manage-web/e2e/auth-test.spec.ts index 8fce154..df66783 100644 --- a/novalon-manage-web/e2e/auth-test.spec.ts +++ b/novalon-manage-web/e2e/auth-test.spec.ts @@ -16,7 +16,7 @@ test.describe('认证和授权测试', () => { }, data: { username: 'admin', - password: 'admin123' + password: 'Test@123' } }); @@ -53,7 +53,7 @@ test.describe('认证和授权测试', () => { }, data: { username: 'admin', - password: 'admin123' + password: 'Test@123' } }); @@ -104,7 +104,7 @@ test.describe('认证和授权测试', () => { }, data: { username: 'admin', - password: 'admin123' + password: 'Test@123' } }); @@ -162,7 +162,7 @@ test.describe('认证和授权测试', () => { const passwordInput = page.locator('input[type="password"]').first(); await usernameInput.fill('admin'); - await passwordInput.fill('admin123'); + await passwordInput.fill('Test@123'); console.log('登录表单填写完成'); }); diff --git a/novalon-manage-web/e2e/basic-ui-test.spec.ts b/novalon-manage-web/e2e/basic-ui-test.spec.ts index 9fd8ba5..047a29f 100644 --- a/novalon-manage-web/e2e/basic-ui-test.spec.ts +++ b/novalon-manage-web/e2e/basic-ui-test.spec.ts @@ -27,7 +27,7 @@ test.describe('基础UI功能测试', () => { await test.step('验证页面导航功能', async () => { // 检查页面是否有基本的导航元素 - 使用更灵活的选择器 const navigationSelectors = [ - 'nav', '.navbar', '.menu', '.el-menu', '.el-header', + 'nav', '.navbar', '.menu', '.ant-menu', '.ant-layout-header', '.layout-header', '.app-header', '[class*="header"]', '[class*="nav"]', '[class*="menu"]' ]; diff --git a/novalon-manage-web/e2e/config-management.spec.ts b/novalon-manage-web/e2e/config-management.spec.ts index 76732f0..b18f092 100644 --- a/novalon-manage-web/e2e/config-management.spec.ts +++ b/novalon-manage-web/e2e/config-management.spec.ts @@ -10,7 +10,7 @@ test.describe('参数配置功能测试', () => { }, data: { username: 'admin', - password: 'admin123' + password: 'Test@123' } }); @@ -28,20 +28,20 @@ test.describe('参数配置功能测试', () => { const loginButton = page.locator('button:has-text("登录")').first(); await usernameInput.fill('admin'); - await passwordInput.fill('admin123'); + await passwordInput.fill('Test@123'); await loginButton.click(); await page.waitForTimeout(2000); // 点击系统管理菜单 - const systemMenu = page.locator('.el-sub-menu:has-text("系统管理")').first(); + const systemMenu = page.locator('.ant-menu-submenu:has-text("系统管理")').first(); if (await systemMenu.count() > 0) { await systemMenu.click(); await page.waitForTimeout(500); } // 点击参数配置 - const configManagement = page.locator('.el-menu-item:has-text("参数配置")').first(); + const configManagement = page.locator('.ant-menu-item:has-text("参数配置")').first(); if (await configManagement.count() > 0) { await configManagement.click(); await page.waitForTimeout(1000); @@ -52,7 +52,7 @@ test.describe('参数配置功能测试', () => { // 检查是否有参数配置列表或表格 const tableSelectors = [ 'table', - '.el-table', + '.ant-table', '[class*="table"]', '.config-list' ]; diff --git a/novalon-manage-web/e2e/dict-management.spec.ts b/novalon-manage-web/e2e/dict-management.spec.ts index a22eeb3..9c5556a 100644 --- a/novalon-manage-web/e2e/dict-management.spec.ts +++ b/novalon-manage-web/e2e/dict-management.spec.ts @@ -10,7 +10,7 @@ test.describe('字典管理功能测试', () => { }, data: { username: 'admin', - password: 'admin123' + password: 'Test@123' } }); @@ -28,20 +28,20 @@ test.describe('字典管理功能测试', () => { const loginButton = page.locator('button:has-text("登录")').first(); await usernameInput.fill('admin'); - await passwordInput.fill('admin123'); + await passwordInput.fill('Test@123'); await loginButton.click(); await page.waitForTimeout(2000); // 点击系统管理菜单 - const systemMenu = page.locator('.el-sub-menu:has-text("系统管理")').first(); + const systemMenu = page.locator('.ant-menu-submenu:has-text("系统管理")').first(); if (await systemMenu.count() > 0) { await systemMenu.click(); await page.waitForTimeout(500); } // 点击字典管理 - const dictManagement = page.locator('.el-menu-item:has-text("字典管理")').first(); + const dictManagement = page.locator('.ant-menu-item:has-text("字典管理")').first(); if (await dictManagement.count() > 0) { await dictManagement.click(); await page.waitForTimeout(1000); @@ -52,7 +52,7 @@ test.describe('字典管理功能测试', () => { // 检查是否有字典管理列表或表格 const tableSelectors = [ 'table', - '.el-table', + '.ant-table', '[class*="table"]', '.dict-list' ]; diff --git a/novalon-manage-web/e2e/helpers/TestDataManager.ts b/novalon-manage-web/e2e/helpers/TestDataManager.ts index 2680568..b2b20bb 100644 --- a/novalon-manage-web/e2e/helpers/TestDataManager.ts +++ b/novalon-manage-web/e2e/helpers/TestDataManager.ts @@ -92,12 +92,12 @@ export class TestDataManager { await this.page.goto('/system/config'); await this.page.waitForLoadState('networkidle'); - const testRows = this.page.locator('.el-table__row').filter({ hasText: 'test' }); + const testRows = this.page.locator('.ant-table__row').filter({ hasText: 'test' }); const count = await testRows.count(); for (let i = 0; i < count; i++) { const row = testRows.nth(i); - const deleteButton = row.locator('.el-button--danger').first(); + const deleteButton = row.locator('.ant-btn--danger').first(); if (await deleteButton.isVisible()) { await deleteButton.click(); @@ -121,12 +121,12 @@ export class TestDataManager { await this.page.goto('/system/notice'); await this.page.waitForLoadState('networkidle'); - const testRows = this.page.locator('.el-table__row').filter({ hasText: '测试通知' }); + const testRows = this.page.locator('.ant-table__row').filter({ hasText: '测试通知' }); const count = await testRows.count(); for (let i = 0; i < count; i++) { const row = testRows.nth(i); - const deleteButton = row.locator('.el-button--danger').first(); + const deleteButton = row.locator('.ant-btn--danger').first(); if (await deleteButton.isVisible()) { await deleteButton.click(); @@ -150,12 +150,12 @@ export class TestDataManager { await this.page.goto('/files'); await this.page.waitForLoadState('networkidle'); - const testRows = this.page.locator('.el-table__row').filter({ hasText: 'test' }); + const testRows = this.page.locator('.ant-table__row').filter({ hasText: 'test' }); const count = await testRows.count(); for (let i = 0; i < count; i++) { const row = testRows.nth(i); - const deleteButton = row.locator('.el-button--danger').first(); + const deleteButton = row.locator('.ant-btn--danger').first(); if (await deleteButton.isVisible()) { await deleteButton.click(); diff --git a/novalon-manage-web/e2e/helpers/TestStabilityHelper.ts b/novalon-manage-web/e2e/helpers/TestStabilityHelper.ts index fa118fc..22987a0 100644 --- a/novalon-manage-web/e2e/helpers/TestStabilityHelper.ts +++ b/novalon-manage-web/e2e/helpers/TestStabilityHelper.ts @@ -57,12 +57,12 @@ export class TestStabilityHelper { async handleModal(): Promise { try { - const modal = this.page.locator('.el-dialog, .el-message-box'); + const modal = this.page.locator('.ant-modal, .ant-message-box'); const isVisible = await modal.isVisible({ timeout: 2000 }); if (isVisible) { - const confirmButton = modal.locator('.el-button--primary').first(); - const cancelButton = modal.locator('.el-button--default').first(); + const confirmButton = modal.locator('.ant-btn--primary').first(); + const cancelButton = modal.locator('.ant-btn--default').first(); if (await confirmButton.isVisible({ timeout: 1000 })) { await confirmButton.click(); @@ -77,7 +77,7 @@ export class TestStabilityHelper { async waitForLoadingComplete(): Promise { try { - const loading = this.page.locator('.el-loading-mask, .loading'); + const loading = this.page.locator('.ant-spin-container, .loading'); await loading.waitFor({ state: 'hidden', timeout: 10000 }); } catch (error) { console.log('Loading element not found or timeout'); @@ -95,7 +95,7 @@ export class TestStabilityHelper { const table = this.page.locator(tableSelector); await expect(table).toBeVisible({ timeout: 10000 }); - const rows = table.locator('.el-table__row'); + const rows = table.locator('.ant-table__row'); const rowCount = await rows.count(); expect(rowCount).toBeGreaterThanOrEqual(minRows); }); @@ -125,7 +125,7 @@ export class TestStabilityHelper { async getErrorMessage(): Promise { try { - const errorElement = this.page.locator('.el-message--error, .error-message'); + const errorElement = this.page.locator('.ant-message-error, .error-message'); const isVisible = await errorElement.isVisible({ timeout: 2000 }); if (isVisible) { diff --git a/novalon-manage-web/e2e/helpers/auth.ts b/novalon-manage-web/e2e/helpers/auth.ts index 23e39da..d042535 100644 --- a/novalon-manage-web/e2e/helpers/auth.ts +++ b/novalon-manage-web/e2e/helpers/auth.ts @@ -1,23 +1,24 @@ import { Page } from '@playwright/test'; +import { LoginPage } from '../pages/LoginPage'; -export async function loginAsAdmin(page: Page) { - await page.goto('/login'); +export async function loginAsAdmin(page: Page): Promise { + const loginPage = new LoginPage(page); + await loginPage.goto(); + await loginPage.login('admin', 'Test@123'); +} + +export async function logout(page: Page): Promise { + const loginPage = new LoginPage(page); + await loginPage.logout(); +} + +export async function navigateViaMenu(page: Page, menuLabel: string, subMenuLabel: string): Promise { + const subMenu = page.locator(`.ant-menu-submenu-title:has-text("${menuLabel}")`); + if (await subMenu.isVisible()) { + await subMenu.click(); + await page.waitForTimeout(500); + } + const menuItem = page.locator(`.ant-menu-item:has-text("${subMenuLabel}")`); + await menuItem.click(); await page.waitForLoadState('networkidle'); - - await page.locator('input[placeholder*="用户名"]').fill('admin'); - await page.locator('input[placeholder*="密码"]').fill('Test@123'); - await page.locator('button:has-text("登录")').click(); - - await page.waitForURL('**/dashboard', { timeout: 30000 }); - - const token = await page.evaluate(() => { - return localStorage.getItem('token') || ''; - }); - - return token; -} - -export async function saveAuthState(page: Page) { - const storage = await page.context().storageState(); - return storage; } diff --git a/novalon-manage-web/e2e/journeys/admin-complete-workflow.spec.ts b/novalon-manage-web/e2e/journeys/admin-complete-workflow.spec.ts index 14b331a..9421c07 100644 --- a/novalon-manage-web/e2e/journeys/admin-complete-workflow.spec.ts +++ b/novalon-manage-web/e2e/journeys/admin-complete-workflow.spec.ts @@ -21,20 +21,20 @@ test.describe('管理员完整工作流', () => { await test.step('点击创建角色按钮', async () => { await page.locator('button:has-text("新增角色")').click(); - await page.waitForSelector('.el-dialog', { state: 'visible', timeout: 5000 }); + await page.waitForSelector('.ant-modal', { state: 'visible', timeout: 5000 }); }); await test.step('填写角色信息', async () => { - const dialog = page.locator('.el-dialog'); + const dialog = page.locator('.ant-modal'); await dialog.locator('input').first().fill(roleName); await dialog.locator('input').nth(1).fill(roleKey); - await dialog.locator('.el-input-number .el-input__inner').fill('99'); + await dialog.locator('.ant-input-number .ant-input__inner').fill('99'); }); await test.step('提交表单', async () => { - await page.locator('.el-dialog button:has-text("确定")').click(); - await page.waitForSelector('.el-dialog', { state: 'hidden', timeout: 10000 }); - await expect(page.locator('.el-message--success')).toBeVisible({ timeout: 5000 }); + await page.locator('.ant-modal button:has-text("确定")').click(); + await page.waitForSelector('.ant-modal', { state: 'hidden', timeout: 10000 }); + await expect(page.locator('.ant-message-success')).toBeVisible({ timeout: 5000 }); }); }); @@ -51,11 +51,11 @@ test.describe('管理员完整工作流', () => { await test.step('点击创建用户按钮', async () => { await page.locator('button:has-text("新增用户")').click(); - await page.waitForSelector('.el-dialog', { state: 'visible', timeout: 5000 }); + await page.waitForSelector('.ant-modal', { state: 'visible', timeout: 5000 }); }); await test.step('填写用户信息', async () => { - const dialog = page.locator('.el-dialog'); + const dialog = page.locator('.ant-modal'); await dialog.locator('input').first().fill(username); await dialog.locator('input[type="password"]').fill('Test@123'); await dialog.locator('input').nth(2).fill(`测试用户${timestamp}`); @@ -64,9 +64,9 @@ test.describe('管理员完整工作流', () => { }); await test.step('提交表单', async () => { - await page.locator('.el-dialog button:has-text("确定")').click(); - await page.waitForSelector('.el-dialog', { state: 'hidden', timeout: 10000 }); - await expect(page.locator('.el-message--success')).toBeVisible({ timeout: 5000 }); + await page.locator('.ant-modal button:has-text("确定")').click(); + await page.waitForSelector('.ant-modal', { state: 'hidden', timeout: 10000 }); + await expect(page.locator('.ant-message-success')).toBeVisible({ timeout: 5000 }); }); await test.step('搜索新创建的用户', async () => { @@ -85,13 +85,13 @@ test.describe('管理员完整工作流', () => { await expect(userRow).toBeVisible({ timeout: 10000 }); await userRow.locator('button:has-text("分配角色")').click(); - await page.waitForSelector('.el-dialog:has-text("分配角色")', { state: 'visible', timeout: 5000 }); + await page.waitForSelector('.ant-modal:has-text("分配角色")', { state: 'visible', timeout: 5000 }); - const transfer = page.locator('.el-transfer'); - const leftPanel = transfer.locator('.el-transfer-panel').first(); - const rightPanel = transfer.locator('.el-transfer-panel').last(); + const transfer = page.locator('.ant-transfer'); + const leftPanel = transfer.locator('.ant-transfer-list').first(); + const rightPanel = transfer.locator('.ant-transfer-list').last(); - const rightPanelItems = await rightPanel.locator('.el-checkbox').all(); + const rightPanelItems = await rightPanel.locator('.ant-checkbox').all(); let hasSuperAdminRole = false; for (const item of rightPanelItems) { @@ -103,7 +103,7 @@ test.describe('管理员完整工作流', () => { } if (!hasSuperAdminRole) { - const leftPanelItems = await leftPanel.locator('.el-checkbox').all(); + const leftPanelItems = await leftPanel.locator('.ant-checkbox').all(); let superAdminCheckbox = null; for (const item of leftPanelItems) { @@ -121,7 +121,7 @@ test.describe('管理员完整工作流', () => { await page.waitForTimeout(500); } - const moveToRightButton = transfer.locator('.el-transfer__buttons button').nth(1); + const moveToRightButton = transfer.locator('.ant-transfer-operation button').nth(1); if (await moveToRightButton.isEnabled()) { await moveToRightButton.click(); await page.waitForTimeout(500); @@ -129,9 +129,9 @@ test.describe('管理员完整工作流', () => { } } - await page.locator('.el-dialog:has-text("分配角色") button:has-text("确定")').click(); - await page.waitForSelector('.el-dialog:has-text("分配角色")', { state: 'hidden', timeout: 10000 }); - await expect(page.locator('.el-message--success').last()).toBeVisible({ timeout: 5000 }); + await page.locator('.ant-modal:has-text("分配角色") button:has-text("确定")').click(); + await page.waitForSelector('.ant-modal:has-text("分配角色")', { state: 'hidden', timeout: 10000 }); + await expect(page.locator('.ant-message-success').last()).toBeVisible({ timeout: 5000 }); }); }); @@ -140,7 +140,7 @@ test.describe('管理员完整工作流', () => { await page.goto('/dashboard'); await page.waitForLoadState('networkidle'); - const avatarButton = page.locator('.el-avatar').first(); + const avatarButton = page.locator('.ant-avatar').first(); await avatarButton.click({ timeout: 10000 }); await page.waitForTimeout(500); @@ -166,7 +166,7 @@ test.describe('管理员完整工作流', () => { await page.goto('/dashboard'); await page.waitForLoadState('networkidle'); - const avatarButton = page.locator('.el-avatar').first(); + const avatarButton = page.locator('.ant-avatar').first(); if (await avatarButton.isVisible()) { await avatarButton.click(); await page.waitForTimeout(500); @@ -187,7 +187,7 @@ test.describe('管理员完整工作流', () => { await page.waitForTimeout(1000); await page.locator('button:has-text("删除")').first().click(); await page.locator('button:has-text("确定")').click(); - await expect(page.locator('.el-message--success')).toBeVisible({ timeout: 5000 }); + await expect(page.locator('.ant-message-success')).toBeVisible({ timeout: 5000 }); }); await test.step('删除测试角色', async () => { @@ -197,7 +197,7 @@ test.describe('管理员完整工作流', () => { await page.waitForTimeout(1000); await page.locator('button:has-text("删除")').first().click(); await page.locator('button:has-text("确定")').click(); - await expect(page.locator('.el-message--success')).toBeVisible({ timeout: 5000 }); + await expect(page.locator('.ant-message-success')).toBeVisible({ timeout: 5000 }); }); }); }); diff --git a/novalon-manage-web/e2e/journeys/audit-log-viewing.spec.ts b/novalon-manage-web/e2e/journeys/audit-log-viewing.spec.ts new file mode 100644 index 0000000..5f297b4 --- /dev/null +++ b/novalon-manage-web/e2e/journeys/audit-log-viewing.spec.ts @@ -0,0 +1,82 @@ +import { test, expect } from '@playwright/test'; +import { LoginPage } from '../pages/LoginPage'; +import { OperationLogPage } from '../pages/OperationLogPage'; +import { LoginLogPage } from '../pages/LoginLogPage'; +import { ExceptionLogPage } from '../pages/ExceptionLogPage'; + +test.describe('User Journey: 审计日志查看', () => { + test.describe.configure({ mode: 'serial' }); + + test('UJ-08: 操作日志查看与搜索', async ({ page }) => { + const loginPage = new LoginPage(page); + const opLogPage = new OperationLogPage(page); + + await test.step('登录', async () => { + await loginPage.goto(); + await loginPage.login('admin', 'Test@123'); + }); + + await test.step('导航到操作日志', async () => { + await opLogPage.goto(); + }); + + await test.step('验证日志表格加载', async () => { + await expect(opLogPage.table).toBeVisible({ timeout: 15000 }); + }); + + await test.step('搜索日志', async () => { + await opLogPage.searchByKeyword('admin'); + await page.waitForLoadState('networkidle'); + }); + + await test.step('刷新日志', async () => { + await opLogPage.reload(); + }); + }); + + test('UJ-09: 登录日志查看', async ({ page }) => { + const loginPage = new LoginPage(page); + const loginLogPage = new LoginLogPage(page); + + await test.step('登录', async () => { + await loginPage.goto(); + await loginPage.login('admin', 'Test@123'); + }); + + await test.step('导航到登录日志', async () => { + await loginLogPage.goto(); + }); + + await test.step('验证日志表格加载', async () => { + await expect(loginLogPage.table).toBeVisible({ timeout: 15000 }); + }); + + await test.step('搜索日志', async () => { + await loginLogPage.searchByKeyword('admin'); + await page.waitForLoadState('networkidle'); + }); + }); + + test('UJ-10: 异常日志查看', async ({ page }) => { + const loginPage = new LoginPage(page); + const exLogPage = new ExceptionLogPage(page); + + await test.step('登录', async () => { + await loginPage.goto(); + await loginPage.login('admin', 'Test@123'); + }); + + await test.step('导航到异常日志', async () => { + await exLogPage.goto(); + }); + + await test.step('验证日志表格加载', async () => { + await expect(exLogPage.table).toBeVisible({ timeout: 15000 }); + }); + + await test.step('搜索日志', async () => { + await exLogPage.search('error'); + await page.waitForLoadState('networkidle'); + }); + }); +}); diff --git a/novalon-manage-web/e2e/journeys/audit-workflow.spec.ts b/novalon-manage-web/e2e/journeys/audit-workflow.spec.ts index 1908060..6c678bf 100644 --- a/novalon-manage-web/e2e/journeys/audit-workflow.spec.ts +++ b/novalon-manage-web/e2e/journeys/audit-workflow.spec.ts @@ -24,17 +24,17 @@ test.describe('审计工作流', () => { await page.locator('text=审计日志').click(); await page.waitForTimeout(1000); - await page.locator('.el-menu-item:has-text("操作日志")').click(); + await page.locator('.ant-menu-item:has-text("操作日志")').click(); await page.waitForLoadState('networkidle'); await page.waitForTimeout(1000); await expect(page).toHaveURL(/.*oplog/, { timeout: 10000 }); - await expect(page.locator('.el-table')).toBeVisible({ timeout: 10000 }); + await expect(page.locator('.ant-table')).toBeVisible({ timeout: 10000 }); }); await test.step('验证操作日志记录', async () => { await page.waitForTimeout(2000); - const logContent = await page.locator('.el-table').textContent(); + const logContent = await page.locator('.ant-table').textContent(); expect(logContent).toMatch(/用户管理|角色管理|菜单管理/); }); }); @@ -47,7 +47,7 @@ test.describe('审计工作流', () => { await page.locator('text=审计日志').click(); await page.waitForTimeout(1000); - await page.locator('.el-menu-item:has-text("登录日志")').click(); + await page.locator('.ant-menu-item:has-text("登录日志")').click(); await page.waitForLoadState('networkidle'); await page.waitForTimeout(1000); @@ -55,8 +55,8 @@ test.describe('审计工作流', () => { }); await test.step('验证登录日志显示', async () => { - await expect(page.locator('.el-table')).toBeVisible({ timeout: 10000 }); - const logContent = await page.locator('.el-table').textContent(); + await expect(page.locator('.ant-table')).toBeVisible({ timeout: 10000 }); + const logContent = await page.locator('.ant-table').textContent(); expect(logContent).toBeTruthy(); expect(logContent.length).toBeGreaterThan(0); }); @@ -70,24 +70,24 @@ test.describe('审计工作流', () => { await page.locator('text=审计日志').click(); await page.waitForTimeout(1000); - await page.locator('.el-menu-item:has-text("操作日志")').click(); + await page.locator('.ant-menu-item:has-text("操作日志")').click(); await page.waitForLoadState('networkidle'); await page.waitForTimeout(1000); - await expect(page.locator('.el-table')).toBeVisible({ timeout: 10000 }); + await expect(page.locator('.ant-table')).toBeVisible({ timeout: 10000 }); }); await test.step('按模块筛选', async () => { - const moduleSelect = page.locator('.el-select:has-text("模块")'); + const moduleSelect = page.locator('.ant-select:has-text("模块")'); if (await moduleSelect.isVisible()) { await moduleSelect.click(); - await page.locator('.el-select-dropdown__item:has-text("用户管理")').click(); + await page.locator('.ant-select-item:has-text("用户管理")').click(); await page.waitForTimeout(1000); } }); await test.step('按时间范围筛选', async () => { - const dateRangePicker = page.locator('.el-date-editor'); + const dateRangePicker = page.locator('.ant-picker'); if (await dateRangePicker.isVisible()) { await dateRangePicker.click(); await page.waitForTimeout(500); diff --git a/novalon-manage-web/e2e/journeys/config-dict-management.spec.ts b/novalon-manage-web/e2e/journeys/config-dict-management.spec.ts new file mode 100644 index 0000000..5331787 --- /dev/null +++ b/novalon-manage-web/e2e/journeys/config-dict-management.spec.ts @@ -0,0 +1,80 @@ +import { test, expect } from '@playwright/test'; +import { LoginPage } from '../pages/LoginPage'; +import { SystemConfigPage } from '../pages/SystemConfigPage'; +import { DictionaryManagementPage } from '../pages/DictionaryManagementPage'; + +test.describe('User Journey: 系统配置与字典管理', () => { + test.describe.configure({ mode: 'serial' }); + + const timestamp = Date.now(); + + test('UJ-04: 系统配置 CRUD', async ({ page }) => { + const loginPage = new LoginPage(page); + const configPage = new SystemConfigPage(page); + + await test.step('登录', async () => { + await loginPage.goto(); + await loginPage.login('admin', 'Test@123'); + }); + + await test.step('导航到系统配置', async () => { + await configPage.goto(); + }); + + await test.step('创建配置', async () => { + await configPage.addConfig( + `E2E配置_${timestamp}`, + `e2e.config.key_${timestamp}`, + 'e2e_test_value', + 'test', + 'E2E测试配置' + ); + }); + + await test.step('验证配置已创建', async () => { + const exists = await configPage.containsText(`e2e.config.key_${timestamp}`); + expect(exists).toBe(true); + }); + + await test.step('编辑配置值', async () => { + await configPage.editConfig(`e2e.config.key_${timestamp}`, 'updated_value'); + }); + + await test.step('删除配置', async () => { + await configPage.deleteConfig(`e2e.config.key_${timestamp}`); + }); + }); + + test('UJ-05: 字典类型与数据 CRUD', async ({ page }) => { + const loginPage = new LoginPage(page); + const dictPage = new DictionaryManagementPage(page); + const dictName = `E2E字典_${timestamp}`; + const dictType = `e2e_dict_${timestamp}`; + + await test.step('登录', async () => { + await loginPage.goto(); + await loginPage.login('admin', 'Test@123'); + }); + + await test.step('导航到字典管理', async () => { + await dictPage.goto(); + }); + + await test.step('创建字典类型', async () => { + await dictPage.createDictType(dictName, dictType, 1, 'E2E测试字典'); + }); + + await test.step('选择字典类型', async () => { + await dictPage.selectDictType(dictType); + }); + + await test.step('创建字典数据', async () => { + await dictPage.createDictData('E2E选项A', 'option_a', 1, 1); + }); + + await test.step('验证字典数据已创建', async () => { + const exists = await dictPage.dataContainsText('E2E选项A'); + expect(exists).toBe(true); + }); + }); +}); diff --git a/novalon-manage-web/e2e/journeys/config-workflow.spec.ts b/novalon-manage-web/e2e/journeys/config-workflow.spec.ts index c35fc42..279feca 100644 --- a/novalon-manage-web/e2e/journeys/config-workflow.spec.ts +++ b/novalon-manage-web/e2e/journeys/config-workflow.spec.ts @@ -113,11 +113,11 @@ test.describe('系统配置工作流', () => { const deleteBtn = firstRow.getByRole('button', { name: '删除' }); if (await deleteBtn.isVisible({ timeout: 3000 }).catch(() => false)) { await deleteBtn.click(); - const confirmBtn = page.locator('.el-message-box'); + const confirmBtn = page.locator('.ant-message-box'); await confirmBtn.waitFor({ state: 'visible', timeout: 3000 }); await test.step('确认删除', async () => { - const confirmBtn = page.locator('.el-message-box').getByRole('button', { name: '确定' }); + const confirmBtn = page.locator('.ant-message-box').getByRole('button', { name: '确定' }); if (await confirmBtn.isVisible({ timeout: 2000 }).catch(() => false)) { await confirmBtn.click(); await page.waitForLoadState('networkidle'); @@ -125,7 +125,7 @@ test.describe('系统配置工作流', () => { }); await test.step('验证删除成功', async () => { - const messageBox = page.locator('.el-message-box'); + const messageBox = page.locator('.ant-message-box'); await expect(messageBox).not.toBeVisible({ timeout: 5000 }); console.log(`配置已删除`); }); diff --git a/novalon-manage-web/e2e/journeys/dict-workflow.spec.ts b/novalon-manage-web/e2e/journeys/dict-workflow.spec.ts index d9fcbb7..c42febf 100644 --- a/novalon-manage-web/e2e/journeys/dict-workflow.spec.ts +++ b/novalon-manage-web/e2e/journeys/dict-workflow.spec.ts @@ -111,11 +111,11 @@ test.describe('字典管理工作流', () => { const deleteBtn = firstRow.getByRole('button', { name: '删除' }); if (await deleteBtn.isVisible({ timeout: 3000 }).catch(() => false)) { await deleteBtn.click(); - const confirmBtn = page.locator('.el-message-box'); + const confirmBtn = page.locator('.ant-message-box'); await confirmBtn.waitFor({ state: 'visible', timeout: 3000 }); await test.step('确认删除', async () => { - const confirmBtn = page.locator('.el-message-box').getByRole('button', { name: '确定' }); + const confirmBtn = page.locator('.ant-message-box').getByRole('button', { name: '确定' }); if (await confirmBtn.isVisible({ timeout: 2000 }).catch(() => false)) { await confirmBtn.click(); await page.waitForLoadState('networkidle'); @@ -123,7 +123,7 @@ test.describe('字典管理工作流', () => { }); await test.step('验证删除成功', async () => { - const messageBox = page.locator('.el-message-box'); + const messageBox = page.locator('.ant-message-box'); await expect(messageBox).not.toBeVisible({ timeout: 5000 }); console.log(`字典已删除`); }); diff --git a/novalon-manage-web/e2e/journeys/dictionary-complete-workflow.spec.ts b/novalon-manage-web/e2e/journeys/dictionary-complete-workflow.spec.ts index 1c25965..b725699 100644 --- a/novalon-manage-web/e2e/journeys/dictionary-complete-workflow.spec.ts +++ b/novalon-manage-web/e2e/journeys/dictionary-complete-workflow.spec.ts @@ -20,19 +20,19 @@ test.describe('数据字典管理完整工作流', () => { await test.step('点击新增字典按钮', async () => { await page.locator('button:has-text("新增字典")').click(); - await page.waitForSelector('.el-dialog', { state: 'visible', timeout: 5000 }); + await page.waitForSelector('.ant-modal', { state: 'visible', timeout: 5000 }); }); await test.step('填写字典信息', async () => { - const dialog = page.locator('.el-dialog'); + const dialog = page.locator('.ant-modal'); await dialog.locator('input').first().fill(dictName); await dialog.locator('input').nth(1).fill(dictType); }); await test.step('提交字典表单', async () => { - await page.locator('.el-dialog button:has-text("确定")').click(); - await page.waitForSelector('.el-dialog', { state: 'hidden', timeout: 10000 }); - await expect(page.locator('.el-message--success')).toBeVisible({ timeout: 5000 }); + await page.locator('.ant-modal button:has-text("确定")').click(); + await page.waitForSelector('.ant-modal', { state: 'hidden', timeout: 10000 }); + await expect(page.locator('.ant-message-success')).toBeVisible({ timeout: 5000 }); }); await test.step('验证字典已创建', async () => { @@ -57,18 +57,18 @@ test.describe('数据字典管理完整工作流', () => { await test.step('编辑字典', async () => { const dictRow = page.locator(`tr:has-text("${dictName}")`); await dictRow.locator('button:has-text("编辑")').click(); - await page.waitForSelector('.el-dialog', { state: 'visible', timeout: 5000 }); + await page.waitForSelector('.ant-modal', { state: 'visible', timeout: 5000 }); }); await test.step('修改字典信息', async () => { - const dialog = page.locator('.el-dialog'); + const dialog = page.locator('.ant-modal'); await dialog.locator('input').first().fill(updatedName); }); await test.step('提交更新', async () => { - await page.locator('.el-dialog button:has-text("确定")').click(); - await page.waitForSelector('.el-dialog', { state: 'hidden', timeout: 10000 }); - await expect(page.locator('.el-message--success')).toBeVisible({ timeout: 5000 }); + await page.locator('.ant-modal button:has-text("确定")').click(); + await page.waitForSelector('.ant-modal', { state: 'hidden', timeout: 10000 }); + await expect(page.locator('.ant-message-success')).toBeVisible({ timeout: 5000 }); }); await test.step('验证字典已更新', async () => { @@ -91,13 +91,13 @@ test.describe('数据字典管理完整工作流', () => { await test.step('删除字典', async () => { const dictRow = page.locator(`tr:has-text("更新字典_${timestamp}")`); await dictRow.locator('button:has-text("删除")').click(); - await page.waitForSelector('.el-message-box', { state: 'visible', timeout: 5000 }); + await page.waitForSelector('.ant-message-box', { state: 'visible', timeout: 5000 }); }); await test.step('确认删除', async () => { - await page.locator('.el-message-box button:has-text("确定")').click(); - await page.waitForSelector('.el-message-box', { state: 'hidden', timeout: 10000 }); - await expect(page.locator('.el-message--success')).toBeVisible({ timeout: 5000 }); + await page.locator('.ant-message-box button:has-text("确定")').click(); + await page.waitForSelector('.ant-message-box', { state: 'hidden', timeout: 10000 }); + await expect(page.locator('.ant-message-success')).toBeVisible({ timeout: 5000 }); }); }); diff --git a/novalon-manage-web/e2e/journeys/exception-log-workflow.spec.ts b/novalon-manage-web/e2e/journeys/exception-log-workflow.spec.ts index 91080f2..4ca9c3c 100644 --- a/novalon-manage-web/e2e/journeys/exception-log-workflow.spec.ts +++ b/novalon-manage-web/e2e/journeys/exception-log-workflow.spec.ts @@ -56,7 +56,7 @@ test.describe('异常日志工作流', () => { await detailButton.click(); await test.step('验证详情对话框显示', async () => { - const dialog = page.locator('.el-dialog'); + const dialog = page.locator('.ant-modal'); await expect(dialog).toBeVisible({ timeout: 5000 }); console.log('异常日志详情对话框已打开'); }); diff --git a/novalon-manage-web/e2e/journeys/file-management-workflow.spec.ts b/novalon-manage-web/e2e/journeys/file-management-workflow.spec.ts index 562619d..cea32fb 100644 --- a/novalon-manage-web/e2e/journeys/file-management-workflow.spec.ts +++ b/novalon-manage-web/e2e/journeys/file-management-workflow.spec.ts @@ -9,11 +9,11 @@ test.describe('文件管理工作流', () => { await page.locator('text=系统管理').click(); await page.waitForTimeout(500); - await page.locator('.el-menu-item:has-text("文件管理")').click(); + await page.locator('.ant-menu-item:has-text("文件管理")').click(); await page.waitForLoadState('networkidle'); await page.waitForTimeout(1000); - await expect(page.locator('.el-table')).toBeVisible({ timeout: 10000 }); + await expect(page.locator('.ant-table')).toBeVisible({ timeout: 10000 }); }); await test.step('上传文件', async () => { @@ -30,7 +30,7 @@ test.describe('文件管理工作流', () => { }); await test.step('验证文件上传成功', async () => { - const successMessage = page.locator('.el-message--success'); + const successMessage = page.locator('.ant-message-success'); if (await successMessage.isVisible()) { expect(await successMessage.textContent()).toContain('成功'); } @@ -54,10 +54,10 @@ test.describe('文件管理工作流', () => { }); await test.step('按类型筛选', async () => { - const typeFilter = page.locator('.el-select:has-text("类型")'); + const typeFilter = page.locator('.ant-select:has-text("类型")'); if (await typeFilter.isVisible()) { await typeFilter.click(); - await page.locator('.el-select-dropdown__item').first().click(); + await page.locator('.ant-select-item').first().click(); await page.waitForTimeout(1000); } }); @@ -71,7 +71,7 @@ test.describe('文件管理工作流', () => { }); await test.step('选择文件', async () => { - const fileCheckbox = page.locator('.el-checkbox').first(); + const fileCheckbox = page.locator('.ant-checkbox').first(); if (await fileCheckbox.isVisible()) { await fileCheckbox.click(); } diff --git a/novalon-manage-web/e2e/journeys/login-dashboard-logout.spec.ts b/novalon-manage-web/e2e/journeys/login-dashboard-logout.spec.ts new file mode 100644 index 0000000..6573348 --- /dev/null +++ b/novalon-manage-web/e2e/journeys/login-dashboard-logout.spec.ts @@ -0,0 +1,81 @@ +import { test, expect } from '@playwright/test'; +import { LoginPage } from '../pages/LoginPage'; +import { DashboardPage } from '../pages/DashboardPage'; + +test.describe('User Journey: 登录 → 仪表盘 → 登出', () => { + test('UJ-01: 管理员完整登录登出流程', async ({ page }) => { + const loginPage = new LoginPage(page); + const dashboardPage = new DashboardPage(page); + + await test.step('访问登录页面', async () => { + await loginPage.goto(); + await expect(page).toHaveURL(/.*login/); + await expect(loginPage.usernameInput).toBeVisible(); + await expect(loginPage.passwordInput).toBeVisible(); + await expect(loginPage.loginButton).toBeVisible(); + }); + + await test.step('输入凭据并登录', async () => { + await loginPage.login('admin', 'Test@123'); + }); + + await test.step('验证登录成功跳转到仪表盘', async () => { + await expect(page).toHaveURL(/.*dashboard/); + }); + + await test.step('验证仪表盘内容加载', async () => { + await expect(page.locator('.ant-statistic').first()).toBeVisible({ timeout: 15000 }); + }); + + await test.step('验证侧边菜单可见', async () => { + await expect(page.locator('.ant-menu')).toBeVisible(); + }); + + await test.step('点击头像下拉菜单', async () => { + await page.locator('.ant-avatar').first().click(); + await page.waitForTimeout(500); + await expect(page.locator('.ant-dropdown-menu-item:has-text("退出登录")')).toBeVisible(); + }); + + await test.step('点击退出登录', async () => { + await page.locator('.ant-dropdown-menu-item:has-text("退出登录")').click(); + await page.waitForURL(/.*login/, { timeout: 10000 }); + }); + + await test.step('验证已跳转到登录页面', async () => { + await expect(page).toHaveURL(/.*login/); + }); + }); + + test('UJ-01b: 登录失败场景', async ({ page }) => { + const loginPage = new LoginPage(page); + + await test.step('访问登录页面', async () => { + await loginPage.goto(); + }); + + await test.step('输入错误密码', async () => { + await loginPage.loginAndExpectError('admin', 'wrongpassword'); + }); + + await test.step('验证错误消息显示', async () => { + const errorMsg = await loginPage.getErrorMessage(); + expect(errorMsg).not.toBeNull(); + }); + + await test.step('验证仍在登录页面', async () => { + await expect(page).toHaveURL(/.*login/); + }); + }); + + test('UJ-01c: 未登录访问受保护页面重定向到登录', async ({ page }) => { + await test.step('直接访问仪表盘', async () => { + await page.goto('/dashboard'); + await page.waitForTimeout(2000); + }); + + await test.step('验证被重定向到登录页面', async () => { + await expect(page).toHaveURL(/.*login/, { timeout: 10000 }); + }); + }); +}); diff --git a/novalon-manage-web/e2e/journeys/menu-management.spec.ts b/novalon-manage-web/e2e/journeys/menu-management.spec.ts new file mode 100644 index 0000000..569c929 --- /dev/null +++ b/novalon-manage-web/e2e/journeys/menu-management.spec.ts @@ -0,0 +1,58 @@ +import { test, expect } from '@playwright/test'; +import { LoginPage } from '../pages/LoginPage'; +import { DashboardPage } from '../pages/DashboardPage'; +import { MenuManagementPage } from '../pages/MenuManagementPage'; + +test.describe('User Journey: 菜单管理全链路', () => { + test.describe.configure({ mode: 'serial' }); + + const timestamp = Date.now(); + const menuName = `E2E菜单_${timestamp}`; + + test('UJ-03: 菜单创建→编辑→删除', async ({ page }) => { + const loginPage = new LoginPage(page); + const menuPage = new MenuManagementPage(page); + + await test.step('登录', async () => { + await loginPage.goto(); + await loginPage.login('admin', 'Test@123'); + }); + + await test.step('导航到菜单管理', async () => { + await menuPage.goto(); + }); + + await test.step('创建新菜单', async () => { + await menuPage.clickCreateMenu(); + await menuPage.fillMenuForm({ + name: menuName, + type: 'menu', + path: `/e2e-test-${timestamp}`, + icon: 'file', + component: `e2e/Test_${timestamp}`, + permission: `e2e:test_${timestamp}`, + sort: 99, + status: 'ACTIVE', + visible: true, + }); + await menuPage.submitForm(); + const success = await menuPage.containsText(menuName); + expect(success).toBe(true); + }); + + await test.step('编辑菜单', async () => { + await menuPage.editMenu(menuName); + const modal = page.locator('.ant-modal').filter({ hasText: /编辑菜单/ }); + const nameInput = modal.locator('.ant-form-item').filter({ hasText: '菜单名称' }).locator('input'); + await nameInput.clear(); + await nameInput.fill(`${menuName}_已编辑`); + await menuPage.submitForm(); + }); + + await test.step('删除菜单', async () => { + await menuPage.goto(); + await menuPage.deleteMenu(`${menuName}_已编辑`); + await menuPage.confirmDelete(); + }); + }); +}); diff --git a/novalon-manage-web/e2e/journeys/notice-workflow.spec.ts b/novalon-manage-web/e2e/journeys/notice-workflow.spec.ts index ec199c0..7dbb379 100644 --- a/novalon-manage-web/e2e/journeys/notice-workflow.spec.ts +++ b/novalon-manage-web/e2e/journeys/notice-workflow.spec.ts @@ -111,11 +111,11 @@ test.describe('通知管理工作流', () => { const deleteBtn = firstRow.getByRole('button', { name: '删除' }); if (await deleteBtn.isVisible({ timeout: 3000 }).catch(() => false)) { await deleteBtn.click(); - const confirmBtn = page.locator('.el-message-box'); + const confirmBtn = page.locator('.ant-message-box'); await confirmBtn.waitFor({ state: 'visible', timeout: 3000 }); await test.step('确认删除', async () => { - const confirmBtn = page.locator('.el-message-box').getByRole('button', { name: '确定' }); + const confirmBtn = page.locator('.ant-message-box').getByRole('button', { name: '确定' }); if (await confirmBtn.isVisible({ timeout: 2000 }).catch(() => false)) { await confirmBtn.click(); await page.waitForLoadState('networkidle'); @@ -123,7 +123,7 @@ test.describe('通知管理工作流', () => { }); await test.step('验证删除成功', async () => { - const messageBox = page.locator('.el-message-box'); + const messageBox = page.locator('.ant-message-box'); await expect(messageBox).not.toBeVisible({ timeout: 5000 }); console.log(`通知已删除`); }); diff --git a/novalon-manage-web/e2e/journeys/notify-file-management.spec.ts b/novalon-manage-web/e2e/journeys/notify-file-management.spec.ts new file mode 100644 index 0000000..1353adb --- /dev/null +++ b/novalon-manage-web/e2e/journeys/notify-file-management.spec.ts @@ -0,0 +1,65 @@ +import { test, expect } from '@playwright/test'; +import { LoginPage } from '../pages/LoginPage'; +import { NotificationPage } from '../pages/NotificationPage'; +import { FileManagementPage } from '../pages/FileManagementPage'; + +test.describe('User Journey: 通知与文件管理', () => { + test.describe.configure({ mode: 'serial' }); + + const timestamp = Date.now(); + + test('UJ-06: 通知管理 CRUD', async ({ page }) => { + const loginPage = new LoginPage(page); + const notifyPage = new NotificationPage(page); + const title = `E2E通知_${timestamp}`; + + await test.step('登录', async () => { + await loginPage.goto(); + await loginPage.login('admin', 'Test@123'); + }); + + await test.step('导航到通知管理', async () => { + await notifyPage.goto(); + }); + + await test.step('创建通知', async () => { + await notifyPage.addNotification(title, 'E2E测试通知内容', '通知'); + }); + + await test.step('验证通知已创建', async () => { + const exists = await notifyPage.containsText(title); + expect(exists).toBe(true); + }); + + await test.step('编辑通知', async () => { + await notifyPage.editNotification(title, '更新后的通知内容'); + }); + + await test.step('删除通知', async () => { + await notifyPage.deleteNotification(title); + }); + }); + + test('UJ-07: 文件上传与管理', async ({ page }) => { + const loginPage = new LoginPage(page); + const filePage = new FileManagementPage(page); + + await test.step('登录', async () => { + await loginPage.goto(); + await loginPage.login('admin', 'Test@123'); + }); + + await test.step('导航到文件管理', async () => { + await filePage.goto(); + }); + + await test.step('验证文件管理页面加载', async () => { + await expect(filePage.uploadButton).toBeVisible({ timeout: 10000 }); + await expect(filePage.refreshButton).toBeVisible(); + }); + + await test.step('刷新文件列表', async () => { + await filePage.reload(); + }); + }); +}); diff --git a/novalon-manage-web/e2e/journeys/system-config-complete-workflow.spec.ts b/novalon-manage-web/e2e/journeys/system-config-complete-workflow.spec.ts index 3908d63..8e0e37b 100644 --- a/novalon-manage-web/e2e/journeys/system-config-complete-workflow.spec.ts +++ b/novalon-manage-web/e2e/journeys/system-config-complete-workflow.spec.ts @@ -21,20 +21,20 @@ test.describe('系统配置管理完整工作流', () => { await test.step('点击新增配置按钮', async () => { await page.locator('button:has-text("新增配置")').click(); - await page.waitForSelector('.el-dialog', { state: 'visible', timeout: 5000 }); + await page.waitForSelector('.ant-modal', { state: 'visible', timeout: 5000 }); }); await test.step('填写配置信息', async () => { - const dialog = page.locator('.el-dialog'); + const dialog = page.locator('.ant-modal'); await dialog.locator('input').first().fill(configName); await dialog.locator('input').nth(1).fill(configKey); await dialog.locator('input').nth(2).fill(configValue); }); await test.step('提交配置表单', async () => { - await page.locator('.el-dialog button:has-text("确定")').click(); - await page.waitForSelector('.el-dialog', { state: 'hidden', timeout: 10000 }); - await expect(page.locator('.el-message--success')).toBeVisible({ timeout: 5000 }); + await page.locator('.ant-modal button:has-text("确定")').click(); + await page.waitForSelector('.ant-modal', { state: 'hidden', timeout: 10000 }); + await expect(page.locator('.ant-message-success')).toBeVisible({ timeout: 5000 }); }); await test.step('验证配置已创建', async () => { @@ -59,18 +59,18 @@ test.describe('系统配置管理完整工作流', () => { await test.step('编辑配置', async () => { const configRow = page.locator(`tr:has-text("${configName}")`); await configRow.locator('button:has-text("编辑")').click(); - await page.waitForSelector('.el-dialog', { state: 'visible', timeout: 5000 }); + await page.waitForSelector('.ant-modal', { state: 'visible', timeout: 5000 }); }); await test.step('修改配置值', async () => { - const dialog = page.locator('.el-dialog'); + const dialog = page.locator('.ant-modal'); await dialog.locator('input').nth(2).fill(updatedValue); }); await test.step('提交更新', async () => { - await page.locator('.el-dialog button:has-text("确定")').click(); - await page.waitForSelector('.el-dialog', { state: 'hidden', timeout: 10000 }); - await expect(page.locator('.el-message--success')).toBeVisible({ timeout: 5000 }); + await page.locator('.ant-modal button:has-text("确定")').click(); + await page.waitForSelector('.ant-modal', { state: 'hidden', timeout: 10000 }); + await expect(page.locator('.ant-message-success')).toBeVisible({ timeout: 5000 }); }); await test.step('验证配置已更新', async () => { @@ -93,13 +93,13 @@ test.describe('系统配置管理完整工作流', () => { await test.step('删除配置', async () => { const configRow = page.locator(`tr:has-text("${configName}")`); await configRow.locator('button:has-text("删除")').click(); - await page.waitForSelector('.el-message-box', { state: 'visible', timeout: 5000 }); + await page.waitForSelector('.ant-message-box', { state: 'visible', timeout: 5000 }); }); await test.step('确认删除', async () => { - await page.locator('.el-message-box button:has-text("确定")').click(); - await page.waitForSelector('.el-message-box', { state: 'hidden', timeout: 10000 }); - await expect(page.locator('.el-message--success')).toBeVisible({ timeout: 5000 }); + await page.locator('.ant-message-box button:has-text("确定")').click(); + await page.waitForSelector('.ant-message-box', { state: 'hidden', timeout: 10000 }); + await expect(page.locator('.ant-message-success')).toBeVisible({ timeout: 5000 }); }); }); diff --git a/novalon-manage-web/e2e/journeys/user-permission-boundary.spec.ts b/novalon-manage-web/e2e/journeys/user-permission-boundary.spec.ts index 034bbce..07b4737 100644 --- a/novalon-manage-web/e2e/journeys/user-permission-boundary.spec.ts +++ b/novalon-manage-web/e2e/journeys/user-permission-boundary.spec.ts @@ -6,21 +6,21 @@ test.describe('用户权限边界验证', () => { await page.goto('/users'); await page.waitForLoadState('networkidle'); await expect(page).toHaveURL(/.*users/); - await expect(page.locator('.el-table')).toBeVisible({ timeout: 10000 }); + await expect(page.locator('.ant-table')).toBeVisible({ timeout: 10000 }); }); await test.step('验证可以访问角色管理', async () => { await page.goto('/roles'); await page.waitForLoadState('networkidle'); await expect(page).toHaveURL(/.*roles/); - await expect(page.locator('.el-table')).toBeVisible({ timeout: 10000 }); + await expect(page.locator('.ant-table')).toBeVisible({ timeout: 10000 }); }); await test.step('验证可以访问菜单管理', async () => { await page.goto('/menus'); await page.waitForLoadState('networkidle'); await expect(page).toHaveURL(/.*menus/); - await expect(page.locator('.el-table')).toBeVisible({ timeout: 10000 }); + await expect(page.locator('.ant-table')).toBeVisible({ timeout: 10000 }); }); }); @@ -29,7 +29,7 @@ test.describe('用户权限边界验证', () => { await page.goto('/dashboard'); await page.waitForLoadState('networkidle'); - const avatarButton = page.locator('.el-avatar').first(); + const avatarButton = page.locator('.ant-avatar').first(); await avatarButton.click({ timeout: 10000 }); await page.waitForTimeout(500); @@ -68,9 +68,9 @@ test.describe('用户权限边界验证', () => { if (await createButton.isVisible()) { await createButton.click(); await page.waitForTimeout(2000); - const errorMessage = page.locator('.el-message--error'); + const errorMessage = page.locator('.ant-message-error'); const hasError = await errorMessage.isVisible().catch(() => false); - expect(hasError || await page.locator('.el-dialog').isVisible()).toBeTruthy(); + expect(hasError || await page.locator('.ant-modal').isVisible()).toBeTruthy(); } }); }); @@ -80,7 +80,7 @@ test.describe('用户权限边界验证', () => { await page.goto('/dashboard'); await page.waitForLoadState('networkidle'); - const avatarButton = page.locator('.el-avatar').first(); + const avatarButton = page.locator('.ant-avatar').first(); await avatarButton.click({ timeout: 10000 }); await page.waitForTimeout(500); diff --git a/novalon-manage-web/e2e/journeys/user-role-management.spec.ts b/novalon-manage-web/e2e/journeys/user-role-management.spec.ts new file mode 100644 index 0000000..04e35e6 --- /dev/null +++ b/novalon-manage-web/e2e/journeys/user-role-management.spec.ts @@ -0,0 +1,134 @@ +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('User Journey: 用户与角色管理全链路', () => { + test.describe.configure({ mode: 'serial' }); + + let loginPage: LoginPage; + let dashboardPage: DashboardPage; + let userPage: UserManagementPage; + let rolePage: RoleManagementPage; + + const timestamp = Date.now(); + const roleName = `E2E角色_${timestamp}`; + const roleKey = `e2e_role_${timestamp}`; + const username = `e2e_user_${timestamp}`; + + test.beforeAll(async ({ browser }) => { + const context = await browser.newContext(); + const page = await context.newPage(); + loginPage = new LoginPage(page); + await loginPage.goto(); + await loginPage.login('admin', 'Test@123'); + dashboardPage = new DashboardPage(page); + userPage = new UserManagementPage(page); + rolePage = new RoleManagementPage(page); + }); + + test('UJ-02a: 创建角色', async () => { + await test.step('导航到角色管理', async () => { + await rolePage.goto(); + }); + + await test.step('点击新增角色', async () => { + await rolePage.clickCreateRole(); + }); + + await test.step('填写角色表单', async () => { + await rolePage.fillRoleForm({ + roleName, + roleKey, + roleSort: 99, + status: 'ACTIVE', + }); + }); + + await test.step('提交表单', async () => { + await rolePage.submitForm(); + }); + + await test.step('验证创建成功', async () => { + const success = await rolePage.waitForSuccessMessage(); + expect(success).toBe(true); + }); + }); + + test('UJ-02b: 创建用户并分配角色', async () => { + await test.step('导航到用户管理', async () => { + await userPage.goto(); + }); + + await test.step('点击新增用户', async () => { + await userPage.clickCreateUser(); + }); + + await test.step('填写用户表单', async () => { + await userPage.fillUserForm({ + username, + password: 'Test@123456', + nickname: `E2E测试用户_${timestamp}`, + email: `e2e_${timestamp}@test.com`, + phone: '13800138000', + }); + }); + + await test.step('提交表单', async () => { + await userPage.submitForm(); + }); + + await test.step('验证创建成功', async () => { + const success = await userPage.waitForSuccessMessage(); + expect(success).toBe(true); + }); + }); + + test('UJ-02c: 编辑用户', async () => { + await test.step('导航到用户管理', async () => { + await userPage.goto(); + await userPage.waitForTableReady(); + }); + + await test.step('编辑第一个用户', async () => { + await userPage.editUser(1); + }); + + await test.step('修改昵称', async () => { + const modal = userPage.page.locator('.ant-modal').filter({ hasText: /编辑用户/ }); + const nicknameInput = modal.locator('.ant-form-item').filter({ hasText: '昵称' }).locator('input'); + await nicknameInput.clear(); + await nicknameInput.fill(`修改昵称_${timestamp}`); + }); + + await test.step('提交修改', async () => { + await userPage.submitForm(); + }); + + await test.step('验证修改成功', async () => { + const success = await userPage.waitForSuccessMessage(); + expect(success).toBe(true); + }); + }); + + test('UJ-02d: 删除用户', async () => { + await test.step('导航到用户管理', async () => { + await userPage.goto(); + await userPage.waitForTableReady(); + }); + + await test.step('删除最后一个用户', async () => { + const count = await userPage.getUserCount(); + if (count > 0) { + await userPage.deleteUser(count); + await userPage.confirmDelete(); + } + }); + + await test.step('验证删除成功', async () => { + const success = await userPage.waitForSuccessMessage(); + expect(success).toBe(true); + }); + }); +}); diff --git a/novalon-manage-web/e2e/menu-management.spec.ts b/novalon-manage-web/e2e/menu-management.spec.ts index 2ab3a8b..5e2d25a 100644 --- a/novalon-manage-web/e2e/menu-management.spec.ts +++ b/novalon-manage-web/e2e/menu-management.spec.ts @@ -10,7 +10,7 @@ test.describe('菜单管理功能测试', () => { }, data: { username: 'admin', - password: 'admin123' + password: 'Test@123' } }); @@ -28,20 +28,20 @@ test.describe('菜单管理功能测试', () => { const loginButton = page.locator('button:has-text("登录")').first(); await usernameInput.fill('admin'); - await passwordInput.fill('admin123'); + await passwordInput.fill('Test@123'); await loginButton.click(); await page.waitForTimeout(2000); // 点击系统管理菜单 - const systemMenu = page.locator('.el-sub-menu:has-text("系统管理")').first(); + const systemMenu = page.locator('.ant-menu-submenu:has-text("系统管理")').first(); if (await systemMenu.count() > 0) { await systemMenu.click(); await page.waitForTimeout(500); } // 点击菜单管理 - const menuManagement = page.locator('.el-menu-item:has-text("菜单管理")').first(); + const menuManagement = page.locator('.ant-menu-item:has-text("菜单管理")').first(); if (await menuManagement.count() > 0) { await menuManagement.click(); await page.waitForTimeout(1000); @@ -52,7 +52,7 @@ test.describe('菜单管理功能测试', () => { // 检查是否有菜单列表或表格 const tableSelectors = [ 'table', - '.el-table', + '.ant-table', '[class*="table"]', '.menu-list' ]; diff --git a/novalon-manage-web/e2e/pages/DashboardPage.ts b/novalon-manage-web/e2e/pages/DashboardPage.ts index 08c0a0f..e7de0f2 100644 --- a/novalon-manage-web/e2e/pages/DashboardPage.ts +++ b/novalon-manage-web/e2e/pages/DashboardPage.ts @@ -15,16 +15,16 @@ export class DashboardPage { constructor(page: Page) { this.page = page; - this.userInfo = page.locator('.el-avatar'); - this.userManagementLink = page.locator('.el-menu-item:has-text("用户管理")'); - this.roleManagementLink = page.locator('.el-menu-item:has-text("角色管理")'); - this.menuManagementLink = page.locator('.el-menu-item:has-text("菜单管理")'); - this.systemConfigLink = page.locator('.el-menu-item:has-text("参数配置")'); - this.noticeManagementLink = page.locator('.el-menu-item:has-text("通知公告")'); - this.fileManagementLink = page.locator('.el-menu-item:has-text("文件列表")'); - this.operationLogLink = page.locator('.el-menu-item:has-text("操作日志")'); - this.loginLogLink = page.locator('.el-menu-item:has-text("登录日志")'); - this.dictionaryLink = page.locator('.el-menu-item:has-text("字典管理")'); + this.userInfo = page.locator('.ant-avatar'); + this.userManagementLink = page.locator('.ant-menu-item:has-text("用户管理")'); + this.roleManagementLink = page.locator('.ant-menu-item:has-text("角色管理")'); + this.menuManagementLink = page.locator('.ant-menu-item:has-text("菜单管理")'); + this.systemConfigLink = page.locator('.ant-menu-item:has-text("参数配置")'); + this.noticeManagementLink = page.locator('.ant-menu-item:has-text("通知公告")'); + this.fileManagementLink = page.locator('.ant-menu-item:has-text("文件列表")'); + this.operationLogLink = page.locator('.ant-menu-item:has-text("操作日志")'); + this.loginLogLink = page.locator('.ant-menu-item:has-text("登录日志")'); + this.dictionaryLink = page.locator('.ant-menu-item:has-text("字典管理")'); } async goto() { @@ -33,97 +33,76 @@ export class DashboardPage { } async navigateToUserManagement() { - const systemMenu = this.page.locator('.el-sub-menu__title:has-text("系统管理")'); - await systemMenu.click(); - await this.page.waitForTimeout(1000); + await this.clickSubMenu('系统管理'); await this.userManagementLink.click(); await this.page.waitForURL('**/users', { timeout: 30000 }); await this.page.waitForLoadState('networkidle'); } async navigateToRoleManagement() { - const systemMenu = this.page.locator('.el-sub-menu__title:has-text("系统管理")'); - await systemMenu.click(); - await this.page.waitForTimeout(1000); + await this.clickSubMenu('系统管理'); await this.roleManagementLink.click(); await this.page.waitForURL('**/roles', { timeout: 30000 }); await this.page.waitForLoadState('networkidle'); } async navigateToMenuManagement() { - const systemMenu = this.page.locator('.el-sub-menu__title:has-text("系统管理")'); - await systemMenu.click(); - await this.page.waitForTimeout(1000); + await this.clickSubMenu('系统管理'); await this.menuManagementLink.click(); await this.page.waitForURL('**/menus', { timeout: 30000 }); await this.page.waitForLoadState('networkidle'); } async navigateToSystemConfig() { - const configMenu = this.page.locator('.el-sub-menu__title:has-text("系统配置")'); - await configMenu.click(); - await this.page.waitForTimeout(1000); + await this.clickSubMenu('系统配置'); await this.systemConfigLink.click(); await this.page.waitForURL('**/sys/config', { timeout: 30000 }); await this.page.waitForLoadState('networkidle'); } async navigateToNoticeManagement() { - const notifyMenu = this.page.locator('.el-sub-menu__title:has-text("通知中心")'); - await notifyMenu.click(); - await this.page.waitForTimeout(1000); + await this.clickSubMenu('通知中心'); await this.noticeManagementLink.click(); await this.page.waitForURL('**/notice', { timeout: 30000 }); await this.page.waitForLoadState('networkidle'); } async navigateToFileManagement() { - const fileMenu = this.page.locator('.el-sub-menu__title:has-text("文件管理")'); - await fileMenu.click(); - await this.page.waitForTimeout(1000); + await this.clickSubMenu('文件管理'); await this.fileManagementLink.click(); await this.page.waitForURL('**/files', { timeout: 30000 }); await this.page.waitForLoadState('networkidle'); } - async navigateToAudit() { - const auditMenu = this.page.locator('.el-sub-menu__title:has-text("审计中心")'); - await auditMenu.click(); - await this.page.waitForTimeout(1000); - } - async navigateToOperationLog() { - await this.navigateToAudit(); + await this.clickSubMenu('审计中心'); await this.operationLogLink.click(); await this.page.waitForURL('**/oplog', { timeout: 30000 }); await this.page.waitForLoadState('networkidle'); } async navigateToLoginLog() { - await this.navigateToAudit(); + await this.clickSubMenu('审计中心'); await this.loginLogLink.click(); await this.page.waitForURL('**/loginlog', { timeout: 30000 }); await this.page.waitForLoadState('networkidle'); } - async navigateToNotification() { - const notifyMenu = this.page.locator('.el-sub-menu__title:has-text("通知中心")'); - await notifyMenu.click(); - await this.page.waitForTimeout(1000); - await this.noticeManagementLink.click(); - await this.page.waitForURL('**/notification', { timeout: 30000 }); - await this.page.waitForLoadState('networkidle'); - } - async navigateToDictionary() { - const configMenu = this.page.locator('.el-sub-menu__title:has-text("系统配置")'); - await configMenu.click(); - await this.page.waitForTimeout(1000); + await this.clickSubMenu('系统配置'); await this.dictionaryLink.click(); await this.page.waitForURL('**/dict', { timeout: 30000 }); await this.page.waitForLoadState('networkidle'); } + private async clickSubMenu(label: string) { + const subMenu = this.page.locator(`.ant-menu-submenu-title:has-text("${label}")`); + if (await subMenu.isVisible()) { + await subMenu.click(); + await this.page.waitForTimeout(500); + } + } + async getUsername(): Promise { return await this.userInfo.textContent(); } diff --git a/novalon-manage-web/e2e/pages/DictionaryManagementPage.ts b/novalon-manage-web/e2e/pages/DictionaryManagementPage.ts index c9baba7..dee51ab 100644 --- a/novalon-manage-web/e2e/pages/DictionaryManagementPage.ts +++ b/novalon-manage-web/e2e/pages/DictionaryManagementPage.ts @@ -2,95 +2,109 @@ import { Page, Locator, expect } from '@playwright/test'; export class DictionaryManagementPage { readonly page: Page; - readonly table: Locator; - readonly createDictButton: Locator; - readonly saveButton: Locator; - readonly dialog: Locator; - readonly dictNameInput: Locator; - readonly dictTypeInput: Locator; - readonly statusSelect: Locator; - readonly remarkInput: Locator; + readonly typeTable: Locator; + readonly dataTable: Locator; + readonly addTypeButton: Locator; + readonly addDataButton: Locator; + readonly successMessage: Locator; + readonly errorMessage: Locator; constructor(page: Page) { this.page = page; - this.table = page.locator('.el-table'); - this.createDictButton = page.getByRole('button', { name: '新增字典' }); - this.saveButton = page.getByRole('button', { name: '确定' }); - this.dialog = page.locator('.el-dialog'); - this.dictNameInput = page.locator('.el-dialog').getByRole('textbox', { name: '字典名称' }); - this.dictTypeInput = page.locator('.el-dialog').getByRole('textbox', { name: '字典类型' }); - this.statusSelect = page.locator('.el-dialog').getByRole('combobox', { name: '状态' }); - this.remarkInput = page.locator('.el-dialog').getByRole('textbox', { name: '备注' }); + this.typeTable = page.locator('.ant-card').filter({ hasText: '字典类型' }).locator('.ant-table').first(); + this.dataTable = page.locator('.ant-card').filter({ hasText: '字典数据' }).locator('.ant-table').first(); + this.addTypeButton = page.locator('.ant-card').filter({ hasText: '字典类型' }).getByRole('button', { name: '新增' }); + this.addDataButton = page.locator('.ant-card').filter({ hasText: '字典数据' }).getByRole('button', { name: '新增' }); + this.successMessage = page.locator('.ant-message-success'); + this.errorMessage = page.locator('.ant-message-error'); } async goto() { - try { - console.log('导航到字典管理页面...'); - await this.page.goto('/dict'); - - await this.page.waitForLoadState('networkidle'); - await this.table.waitFor({ state: 'visible', timeout: 10000 }); - await expect(this.page).toHaveURL(/.*dict/); - - console.log('字典管理页面加载完成'); - } catch (error) { - await this.page.screenshot({ path: `test-results/dict-management-error-${Date.now()}.png` }); - console.error('导航到字典管理页面失败:', error); - throw new Error(`导航到字典管理页面失败: ${error instanceof Error ? error.message : String(error)}`); - } + await this.page.goto('/dict'); + await this.page.waitForLoadState('networkidle'); + await this.typeTable.waitFor({ state: 'visible', timeout: 15000 }).catch(() => {}); + await expect(this.page).toHaveURL(/.*dict/); } - async createDict(dictName: string, dictType: string, status: string = '0', remark?: string) { - await this.createDictButton.click(); + async createDictType(dictName: string, dictType: string, status: number = 1, remark?: string) { + await this.addTypeButton.click(); await this.page.waitForTimeout(500); - - await this.dictNameInput.fill(dictName); - await this.dictTypeInput.fill(dictType); - - if (status) { - await this.statusSelect.click(); - await this.page.waitForTimeout(300); - await this.page.getByRole('option', { name: status === '0' ? '正常' : '停用' }).click(); - } - + + const modal = this.page.locator('.ant-modal').filter({ hasText: /新增字典类型|编辑字典类型/ }); + await modal.locator('.ant-form-item').filter({ hasText: '字典名称' }).locator('input').fill(dictName); + await modal.locator('.ant-form-item').filter({ hasText: '字典类型' }).locator('input').fill(dictType); + + const statusSelect = modal.locator('.ant-form-item').filter({ hasText: '状态' }).locator('.ant-select'); + await statusSelect.click(); + await this.page.waitForTimeout(300); + const statusText = status === 1 ? '正常' : '停用'; + await this.page.locator('.ant-select-dropdown').locator('.ant-select-item').filter({ hasText: statusText }).first().click(); + if (remark) { - await this.remarkInput.fill(remark); + await modal.locator('.ant-form-item').filter({ hasText: '备注' }).locator('textarea').fill(remark); } - - await this.saveButton.click(); - await this.page.waitForLoadState('networkidle'); + + await modal.getByRole('button', { name: /确\s*定/ }).click(); + await this.page.waitForTimeout(1000); } - async editDict(dictName: string, newDictName: string) { - const row = this.table.locator('tr').filter({ hasText: dictName }).first(); - const editBtn = row.getByRole('button', { name: '编辑' }); - await editBtn.click(); + async editDictType(dictName: string, newDictName: string) { + const row = this.typeTable.locator('tbody tr').filter({ hasText: dictName }).first(); + await row.locator('.ant-btn').filter({ has: this.page.locator('.anticon-edit') }).click(); await this.page.waitForTimeout(500); - - await this.dictNameInput.clear(); - await this.dictNameInput.fill(newDictName); - - await this.saveButton.click(); - await this.page.waitForLoadState('networkidle'); + + const modal = this.page.locator('.ant-modal').filter({ hasText: /新增字典类型|编辑字典类型/ }); + const nameInput = modal.locator('.ant-form-item').filter({ hasText: '字典名称' }).locator('input'); + await nameInput.clear(); + await nameInput.fill(newDictName); + + await modal.getByRole('button', { name: /确\s*定/ }).click(); + await this.page.waitForTimeout(1000); } - async deleteDict(dictName: string) { - const row = this.table.locator('tr').filter({ hasText: dictName }).first(); - const deleteBtn = row.getByRole('button', { name: '删除' }); - await deleteBtn.click(); + async deleteDictType(dictName: string) { + const row = this.typeTable.locator('tbody tr').filter({ hasText: dictName }).first(); + await row.locator('.ant-btn').filter({ has: this.page.locator('.anticon-delete') }).click(); + await this.page.waitForTimeout(300); + await this.page.locator('.ant-popconfirm').getByRole('button', { name: /确\s*定/ }).click(); + await this.page.waitForTimeout(1000); + } + + async selectDictType(dictType: string) { + const link = this.typeTable.locator('a').filter({ hasText: dictType }); + await link.click(); await this.page.waitForTimeout(500); - - const confirmBtn = this.page.locator('.el-message-box').getByRole('button', { name: '确定' }); - await confirmBtn.click(); - await this.page.waitForLoadState('networkidle'); } - async getDictCount() { - const rows = await this.table.locator('.el-table__row').count(); - return rows; + async createDictData(dictLabel: string, dictValue: string, sort: number = 0, status: number = 1) { + await this.addDataButton.click(); + await this.page.waitForTimeout(500); + + const modal = this.page.locator('.ant-modal').filter({ hasText: /新增字典数据|编辑字典数据/ }); + await modal.locator('.ant-form-item').filter({ hasText: '字典标签' }).locator('input').fill(dictLabel); + await modal.locator('.ant-form-item').filter({ hasText: '字典值' }).locator('input').fill(dictValue); + + if (sort > 0) { + const sortInput = modal.locator('.ant-form-item').filter({ hasText: '排序' }).locator('.ant-input-number input'); + await sortInput.clear(); + await sortInput.fill(String(sort)); + } + + const statusSelect = modal.locator('.ant-form-item').filter({ hasText: '状态' }).locator('.ant-select'); + await statusSelect.click(); + await this.page.waitForTimeout(300); + const statusText = status === 1 ? '正常' : '停用'; + await this.page.locator('.ant-select-dropdown').locator('.ant-select-item').filter({ hasText: statusText }).first().click(); + + await modal.getByRole('button', { name: /确\s*定/ }).click(); + await this.page.waitForTimeout(1000); } - async containsText(text: string): Promise { - return await this.table.getByText(text).count() > 0; + async typeContainsText(text: string): Promise { + return this.typeTable.getByText(text).count() > 0; + } + + async dataContainsText(text: string): Promise { + return this.dataTable.getByText(text).count() > 0; } } diff --git a/novalon-manage-web/e2e/pages/ExceptionLogPage.ts b/novalon-manage-web/e2e/pages/ExceptionLogPage.ts index a827cd5..9c9d27a 100644 --- a/novalon-manage-web/e2e/pages/ExceptionLogPage.ts +++ b/novalon-manage-web/e2e/pages/ExceptionLogPage.ts @@ -4,98 +4,44 @@ export class ExceptionLogPage { readonly page: Page; readonly table: Locator; readonly searchInput: Locator; - readonly searchButton: Locator; - readonly exportButton: Locator; readonly refreshButton: Locator; - readonly detailButton: Locator; - readonly successMessage: Locator; constructor(page: Page) { this.page = page; - this.table = page.locator('.el-table').or(page.locator('.exception-log-table')); - 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.exportButton = page.getByRole('button', { name: '导出' }).or(page.locator('button:has-text("导出")')); - this.refreshButton = page.getByRole('button', { name: '刷新' }).or(page.locator('button:has-text("刷新")')); - this.detailButton = page.getByRole('button', { name: '详情' }).or(page.locator('.detail-button')); - this.successMessage = page.locator('.el-message--success').or(page.locator('.success-message')); + this.table = page.locator('.ant-table').first(); + this.searchInput = page.getByPlaceholder('搜索路径/异常信息'); + this.refreshButton = page.getByRole('button', { name: '刷新' }); } async goto() { - try { - console.log('导航到异常日志页面...'); - await this.page.goto('/exceptionlog'); - - await this.page.waitForLoadState('networkidle'); - await this.table.waitFor({ state: 'visible', timeout: 10000 }); - await expect(this.page).toHaveURL(/.*exceptionlog/); - - console.log('异常日志页面加载完成'); - } catch (error) { - await this.page.screenshot({ path: `test-results/exception-log-error-${Date.now()}.png` }); - console.error('导航到异常日志页面失败:', error); - throw new Error(`导航到异常日志页面失败: ${error instanceof Error ? error.message : String(error)}`); - } + await this.page.goto('/exceptionlog'); + await this.page.waitForLoadState('networkidle'); + await this.table.waitFor({ state: 'visible', timeout: 15000 }).catch(() => {}); + await expect(this.page).toHaveURL(/.*exceptionlog/); } async search(keyword: string) { await this.searchInput.fill(keyword); - await this.searchButton.click(); - await this.page.waitForTimeout(1000); - } - - async clearSearch() { - await this.searchInput.fill(''); - await this.searchButton.click(); - await this.page.waitForTimeout(1000); - } - - async exportData() { - await this.exportButton.click(); - } - - async refresh() { - await this.refreshButton.click(); + await this.searchInput.press('Enter'); await this.page.waitForLoadState('networkidle'); } - async viewDetail(exceptionId: string) { - const exceptionRow = this.table.locator('tbody tr').filter({ hasText: exceptionId }); - await exceptionRow.locator('.detail-button').or(this.page.getByRole('button', { name: '详情' })).click(); - } - - async closeDetailDialog() { - await this.page.getByRole('button', { name: '关闭' }).or(this.page.locator('.el-dialog .close-button')).click(); - } - - async containsText(text: string): Promise { - return await this.table.getByText(text).count() > 0; + async clearSearch() { + await this.searchInput.clear(); + await this.searchInput.press('Enter'); + await this.page.waitForLoadState('networkidle'); } async getTableRowCount(): Promise { - return await this.table.locator('tbody tr').count(); + return this.table.locator('tbody tr').count(); } - async isSuccessMessageVisible(): Promise { - try { - return await this.successMessage.isVisible({ timeout: 3000 }); - } catch { - return false; - } + async containsText(text: string): Promise { + return this.table.getByText(text).count() > 0; } async reload() { - await this.page.reload(); - } - - async verifyTableContains(text: string): Promise { - const contains = await this.containsText(text); - if (!contains) { - throw new Error(`Table does not contain text: ${text}`); - } - } - - async getLogCount(): Promise { - return await this.table.locator('tbody tr').count(); + await this.refreshButton.click(); + await this.page.waitForLoadState('networkidle'); } } diff --git a/novalon-manage-web/e2e/pages/FileManagementPage.ts b/novalon-manage-web/e2e/pages/FileManagementPage.ts index c881c31..54477c7 100644 --- a/novalon-manage-web/e2e/pages/FileManagementPage.ts +++ b/novalon-manage-web/e2e/pages/FileManagementPage.ts @@ -1,106 +1,65 @@ -import { Page, expect } from '@playwright/test'; +import { Page, Locator, expect } from '@playwright/test'; export class FileManagementPage { readonly page: Page; - readonly uploadButton; - readonly fileInput; - readonly table; - readonly deleteButton; - readonly downloadButton; - readonly searchInput; + readonly uploadButton: Locator; + readonly refreshButton: Locator; + readonly table: Locator; + readonly successMessage: Locator; + readonly errorMessage: Locator; constructor(page: Page) { this.page = page; - this.uploadButton = page.locator('.el-upload--text').first(); - this.fileInput = page.locator('input[type="file"]'); - this.table = page.locator('.el-table'); - this.deleteButton = page.getByRole('button', { name: '删除' }); - this.downloadButton = page.getByRole('button', { name: '下载' }); - this.searchInput = page.locator('.search-bar .el-input__inner'); + this.uploadButton = page.getByRole('button', { name: '上传文件' }); + this.refreshButton = page.getByRole('button', { name: '刷新' }); + this.table = page.locator('.ant-table').first(); + this.successMessage = page.locator('.ant-message-success'); + this.errorMessage = page.locator('.ant-message-error'); } async goto() { - try { - console.log('导航到文件管理页面...'); - await this.page.goto('/files'); - - await this.page.waitForLoadState('networkidle'); - await this.table.waitFor({ state: 'visible', timeout: 10000 }); - await expect(this.page).toHaveURL(/.*files/); - - console.log('文件管理页面加载完成'); - } catch (error) { - await this.page.screenshot({ path: `test-results/file-management-error-${Date.now()}.png` }); - console.error('导航到文件管理页面失败:', error); - throw new Error(`导航到文件管理页面失败: ${error instanceof Error ? error.message : String(error)}`); - } + await this.page.goto('/files'); + await this.page.waitForLoadState('networkidle'); + await this.table.waitFor({ state: 'visible', timeout: 15000 }).catch(() => {}); + await expect(this.page).toHaveURL(/.*files/); } async uploadFile(filePath: string) { - await this.uploadButton.waitFor({ state: 'visible', timeout: 10000 }); await this.uploadButton.click(); - const fileInput = this.page.locator('input[type="file"]'); await fileInput.setInputFiles(filePath); - - await this.page.waitForTimeout(1000); + await this.page.waitForTimeout(2000); } async deleteFile(fileName: string) { - const row = this.table.locator('tr').filter({ hasText: fileName }).first(); - await row.locator('.el-button--danger').click(); + const row = this.table.locator('tbody tr').filter({ hasText: fileName }).first(); + await row.locator('.ant-btn').filter({ has: this.page.locator('.anticon-delete') }).click(); + await this.page.waitForTimeout(300); + await this.page.locator('.ant-popconfirm').getByRole('button', { name: /确\s*定/ }).click(); + await this.page.waitForTimeout(1000); + } - const confirmButton = this.page.getByRole('button', { name: '确定' }); - await confirmButton.click(); + async previewFile(fileName: string) { + const row = this.table.locator('tbody tr').filter({ hasText: fileName }).first(); + await row.getByRole('button', { name: '预览' }).click(); + await this.page.waitForTimeout(500); + } + + async closePreview() { + const modal = this.page.locator('.ant-modal').filter({ hasText: /预览/ }); + await modal.getByRole('button', { name: '关闭' }).click(); + } + + async getTableRowCount(): Promise { + return this.table.locator('tbody tr').count(); + } + + async containsText(text: string): Promise { + return this.table.getByText(text).count() > 0; + } + + async reload() { + await this.refreshButton.click(); await this.page.waitForLoadState('networkidle'); } - - async downloadFile(fileName: string) { - const row = this.table.locator('tr').filter({ hasText: fileName }).first(); - const downloadButton = row.locator('.el-button--primary').first(); - await downloadButton.click(); - } - - async searchFile(keyword: string) { - await this.searchInput.fill(keyword); - await this.page.waitForTimeout(500); - } - - async clearSearch() { - await this.searchInput.clear(); - await this.page.waitForTimeout(500); - } - - async verifyTableContains(text: string) { - await expect(this.table).toContainText(text); - } - - async verifyTableNotContains(text: string) { - await expect(this.table).not.toContainText(text); - } - - async getTableRowCount() { - const rows = await this.table.locator('.el-table__row').count(); - return rows; - } - - async clickUploadButton() { - await this.uploadButton.waitFor({ state: 'visible', timeout: 10000 }); - await this.uploadButton.click(); - } - - async submitUpload() { - const confirmButton = this.page.getByRole('button', { name: '确定' }).or(this.page.locator('.el-dialog .el-button--primary')); - await confirmButton.click(); - } - - async clickDeleteButton(rowNumber: number) { - const row = this.table.locator(`tbody tr:nth-child(${rowNumber})`); - await row.locator('.el-button--danger').click(); - } - - async clickDownloadButton(rowNumber: number) { - const row = this.table.locator(`tbody tr:nth-child(${rowNumber})`); - await row.locator('.el-button--primary').first().click(); - } -} \ No newline at end of file +} diff --git a/novalon-manage-web/e2e/pages/LoginLogPage.ts b/novalon-manage-web/e2e/pages/LoginLogPage.ts index 7d59476..566749a 100644 --- a/novalon-manage-web/e2e/pages/LoginLogPage.ts +++ b/novalon-manage-web/e2e/pages/LoginLogPage.ts @@ -1,63 +1,47 @@ -import { Page, expect } from '@playwright/test'; +import { Page, Locator, expect } from '@playwright/test'; export class LoginLogPage { readonly page: Page; - readonly searchInput; - readonly searchButton; - readonly table; - readonly exportButton; + readonly searchInput: Locator; + readonly refreshButton: Locator; + readonly table: Locator; constructor(page: Page) { this.page = page; - this.searchInput = page.getByPlaceholder('搜索用户名或IP地址'); - this.searchButton = page.getByRole('button', { name: '搜索' }); - this.table = page.locator('.el-table'); - this.exportButton = page.getByRole('button', { name: '导出' }); + this.searchInput = page.getByPlaceholder('搜索用户名/IP'); + this.refreshButton = page.getByRole('button', { name: '刷新' }); + this.table = page.locator('.ant-table').first(); } async goto() { - try { - console.log('导航到登录日志页面...'); - await this.page.goto('/loginlog'); - - await this.page.waitForLoadState('networkidle'); - await this.table.waitFor({ state: 'visible', timeout: 10000 }); - await expect(this.page).toHaveURL(/.*loginlog/); - - console.log('登录日志页面加载完成'); - } catch (error) { - await this.page.screenshot({ path: `test-results/login-log-error-${Date.now()}.png` }); - console.error('导航到登录日志页面失败:', error); - throw new Error(`导航到登录日志页面失败: ${error instanceof Error ? error.message : String(error)}`); - } + await this.page.goto('/loginlog'); + await this.page.waitForLoadState('networkidle'); + await this.table.waitFor({ state: 'visible', timeout: 15000 }).catch(() => {}); + await expect(this.page).toHaveURL(/.*loginlog/); } async searchByKeyword(keyword: string) { await this.searchInput.fill(keyword); - await this.searchButton.click(); + await this.searchInput.press('Enter'); await this.page.waitForLoadState('networkidle'); } async clearSearch() { await this.searchInput.clear(); - await this.searchButton.click(); + await this.searchInput.press('Enter'); await this.page.waitForLoadState('networkidle'); } - async verifyTableContains(text: string) { - await expect(this.table).toContainText(text); + async getTableRowCount(): Promise { + return this.table.locator('tbody tr').count(); } - async verifyTableNotContains(text: string) { - await expect(this.table).not.toContainText(text); + async containsText(text: string): Promise { + return this.table.getByText(text).count() > 0; } - async getTableRowCount() { - const rows = await this.table.locator('.el-table__row').count(); - return rows; + async reload() { + await this.refreshButton.click(); + await this.page.waitForLoadState('networkidle'); } - - async exportData() { - await this.exportButton.click(); - } -} \ No newline at end of file +} diff --git a/novalon-manage-web/e2e/pages/LoginPage.ts b/novalon-manage-web/e2e/pages/LoginPage.ts index dd1c863..62764eb 100644 --- a/novalon-manage-web/e2e/pages/LoginPage.ts +++ b/novalon-manage-web/e2e/pages/LoginPage.ts @@ -6,15 +6,15 @@ export class LoginPage { readonly passwordInput: Locator; readonly loginButton: Locator; readonly errorMessage: Locator; - readonly logoutButton: Locator; + readonly successMessage: Locator; constructor(page: Page) { this.page = page; - this.usernameInput = page.locator('input[placeholder="请输入用户名"]'); - this.passwordInput = page.locator('input[placeholder="请输入密码"]'); - this.loginButton = page.locator('button:has-text("登录")'); - this.errorMessage = page.locator('.el-message--error .el-message__content'); - this.logoutButton = page.getByRole('button', { name: '退出登录' }); + this.usernameInput = page.locator('input[placeholder="用户名"]'); + this.passwordInput = page.locator('input[placeholder="密码"]'); + this.loginButton = page.getByRole('button', { name: /登\s*录/ }); + this.errorMessage = page.locator('.ant-message-error .ant-message-notice-content'); + this.successMessage = page.locator('.ant-message-success .ant-message-notice-content'); } async goto() { @@ -22,87 +22,39 @@ export class LoginPage { await this.page.waitForLoadState('networkidle'); } - async login(username: string, password: string, maxRetries: number = 3) { - let lastError: Error | null = null; - - for (let attempt = 1; attempt <= maxRetries; attempt++) { - console.log(`Login attempt ${attempt}/${maxRetries}`); - - try { - await this.usernameInput.fill(username); - await this.passwordInput.fill(password); - console.log('Filled username and password'); - - await this.loginButton.click(); - console.log('Clicked login button'); + async login(username: string, password: string) { + await this.usernameInput.fill(username); + await this.passwordInput.fill(password); + await this.loginButton.click(); + await this.page.waitForURL(/\/(dashboard)/, { timeout: 30000 }); + await this.page.waitForLoadState('networkidle'); + } - await this.page.waitForURL(/\/(dashboard|\/)$/, { timeout: 30000 }); - console.log('Successfully navigated to dashboard or home'); - await this.page.waitForLoadState('networkidle'); - console.log('Network idle achieved'); - await this.page.waitForTimeout(2000); - console.log('Login completed successfully'); - return; - } catch (error) { - lastError = error as Error; - console.log(`Login attempt ${attempt} failed:`, error); - - const currentUrl = this.page.url(); - console.log('Current URL:', currentUrl); - - const errorMessage = await this.getErrorMessage(); - if (errorMessage) { - console.log('Login error message:', errorMessage); - } - - const token = await this.page.evaluate(() => localStorage.getItem('token')); - console.log('Token in localStorage:', token ? 'exists' : 'not found'); - - if (attempt < maxRetries) { - console.log(`Waiting 2 seconds before retry...`); - await this.page.waitForTimeout(2000); - - await this.goto(); - console.log('Navigated back to login page for retry'); - } - } - } - - console.log(`All ${maxRetries} login attempts failed`); - throw lastError || new Error('Login failed after all retries'); + async loginAndExpectError(username: string, password: string) { + await this.usernameInput.fill(username); + await this.passwordInput.fill(password); + await this.loginButton.click(); } async getErrorMessage(): Promise { try { - await this.page.waitForSelector('.el-message--error', { timeout: 10000 }); - await this.page.waitForTimeout(500); - const messageElement = await this.page.locator('.el-message--error .el-message__content').first(); - const text = await messageElement.textContent(); - return text; + await this.errorMessage.waitFor({ state: 'visible', timeout: 5000 }); + return await this.errorMessage.textContent(); } catch { - try { - await this.page.waitForSelector('.el-message', { timeout: 5000 }); - await this.page.waitForTimeout(500); - const messageElement = await this.page.locator('.el-message .el-message__content').first(); - const text = await messageElement.textContent(); - return text; - } catch { - return null; - } + return null; } } async logout() { - const avatar = this.page.locator('.el-avatar'); + const avatar = this.page.locator('.ant-avatar').first(); await avatar.click(); - await this.page.waitForTimeout(1000); - - const logoutButton = this.page.locator('.el-dropdown-menu').getByText('退出登录'); - await logoutButton.click(); + await this.page.waitForTimeout(500); + const logoutItem = this.page.locator('.ant-dropdown-menu-item:has-text("退出登录")'); + await logoutItem.click(); await this.page.waitForURL('**/login', { timeout: 10000 }); } async isLoggedIn(): Promise { - return this.page.url().includes('/dashboard') || this.page.url() === this.page.url().split('?')[0].split('#')[0]; + return this.page.url().includes('/dashboard'); } } diff --git a/novalon-manage-web/e2e/pages/MenuManagementPage.ts b/novalon-manage-web/e2e/pages/MenuManagementPage.ts index efbc043..5224a9a 100644 --- a/novalon-manage-web/e2e/pages/MenuManagementPage.ts +++ b/novalon-manage-web/e2e/pages/MenuManagementPage.ts @@ -4,44 +4,24 @@ export class MenuManagementPage { readonly page: Page; readonly table: Locator; readonly createMenuButton: Locator; - readonly searchInput: Locator; - readonly searchButton: Locator; + readonly refreshButton: Locator; readonly successMessage: Locator; - readonly treeContainer: Locator; - readonly expandAllButton: Locator; - readonly collapseAllButton: Locator; + readonly errorMessage: Locator; constructor(page: Page) { this.page = page; - this.table = page.locator('.el-table').or(page.locator('.menu-table')); - this.createMenuButton = 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.treeContainer = page.locator('.el-tree').or(page.locator('.menu-tree')); - this.expandAllButton = page.getByRole('button', { name: '展开全部' }).or(page.locator('button:has-text("展开全部")')); - this.collapseAllButton = page.getByRole('button', { name: '折叠全部' }).or(page.locator('button:has-text("折叠全部")')); + this.table = page.locator('.ant-table').first(); + this.createMenuButton = page.getByRole('button', { name: '新增菜单' }); + this.refreshButton = page.getByRole('button', { name: '刷新' }); + this.successMessage = page.locator('.ant-message-success'); + this.errorMessage = page.locator('.ant-message-error'); } async goto() { - try { - console.log('导航到菜单管理页面...'); - await this.page.goto('/menus'); - - await this.page.waitForLoadState('networkidle'); - - await this.page.waitForSelector('.el-tree', { timeout: 10000 }).catch(() => { - return this.page.waitForSelector('.el-table', { timeout: 5000 }); - }); - - await expect(this.page).toHaveURL(/.*menus/); - - console.log('菜单管理页面加载完成'); - } catch (error) { - await this.page.screenshot({ path: `test-results/menu-management-error-${Date.now()}.png` }); - console.error('导航到菜单管理页面失败:', error); - throw new Error(`导航到菜单管理页面失败: ${error instanceof Error ? error.message : String(error)}`); - } + await this.page.goto('/menus'); + await this.page.waitForLoadState('networkidle'); + await this.table.waitFor({ state: 'visible', timeout: 15000 }).catch(() => {}); + await expect(this.page).toHaveURL(/.*menus/); } async clickCreateMenu() { @@ -49,120 +29,110 @@ export class MenuManagementPage { await this.page.waitForTimeout(500); } + async clickAddChildMenu(parentMenuName: string) { + const row = this.table.locator('tbody tr').filter({ hasText: parentMenuName }); + await row.getByRole('button', { name: '新增子菜单' }).click(); + await this.page.waitForTimeout(500); + } + async fillMenuForm(menuData: { - menuName: string; - menuType?: string; + name: string; + type?: string; path?: string; + icon?: string; component?: string; permission?: string; sort?: number; - visible?: string; status?: string; + visible?: boolean; }) { - const dialog = this.page.locator('.el-dialog'); - - await dialog.locator('input').first().fill(menuData.menuName); - - if (menuData.menuType) { - const menuTypeSelect = dialog.locator('.el-select').first(); - await menuTypeSelect.click(); + const modal = this.page.locator('.ant-modal').filter({ hasText: /新增菜单|编辑菜单/ }); + + await modal.locator('.ant-form-item').filter({ hasText: '菜单名称' }).locator('input').fill(menuData.name); + + if (menuData.type) { + const typeSelect = modal.locator('.ant-form-item').filter({ hasText: '类型' }).locator('.ant-select'); + await typeSelect.click(); await this.page.waitForTimeout(300); - await this.page.getByRole('option', { name: menuData.menuType }).click(); + const typeMap: Record = { directory: '目录', menu: '菜单', button: '按钮' }; + await this.page.locator('.ant-select-dropdown').locator('.ant-select-item').filter({ hasText: typeMap[menuData.type] || menuData.type }).first().click(); } - + if (menuData.path) { - const pathInput = dialog.locator('input[placeholder*="路径"]'); - if (await pathInput.count() > 0) { - await pathInput.fill(menuData.path); - } + await modal.locator('.ant-form-item').filter({ hasText: '路径' }).locator('input').fill(menuData.path); + } + if (menuData.icon) { + await modal.locator('.ant-form-item').filter({ hasText: '图标' }).locator('input').fill(menuData.icon); } - if (menuData.component) { - const componentInput = dialog.locator('input[placeholder*="组件"]'); - if (await componentInput.count() > 0) { - await componentInput.fill(menuData.component); - } + await modal.locator('.ant-form-item').filter({ hasText: '组件路径' }).locator('input').fill(menuData.component); } - if (menuData.permission) { - const permissionInput = dialog.locator('input[placeholder*="权限"]'); - if (await permissionInput.count() > 0) { - await permissionInput.fill(menuData.permission); - } + await modal.locator('.ant-form-item').filter({ hasText: '权限标识' }).locator('input').fill(menuData.permission); } - if (menuData.sort !== undefined) { - const sortInput = dialog.locator('input[type="number"]'); - if (await sortInput.count() > 0) { - await sortInput.fill(String(menuData.sort)); - } + const sortInput = modal.locator('.ant-form-item').filter({ hasText: '排序' }).locator('.ant-input-number input'); + await sortInput.clear(); + await sortInput.fill(String(menuData.sort)); } - - if (menuData.visible) { - const visibleRadio = dialog.locator(`input[value="${menuData.visible}"]`); - if (await visibleRadio.count() > 0) { - await visibleRadio.check(); - } - } - if (menuData.status) { - const statusRadio = dialog.locator(`input[value="${menuData.status}"]`); - if (await statusRadio.count() > 0) { - await statusRadio.check(); - } + const statusSelect = modal.locator('.ant-form-item').filter({ hasText: '状态' }).locator('.ant-select'); + await statusSelect.click(); + await this.page.waitForTimeout(300); + const statusText = menuData.status === 'ACTIVE' ? '正常' : '停用'; + await this.page.locator('.ant-select-dropdown').locator('.ant-select-item').filter({ hasText: statusText }).first().click(); + } + if (menuData.visible !== undefined) { + const visibleSelect = modal.locator('.ant-form-item').filter({ hasText: '可见' }).locator('.ant-select'); + await visibleSelect.click(); + await this.page.waitForTimeout(300); + const visibleText = menuData.visible ? '显示' : '隐藏'; + await this.page.locator('.ant-select-dropdown').locator('.ant-select-item').filter({ hasText: visibleText }).first().click(); } } async submitForm() { - await this.page.getByRole('button', { name: '确定' }).or(this.page.locator('button:has-text("确定")')).click(); + const modal = this.page.locator('.ant-modal').filter({ hasText: /新增菜单|编辑菜单/ }); + await modal.getByRole('button', { name: /确\s*定/ }).click(); + await this.page.waitForTimeout(1000); + } + + async cancelForm() { + const modal = this.page.locator('.ant-modal').filter({ hasText: /新增菜单|编辑菜单/ }); + await modal.getByRole('button', { name: /取\s*消/ }).click(); } async editMenu(menuName: string) { - const menuRow = this.table.locator('tbody tr').filter({ hasText: menuName }); - await menuRow.getByRole('button', { name: '编辑' }).or(this.page.locator('.edit-button')).click(); + const row = this.table.locator('tbody tr').filter({ hasText: menuName }); + await row.locator('.ant-btn').filter({ has: this.page.locator('.anticon-edit') }).click(); + await this.page.waitForTimeout(500); } async deleteMenu(menuName: string) { - const menuRow = this.table.locator('tbody tr').filter({ hasText: menuName }); - await menuRow.getByRole('button', { name: '删除' }).or(this.page.locator('.delete-button')).click(); + const row = this.table.locator('tbody tr').filter({ hasText: menuName }); + await row.locator('.ant-btn').filter({ has: this.page.locator('.anticon-delete') }).click(); + await this.page.waitForTimeout(300); } async confirmDelete() { - await this.page.getByRole('button', { name: '确定' }).or(this.page.locator('.confirm-dialog .confirm-button')).click(); + await this.page.locator('.ant-popconfirm').getByRole('button', { name: /确\s*定/ }).click(); + await this.page.waitForTimeout(1000); } - async search(keyword: string) { - await this.searchInput.fill(keyword); - await this.searchButton.click(); - } - - async expandAll() { - await this.expandAllButton.click(); - await this.page.waitForTimeout(500); - } - - async collapseAll() { - await this.collapseAllButton.click(); - await this.page.waitForTimeout(500); + async cancelDelete() { + await this.page.locator('.ant-popconfirm').getByRole('button', { name: /取\s*消/ }).click(); } async containsText(text: string): Promise { - return await this.table.getByText(text).count() > 0; - } - - async isSuccessMessageVisible(): Promise { - try { - return await this.successMessage.isVisible({ timeout: 3000 }); - } catch { - return false; - } + return this.table.getByText(text).count() > 0; } async getMenuCount(): Promise { - return await this.table.locator('tbody tr').count(); + return this.table.locator('tbody tr').count(); } async reload() { - await this.page.reload(); + await this.refreshButton.click(); + await this.page.waitForLoadState('networkidle'); } } diff --git a/novalon-manage-web/e2e/pages/NotificationPage.ts b/novalon-manage-web/e2e/pages/NotificationPage.ts index 4996ece..076748f 100644 --- a/novalon-manage-web/e2e/pages/NotificationPage.ts +++ b/novalon-manage-web/e2e/pages/NotificationPage.ts @@ -1,88 +1,81 @@ -import { Page, expect } from '@playwright/test'; +import { Page, Locator, expect } from '@playwright/test'; export class NotificationPage { readonly page: Page; - readonly table; - readonly addButton; - readonly saveButton; - readonly cancelButton; - readonly dialog; - readonly titleInput; - readonly contentInput; - readonly noticeTypeSelect; - readonly statusSelect; + readonly table: Locator; + readonly addButton: Locator; + readonly refreshButton: Locator; + readonly successMessage: Locator; + readonly errorMessage: Locator; constructor(page: Page) { this.page = page; - this.table = page.locator('.el-table'); - this.addButton = page.getByRole('button', { name: '新增公告' }); - this.saveButton = page.getByRole('button', { name: '确定' }); - this.cancelButton = page.getByRole('button', { name: '取消' }); - this.dialog = page.locator('.el-dialog'); - this.titleInput = page.locator('.el-dialog').getByRole('textbox', { name: '公告标题' }); - this.contentInput = page.locator('.el-dialog').getByRole('textbox', { name: '公告内容' }); - this.noticeTypeSelect = page.locator('.el-dialog').getByRole('combobox', { name: '公告类型' }); - this.statusSelect = page.locator('.el-dialog').getByRole('combobox', { name: '状态' }); + this.table = page.locator('.ant-table').first(); + this.addButton = page.getByRole('button', { name: '新增通知' }); + this.refreshButton = page.getByRole('button', { name: '刷新' }); + this.successMessage = page.locator('.ant-message-success'); + this.errorMessage = page.locator('.ant-message-error'); } async goto() { - try { - console.log('导航到通知管理页面...'); - await this.page.goto('/notice'); - - await this.page.waitForLoadState('networkidle'); - await this.table.waitFor({ state: 'visible', timeout: 10000 }); - await expect(this.page).toHaveURL(/.*notice/); - - console.log('通知管理页面加载完成'); - } catch (error) { - await this.page.screenshot({ path: `test-results/notification-error-${Date.now()}.png` }); - console.error('导航到通知管理页面失败:', error); - throw new Error(`导航到通知管理页面失败: ${error instanceof Error ? error.message : String(error)}`); - } + await this.page.goto('/notice'); + await this.page.waitForLoadState('networkidle'); + await this.table.waitFor({ state: 'visible', timeout: 15000 }).catch(() => {}); + await expect(this.page).toHaveURL(/.*notice/); } - async addNotification(title: string, content: string) { + async addNotification(title: string, content: string, type?: string) { await this.addButton.click(); await this.page.waitForTimeout(500); - await this.titleInput.fill(title); - await this.contentInput.fill(content); + const modal = this.page.locator('.ant-modal').filter({ hasText: /新增通知|编辑通知/ }); + await modal.locator('.ant-form-item').filter({ hasText: '标题' }).locator('input').fill(title); - await this.saveButton.click(); - await this.page.waitForLoadState('networkidle'); + if (type) { + const typeSelect = modal.locator('.ant-form-item').filter({ hasText: '类型' }).locator('.ant-select'); + await typeSelect.click(); + await this.page.waitForTimeout(300); + await this.page.locator('.ant-select-dropdown').locator('.ant-select-item').filter({ hasText: type }).first().click(); + } + + await modal.locator('.ant-form-item').filter({ hasText: '内容' }).locator('textarea').fill(content); + + await modal.getByRole('button', { name: /确\s*定/ }).click(); + await this.page.waitForTimeout(1000); } async editNotification(title: string, newContent: string) { - const row = this.table.locator('tr').filter({ hasText: title }).first(); - const editBtn = row.getByRole('button', { name: '编辑' }); - await editBtn.click(); + const row = this.table.locator('tbody tr').filter({ hasText: title }).first(); + await row.locator('.ant-btn').filter({ has: this.page.locator('.anticon-edit') }).click(); await this.page.waitForTimeout(500); - await this.contentInput.clear(); - await this.contentInput.fill(newContent); + const modal = this.page.locator('.ant-modal').filter({ hasText: /新增通知|编辑通知/ }); + const contentTextarea = modal.locator('.ant-form-item').filter({ hasText: '内容' }).locator('textarea'); + await contentTextarea.clear(); + await contentTextarea.fill(newContent); - await this.saveButton.click(); - await this.page.waitForLoadState('networkidle'); + await modal.getByRole('button', { name: /确\s*定/ }).click(); + await this.page.waitForTimeout(1000); } async deleteNotification(title: string) { - const row = this.table.locator('tr').filter({ hasText: title }).first(); - const deleteBtn = row.getByRole('button', { name: '删除' }); - await deleteBtn.click(); - await this.page.waitForTimeout(500); + const row = this.table.locator('tbody tr').filter({ hasText: title }).first(); + await row.locator('.ant-btn').filter({ has: this.page.locator('.anticon-delete') }).click(); + await this.page.waitForTimeout(300); + await this.page.locator('.ant-popconfirm').getByRole('button', { name: /确\s*定/ }).click(); + await this.page.waitForTimeout(1000); + } - const confirmBtn = this.page.locator('.el-message-box').getByRole('button', { name: '确定' }); - await confirmBtn.click(); + async getTableRowCount(): Promise { + return this.table.locator('tbody tr').count(); + } + + async containsText(text: string): Promise { + return this.table.getByText(text).count() > 0; + } + + async reload() { + await this.refreshButton.click(); await this.page.waitForLoadState('networkidle'); } - - async getTableRowCount() { - const rows = await this.table.locator('.el-table__row').count(); - return rows; - } - - async verifyTableContains(text: string) { - await expect(this.table).toContainText(text); - } } diff --git a/novalon-manage-web/e2e/pages/OperationLogPage.ts b/novalon-manage-web/e2e/pages/OperationLogPage.ts index 1fc350f..f8f9716 100644 --- a/novalon-manage-web/e2e/pages/OperationLogPage.ts +++ b/novalon-manage-web/e2e/pages/OperationLogPage.ts @@ -1,63 +1,47 @@ -import { Page, expect } from '@playwright/test'; +import { Page, Locator, expect } from '@playwright/test'; export class OperationLogPage { readonly page: Page; - readonly searchInput; - readonly searchButton; - readonly table; - readonly exportButton; + readonly searchInput: Locator; + readonly refreshButton: Locator; + readonly table: Locator; constructor(page: Page) { this.page = page; - this.searchInput = page.getByPlaceholder('搜索操作人或操作模块'); - this.searchButton = page.getByRole('button', { name: '搜索' }); - this.table = page.locator('.el-table'); - this.exportButton = page.getByRole('button', { name: '导出' }); + this.searchInput = page.getByPlaceholder('搜索操作人/描述'); + this.refreshButton = page.getByRole('button', { name: '刷新' }); + this.table = page.locator('.ant-table').first(); } async goto() { - try { - console.log('导航到操作日志页面...'); - await this.page.goto('/oplog'); - - await this.page.waitForLoadState('networkidle'); - await this.table.waitFor({ state: 'visible', timeout: 10000 }); - await expect(this.page).toHaveURL(/.*oplog/); - - console.log('操作日志页面加载完成'); - } catch (error) { - await this.page.screenshot({ path: `test-results/operation-log-error-${Date.now()}.png` }); - console.error('导航到操作日志页面失败:', error); - throw new Error(`导航到操作日志页面失败: ${error instanceof Error ? error.message : String(error)}`); - } + await this.page.goto('/oplog'); + await this.page.waitForLoadState('networkidle'); + await this.table.waitFor({ state: 'visible', timeout: 15000 }).catch(() => {}); + await expect(this.page).toHaveURL(/.*oplog/); } async searchByKeyword(keyword: string) { await this.searchInput.fill(keyword); - await this.searchButton.click(); + await this.searchInput.press('Enter'); await this.page.waitForLoadState('networkidle'); } async clearSearch() { await this.searchInput.clear(); - await this.searchButton.click(); + await this.searchInput.press('Enter'); await this.page.waitForLoadState('networkidle'); } - async verifyTableContains(text: string) { - await expect(this.table).toContainText(text); + async getTableRowCount(): Promise { + return this.table.locator('tbody tr').count(); } - async verifyTableNotContains(text: string) { - await expect(this.table).not.toContainText(text); + async containsText(text: string): Promise { + return this.table.getByText(text).count() > 0; } - async getTableRowCount() { - const rows = await this.table.locator('.el-table__row').count(); - return rows; + async reload() { + await this.refreshButton.click(); + await this.page.waitForLoadState('networkidle'); } - - async exportData() { - await this.exportButton.click(); - } -} \ No newline at end of file +} diff --git a/novalon-manage-web/e2e/pages/RoleManagementPage.ts b/novalon-manage-web/e2e/pages/RoleManagementPage.ts index afc50c9..2e9aa0f 100644 --- a/novalon-manage-web/e2e/pages/RoleManagementPage.ts +++ b/novalon-manage-web/e2e/pages/RoleManagementPage.ts @@ -4,68 +4,34 @@ export class RoleManagementPage { readonly page: Page; readonly table: Locator; readonly createRoleButton: Locator; + readonly refreshButton: 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; - readonly searchInput: Locator; - readonly searchButton: Locator; + readonly errorMessage: Locator; readonly pagination: Locator; - readonly nextPageButton: Locator; - readonly prevPageButton: Locator; constructor(page: Page) { this.page = page; - this.table = page.locator('.el-table').first(); - 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')); - 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.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')); + this.table = page.locator('.ant-table').first(); + this.createRoleButton = page.getByRole('button', { name: '新增角色' }); + this.refreshButton = page.getByRole('button', { name: '刷新' }); + this.successMessage = page.locator('.ant-message-success'); + this.errorMessage = page.locator('.ant-message-error'); + this.pagination = page.locator('.ant-pagination'); } async goto() { - try { - console.log('导航到角色管理页面...'); - await this.page.goto('/roles'); - - await this.page.waitForLoadState('networkidle'); - await this.table.waitFor({ state: 'visible', timeout: 10000 }); - await expect(this.page).toHaveURL(/.*roles/); - - console.log('角色管理页面加载完成'); - } catch (error) { - await this.page.screenshot({ path: `test-results/role-management-error-${Date.now()}.png` }); - console.error('导航到角色管理页面失败:', error); - throw new Error(`导航到角色管理页面失败: ${error instanceof Error ? error.message : String(error)}`); - } + await this.page.goto('/roles'); + await this.page.waitForLoadState('networkidle'); + await this.table.waitFor({ state: 'visible', timeout: 15000 }).catch(() => {}); + await expect(this.page).toHaveURL(/.*roles/); } async waitForTableReady() { await this.table.waitFor({ state: 'visible', timeout: 10000 }); - await this.page.waitForFunction( - () => { - const rows = document.querySelectorAll('.el-table__body-wrapper tbody tr'); - return rows.length > 0; - }, + () => document.querySelectorAll('.ant-table-tbody > tr').length > 0, { timeout: 5000 } - ).catch(() => { - console.log('表格没有数据,继续执行'); - }); + ).catch(() => {}); } async clickCreateRole() { @@ -76,176 +42,94 @@ export class RoleManagementPage { async fillRoleForm(roleData: { roleName: string; roleKey: string; - roleSort?: string; + roleSort?: number; status?: string; - remark?: string; }) { - await this.page.locator('.el-dialog').locator('input').first().fill(roleData.roleName); - await this.page.locator('.el-dialog').locator('input').nth(1).fill(roleData.roleKey); - - if (roleData.roleSort) { - const sortInput = this.page.locator('.el-dialog').locator('.el-input-number'); - if (await sortInput.count() > 0) { - const input = sortInput.locator('input'); - await input.fill(roleData.roleSort); - } + const modal = this.page.locator('.ant-modal').filter({ hasText: /新增角色|编辑角色/ }); + + await modal.locator('.ant-form-item').filter({ hasText: '角色名称' }).locator('input').fill(roleData.roleName); + await modal.locator('.ant-form-item').filter({ hasText: '角色标识' }).locator('input').fill(roleData.roleKey); + + if (roleData.roleSort !== undefined) { + const sortInput = modal.locator('.ant-form-item').filter({ hasText: '排序' }).locator('.ant-input-number input'); + await sortInput.clear(); + await sortInput.fill(String(roleData.roleSort)); } - + if (roleData.status) { - const statusSelect = this.page.locator('.el-dialog').locator('.el-form-item').filter({ hasText: '状态' }).locator('.el-select'); - if (await statusSelect.count() > 0) { - await statusSelect.click(); - await this.page.waitForTimeout(500); - - const statusText = roleData.status === 'ACTIVE' ? '正常' : '禁用'; - const dropdown = this.page.locator('.el-select-dropdown'); - if (await dropdown.count() > 0) { - const options = dropdown.locator('.el-select-dropdown__item'); - const optionCount = await options.count(); - - for (let i = 0; i < optionCount; i++) { - const optionText = await options.nth(i).textContent(); - if (optionText && optionText.includes(statusText)) { - await options.nth(i).click(); - break; - } - } - } - - await this.page.waitForTimeout(300); - } + const statusSelect = modal.locator('.ant-form-item').filter({ hasText: '状态' }).locator('.ant-select'); + await statusSelect.click(); + await this.page.waitForTimeout(300); + const statusText = roleData.status === 'ACTIVE' ? '正常' : '禁用'; + await this.page.locator('.ant-select-dropdown').locator('.ant-select-item').filter({ hasText: statusText }).first().click(); } - - if (roleData.remark) { - await this.page.locator('.el-dialog').locator('textarea').fill(roleData.remark); + } + + async selectPermissions(permissionLabels: string[]) { + const modal = this.page.locator('.ant-modal').filter({ hasText: /新增角色|编辑角色/ }); + const treeSelect = modal.locator('.ant-form-item').filter({ hasText: '权限' }).locator('.ant-tree-select'); + await treeSelect.click(); + await this.page.waitForTimeout(300); + + for (const label of permissionLabels) { + const checkbox = this.page.locator('.ant-tree-select-dropdown .ant-tree-treenode').filter({ hasText: label }).locator('.ant-tree-checkbox'); + if (await checkbox.count() > 0 && !(await checkbox.isChecked())) { + await checkbox.click(); + } } } async submitForm() { - const dialog = this.page.locator('.el-dialog'); - const submitButton = dialog.getByRole('button', { name: '确定' }).or(dialog.locator('button:has-text("确定")')); - - await submitButton.click(); - + const modal = this.page.locator('.ant-modal').filter({ hasText: /新增角色|编辑角色/ }); + await modal.getByRole('button', { name: /确\s*定/ }).click(); await this.page.waitForTimeout(1000); } - async waitForSuccessMessage(timeout: number = 10000): Promise { - try { - const message = this.page.locator('.el-message--success').or(this.page.locator('.el-message')); - await message.waitFor({ state: 'visible', timeout }); - return true; - } catch (error) { - console.log('等待成功消息超时,检查是否有错误消息'); - - try { - const errorMessage = this.page.locator('.el-message--error').or(this.page.locator('.el-message--warning')); - if (await errorMessage.count() > 0) { - const errorText = await errorMessage.first().textContent(); - console.log('发现错误消息:', errorText); - } - } catch (e) { - console.log('没有发现错误消息'); - } - - return false; - } + async cancelForm() { + const modal = this.page.locator('.ant-modal').filter({ hasText: /新增角色|编辑角色/ }); + await modal.getByRole('button', { name: /取\s*消/ }).click(); } async editRole(rowNumber: number) { - await this.table.locator(`tbody tr:nth-child(${rowNumber})`).getByRole('button', { name: '编辑' }).or(this.page.locator(`tbody tr:nth-child(${rowNumber}) .edit-button`)).click(); + const row = this.table.locator('tbody tr').nth(rowNumber - 1); + await row.locator('.ant-btn').filter({ has: this.page.locator('.anticon-edit') }).click(); + await this.page.waitForTimeout(500); } async deleteRole(rowNumber: number) { - await this.table.locator(`tbody tr:nth-child(${rowNumber})`).getByRole('button', { name: '删除' }).or(this.page.locator(`tbody tr:nth-child(${rowNumber}) .delete-button`)).click(); + const row = this.table.locator('tbody tr').nth(rowNumber - 1); + await row.locator('.ant-btn').filter({ has: this.page.locator('.anticon-delete') }).click(); + await this.page.waitForTimeout(300); } async confirmDelete() { - await this.page.getByRole('button', { name: '确定' }).or(this.page.locator('.confirm-dialog .confirm-button')).click(); + await this.page.locator('.ant-popconfirm').getByRole('button', { name: /确\s*定/ }).click(); + await this.page.waitForTimeout(1000); } - async openPermissionDialog(rowNumber: number) { - await this.table.locator(`tbody tr:nth-child(${rowNumber})`).getByRole('button', { name: '权限' }).or(this.page.locator(`tbody tr:nth-child(${rowNumber}) .permission-button`)).click(); + async cancelDelete() { + await this.page.locator('.ant-popconfirm').getByRole('button', { name: /取\s*消/ }).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 { - return await this.table.getByText(text).count() > 0; - } - - async isSuccessMessageVisible(): Promise { + async waitForSuccessMessage(timeout = 10000): Promise { try { - return await this.successMessage.isVisible({ timeout: 3000 }); + await this.successMessage.waitFor({ state: 'visible', timeout }); + return true; } catch { return false; } } + async getRoleCount(): Promise { + return this.table.locator('tbody tr').count(); + } + + async containsText(text: string): Promise { + return this.table.getByText(text).count() > 0; + } + async reload() { - await this.page.reload(); - } - - async getRoleName(rowNumber: number): Promise { - return await this.table.locator(`tbody tr:nth-child(${rowNumber}) td:first-child`).textContent(); - } - - async clickPermissionButton(rowNumber: number) { - await this.table.locator(`tbody tr:nth-child(${rowNumber})`).getByRole('button', { name: '权限' }).or(this.page.locator(`tbody tr:nth-child(${rowNumber}) .permission-button`)).click(); - } - - async deselectPermission(permissionValue: string) { - const checkbox = this.page.locator(`input[type="checkbox"][value="${permissionValue}"]`); - if (await checkbox.isChecked()) { - await checkbox.click(); - } - } - - async search(keyword: string) { - await this.searchInput.fill(keyword); - await this.searchButton.click(); - } - - async clearSearch() { - await this.searchInput.fill(''); - await this.searchButton.click(); - } - - async clickStatusButton(rowNumber: number) { - const row = this.table.locator(`tbody tr:nth-child(${rowNumber})`); - await row.locator('.el-button--text').filter({ hasText: /状态|启用|禁用/ }).first().click(); - } - - async getCurrentPage(): Promise { - try { - const activePage = this.page.locator('.el-pager li.is-active'); - if (await activePage.count() > 0) { - return await activePage.textContent() || '1'; - } - - const currentPage = this.page.locator('.el-pagination__current'); - if (await currentPage.count() > 0) { - return await currentPage.textContent() || '1'; - } - - return '1'; - } catch (error) { - console.log('获取当前页码失败,返回默认值'); - return '1'; - } - } - - async nextPage() { - await this.nextPageButton.click(); - } - - async prevPage() { - await this.prevPageButton.click(); + await this.refreshButton.click(); + await this.page.waitForLoadState('networkidle'); } } diff --git a/novalon-manage-web/e2e/pages/SystemConfigPage.ts b/novalon-manage-web/e2e/pages/SystemConfigPage.ts index 18dfb1a..8e32b4e 100644 --- a/novalon-manage-web/e2e/pages/SystemConfigPage.ts +++ b/novalon-manage-web/e2e/pages/SystemConfigPage.ts @@ -1,87 +1,81 @@ -import { Page, expect } from '@playwright/test'; +import { Page, Locator, expect } from '@playwright/test'; export class SystemConfigPage { readonly page: Page; - readonly table; - readonly addButton; - readonly saveButton; - readonly cancelButton; - readonly dialog; - readonly configNameInput; - readonly configKeyInput; - readonly configValueInput; + readonly table: Locator; + readonly addButton: Locator; + readonly refreshButton: Locator; + readonly successMessage: Locator; + readonly errorMessage: Locator; constructor(page: Page) { this.page = page; - this.table = page.locator('.el-table'); + this.table = page.locator('.ant-table').first(); this.addButton = page.getByRole('button', { name: '新增配置' }); - this.saveButton = page.getByRole('button', { name: '确定' }); - this.cancelButton = page.getByRole('button', { name: '取消' }); - this.dialog = page.locator('.el-dialog'); - this.configNameInput = page.locator('.el-dialog').getByRole('textbox', { name: '参数名称' }); - this.configKeyInput = page.locator('.el-dialog').getByRole('textbox', { name: '参数键名' }); - this.configValueInput = page.locator('.el-dialog').getByRole('textbox', { name: '参数值' }); + this.refreshButton = page.getByRole('button', { name: '刷新' }); + this.successMessage = page.locator('.ant-message-success'); + this.errorMessage = page.locator('.ant-message-error'); } async goto() { - try { - console.log('导航到系统配置页面...'); - await this.page.goto('/sys/config'); - - await this.page.waitForLoadState('networkidle'); - await this.table.waitFor({ state: 'visible', timeout: 10000 }); - await expect(this.page).toHaveURL(/.*config/); - - console.log('系统配置页面加载完成'); - } catch (error) { - await this.page.screenshot({ path: `test-results/system-config-error-${Date.now()}.png` }); - console.error('导航到系统配置页面失败:', error); - throw new Error(`导航到系统配置页面失败: ${error instanceof Error ? error.message : String(error)}`); - } + await this.page.goto('/sys/config'); + await this.page.waitForLoadState('networkidle'); + await this.table.waitFor({ state: 'visible', timeout: 15000 }).catch(() => {}); + await expect(this.page).toHaveURL(/.*config/); } - async addConfig(configName: string, configKey: string, configValue: string) { + async addConfig(configName: string, configKey: string, configValue: string, configType?: string, remark?: string) { await this.addButton.click(); await this.page.waitForTimeout(500); - - await this.configNameInput.fill(configName); - await this.configKeyInput.fill(configKey); - await this.configValueInput.fill(configValue); - - await this.saveButton.click(); - await this.page.waitForLoadState('networkidle'); + + const modal = this.page.locator('.ant-modal').filter({ hasText: /新增配置|编辑配置/ }); + await modal.locator('.ant-form-item').filter({ hasText: '配置名称' }).locator('input').fill(configName); + await modal.locator('.ant-form-item').filter({ hasText: '配置键' }).locator('input').fill(configKey); + await modal.locator('.ant-form-item').filter({ hasText: '配置值' }).locator('input').fill(configValue); + + if (configType) { + await modal.locator('.ant-form-item').filter({ hasText: '类型' }).locator('input').fill(configType); + } + if (remark) { + await modal.locator('.ant-form-item').filter({ hasText: '备注' }).locator('textarea').fill(remark); + } + + await modal.getByRole('button', { name: /确\s*定/ }).click(); + await this.page.waitForTimeout(1000); } async editConfig(configKey: string, newValue: string) { - const row = this.table.locator('tr').filter({ hasText: configKey }).first(); - const editBtn = row.getByRole('button', { name: '编辑' }); - await editBtn.click(); + const row = this.table.locator('tbody tr').filter({ hasText: configKey }).first(); + await row.locator('.ant-btn').filter({ has: this.page.locator('.anticon-edit') }).click(); await this.page.waitForTimeout(500); - - await this.configValueInput.clear(); - await this.configValueInput.fill(newValue); - - await this.saveButton.click(); - await this.page.waitForLoadState('networkidle'); + + const modal = this.page.locator('.ant-modal').filter({ hasText: /新增配置|编辑配置/ }); + const valueInput = modal.locator('.ant-form-item').filter({ hasText: '配置值' }).locator('input'); + await valueInput.clear(); + await valueInput.fill(newValue); + + await modal.getByRole('button', { name: /确\s*定/ }).click(); + await this.page.waitForTimeout(1000); } async deleteConfig(configKey: string) { - const row = this.table.locator('tr').filter({ hasText: configKey }).first(); - const deleteBtn = row.getByRole('button', { name: '删除' }); - await deleteBtn.click(); - await this.page.waitForTimeout(500); - - const confirmBtn = this.page.locator('.el-message-box').getByRole('button', { name: '确定' }); - await confirmBtn.click(); + const row = this.table.locator('tbody tr').filter({ hasText: configKey }).first(); + await row.locator('.ant-btn').filter({ has: this.page.locator('.anticon-delete') }).click(); + await this.page.waitForTimeout(300); + await this.page.locator('.ant-popconfirm').getByRole('button', { name: /确\s*定/ }).click(); + await this.page.waitForTimeout(1000); + } + + async getTableRowCount(): Promise { + return this.table.locator('tbody tr').count(); + } + + async containsText(text: string): Promise { + return this.table.getByText(text).count() > 0; + } + + async reload() { + await this.refreshButton.click(); await this.page.waitForLoadState('networkidle'); } - - async getTableRowCount() { - const rows = await this.table.locator('.el-table__row').count(); - return rows; - } - - async verifyTableContains(text: string) { - await expect(this.table).toContainText(text); - } } diff --git a/novalon-manage-web/e2e/pages/UserManagementPage.ts b/novalon-manage-web/e2e/pages/UserManagementPage.ts index a83d18d..9ec08a0 100644 --- a/novalon-manage-web/e2e/pages/UserManagementPage.ts +++ b/novalon-manage-web/e2e/pages/UserManagementPage.ts @@ -4,58 +4,34 @@ export class UserManagementPage { readonly page: Page; readonly table: Locator; readonly createUserButton: Locator; - readonly searchInput: Locator; - readonly searchButton: Locator; + readonly refreshButton: Locator; readonly successMessage: Locator; + readonly errorMessage: Locator; readonly pagination: Locator; - readonly nextPageButton: Locator; - readonly prevPageButton: Locator; constructor(page: Page) { this.page = page; - this.table = page.locator('.el-table').first(); - 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')); + this.table = page.locator('.ant-table').first(); + this.createUserButton = page.getByRole('button', { name: '新增用户' }); + this.refreshButton = page.getByRole('button', { name: '刷新' }); + this.successMessage = page.locator('.ant-message-success'); + this.errorMessage = page.locator('.ant-message-error'); + this.pagination = page.locator('.ant-pagination'); } async goto() { - try { - console.log('导航到用户管理页面...'); - await this.page.goto('/users'); - - await this.page.waitForLoadState('networkidle'); - - await this.table.waitFor({ state: 'visible', timeout: 10000 }); - - await expect(this.page).toHaveURL(/.*users/); - - console.log('用户管理页面加载完成'); - } catch (error) { - await this.page.screenshot({ path: `test-results/user-management-error-${Date.now()}.png` }); - - console.error('导航到用户管理页面失败:', error); - - throw new Error(`导航到用户管理页面失败: ${error instanceof Error ? error.message : String(error)}`); - } + await this.page.goto('/users'); + await this.page.waitForLoadState('networkidle'); + await this.table.waitFor({ state: 'visible', timeout: 15000 }).catch(() => {}); + await expect(this.page).toHaveURL(/.*users/); } async waitForTableReady() { await this.table.waitFor({ state: 'visible', timeout: 10000 }); - await this.page.waitForFunction( - () => { - const rows = document.querySelectorAll('.el-table__body-wrapper tbody tr'); - return rows.length > 0; - }, + () => document.querySelectorAll('.ant-table-tbody > tr').length > 0, { timeout: 5000 } - ).catch(() => { - console.log('表格没有数据,继续执行'); - }); + ).catch(() => {}); } async clickCreateUser() { @@ -64,233 +40,97 @@ export class UserManagementPage { } async fillUserForm(userData: { - username: string; + username?: string; + password?: string; nickname?: string; - email: string; + email?: string; phone?: string; - password: string; - confirmPassword?: string; status?: string; }) { - const dialog = this.page.locator('.el-dialog'); - const isCreateMode = !userData.hasOwnProperty('id'); - - // 表单字段顺序: - // 创建模式:用户名(0), 密码(1), 昵称(2), 邮箱(3), 手机号(4) - // 编辑模式:用户名(0), 昵称(1), 邮箱(2), 手机号(3) - - await dialog.locator('input').first().fill(userData.username); - - if (isCreateMode && userData.password) { - await dialog.locator('input[type="password"]').fill(userData.password); + const modal = this.page.locator('.ant-modal').filter({ hasText: /新增用户|编辑用户/ }); + + if (userData.username) { + await modal.locator('.ant-form-item').filter({ hasText: '用户名' }).locator('input').fill(userData.username); + } + if (userData.password) { + await modal.locator('.ant-form-item').filter({ hasText: '密码' }).locator('input[type="password"]').fill(userData.password); } - if (userData.nickname) { - const nicknameIndex = isCreateMode ? 2 : 1; - await dialog.locator('input').nth(nicknameIndex).fill(userData.nickname); + await modal.locator('.ant-form-item').filter({ hasText: '昵称' }).locator('input').fill(userData.nickname); } - if (userData.email) { - const emailIndex = isCreateMode ? 3 : 2; - await dialog.locator('input').nth(emailIndex).fill(userData.email); + await modal.locator('.ant-form-item').filter({ hasText: '邮箱' }).locator('input').fill(userData.email); } - if (userData.phone) { - const phoneIndex = isCreateMode ? 4 : 3; - await dialog.locator('input').nth(phoneIndex).fill(userData.phone); + await modal.locator('.ant-form-item').filter({ hasText: '手机' }).locator('input').fill(userData.phone); } - if (userData.status) { - const statusSelect = dialog.locator('.el-form-item').filter({ hasText: '状态' }).locator('.el-select'); - if (await statusSelect.count() > 0) { - await statusSelect.click(); - await this.page.waitForTimeout(500); - - const statusText = userData.status === '1' || userData.status === 'ACTIVE' ? '正常' : '禁用'; - const dropdown = this.page.locator('.el-select-dropdown'); - if (await dropdown.count() > 0) { - const options = dropdown.locator('.el-select-dropdown__item'); - const optionCount = await options.count(); - - for (let i = 0; i < optionCount; i++) { - const optionText = await options.nth(i).textContent(); - if (optionText && optionText.includes(statusText)) { - await options.nth(i).click(); - break; - } - } - } - + const statusFormItem = modal.locator('.ant-form-item').filter({ hasText: '状态' }).locator('.ant-select'); + if (await statusFormItem.count() > 0) { + await statusFormItem.click(); await this.page.waitForTimeout(300); + const statusText = userData.status === 'ACTIVE' ? '正常' : '禁用'; + await this.page.locator('.ant-select-dropdown').locator('.ant-select-item').filter({ hasText: statusText }).first().click(); } } } async submitForm() { - const dialog = this.page.locator('.el-dialog'); - const submitButton = dialog.getByRole('button', { name: '确定' }).or(dialog.locator('button:has-text("确定")')); - - await submitButton.click(); - + const modal = this.page.locator('.ant-modal').filter({ hasText: /新增用户|编辑用户/ }); + await modal.getByRole('button', { name: /确\s*定/ }).click(); await this.page.waitForTimeout(1000); } - async waitForSuccessMessage(timeout: number = 10000): Promise { - try { - const message = this.page.locator('.el-message--success').or(this.page.locator('.el-message')); - await message.waitFor({ state: 'visible', timeout }); - return true; - } catch (error) { - console.log('等待成功消息超时,检查是否有错误消息'); - - try { - const errorMessage = this.page.locator('.el-message--error').or(this.page.locator('.el-message--warning')); - if (await errorMessage.count() > 0) { - const errorText = await errorMessage.first().textContent(); - console.log('发现错误消息:', errorText); - } - } catch (e) { - console.log('没有发现错误消息'); - } - - return false; - } + async cancelForm() { + const modal = this.page.locator('.ant-modal').filter({ hasText: /新增用户|编辑用户/ }); + await modal.getByRole('button', { name: /取\s*消/ }).click(); } async editUser(rowNumber: number) { - await this.table.locator(`tbody tr:nth-child(${rowNumber})`).getByRole('button', { name: '编辑' }).or(this.page.locator(`tbody tr:nth-child(${rowNumber}) .edit-button`)).click(); + const row = this.table.locator('tbody tr').nth(rowNumber - 1); + await row.locator('.ant-btn').filter({ has: this.page.locator('.anticon-edit') }).click(); + await this.page.waitForTimeout(500); } async deleteUser(rowNumber: number) { - await this.table.locator(`tbody tr:nth-child(${rowNumber})`).getByRole('button', { name: '删除' }).or(this.page.locator(`tbody tr:nth-child(${rowNumber}) .delete-button`)).click(); + const row = this.table.locator('tbody tr').nth(rowNumber - 1); + await row.locator('.ant-btn').filter({ has: this.page.locator('.anticon-delete') }).click(); + await this.page.waitForTimeout(300); } async confirmDelete() { - await this.page.getByRole('button', { name: '确定' }).or(this.page.locator('.confirm-dialog .confirm-button')).click(); + await this.page.locator('.ant-popconfirm').getByRole('button', { name: /确\s*定/ }).click(); + await this.page.waitForTimeout(1000); } - async search(keyword: string) { - await this.searchInput.fill(keyword); - await this.searchButton.click(); + async cancelDelete() { + await this.page.locator('.ant-popconfirm').getByRole('button', { name: /取\s*消/ }).click(); } - async nextPage() { - await this.nextPageButton.click(); - } - - async prevPage() { - await this.prevPageButton.click(); - } - - async getCurrentPage(): Promise { + async waitForSuccessMessage(timeout = 10000): Promise { try { - const activePage = this.page.locator('.el-pager li.is-active'); - if (await activePage.count() > 0) { - return await activePage.textContent() || '1'; - } - - const currentPage = this.page.locator('.el-pagination__current'); - if (await currentPage.count() > 0) { - return await currentPage.textContent() || '1'; - } - - return '1'; - } catch (error) { - console.log('获取当前页码失败,返回默认值'); - return '1'; - } - } - - async getUserCount(): Promise { - return await this.table.locator('tbody tr').count(); - } - - async getUserName(rowNumber: number): Promise { - return await this.table.locator(`tbody tr:nth-child(${rowNumber}) td:first-child`).textContent(); - } - - async containsText(text: string): Promise { - return await this.table.getByText(text).count() > 0; - } - - async isSuccessMessageVisible(): Promise { - try { - return await this.successMessage.isVisible({ timeout: 3000 }); + await this.successMessage.waitFor({ state: 'visible', timeout }); + return true; } catch { return false; } } + async getUserCount(): Promise { + return this.table.locator('tbody tr').count(); + } + + async getTableCellText(rowNumber: number, colIndex: number): Promise { + const row = this.table.locator('tbody tr').nth(rowNumber - 1); + return row.locator('td').nth(colIndex).textContent(); + } + + async containsText(text: string): Promise { + return this.table.getByText(text).count() > 0; + } + async reload() { - await this.page.reload(); - } - - async clickStatusButton(rowNumber: number) { - const row = this.table.locator(`tbody tr:nth-child(${rowNumber})`); - await row.locator('.el-tag').first().click(); - await this.page.waitForTimeout(500); - - const dropdown = this.page.locator('.el-dropdown'); - if (await dropdown.count() > 0) { - const options = dropdown.locator('.el-dropdown-menu__item'); - const optionCount = await options.count(); - - for (let i = 0; i < optionCount; i++) { - const optionText = await options.nth(i).textContent(); - if (optionText && (optionText.includes('启用') || optionText.includes('禁用'))) { - await options.nth(i).click(); - break; - } - } - } - - await this.page.waitForTimeout(300); - } - - async clickEditButton(rowNumber: number) { - await this.table.locator(`tbody tr:nth-child(${rowNumber})`).getByRole('button', { name: '编辑' }).or(this.page.locator(`tbody tr:nth-child(${rowNumber}) .edit-button`)).click(); - } - - async clickDeleteButton(rowNumber: number) { - await this.table.locator(`tbody tr:nth-child(${rowNumber})`).getByRole('button', { name: '删除' }).or(this.page.locator(`tbody tr:nth-child(${rowNumber}) .delete-button`)).click(); - } - - async fillNickname(nickname: string) { - const dialog = this.page.locator('.el-dialog'); - await dialog.locator('input').nth(1).fill(nickname); - } - - async selectRole(roleName: string) { - const dialog = this.page.locator('.el-dialog'); - const roleSelect = dialog.locator('.el-select'); - if (await roleSelect.count() > 0) { - await roleSelect.first().click(); - await this.page.waitForTimeout(500); - - const dropdown = this.page.locator('.el-select-dropdown'); - if (await dropdown.count() > 0) { - const options = dropdown.locator('.el-select-dropdown__item'); - const optionCount = await options.count(); - - for (let i = 0; i < optionCount; i++) { - const optionText = await options.nth(i).textContent(); - if (optionText && optionText.includes(roleName)) { - await options.nth(i).click(); - break; - } - } - } - - await this.page.waitForTimeout(300); - } - } - - async clearSearch() { - await this.searchInput.fill(''); - await this.searchButton.click(); - } - - async getTableRowCount(): Promise { - return await this.table.locator('tbody tr').count(); + await this.refreshButton.click(); + await this.page.waitForLoadState('networkidle'); } } diff --git a/novalon-manage-web/e2e/smoke/login-logout.spec.ts b/novalon-manage-web/e2e/smoke/login-logout.spec.ts index 0cd0088..b2f68ce 100644 --- a/novalon-manage-web/e2e/smoke/login-logout.spec.ts +++ b/novalon-manage-web/e2e/smoke/login-logout.spec.ts @@ -22,7 +22,7 @@ test.describe('冒烟测试 - 基础流程', () => { }); await test.step('点击用户菜单', async () => { - const avatarButton = page.locator('.el-avatar').first(); + const avatarButton = page.locator('.ant-avatar').first(); await avatarButton.click(); await page.waitForTimeout(500); }); diff --git a/novalon-manage-web/e2e/uat/auth-acceptance.spec.ts b/novalon-manage-web/e2e/uat/auth-acceptance.spec.ts new file mode 100644 index 0000000..6678be7 --- /dev/null +++ b/novalon-manage-web/e2e/uat/auth-acceptance.spec.ts @@ -0,0 +1,65 @@ +import { test, expect } from '@playwright/test'; +import { LoginPage } from '../pages/LoginPage'; + +test.describe('UAT: 认证功能验收', () => { + test('UAT-AUTH-01: 有效凭据登录成功', async ({ page }) => { + const loginPage = new LoginPage(page); + await loginPage.goto(); + await loginPage.login('admin', 'Test@123'); + await expect(page).toHaveURL(/.*dashboard/); + }); + + test('UAT-AUTH-02: 无效密码登录失败', async ({ page }) => { + const loginPage = new LoginPage(page); + await loginPage.goto(); + await loginPage.loginAndExpectError('admin', 'wrong_password'); + await expect(page).toHaveURL(/.*login/); + }); + + test('UAT-AUTH-03: 空用户名提交被阻止', async ({ page }) => { + const loginPage = new LoginPage(page); + await loginPage.goto(); + await loginPage.passwordInput.fill('somepassword'); + await loginPage.loginButton.click(); + await expect(page.locator('.ant-form-item-explain-error').first()).toBeVisible({ timeout: 5000 }); + }); + + test('UAT-AUTH-04: 空密码提交被阻止', async ({ page }) => { + const loginPage = new LoginPage(page); + await loginPage.goto(); + await loginPage.usernameInput.fill('admin'); + await loginPage.loginButton.click(); + await expect(page.locator('.ant-form-item-explain-error').first()).toBeVisible({ timeout: 5000 }); + }); + + test('UAT-AUTH-05: 登出后 Token 被清除', async ({ page }) => { + const loginPage = new LoginPage(page); + await loginPage.goto(); + await loginPage.login('admin', 'Test@123'); + await expect(page).toHaveURL(/.*dashboard/); + + const tokenBefore = await page.evaluate(() => localStorage.getItem('token')); + expect(tokenBefore).not.toBeNull(); + + await loginPage.logout(); + const tokenAfter = await page.evaluate(() => localStorage.getItem('token')); + expect(tokenAfter).toBeNull(); + }); + + test('UAT-AUTH-06: 未认证访问受保护路由重定向', async ({ page }) => { + await page.goto('/users'); + await page.waitForTimeout(2000); + await expect(page).toHaveURL(/.*login/, { timeout: 10000 }); + }); + + test('UAT-AUTH-07: 登录页面 UI 元素完整性', async ({ page }) => { + const loginPage = new LoginPage(page); + await loginPage.goto(); + + await expect(loginPage.usernameInput).toBeVisible(); + await expect(loginPage.passwordInput).toBeVisible(); + await expect(loginPage.loginButton).toBeVisible(); + await expect(page.locator('input[placeholder="用户名"]')).toBeVisible(); + await expect(page.locator('input[placeholder="密码"]')).toBeVisible(); + }); +}); diff --git a/novalon-manage-web/e2e/uat/error-handling.spec.ts b/novalon-manage-web/e2e/uat/error-handling.spec.ts new file mode 100644 index 0000000..76b7c85 --- /dev/null +++ b/novalon-manage-web/e2e/uat/error-handling.spec.ts @@ -0,0 +1,180 @@ +import { test, expect } from '@playwright/test'; +import { LoginPage } from '../pages/LoginPage'; +import { UserManagementPage } from '../pages/UserManagementPage'; +import { SystemConfigPage } from '../pages/SystemConfigPage'; +import { DictionaryManagementPage } from '../pages/DictionaryManagementPage'; +import { NotificationPage } from '../pages/NotificationPage'; + +test.describe('UAT: 异常处理与边界条件', () => { + test('UAT-ERR-01: 用户管理 - 重复用户名处理', async ({ page }) => { + const loginPage = new LoginPage(page); + const userPage = new UserManagementPage(page); + + await loginPage.goto(); + await loginPage.login('admin', 'admin123'); + await userPage.goto(); + + await test.step('尝试创建已存在的用户名', async () => { + await userPage.clickCreateUser(); + await userPage.fillUserForm({ + username: 'admin', + password: 'Test@123456', + nickname: '重复用户', + email: 'duplicate@test.com', + }); + await userPage.submitForm(); + }); + + await test.step('验证错误提示或表单验证', async () => { + await page.waitForTimeout(2000); + const hasError = (await page.locator('.ant-message-error').count()) > 0 || + (await page.locator('.ant-form-item-explain-error').count()) > 0; + expect(hasError).toBe(true); + }); + }); + + test('UAT-ERR-02: 用户管理 - 邮箱格式验证', async ({ page }) => { + const loginPage = new LoginPage(page); + const userPage = new UserManagementPage(page); + + await loginPage.goto(); + await loginPage.login('admin', 'admin123'); + await userPage.goto(); + await userPage.clickCreateUser(); + + await test.step('输入无效邮箱格式', async () => { + const timestamp = Date.now(); + await userPage.fillUserForm({ + username: `err_user_${timestamp}`, + password: 'Test@123456', + nickname: '邮箱测试', + email: 'invalid-email', + }); + }); + + await test.step('验证邮箱格式错误提示', async () => { + await userPage.submitForm(); + await page.waitForTimeout(1000); + const hasValidationError = (await page.locator('.ant-form-item-explain-error').count()) > 0; + expect(hasValidationError).toBe(true); + }); + }); + + test('UAT-ERR-03: 系统配置 - 空配置键验证', async ({ page }) => { + const loginPage = new LoginPage(page); + const configPage = new SystemConfigPage(page); + + await loginPage.goto(); + await loginPage.login('admin', 'admin123'); + await configPage.goto(); + + await test.step('点击新增配置但不填写', async () => { + await configPage.addButton.click(); + await page.waitForTimeout(500); + const modal = page.locator('.ant-modal').filter({ hasText: /新增配置/ }); + await modal.getByRole('button', { name: '确 定' }).click(); + }); + + await test.step('验证表单验证错误', async () => { + await page.waitForTimeout(1000); + const hasError = (await page.locator('.ant-form-item-explain-error').count()) > 0; + expect(hasError).toBe(true); + }); + }); + + test('UAT-ERR-04: 字典管理 - 空字典名称验证', async ({ page }) => { + const loginPage = new LoginPage(page); + const dictPage = new DictionaryManagementPage(page); + + await loginPage.goto(); + await loginPage.login('admin', 'admin123'); + await dictPage.goto(); + + await test.step('点击新增字典类型但不填写', async () => { + await dictPage.addTypeButton.click(); + await page.waitForTimeout(500); + const modal = page.locator('.ant-modal').filter({ hasText: /新增字典类型/ }); + await modal.getByRole('button', { name: '确 定' }).click(); + }); + + await test.step('验证表单验证错误', async () => { + await page.waitForTimeout(1000); + const hasError = (await page.locator('.ant-form-item-explain-error').count()) > 0; + expect(hasError).toBe(true); + }); + }); + + test('UAT-ERR-05: 通知管理 - 空标题验证', async ({ page }) => { + const loginPage = new LoginPage(page); + const notifyPage = new NotificationPage(page); + + await loginPage.goto(); + await loginPage.login('admin', 'admin123'); + await notifyPage.goto(); + + await test.step('点击新增通知但不填写标题', async () => { + await notifyPage.addButton.click(); + await page.waitForTimeout(500); + const modal = page.locator('.ant-modal').filter({ hasText: /新增通知/ }); + await modal.locator('.ant-form-item').filter({ hasText: '内容' }).locator('textarea').fill('测试内容'); + await modal.getByRole('button', { name: '确 定' }).click(); + }); + + await test.step('验证表单验证错误', async () => { + await page.waitForTimeout(1000); + const hasError = (await page.locator('.ant-form-item-explain-error').count()) > 0; + expect(hasError).toBe(true); + }); + }); + + test('UAT-ERR-06: 删除确认弹窗 - 取消操作', async ({ page }) => { + const loginPage = new LoginPage(page); + const userPage = new UserManagementPage(page); + + await loginPage.goto(); + await loginPage.login('admin', 'admin123'); + await userPage.goto(); + await userPage.waitForTableReady(); + + const countBefore = await userPage.getUserCount(); + if (countBefore > 0) { + await test.step('点击删除按钮', async () => { + const row = userPage.table.locator('tbody tr').first(); + await row.locator('.ant-btn').filter({ has: page.locator('.anticon-delete') }).click(); + await page.waitForTimeout(300); + }); + + await test.step('点击取消', async () => { + const popconfirm = page.locator('.ant-popconfirm'); + await popconfirm.getByRole('button', { name: '取 消' }).click(); + await page.waitForTimeout(500); + }); + + await test.step('验证数据未被删除', async () => { + const countAfter = await userPage.getUserCount(); + expect(countAfter).toBe(countBefore); + }); + } + }); + + test('UAT-ERR-07: 模态框关闭 - 点击取消按钮', async ({ page }) => { + const loginPage = new LoginPage(page); + const userPage = new UserManagementPage(page); + + await loginPage.goto(); + await loginPage.login('admin', 'admin123'); + await userPage.goto(); + await userPage.clickCreateUser(); + + await test.step('点击取消按钮关闭模态框', async () => { + const modal = page.locator('.ant-modal').filter({ hasText: /新增用户/ }); + await modal.getByRole('button', { name: '取 消' }).click(); + await page.waitForTimeout(500); + }); + + await test.step('验证模态框已关闭', async () => { + const modalVisible = await page.locator('.ant-modal:visible').count(); + expect(modalVisible).toBe(0); + }); + }); +}); diff --git a/novalon-manage-web/e2e/uat/permission-boundary.spec.ts b/novalon-manage-web/e2e/uat/permission-boundary.spec.ts new file mode 100644 index 0000000..b032b0c --- /dev/null +++ b/novalon-manage-web/e2e/uat/permission-boundary.spec.ts @@ -0,0 +1,73 @@ +import { test, expect } from '@playwright/test'; +import { LoginPage } from '../pages/LoginPage'; +import { DashboardPage } from '../pages/DashboardPage'; + +test.describe('UAT: 权限边界与访问控制', () => { + test('UAT-PERM-01: 管理员可访问所有菜单', async ({ page }) => { + const loginPage = new LoginPage(page); + + await loginPage.goto(); + await loginPage.login('admin', 'Test@123'); + await expect(page).toHaveURL(/.*dashboard/); + + const menuItems = page.locator('.ant-menu-submenu-title'); + const count = await menuItems.count(); + expect(count).toBeGreaterThan(0); + }); + + test('UAT-PERM-02: 未登录用户无法访问任何功能页面', async ({ page }) => { + const protectedRoutes = ['/users', '/roles', '/menus', '/dict', '/sys/config', '/oplog', '/loginlog', '/exceptionlog']; + + for (const route of protectedRoutes) { + await page.goto(route); + await page.waitForTimeout(2000); + await expect(page).toHaveURL(/.*login/, { timeout: 5000 }); + } + }); + + test('UAT-PERM-03: 登出后无法回退访问受保护页面', async ({ page }) => { + const loginPage = new LoginPage(page); + + await loginPage.goto(); + await loginPage.login('admin', 'Test@123'); + await expect(page).toHaveURL(/.*dashboard/); + + await loginPage.logout(); + await expect(page).toHaveURL(/.*login/); + + await page.goBack(); + await page.waitForTimeout(2000); + await expect(page).toHaveURL(/.*login/, { timeout: 5000 }); + }); + + test('UAT-PERM-04: 侧边栏菜单折叠与展开', async ({ page }) => { + const loginPage = new LoginPage(page); + + await loginPage.goto(); + await loginPage.login('admin', 'Test@123'); + + const collapseButton = page.locator('.ant-layout-sider-trigger, [class*="sider-trigger"]').first(); + if (await collapseButton.isVisible()) { + await collapseButton.click(); + await page.waitForTimeout(500); + + const sider = page.locator('.ant-layout-sider-collapsed'); + const isCollapsed = await sider.count() > 0; + expect(typeof isCollapsed).toBe('boolean'); + + await collapseButton.click(); + await page.waitForTimeout(500); + } + }); + + test('UAT-PERM-05: 仪表盘统计卡片可见', async ({ page }) => { + const loginPage = new LoginPage(page); + const dashboardPage = new DashboardPage(page); + + await loginPage.goto(); + await loginPage.login('admin', 'Test@123'); + await expect(page).toHaveURL(/.*dashboard/); + + await expect(page.locator('.ant-statistic').first()).toBeVisible({ timeout: 15000 }); + }); +}); diff --git a/novalon-manage-web/e2e/uat/system-management.spec.ts b/novalon-manage-web/e2e/uat/system-management.spec.ts new file mode 100644 index 0000000..008e140 --- /dev/null +++ b/novalon-manage-web/e2e/uat/system-management.spec.ts @@ -0,0 +1,123 @@ +import { test, expect } from '@playwright/test'; +import { LoginPage } from '../pages/LoginPage'; +import { UserManagementPage } from '../pages/UserManagementPage'; +import { RoleManagementPage } from '../pages/RoleManagementPage'; +import { MenuManagementPage } from '../pages/MenuManagementPage'; + +test.describe('UAT: 系统管理功能验收', () => { + const timestamp = Date.now(); + + test('UAT-SYS-01: 用户管理 - 新增用户表单验证', async ({ page }) => { + const loginPage = new LoginPage(page); + const userPage = new UserManagementPage(page); + + await loginPage.goto(); + await loginPage.login('admin', 'Test@123'); + await userPage.goto(); + await userPage.clickCreateUser(); + + await test.step('空表单提交应显示验证错误', async () => { + await userPage.submitForm(); + await expect(page.locator('.ant-form-item-explain-error').first()).toBeVisible({ timeout: 5000 }); + }); + }); + + test('UAT-SYS-02: 用户管理 - 完整 CRUD', async ({ page }) => { + const loginPage = new LoginPage(page); + const userPage = new UserManagementPage(page); + const username = `uat_user_${timestamp}`; + + await loginPage.goto(); + await loginPage.login('admin', 'Test@123'); + + await test.step('创建用户', async () => { + await userPage.goto(); + await userPage.clickCreateUser(); + await userPage.fillUserForm({ + username, + password: 'Uat@123456', + nickname: `UAT用户_${timestamp}`, + email: `uat_${timestamp}@test.com`, + phone: '13900139000', + }); + await userPage.submitForm(); + const success = await userPage.waitForSuccessMessage(); + expect(success).toBe(true); + }); + + await test.step('验证用户出现在列表中', async () => { + await userPage.goto(); + await userPage.waitForTableReady(); + const exists = await userPage.containsText(username); + expect(exists).toBe(true); + }); + + await test.step('编辑用户', async () => { + await userPage.editUser(1); + const modal = page.locator('.ant-modal').filter({ hasText: /编辑用户/ }); + const nicknameInput = modal.locator('.ant-form-item').filter({ hasText: '昵称' }).locator('input'); + await nicknameInput.clear(); + await nicknameInput.fill(`UAT修改_${timestamp}`); + await userPage.submitForm(); + }); + }); + + test('UAT-SYS-03: 角色管理 - 新增角色', async ({ page }) => { + const loginPage = new LoginPage(page); + const rolePage = new RoleManagementPage(page); + const roleName = `UAT角色_${timestamp}`; + const roleKey = `uat_role_${timestamp}`; + + await loginPage.goto(); + await loginPage.login('admin', 'Test@123'); + + await test.step('创建角色', async () => { + await rolePage.goto(); + await rolePage.clickCreateRole(); + await rolePage.fillRoleForm({ + roleName, + roleKey, + roleSort: 50, + status: 'ACTIVE', + }); + await rolePage.submitForm(); + }); + + await test.step('验证角色出现在列表中', async () => { + await rolePage.goto(); + const exists = await rolePage.containsText(roleName); + expect(exists).toBe(true); + }); + }); + + test('UAT-SYS-04: 菜单管理 - 新增菜单', async ({ page }) => { + const loginPage = new LoginPage(page); + const menuPage = new MenuManagementPage(page); + const menuName = `UAT菜单_${timestamp}`; + + await loginPage.goto(); + await loginPage.login('admin', 'Test@123'); + + await test.step('创建菜单', async () => { + await menuPage.goto(); + await menuPage.clickCreateMenu(); + await menuPage.fillMenuForm({ + name: menuName, + type: 'menu', + path: `/uat-test-${timestamp}`, + icon: 'setting', + component: `uat/Test_${timestamp}`, + permission: `uat:test_${timestamp}`, + sort: 50, + status: 'ACTIVE', + }); + await menuPage.submitForm(); + }); + + await test.step('验证菜单出现在列表中', async () => { + await menuPage.goto(); + const exists = await menuPage.containsText(menuName); + expect(exists).toBe(true); + }); + }); +}); diff --git a/novalon-manage-web/e2e/uat/ui-consistency.spec.ts b/novalon-manage-web/e2e/uat/ui-consistency.spec.ts new file mode 100644 index 0000000..cb2c660 --- /dev/null +++ b/novalon-manage-web/e2e/uat/ui-consistency.spec.ts @@ -0,0 +1,97 @@ +import { test, expect } from '@playwright/test'; +import { LoginPage } from '../pages/LoginPage'; +import { DashboardPage } from '../pages/DashboardPage'; + +test.describe('UAT: UI 一致性与响应性验收', () => { + test('UAT-UI-01: 登录页面布局一致性', async ({ page }) => { + const loginPage = new LoginPage(page); + await loginPage.goto(); + + await expect(page.locator('input[placeholder="用户名"]')).toBeVisible(); + await expect(page.locator('input[placeholder="密码"]')).toBeVisible(); + await expect(page.locator('button:has-text("登录")')).toBeVisible(); + + const bodyWidth = await page.evaluate(() => document.body.offsetWidth); + expect(bodyWidth).toBeGreaterThan(0); + }); + + test('UAT-UI-02: 仪表盘布局一致性', async ({ page }) => { + const loginPage = new LoginPage(page); + await loginPage.goto(); + await loginPage.login('admin', 'admin123'); + + await expect(page.locator('.ant-layout')).toBeVisible(); + await expect(page.locator('.ant-layout-sider')).toBeVisible({ timeout: 10000 }); + await expect(page.locator('.ant-layout-header')).toBeVisible({ timeout: 10000 }); + await expect(page.locator('.ant-layout-content')).toBeVisible({ timeout: 10000 }); + }); + + test('UAT-UI-03: 表格分页功能', async ({ page }) => { + const loginPage = new LoginPage(page); + await loginPage.goto(); + await loginPage.login('admin', 'admin123'); + + await page.goto('/users'); + await page.waitForLoadState('networkidle'); + await page.waitForTimeout(1000); + + const pagination = page.locator('.ant-pagination'); + if (await pagination.isVisible()) { + await expect(pagination).toBeVisible(); + const nextButton = pagination.locator('.ant-pagination-next'); + if (await nextButton.isEnabled()) { + await nextButton.click(); + await page.waitForLoadState('networkidle'); + await page.waitForTimeout(1000); + } + } + }); + + test('UAT-UI-04: 面包屑导航', async ({ page }) => { + const loginPage = new LoginPage(page); + await loginPage.goto(); + await loginPage.login('admin', 'admin123'); + + await page.goto('/users'); + await page.waitForLoadState('networkidle'); + + const breadcrumb = page.locator('.ant-breadcrumb'); + if (await breadcrumb.isVisible()) { + await expect(breadcrumb).toBeVisible(); + } + }); + + test('UAT-UI-05: 全局消息提示样式', async ({ page }) => { + const loginPage = new LoginPage(page); + await loginPage.goto(); + await loginPage.login('admin', 'admin123'); + + await page.goto('/users'); + await page.waitForLoadState('networkidle'); + + const table = page.locator('.ant-table').first(); + if (await table.isVisible()) { + await expect(table).toBeVisible(); + } + }); + + test('UAT-UI-06: 页面标题一致性', async ({ page }) => { + const loginPage = new LoginPage(page); + await loginPage.goto(); + await loginPage.login('admin', 'admin123'); + + const routes = [ + { path: '/dashboard', title: /仪表盘|Dashboard/ }, + { path: '/users', title: /用户/ }, + { path: '/roles', title: /角色/ }, + ]; + + for (const route of routes) { + await page.goto(route.path); + await page.waitForLoadState('networkidle'); + await page.waitForTimeout(1000); + const pageTitle = await page.title(); + expect(pageTitle.length).toBeGreaterThan(0); + } + }); +}); diff --git a/novalon-manage-web/e2e/utils/TestDataCleanup.ts b/novalon-manage-web/e2e/utils/TestDataCleanup.ts index 7a5bd3d..cdf75c3 100644 --- a/novalon-manage-web/e2e/utils/TestDataCleanup.ts +++ b/novalon-manage-web/e2e/utils/TestDataCleanup.ts @@ -100,7 +100,7 @@ export class TestDataCleanup { await this.page.goto('/users'); await this.page.waitForLoadState('networkidle', { timeout: 10000 }); - const searchInput = this.page.locator('input[placeholder*="搜索"], input[name*="keyword"], .el-input__inner').first(); + const searchInput = this.page.locator('input[placeholder*="搜索"], input[name*="keyword"], .ant-input__inner').first(); await searchInput.fill(username); const searchButton = this.page.getByRole('button', { name: '搜索' }).or(this.page.locator('button:has-text("搜索")')); @@ -111,11 +111,11 @@ export class TestDataCleanup { const rowCount = await userRow.count(); if (rowCount > 0) { - const deleteButton = userRow.locator('.delete-button, .el-button--danger').first(); + const deleteButton = userRow.locator('.delete-button, .ant-btn--danger').first(); await deleteButton.click(); await this.page.waitForTimeout(500); - const confirmButton = this.page.getByRole('button', { name: '确定' }).or(this.page.locator('.el-button--primary:has-text("确定")')); + const confirmButton = this.page.getByRole('button', { name: '确定' }).or(this.page.locator('.ant-btn--primary:has-text("确定")')); await confirmButton.click(); await this.page.waitForTimeout(1500); } @@ -129,7 +129,7 @@ export class TestDataCleanup { await this.page.goto('/roles'); await this.page.waitForLoadState('networkidle', { timeout: 10000 }); - const searchInput = this.page.locator('input[placeholder*="搜索"], input[name*="keyword"], .el-input__inner').first(); + const searchInput = this.page.locator('input[placeholder*="搜索"], input[name*="keyword"], .ant-input__inner').first(); await searchInput.fill(roleName); const searchButton = this.page.getByRole('button', { name: '搜索' }).or(this.page.locator('button:has-text("搜索")')); @@ -140,11 +140,11 @@ export class TestDataCleanup { const rowCount = await roleRow.count(); if (rowCount > 0) { - const deleteButton = roleRow.locator('.delete-button, .el-button--danger').first(); + const deleteButton = roleRow.locator('.delete-button, .ant-btn--danger').first(); await deleteButton.click(); await this.page.waitForTimeout(500); - const confirmButton = this.page.getByRole('button', { name: '确定' }).or(this.page.locator('.el-button--primary:has-text("确定")')); + const confirmButton = this.page.getByRole('button', { name: '确定' }).or(this.page.locator('.ant-btn--primary:has-text("确定")')); await confirmButton.click(); await this.page.waitForTimeout(1500); } @@ -162,11 +162,11 @@ export class TestDataCleanup { const rowCount = await menuRow.count(); if (rowCount > 0) { - const deleteButton = menuRow.locator('.delete-button, .el-button--danger').first(); + const deleteButton = menuRow.locator('.delete-button, .ant-btn--danger').first(); await deleteButton.click(); await this.page.waitForTimeout(500); - const confirmButton = this.page.getByRole('button', { name: '确定' }).or(this.page.locator('.el-button--primary:has-text("确定")')); + const confirmButton = this.page.getByRole('button', { name: '确定' }).or(this.page.locator('.ant-btn--primary:has-text("确定")')); await confirmButton.click(); await this.page.waitForTimeout(1500); } @@ -184,11 +184,11 @@ export class TestDataCleanup { const rowCount = await dictRow.count(); if (rowCount > 0) { - const deleteButton = dictRow.locator('.delete-button, .el-button--danger').first(); + const deleteButton = dictRow.locator('.delete-button, .ant-btn--danger').first(); await deleteButton.click(); await this.page.waitForTimeout(500); - const confirmButton = this.page.getByRole('button', { name: '确定' }).or(this.page.locator('.el-button--primary:has-text("确定")')); + const confirmButton = this.page.getByRole('button', { name: '确定' }).or(this.page.locator('.ant-btn--primary:has-text("确定")')); await confirmButton.click(); await this.page.waitForTimeout(1500); } @@ -206,11 +206,11 @@ export class TestDataCleanup { const rowCount = await dictRow.count(); if (rowCount > 0) { - const deleteButton = dictRow.locator('.delete-button, .el-button--danger').first(); + const deleteButton = dictRow.locator('.delete-button, .ant-btn--danger').first(); await deleteButton.click(); await this.page.waitForTimeout(500); - const confirmButton = this.page.getByRole('button', { name: '确定' }).or(this.page.locator('.el-button--primary:has-text("确定")')); + const confirmButton = this.page.getByRole('button', { name: '确定' }).or(this.page.locator('.ant-btn--primary:has-text("确定")')); await confirmButton.click(); await this.page.waitForTimeout(1500); } diff --git a/novalon-manage-web/e2e/utils/TestDataFactory.ts b/novalon-manage-web/e2e/utils/TestDataFactory.ts index fc32255..a16b5ea 100644 --- a/novalon-manage-web/e2e/utils/TestDataFactory.ts +++ b/novalon-manage-web/e2e/utils/TestDataFactory.ts @@ -94,8 +94,8 @@ export class TestDataFactory { nickname: '管理员', email: 'admin@example.com', phone: '13800138000', - password: 'admin123', - confirmPassword: 'admin123' + password: 'Test@123', + confirmPassword: 'Test@123' }; } diff --git a/novalon-manage-web/e2e/utils/TestHelpers.ts b/novalon-manage-web/e2e/utils/TestHelpers.ts index 3eae6c1..014c4bd 100644 --- a/novalon-manage-web/e2e/utils/TestHelpers.ts +++ b/novalon-manage-web/e2e/utils/TestHelpers.ts @@ -211,7 +211,7 @@ export class TestHelpers { } static async waitForSuccessMessage(page: Page, timeout: number = 5000): Promise { - const successMessage = page.locator('.el-message--success, .success-message, [class*="success"]'); + const successMessage = page.locator('.ant-message-success, .success-message, [class*="success"]'); try { await successMessage.waitFor({ state: 'visible', timeout }); return true; @@ -221,7 +221,7 @@ export class TestHelpers { } static async waitForErrorMessage(page: Page, timeout: number = 5000): Promise { - const errorMessage = page.locator('.el-message--error, .error-message, [class*="error"]'); + const errorMessage = page.locator('.ant-message-error, .error-message, [class*="error"]'); try { await errorMessage.waitFor({ state: 'visible', timeout }); return true; @@ -231,7 +231,7 @@ export class TestHelpers { } static async waitForLoadingComplete(page: Page, timeout: number = 10000): Promise { - const loadingSpinner = page.locator('.el-loading-mask, .loading, [class*="loading"]'); + const loadingSpinner = page.locator('.ant-spin-container, .loading, [class*="loading"]'); try { await loadingSpinner.waitFor({ state: 'visible', timeout: 2000 }); @@ -242,7 +242,7 @@ export class TestHelpers { } static async waitForModal(page: Page, timeout: number = 5000): Promise { - const modal = page.locator('.el-dialog, .modal, [role="dialog"]'); + const modal = page.locator('.ant-modal, .modal, [role="dialog"]'); try { await modal.waitFor({ state: 'visible', timeout }); return true; @@ -252,7 +252,7 @@ export class TestHelpers { } static async closeModal(page: Page): Promise { - const closeButton = page.locator('.el-dialog__close, .modal-close, button[aria-label="Close"]'); + const closeButton = page.locator('.ant-modal__close, .modal-close, button[aria-label="Close"]'); try { await closeButton.click(); return true; @@ -262,7 +262,7 @@ export class TestHelpers { } static async waitForSelectDropdown(page: Page, timeout: number = 5000): Promise { - const dropdown = page.locator('.el-select-dropdown, .select-dropdown'); + const dropdown = page.locator('.ant-select-dropdown, .select-dropdown'); try { await dropdown.waitFor({ state: 'visible', timeout }); return true; @@ -272,7 +272,7 @@ export class TestHelpers { } static async selectFromDropdown(page: Page, value: string): Promise { - const option = page.locator('.el-select-dropdown__item, .select-option').filter({ hasText: value }); + const option = page.locator('.ant-select-item, .select-option').filter({ hasText: value }); try { await option.click(); return true; diff --git a/novalon-manage-web/e2e/utils/testHelper.ts b/novalon-manage-web/e2e/utils/testHelper.ts index 22a7272..65243e0 100644 --- a/novalon-manage-web/e2e/utils/testHelper.ts +++ b/novalon-manage-web/e2e/utils/testHelper.ts @@ -124,15 +124,15 @@ export class TestHelper { message: string, timeout: number = 5000 ): Promise { - await expect(page.locator('.el-message')).toContainText(message, { timeout }); + await expect(page.locator('.ant-message')).toContainText(message, { timeout }); } static async waitForSuccessMessage(page: Page, timeout: number = 5000): Promise { - await expect(page.locator('.el-message--success')).toBeVisible({ timeout }); + await expect(page.locator('.ant-message-success')).toBeVisible({ timeout }); } static async waitForErrorMessage(page: Page, timeout: number = 5000): Promise { - await expect(page.locator('.el-message--error')).toBeVisible({ timeout }); + await expect(page.locator('.ant-message-error')).toBeVisible({ timeout }); } static async getElementText(page: Page, selector: string): Promise { diff --git a/novalon-manage-web/playwright/.auth/user.json b/novalon-manage-web/playwright/.auth/user.json index 4f98858..f1f5390 100644 --- a/novalon-manage-web/playwright/.auth/user.json +++ b/novalon-manage-web/playwright/.auth/user.json @@ -2,19 +2,19 @@ "cookies": [], "origins": [ { - "origin": "http://localhost:3002", + "origin": "http://localhost:5174", "localStorage": [ { "name": "token", - "value": "eyJhbGciOiJIUzM4NCJ9.eyJyb2xlcyI6WyJhZG1pbiJdLCJ1c2VySWQiOjEsInVzZXJuYW1lIjoiYWRtaW4iLCJzdWIiOiJhZG1pbiIsImlhdCI6MTc3NzM2NTAzMCwiZXhwIjoxNzc3NDUxNDMwfQ.RdGoO70nPfMTKtxp6qoy9eSRq0rvOH6j5hB8pVlhA71wNI6R_2sfSXALyiFckeSF" + "value": "eyJhbGciOiJIUzM4NCJ9.eyJyb2xlcyI6WyJhZG1pbiJdLCJ1c2VySWQiOjEsInVzZXJuYW1lIjoiYWRtaW4iLCJzdWIiOiJhZG1pbiIsImlhdCI6MTc3Nzg5ODg5MywiZXhwIjoxNzc3OTg1MjkzfQ.nA1pd2z6GPzkxf2wPSQKyuR1GzJ3Kpjc6FP_bTNEFaWQyhOwfLPC25milq3s07BJ" }, { "name": "permission", - "value": "{\"roles\":[\"admin\"],\"permissions\":[\"system:user:list\",\"system:role:list\",\"system:menu:list\",\"system:dept:list\",\"system:dict:list\",\"system:config:list\",\"system:notice:list\",\"system:file:list\",\"system:user:query\",\"system:user:add\",\"system:user:edit\",\"system:user:remove\",\"system:user:export\",\"system:user:import\",\"system:user:resetPwd\",\"system:role:query\",\"system:role:add\",\"system:role:edit\",\"system:role:remove\",\"system:role:export\",\"system:menu:query\",\"system:menu:add\",\"system:menu:edit\",\"system:menu:remove\",\"audit:operation:list\",\"audit:login:list\",\"audit:exception:list\",\"audit:operation:query\",\"audit:operation:remove\",\"audit:operation:export\",\"audit:login:query\",\"audit:login:remove\",\"audit:login:export\",\"audit:exception:query\",\"audit:exception:remove\",\"audit:exception:export\",\"monitor:online:list\",\"monitor:job:list\",\"monitor:data:list\",\"monitor:server:list\",\"monitor:cache:list\",\"monitor:online:query\",\"monitor:online:forceLogout\",\"monitor:job:query\",\"monitor:job:add\",\"monitor:job:edit\",\"monitor:job:remove\",\"monitor:job:execute\"],\"menus\":[{\"id\":\"1\",\"name\":\"系统管理\",\"path\":\"\",\"icon\":\"Setting\",\"sort\":1,\"children\":[{\"id\":\"11\",\"name\":\"用户管理\",\"path\":\"/users\",\"icon\":\"User\",\"parentId\":\"1\",\"sort\":1},{\"id\":\"12\",\"name\":\"角色管理\",\"path\":\"/roles\",\"icon\":\"UserFilled\",\"parentId\":\"1\",\"sort\":2},{\"id\":\"13\",\"name\":\"菜单管理\",\"path\":\"/menus\",\"icon\":\"Menu\",\"parentId\":\"1\",\"sort\":3},{\"id\":\"14\",\"name\":\"部门管理\",\"path\":\"/dept\",\"icon\":\"Document\",\"parentId\":\"1\",\"sort\":4},{\"id\":\"15\",\"name\":\"字典管理\",\"path\":\"/dict\",\"icon\":\"Collection\",\"parentId\":\"1\",\"sort\":5},{\"id\":\"16\",\"name\":\"参数管理\",\"path\":\"/sys/config\",\"icon\":\"Document\",\"parentId\":\"1\",\"sort\":6},{\"id\":\"17\",\"name\":\"通知公告\",\"path\":\"/notice\",\"icon\":\"Bell\",\"parentId\":\"1\",\"sort\":7},{\"id\":\"18\",\"name\":\"文件管理\",\"path\":\"/files\",\"icon\":\"Folder\",\"parentId\":\"1\",\"sort\":8}]},{\"id\":\"2\",\"name\":\"审计日志\",\"path\":\"\",\"icon\":\"Document\",\"sort\":2,\"children\":[{\"id\":\"21\",\"name\":\"操作日志\",\"path\":\"/oplog\",\"icon\":\"Document\",\"parentId\":\"2\",\"sort\":1},{\"id\":\"22\",\"name\":\"登录日志\",\"path\":\"/loginlog\",\"icon\":\"Document\",\"parentId\":\"2\",\"sort\":2},{\"id\":\"23\",\"name\":\"异常日志\",\"path\":\"/exceptionlog\",\"icon\":\"Warning\",\"parentId\":\"2\",\"sort\":3}]},{\"id\":\"3\",\"name\":\"系统监控\",\"path\":\"\",\"icon\":\"Monitor\",\"sort\":3,\"children\":[{\"id\":\"31\",\"name\":\"在线用户\",\"path\":\"/monitor/online\",\"icon\":\"Document\",\"parentId\":\"3\",\"sort\":1},{\"id\":\"32\",\"name\":\"定时任务\",\"path\":\"/monitor/job\",\"icon\":\"Document\",\"parentId\":\"3\",\"sort\":2},{\"id\":\"33\",\"name\":\"数据监控\",\"path\":\"/monitor/data\",\"icon\":\"Document\",\"parentId\":\"3\",\"sort\":3},{\"id\":\"34\",\"name\":\"服务监控\",\"path\":\"/monitor/server\",\"icon\":\"Document\",\"parentId\":\"3\",\"sort\":4},{\"id\":\"35\",\"name\":\"缓存监控\",\"path\":\"/monitor/cache\",\"icon\":\"Document\",\"parentId\":\"3\",\"sort\":5}]}]}" + "value": "{\"permissions\":[],\"menus\":[{\"id\":\"1\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"系统管理\",\"parentId\":\"0\",\"orderNum\":1,\"menuType\":\"M\",\"perms\":null,\"component\":null,\"status\":1,\"children\":[{\"id\":\"11\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"用户管理\",\"parentId\":\"1\",\"orderNum\":1,\"menuType\":\"C\",\"perms\":\"system:user:list\",\"component\":\"system/user/index\",\"status\":1,\"children\":[{\"id\":\"111\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"用户查询\",\"parentId\":\"11\",\"orderNum\":1,\"menuType\":\"F\",\"perms\":\"system:user:query\",\"component\":null,\"status\":1,\"children\":[]},{\"id\":\"112\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"用户新增\",\"parentId\":\"11\",\"orderNum\":2,\"menuType\":\"F\",\"perms\":\"system:user:add\",\"component\":null,\"status\":1,\"children\":[]},{\"id\":\"113\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"用户修改\",\"parentId\":\"11\",\"orderNum\":3,\"menuType\":\"F\",\"perms\":\"system:user:edit\",\"component\":null,\"status\":1,\"children\":[]},{\"id\":\"114\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"用户删除\",\"parentId\":\"11\",\"orderNum\":4,\"menuType\":\"F\",\"perms\":\"system:user:remove\",\"component\":null,\"status\":1,\"children\":[]},{\"id\":\"115\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"用户导出\",\"parentId\":\"11\",\"orderNum\":5,\"menuType\":\"F\",\"perms\":\"system:user:export\",\"component\":null,\"status\":1,\"children\":[]},{\"id\":\"116\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"用户导入\",\"parentId\":\"11\",\"orderNum\":6,\"menuType\":\"F\",\"perms\":\"system:user:import\",\"component\":null,\"status\":1,\"children\":[]},{\"id\":\"117\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"重置密码\",\"parentId\":\"11\",\"orderNum\":7,\"menuType\":\"F\",\"perms\":\"system:user:resetPwd\",\"component\":null,\"status\":1,\"children\":[]}]},{\"id\":\"12\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"角色管理\",\"parentId\":\"1\",\"orderNum\":2,\"menuType\":\"C\",\"perms\":\"system:role:list\",\"component\":\"system/role/index\",\"status\":1,\"children\":[{\"id\":\"121\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"角色查询\",\"parentId\":\"12\",\"orderNum\":1,\"menuType\":\"F\",\"perms\":\"system:role:query\",\"component\":null,\"status\":1,\"children\":[]},{\"id\":\"122\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"角色新增\",\"parentId\":\"12\",\"orderNum\":2,\"menuType\":\"F\",\"perms\":\"system:role:add\",\"component\":null,\"status\":1,\"children\":[]},{\"id\":\"123\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"角色修改\",\"parentId\":\"12\",\"orderNum\":3,\"menuType\":\"F\",\"perms\":\"system:role:edit\",\"component\":null,\"status\":1,\"children\":[]},{\"id\":\"124\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"角色删除\",\"parentId\":\"12\",\"orderNum\":4,\"menuType\":\"F\",\"perms\":\"system:role:remove\",\"component\":null,\"status\":1,\"children\":[]},{\"id\":\"125\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"角色导出\",\"parentId\":\"12\",\"orderNum\":5,\"menuType\":\"F\",\"perms\":\"system:role:export\",\"component\":null,\"status\":1,\"children\":[]}]},{\"id\":\"13\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"菜单管理\",\"parentId\":\"1\",\"orderNum\":3,\"menuType\":\"C\",\"perms\":\"system:menu:list\",\"component\":\"system/menu/index\",\"status\":1,\"children\":[{\"id\":\"131\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"菜单查询\",\"parentId\":\"13\",\"orderNum\":1,\"menuType\":\"F\",\"perms\":\"system:menu:query\",\"component\":null,\"status\":1,\"children\":[]},{\"id\":\"132\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"菜单新增\",\"parentId\":\"13\",\"orderNum\":2,\"menuType\":\"F\",\"perms\":\"system:menu:add\",\"component\":null,\"status\":1,\"children\":[]},{\"id\":\"133\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"菜单修改\",\"parentId\":\"13\",\"orderNum\":3,\"menuType\":\"F\",\"perms\":\"system:menu:edit\",\"component\":null,\"status\":1,\"children\":[]},{\"id\":\"134\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"菜单删除\",\"parentId\":\"13\",\"orderNum\":4,\"menuType\":\"F\",\"perms\":\"system:menu:remove\",\"component\":null,\"status\":1,\"children\":[]}]},{\"id\":\"14\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"部门管理\",\"parentId\":\"1\",\"orderNum\":4,\"menuType\":\"C\",\"perms\":\"system:dept:list\",\"component\":\"system/dept/index\",\"status\":1,\"children\":[]},{\"id\":\"15\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"字典管理\",\"parentId\":\"1\",\"orderNum\":5,\"menuType\":\"C\",\"perms\":\"system:dict:list\",\"component\":\"system/dict/index\",\"status\":1,\"children\":[]},{\"id\":\"16\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"参数管理\",\"parentId\":\"1\",\"orderNum\":6,\"menuType\":\"C\",\"perms\":\"system:config:list\",\"component\":\"system/config/index\",\"status\":1,\"children\":[]},{\"id\":\"17\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"通知公告\",\"parentId\":\"1\",\"orderNum\":7,\"menuType\":\"C\",\"perms\":\"system:notice:list\",\"component\":\"system/notice/index\",\"status\":1,\"children\":[]},{\"id\":\"18\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"文件管理\",\"parentId\":\"1\",\"orderNum\":8,\"menuType\":\"C\",\"perms\":\"system:file:list\",\"component\":\"system/file/index\",\"status\":1,\"children\":[]}]},{\"id\":\"2\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"审计日志\",\"parentId\":\"0\",\"orderNum\":2,\"menuType\":\"M\",\"perms\":null,\"component\":null,\"status\":1,\"children\":[{\"id\":\"21\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"操作日志\",\"parentId\":\"2\",\"orderNum\":1,\"menuType\":\"C\",\"perms\":\"audit:operation:list\",\"component\":\"audit/operation/index\",\"status\":1,\"children\":[{\"id\":\"211\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"操作查询\",\"parentId\":\"21\",\"orderNum\":1,\"menuType\":\"F\",\"perms\":\"audit:operation:query\",\"component\":null,\"status\":1,\"children\":[]},{\"id\":\"212\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"操作删除\",\"parentId\":\"21\",\"orderNum\":2,\"menuType\":\"F\",\"perms\":\"audit:operation:remove\",\"component\":null,\"status\":1,\"children\":[]},{\"id\":\"213\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"操作导出\",\"parentId\":\"21\",\"orderNum\":3,\"menuType\":\"F\",\"perms\":\"audit:operation:export\",\"component\":null,\"status\":1,\"children\":[]}]},{\"id\":\"22\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"登录日志\",\"parentId\":\"2\",\"orderNum\":2,\"menuType\":\"C\",\"perms\":\"audit:login:list\",\"component\":\"audit/login/index\",\"status\":1,\"children\":[{\"id\":\"221\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"登录查询\",\"parentId\":\"22\",\"orderNum\":1,\"menuType\":\"F\",\"perms\":\"audit:login:query\",\"component\":null,\"status\":1,\"children\":[]},{\"id\":\"222\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"登录删除\",\"parentId\":\"22\",\"orderNum\":2,\"menuType\":\"F\",\"perms\":\"audit:login:remove\",\"component\":null,\"status\":1,\"children\":[]},{\"id\":\"223\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"登录导出\",\"parentId\":\"22\",\"orderNum\":3,\"menuType\":\"F\",\"perms\":\"audit:login:export\",\"component\":null,\"status\":1,\"children\":[]}]},{\"id\":\"23\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"异常日志\",\"parentId\":\"2\",\"orderNum\":3,\"menuType\":\"C\",\"perms\":\"audit:exception:list\",\"component\":\"audit/exception/index\",\"status\":1,\"children\":[{\"id\":\"231\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"异常查询\",\"parentId\":\"23\",\"orderNum\":1,\"menuType\":\"F\",\"perms\":\"audit:exception:query\",\"component\":null,\"status\":1,\"children\":[]},{\"id\":\"232\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"异常删除\",\"parentId\":\"23\",\"orderNum\":2,\"menuType\":\"F\",\"perms\":\"audit:exception:remove\",\"component\":null,\"status\":1,\"children\":[]},{\"id\":\"233\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"异常导出\",\"parentId\":\"23\",\"orderNum\":3,\"menuType\":\"F\",\"perms\":\"audit:exception:export\",\"component\":null,\"status\":1,\"children\":[]}]}]},{\"id\":\"3\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"系统监控\",\"parentId\":\"0\",\"orderNum\":3,\"menuType\":\"M\",\"perms\":null,\"component\":null,\"status\":1,\"children\":[{\"id\":\"31\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"在线用户\",\"parentId\":\"3\",\"orderNum\":1,\"menuType\":\"C\",\"perms\":\"monitor:online:list\",\"component\":\"monitor/online/index\",\"status\":1,\"children\":[{\"id\":\"311\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"在线查询\",\"parentId\":\"31\",\"orderNum\":1,\"menuType\":\"F\",\"perms\":\"monitor:online:query\",\"component\":null,\"status\":1,\"children\":[]},{\"id\":\"312\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"在线强退\",\"parentId\":\"31\",\"orderNum\":2,\"menuType\":\"F\",\"perms\":\"monitor:online:forceLogout\",\"component\":null,\"status\":1,\"children\":[]}]},{\"id\":\"32\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"定时任务\",\"parentId\":\"3\",\"orderNum\":2,\"menuType\":\"C\",\"perms\":\"monitor:job:list\",\"component\":\"monitor/job/index\",\"status\":1,\"children\":[{\"id\":\"321\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"任务查询\",\"parentId\":\"32\",\"orderNum\":1,\"menuType\":\"F\",\"perms\":\"monitor:job:query\",\"component\":null,\"status\":1,\"children\":[]},{\"id\":\"322\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"任务新增\",\"parentId\":\"32\",\"orderNum\":2,\"menuType\":\"F\",\"perms\":\"monitor:job:add\",\"component\":null,\"status\":1,\"children\":[]},{\"id\":\"323\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"任务修改\",\"parentId\":\"32\",\"orderNum\":3,\"menuType\":\"F\",\"perms\":\"monitor:job:edit\",\"component\":null,\"status\":1,\"children\":[]},{\"id\":\"324\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"任务删除\",\"parentId\":\"32\",\"orderNum\":4,\"menuType\":\"F\",\"perms\":\"monitor:job:remove\",\"component\":null,\"status\":1,\"children\":[]},{\"id\":\"325\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"任务执行\",\"parentId\":\"32\",\"orderNum\":5,\"menuType\":\"F\",\"perms\":\"monitor:job:execute\",\"component\":null,\"status\":1,\"children\":[]}]},{\"id\":\"33\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"数据监控\",\"parentId\":\"3\",\"orderNum\":3,\"menuType\":\"C\",\"perms\":\"monitor:data:list\",\"component\":\"monitor/data/index\",\"status\":1,\"children\":[]},{\"id\":\"34\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"服务监控\",\"parentId\":\"3\",\"orderNum\":4,\"menuType\":\"C\",\"perms\":\"monitor:server:list\",\"component\":\"monitor/server/index\",\"status\":1,\"children\":[]},{\"id\":\"35\",\"createBy\":null,\"updateBy\":null,\"createdAt\":\"2026-04-28T17:26:23.541925\",\"updatedAt\":\"2026-04-28T17:26:23.541925\",\"deletedAt\":null,\"menuName\":\"缓存监控\",\"parentId\":\"3\",\"orderNum\":5,\"menuType\":\"C\",\"perms\":\"monitor:cache:list\",\"component\":\"monitor/cache/index\",\"status\":1,\"children\":[]}]}]}" }, { "name": "userId", - "value": "1" + "value": "admin" }, { "name": "username",