import { Page, Locator, expect } from '@playwright/test'; export class MenuManagementPage { readonly page: Page; readonly table: Locator; readonly createMenuButton: Locator; readonly refreshButton: Locator; readonly successMessage: Locator; readonly errorMessage: Locator; constructor(page: Page) { this.page = page; 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() { 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() { await this.createMenuButton.click(); 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: { name: string; type?: string; path?: string; icon?: string; component?: string; permission?: string; sort?: number; status?: string; visible?: boolean; }) { 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); 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) { 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) { await modal.locator('.ant-form-item').filter({ hasText: '组件路径' }).locator('input').fill(menuData.component); } if (menuData.permission) { await modal.locator('.ant-form-item').filter({ hasText: '权限标识' }).locator('input').fill(menuData.permission); } if (menuData.sort !== undefined) { 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.status) { 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() { 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 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 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.locator('.ant-popconfirm').getByRole('button', { name: /确\s*定/ }).click(); await this.page.waitForTimeout(1000); } async cancelDelete() { await this.page.locator('.ant-popconfirm').getByRole('button', { name: /取\s*消/ }).click(); } async containsText(text: string): Promise { return this.table.getByText(text).count() > 0; } async getMenuCount(): Promise { return this.table.locator('tbody tr').count(); } async reload() { await this.refreshButton.click(); await this.page.waitForLoadState('networkidle'); } }