feat(admin): 添加用户管理相关文件

添加用户管理视图、API和状态管理文件
This commit is contained in:
张翔
2026-03-28 14:37:29 +08:00
commit 08ea5fbe98
1643 changed files with 255646 additions and 0 deletions
@@ -0,0 +1,414 @@
import { Page, Locator, expect } from '@playwright/test';
import { testConfig } from '../core/test-config';
import { testLogger } from '../core/test-logger';
import { ScreenshotHelper } from '../helpers/screenshot-helper';
export class BasePage {
protected page: Page;
protected screenshotHelper: ScreenshotHelper;
protected baseURL: string;
protected timeout: {
default: number;
navigation: number;
element: number;
network: number;
};
constructor(page: Page) {
this.page = page;
this.screenshotHelper = new ScreenshotHelper(page);
this.baseURL = testConfig.getBaseURL();
this.timeout = testConfig.getEnvironment().timeout;
}
async navigate(path: string = ''): Promise<void> {
const url = path.startsWith('http') ? path : `${this.baseURL}${path}`;
testLogger.info(`导航到页面: ${url}`);
try {
await this.page.goto(url, {
timeout: this.timeout.navigation,
waitUntil: 'networkidle'
});
await this.page.waitForLoadState('networkidle', {
timeout: this.timeout.network
});
testLogger.info(`页面加载完成: ${url}`);
} catch (error) {
testLogger.error(`页面导航失败: ${url}`, error as Error);
await this.screenshotHelper.takeScreenshot('navigation-error');
throw error;
}
}
async reload(): Promise<void> {
testLogger.info('重新加载页面');
try {
await this.page.reload({
timeout: this.timeout.navigation,
waitUntil: 'networkidle'
});
await this.page.waitForLoadState('networkidle', {
timeout: this.timeout.network
});
testLogger.info('页面重新加载完成');
} catch (error) {
testLogger.error('页面重新加载失败', error as Error);
await this.screenshotHelper.takeScreenshot('reload-error');
throw error;
}
}
async goBack(): Promise<void> {
testLogger.info('返回上一页');
await this.page.goBack();
}
async goForward(): Promise<void> {
testLogger.info('前进到下一页');
await this.page.goForward();
}
async waitForLoad(timeout?: number): Promise<void> {
const loadTimeout = timeout || this.timeout.navigation;
testLogger.debug(`等待页面加载完成, 超时时间: ${loadTimeout}ms`);
try {
await this.page.waitForLoadState('networkidle', {
timeout: loadTimeout
});
testLogger.debug('页面加载完成');
} catch (error) {
testLogger.error('等待页面加载超时', error as Error);
await this.screenshotHelper.takeScreenshot('wait-load-error');
throw error;
}
}
async waitForURL(urlPattern: string | RegExp, timeout?: number): Promise<void> {
const waitTimeout = timeout || this.timeout.navigation;
testLogger.debug(`等待URL匹配: ${urlPattern}, 超时时间: ${waitTimeout}ms`);
try {
await this.page.waitForURL(urlPattern, {
timeout: waitTimeout
});
testLogger.debug(`URL匹配成功: ${this.page.url()}`);
} catch (error) {
testLogger.error(`等待URL匹配失败: ${urlPattern}`, error as Error);
await this.screenshotHelper.takeScreenshot('wait-url-error');
throw error;
}
}
async waitForElement(selector: string, timeout?: number): Promise<Locator> {
const waitTimeout = timeout || this.timeout.element;
testLogger.debug(`等待元素可见: ${selector}, 超时时间: ${waitTimeout}ms`);
try {
const locator = this.page.locator(selector);
await locator.waitFor({
state: 'visible',
timeout: waitTimeout
});
testLogger.debug(`元素可见: ${selector}`);
return locator;
} catch (error) {
testLogger.error(`等待元素超时: ${selector}`, error as Error);
await this.screenshotHelper.takeScreenshot('wait-element-error');
throw error;
}
}
async waitForElementHidden(selector: string, timeout?: number): Promise<void> {
const waitTimeout = timeout || this.timeout.element;
testLogger.debug(`等待元素隐藏: ${selector}, 超时时间: ${waitTimeout}ms`);
try {
const locator = this.page.locator(selector);
await locator.waitFor({
state: 'hidden',
timeout: waitTimeout
});
testLogger.debug(`元素已隐藏: ${selector}`);
} catch (error) {
testLogger.error(`等待元素隐藏超时: ${selector}`, error as Error);
await this.screenshotHelper.takeScreenshot('wait-hidden-error');
throw error;
}
}
async click(selector: string, options?: { timeout?: number; force?: boolean }): Promise<void> {
const clickTimeout = options?.timeout || this.timeout.element;
testLogger.debug(`点击元素: ${selector}`);
try {
const locator = this.page.locator(selector);
await locator.waitFor({
state: 'visible',
timeout: clickTimeout
});
await locator.click({
timeout: clickTimeout,
force: options?.force
});
testLogger.debug(`元素点击成功: ${selector}`);
} catch (error) {
testLogger.error(`点击元素失败: ${selector}`, error as Error);
await this.screenshotHelper.takeScreenshot('click-error');
throw error;
}
}
async fill(selector: string, value: string, options?: { timeout?: number }): Promise<void> {
const fillTimeout = options?.timeout || this.timeout.element;
testLogger.debug(`填充输入框: ${selector}, 值: ${value}`);
try {
const locator = this.page.locator(selector);
await locator.waitFor({
state: 'visible',
timeout: fillTimeout
});
await locator.fill(value, {
timeout: fillTimeout
});
testLogger.debug(`输入框填充成功: ${selector}`);
} catch (error) {
testLogger.error(`填充输入框失败: ${selector}`, error as Error);
await this.screenshotHelper.takeScreenshot('fill-error');
throw error;
}
}
async selectOption(selector: string, value: string | string[], options?: { timeout?: number }): Promise<void> {
const selectTimeout = options?.timeout || this.timeout.element;
testLogger.debug(`选择下拉选项: ${selector}, 值: ${value}`);
try {
const locator = this.page.locator(selector);
await locator.waitFor({
state: 'visible',
timeout: selectTimeout
});
await locator.selectOption(value, {
timeout: selectTimeout
});
testLogger.debug(`下拉选项选择成功: ${selector}`);
} catch (error) {
testLogger.error(`选择下拉选项失败: ${selector}`, error as Error);
await this.screenshotHelper.takeScreenshot('select-error');
throw error;
}
}
async check(selector: string, options?: { timeout?: number }): Promise<void> {
const checkTimeout = options?.timeout || this.timeout.element;
testLogger.debug(`勾选复选框: ${selector}`);
try {
const locator = this.page.locator(selector);
await locator.waitFor({
state: 'visible',
timeout: checkTimeout
});
await locator.check({
timeout: checkTimeout
});
testLogger.debug(`复选框勾选成功: ${selector}`);
} catch (error) {
testLogger.error(`勾选复选框失败: ${selector}`, error as Error);
await this.screenshotHelper.takeScreenshot('check-error');
throw error;
}
}
async uncheck(selector: string, options?: { timeout?: number }): Promise<void> {
const uncheckTimeout = options?.timeout || this.timeout.element;
testLogger.debug(`取消勾选复选框: ${selector}`);
try {
const locator = this.page.locator(selector);
await locator.waitFor({
state: 'visible',
timeout: uncheckTimeout
});
await locator.uncheck({
timeout: uncheckTimeout
});
testLogger.debug(`复选框取消勾选成功: ${selector}`);
} catch (error) {
testLogger.error(`取消勾选复选框失败: ${selector}`, error as Error);
await this.screenshotHelper.takeScreenshot('uncheck-error');
throw error;
}
}
async getText(selector: string, options?: { timeout?: number }): Promise<string> {
const textTimeout = options?.timeout || this.timeout.element;
testLogger.debug(`获取元素文本: ${selector}`);
try {
const locator = this.page.locator(selector);
await locator.waitFor({
state: 'visible',
timeout: textTimeout
});
const text = await locator.textContent();
testLogger.debug(`元素文本: ${selector} = ${text}`);
return text || '';
} catch (error) {
testLogger.error(`获取元素文本失败: ${selector}`, error as Error);
await this.screenshotHelper.takeScreenshot('get-text-error');
throw error;
}
}
async getAttribute(selector: string, attributeName: string, options?: { timeout?: number }): Promise<string | null> {
const attrTimeout = options?.timeout || this.timeout.element;
testLogger.debug(`获取元素属性: ${selector}, 属性名: ${attributeName}`);
try {
const locator = this.page.locator(selector);
await locator.waitFor({
state: 'visible',
timeout: attrTimeout
});
const attribute = await locator.getAttribute(attributeName);
testLogger.debug(`元素属性: ${selector}[${attributeName}] = ${attribute}`);
return attribute;
} catch (error) {
testLogger.error(`获取元素属性失败: ${selector}`, error as Error);
await this.screenshotHelper.takeScreenshot('get-attribute-error');
throw error;
}
}
async isVisible(selector: string): Promise<boolean> {
try {
const locator = this.page.locator(selector);
const visible = await locator.isVisible({ timeout: 5000 });
testLogger.debug(`元素可见性: ${selector} = ${visible}`);
return visible;
} catch (error) {
testLogger.debug(`元素不可见: ${selector}`);
return false;
}
}
async isEnabled(selector: string): Promise<boolean> {
try {
const locator = this.page.locator(selector);
const enabled = await locator.isEnabled({ timeout: 5000 });
testLogger.debug(`元素可用性: ${selector} = ${enabled}`);
return enabled;
} catch (error) {
testLogger.debug(`元素不可用: ${selector}`);
return false;
}
}
async waitForTimeout(ms: number): Promise<void> {
testLogger.debug(`等待 ${ms}ms`);
await this.page.waitForTimeout(ms);
}
async executeScript(script: string, ...args: any[]): Promise<any> {
testLogger.debug('执行JavaScript脚本');
try {
const result = await this.page.evaluate(script, ...args);
testLogger.debug('JavaScript脚本执行成功');
return result;
} catch (error) {
testLogger.error('JavaScript脚本执行失败', error as Error);
throw error;
}
}
async scrollToElement(selector: string): Promise<void> {
testLogger.debug(`滚动到元素: ${selector}`);
try {
const locator = this.page.locator(selector);
await locator.scrollIntoViewIfNeeded();
testLogger.debug(`滚动到元素成功: ${selector}`);
} catch (error) {
testLogger.error(`滚动到元素失败: ${selector}`, error as Error);
throw error;
}
}
async takeScreenshot(name: string): Promise<string> {
return await this.screenshotHelper.takeScreenshot(name);
}
async takeElementScreenshot(selector: string, name: string): Promise<string> {
return await this.screenshotHelper.takeElementScreenshot(selector, name);
}
getCurrentURL(): string {
return this.page.url();
}
getTitle(): Promise<string> {
return this.page.title();
}
async expectVisible(selector: string, timeout?: number): Promise<void> {
const locator = await this.waitForElement(selector, timeout);
await expect(locator).toBeVisible();
}
async expectHidden(selector: string, timeout?: number): Promise<void> {
const locator = this.page.locator(selector);
await expect(locator).toBeHidden({ timeout });
}
async expectText(selector: string, expectedText: string, timeout?: number): Promise<void> {
const locator = await this.waitForElement(selector, timeout);
await expect(locator).toHaveText(expectedText);
}
async expectValue(selector: string, expectedValue: string, timeout?: number): Promise<void> {
const locator = await this.waitForElement(selector, timeout);
await expect(locator).toHaveValue(expectedValue);
}
async expectAttribute(selector: string, attributeName: string, expectedValue: string, timeout?: number): Promise<void> {
const locator = await this.waitForElement(selector, timeout);
await expect(locator).toHaveAttribute(attributeName, expectedValue);
}
async expectCount(selector: string, expectedCount: number, timeout?: number): Promise<void> {
const locator = this.page.locator(selector);
await expect(locator).toHaveCount(expectedCount, { timeout });
}
}
@@ -0,0 +1,191 @@
import { Page, expect } from '@playwright/test';
import { BasePage } from './base-page';
import { SELECTORS, TIMEOUTS } from '../constants';
export class DashboardPage extends BasePage {
private readonly selectors = {
dashboardContainer: '.dashboard-container',
pageTitle: '.page-title',
statisticsCards: '.statistic-card',
charts: '.chart-container',
menuItems: '.ant-menu-item',
welcomeMessage: '.welcome-message',
quickActions: '.quick-action'
};
constructor(page: Page) {
super(page);
}
async navigate(): Promise<void> {
testLogger.info('导航到仪表盘页面');
await super.navigate('/dashboard');
}
async waitForLoad(): Promise<void> {
testLogger.info('等待仪表盘页面加载');
try {
await this.page.waitForSelector(this.selectors.dashboardContainer, {
state: 'visible',
timeout: this.timeout.default
});
testLogger.info('仪表盘页面加载完成');
} catch (error) {
testLogger.error('仪表盘页面加载超时', error as Error);
await this.screenshotHelper.takeScreenshot('dashboard-load-error');
throw error;
}
}
async getPageTitle(): Promise<string> {
testLogger.debug('获取页面标题');
try {
const titleElement = this.page.locator(this.selectors.pageTitle);
await titleElement.waitFor({ state: 'visible', timeout: this.timeout.element });
const title = await titleElement.textContent();
testLogger.debug(`页面标题: ${title}`);
return title || '';
} catch (error) {
testLogger.error('获取页面标题失败', error as Error);
return '';
}
}
async getWelcomeMessage(): Promise<string> {
testLogger.debug('获取欢迎消息');
try {
const welcomeElement = this.page.locator(this.selectors.welcomeMessage);
await welcomeElement.waitFor({ state: 'visible', timeout: this.timeout.element });
const message = await welcomeElement.textContent();
testLogger.debug(`欢迎消息: ${message}`);
return message || '';
} catch (error) {
testLogger.error('获取欢迎消息失败', error as Error);
return '';
}
}
async getStatisticsCardCount(): Promise<number> {
testLogger.debug('获取统计卡片数量');
try {
const cards = this.page.locator(this.selectors.statisticsCards);
const count = await cards.count();
testLogger.debug(`统计卡片数量: ${count}`);
return count;
} catch (error) {
testLogger.error('获取统计卡片数量失败', error as Error);
return 0;
}
}
async getChartCount(): Promise<number> {
testLogger.debug('获取图表数量');
try {
const charts = this.page.locator(this.selectors.charts);
const count = await charts.count();
testLogger.debug(`图表数量: ${count}`);
return count;
} catch (error) {
testLogger.error('获取图表数量失败', error as Error);
return 0;
}
}
async clickMenuItem(menuName: string): Promise<void> {
testLogger.info(`点击菜单项: ${menuName}`);
try {
const menuItem = this.page.locator(this.selectors.menuItems).filter({ hasText: menuName });
await menuItem.waitFor({ state: 'visible', timeout: this.timeout.element });
await menuItem.click();
testLogger.info(`菜单项点击成功: ${menuName}`);
} catch (error) {
testLogger.error(`点击菜单项失败: ${menuName}`, error as Error);
await this.screenshotHelper.takeScreenshot(`click-menu-${menuName}-error`);
throw error;
}
}
async clickQuickAction(actionName: string): Promise<void> {
testLogger.info(`点击快捷操作: ${actionName}`);
try {
const quickAction = this.page.locator(this.selectors.quickActions).filter({ hasText: actionName });
await quickAction.waitFor({ state: 'visible', timeout: this.timeout.element });
await quickAction.click();
testLogger.info(`快捷操作点击成功: ${actionName}`);
} catch (error) {
testLogger.error(`点击快捷操作失败: ${actionName}`, error as Error);
await this.screenshotHelper.takeScreenshot(`click-quick-action-${actionName}-error`);
throw error;
}
}
async isDashboardVisible(): Promise<boolean> {
testLogger.debug('检查仪表盘是否可见');
try {
const dashboard = this.page.locator(this.selectors.dashboardContainer);
const isVisible = await dashboard.isVisible();
testLogger.debug(`仪表盘可见性: ${isVisible}`);
return isVisible;
} catch (error) {
testLogger.error('检查仪表盘可见性失败', error as Error);
return false;
}
}
async waitForStatistics(): Promise<void> {
testLogger.info('等待统计数据加载');
try {
await this.page.waitForSelector(this.selectors.statisticsCards, {
state: 'visible',
timeout: this.timeout.network
});
testLogger.info('统计数据加载完成');
} catch (error) {
testLogger.error('等待统计数据加载超时', error as Error);
await this.screenshotHelper.takeScreenshot('statistics-load-error');
throw error;
}
}
async waitForCharts(): Promise<void> {
testLogger.info('等待图表加载');
try {
await this.page.waitForSelector(this.selectors.charts, {
state: 'visible',
timeout: this.timeout.network
});
testLogger.info('图表加载完成');
} catch (error) {
testLogger.error('等待图表加载超时', error as Error);
await this.screenshotHelper.takeScreenshot('charts-load-error');
throw error;
}
}
}
@@ -0,0 +1,233 @@
import { Page, expect } from '@playwright/test';
import { BasePage } from './base-page';
import { testLogger } from '../core/test-logger';
export class LoginPage extends BasePage {
private readonly selectors = {
usernameInput: '[data-testid="username-input"]',
passwordInput: '[data-testid="password-input"]',
loginButton: '[data-testid="login-button"]',
errorMessage: '.ant-message-error',
successMessage: '.ant-message-success',
loginForm: '[data-testid="login-form"]',
rememberMeCheckbox: '[data-testid="remember-checkbox"]',
forgotPasswordLink: 'text=忘记密码',
registerLink: 'text=注册账号'
};
constructor(page: Page) {
super(page);
}
async navigate(): Promise<void> {
testLogger.info('导航到登录页面');
await super.navigate('/login');
}
async waitForLoad(): Promise<void> {
testLogger.debug('等待登录页面加载完成');
try {
await Promise.all([
this.waitForElement(this.selectors.usernameInput),
this.waitForElement(this.selectors.passwordInput),
this.waitForElement(this.selectors.loginButton)
]);
testLogger.info('登录页面加载完成');
} catch (error) {
testLogger.error('登录页面加载失败', error as Error);
throw error;
}
}
async getUsernameInput() {
return this.page.locator(this.selectors.usernameInput);
}
async getPasswordInput() {
return this.page.locator(this.selectors.passwordInput);
}
async getLoginButton() {
return this.page.locator(this.selectors.loginButton);
}
async getErrorMessage() {
return this.page.locator(this.selectors.errorMessage);
}
async getSuccessMessage() {
return this.page.locator(this.selectors.successMessage);
}
async fillUsername(username: string): Promise<void> {
testLogger.info(`填写用户名: ${username}`);
await this.fill(this.selectors.usernameInput, username);
}
async fillPassword(password: string): Promise<void> {
testLogger.info('填写密码');
await this.fill(this.selectors.passwordInput, password);
}
async clickLoginButton(): Promise<void> {
testLogger.info('点击登录按钮');
await this.click(this.selectors.loginButton);
}
async toggleRememberMe(remember: boolean): Promise<void> {
testLogger.info(`设置记住密码: ${remember}`);
if (remember) {
await this.check(this.selectors.rememberMeCheckbox);
} else {
await this.uncheck(this.selectors.rememberMeCheckbox);
}
}
async login(username: string, password: string, rememberMe: boolean = false): Promise<void> {
testLogger.startStep('用户登录');
try {
await this.waitForLoad();
await this.fillUsername(username);
await this.fillPassword(password);
if (rememberMe) {
await this.toggleRememberMe(true);
}
await this.clickLoginButton();
testLogger.endStep('用户登录', 'passed');
} catch (error) {
testLogger.endStep('用户登录', 'failed', error as Error);
throw error;
}
}
async loginAndWaitForDashboard(username: string, password: string, rememberMe: boolean = false): Promise<void> {
testLogger.startStep('登录并等待跳转到仪表盘');
try {
await this.login(username, password, rememberMe);
await this.waitForURL(/.*dashboard/, this.timeout.navigation);
testLogger.endStep('登录并等待跳转到仪表盘', 'passed');
} catch (error) {
testLogger.endStep('登录并等待跳转到仪表盘', 'failed', error as Error);
throw error;
}
}
async expectErrorMessage(message: string): Promise<void> {
testLogger.debug(`期望错误消息: ${message}`);
const errorLocator = this.getErrorMessage();
await expect(errorLocator).toBeVisible({ timeout: 5000 });
await expect(errorLocator).toContainText(message);
}
async expectSuccessMessage(message: string): Promise<void> {
testLogger.debug(`期望成功消息: ${message}`);
const successLocator = this.getSuccessMessage();
await expect(successLocator).toBeVisible({ timeout: 5000 });
await expect(successLocator).toContainText(message);
}
async hasErrorMessage(): Promise<boolean> {
const errorLocator = this.getErrorMessage();
const count = await errorLocator.count();
return count > 0;
}
async hasSuccessMessage(): Promise<boolean> {
const successLocator = this.getSuccessMessage();
const count = await successLocator.count();
return count > 0;
}
async getErrorMessageText(): Promise<string> {
const errorLocator = this.getErrorMessage();
const text = await errorLocator.textContent();
return text || '';
}
async getSuccessMessageText(): Promise<string> {
const successLocator = this.getSuccessMessage();
const text = await successLocator.textContent();
return text || '';
}
async isLoginButtonEnabled(): Promise<boolean> {
const loginButton = this.getLoginButton();
return await loginButton.isEnabled();
}
async isUsernameInputVisible(): Promise<boolean> {
return await this.isVisible(this.selectors.usernameInput);
}
async isPasswordInputVisible(): Promise<boolean> {
return await this.isVisible(this.selectors.passwordInput);
}
async isLoginFormVisible(): Promise<boolean> {
return await this.isVisible(this.selectors.loginForm);
}
async clearUsername(): Promise<void> {
testLogger.info('清空用户名输入框');
const usernameInput = this.getUsernameInput();
await usernameInput.fill('');
}
async clearPassword(): Promise<void> {
testLogger.info('清空密码输入框');
const passwordInput = this.getPasswordInput();
await passwordInput.fill('');
}
async clearAllFields(): Promise<void> {
await this.clearUsername();
await this.clearPassword();
}
async clickForgotPassword(): Promise<void> {
testLogger.info('点击忘记密码链接');
await this.click(this.selectors.forgotPasswordLink);
}
async clickRegister(): Promise<void> {
testLogger.info('点击注册账号链接');
await this.click(this.selectors.registerLink);
}
async pressEnter(): Promise<void> {
testLogger.info('按Enter键提交登录');
await this.page.keyboard.press('Enter');
}
async loginWithEnter(username: string, password: string): Promise<void> {
testLogger.startStep('使用Enter键登录');
try {
await this.waitForLoad();
await this.fillUsername(username);
await this.fillPassword(password);
await this.pressEnter();
testLogger.endStep('使用Enter键登录', 'passed');
} catch (error) {
testLogger.endStep('使用Enter键登录', 'failed', error as Error);
throw error;
}
}
async takeScreenshotOnLogin(name: string = 'login-page'): Promise<string> {
return await this.takeScreenshot(name);
}
}
@@ -0,0 +1,262 @@
import { Page } from '@playwright/test';
import { BasePage } from './base-page';
import { FormHelper } from '../helpers/form-helper';
import { TableHelper } from '../helpers/table-helper';
import { ScreenshotHelper } from '../helpers/screenshot-helper';
import { testLogger } from '../core/test-logger';
export class MenuManagementPage extends BasePage {
private formHelper: FormHelper;
private tableHelper: TableHelper;
private screenshotHelper: ScreenshotHelper;
private readonly selectors = {
menuTree: '.menu-tree',
menuTable: '.menu-table',
addMenuButton: 'button:has-text("新增")',
editButton: 'button:has-text("编辑")',
deleteButton: 'button:has-text("删除")',
searchInput: 'input[placeholder*="搜索"]',
searchButton: 'button:has-text("查询")',
resetButton: 'button:has-text("重置")',
modal: '.ant-modal',
modalTitle: '.ant-modal-title',
modalConfirmButton: '.ant-modal-confirm-btn',
modalCancelButton: '.ant-modal-cancel-btn',
successMessage: '.ant-message-success',
errorMessage: '.ant-message-error',
menuForm: '.menu-form',
menuNameInput: 'input[name="menuName"]',
menuTypeSelect: 'select[name="menuType"]',
menuIconInput: 'input[name="icon"]',
orderNumInput: 'input[name="orderNum"]',
pathInput: 'input[name="path"]',
componentInput: 'input[name="component"]',
statusSelect: 'select[name="status"]',
visibleSelect: 'select[name="visible"]',
remarkInput: 'textarea[name="remark"]',
treeNode: '.ant-tree-node',
treeExpandButton: '.ant-tree-switcher'
};
constructor(page: Page) {
super(page);
this.formHelper = new FormHelper(page);
this.tableHelper = new TableHelper(page);
this.screenshotHelper = new ScreenshotHelper(page);
}
async navigate(): Promise<void> {
testLogger.info('导航到菜单管理页面');
await super.navigate('/system/menu');
}
async waitForLoad(): Promise<void> {
testLogger.info('等待菜单管理页面加载');
try {
await this.page.waitForSelector(this.selectors.menuTree, {
state: 'visible',
timeout: this.timeout.default
});
testLogger.info('菜单管理页面加载完成');
} catch (error) {
testLogger.error('菜单管理页面加载超时', error as Error);
await this.screenshotHelper.takeScreenshot('menu-management-load-error');
throw error;
}
}
async clickAddMenu(): Promise<void> {
testLogger.info('点击新增菜单按钮');
try {
await this.page.waitForSelector(this.selectors.addMenuButton, {
state: 'visible',
timeout: this.timeout.element
});
await this.page.click(this.selectors.addMenuButton);
await this.page.waitForSelector(this.selectors.modal, {
state: 'visible',
timeout: this.timeout.element
});
testLogger.info('新增菜单对话框已打开');
} catch (error) {
testLogger.error('点击新增菜单按钮失败', error as Error);
await this.screenshotHelper.takeScreenshot('click-add-menu-error');
throw error;
}
}
async clickEditMenu(menuName: string): Promise<void> {
testLogger.info(`点击编辑菜单按钮,菜单名称: ${menuName}`);
try {
const editButtons = this.page.locator(this.selectors.editButton);
await editButtons.first().waitFor({ state: 'visible', timeout: this.timeout.element });
await editButtons.first().click();
await this.page.waitForSelector(this.selectors.modal, {
state: 'visible',
timeout: this.timeout.element
});
testLogger.info('编辑菜单对话框已打开');
} catch (error) {
testLogger.error(`点击编辑菜单按钮失败,菜单名称: ${menuName}`, error as Error);
await this.screenshotHelper.takeScreenshot(`click-edit-menu-${menuName}-error`);
throw error;
}
}
async clickDeleteMenu(menuName: string): Promise<void> {
testLogger.info(`点击删除菜单按钮,菜单名称: ${menuName}`);
try {
const deleteButtons = this.page.locator(this.selectors.deleteButton);
await deleteButtons.first().waitFor({ state: 'visible', timeout: this.timeout.element });
await deleteButtons.first().click();
testLogger.info('删除确认对话框已打开');
} catch (error) {
testLogger.error(`点击删除菜单按钮失败,菜单名称: ${menuName}`, error as Error);
await this.screenshotHelper.takeScreenshot(`click-delete-menu-${menuName}-error`);
throw error;
}
}
async confirmDelete(): Promise<void> {
testLogger.info('确认删除菜单');
try {
await this.page.waitForSelector(this.selectors.modalConfirmButton, {
state: 'visible',
timeout: this.timeout.element
});
await this.page.click(this.selectors.modalConfirmButton);
await this.page.waitForSelector(this.selectors.modal, {
state: 'hidden',
timeout: this.timeout.element
});
testLogger.info('菜单删除确认成功');
} catch (error) {
testLogger.error('确认删除菜单失败', error as Error);
await this.screenshotHelper.takeScreenshot('confirm-delete-error');
throw error;
}
}
async searchMenu(keyword: string): Promise<void> {
testLogger.info(`搜索菜单,关键词: ${keyword}`);
try {
await this.page.waitForSelector(this.selectors.searchInput, {
state: 'visible',
timeout: this.timeout.element
});
await this.page.fill(this.selectors.searchInput, keyword);
await this.page.click(this.selectors.searchButton);
await this.page.waitForLoadState('networkidle', {
timeout: this.timeout.network
});
testLogger.info('菜单搜索完成');
} catch (error) {
testLogger.error(`搜索菜单失败,关键词: ${keyword}`, error as Error);
await this.screenshotHelper.takeScreenshot('search-menu-error');
throw error;
}
}
async expandTreeNode(nodeIndex: number): Promise<void> {
testLogger.info(`展开树节点,索引: ${nodeIndex}`);
try {
const expandButtons = this.page.locator(this.selectors.treeExpandButton);
await expandButtons.nth(nodeIndex).waitFor({ state: 'visible', timeout: this.timeout.element });
await expandButtons.nth(nodeIndex).click();
testLogger.info(`树节点已展开,索引: ${nodeIndex}`);
} catch (error) {
testLogger.error(`展开树节点失败,索引: ${nodeIndex}`, error as Error);
await this.screenshotHelper.takeScreenshot(`expand-tree-node-${nodeIndex}-error`);
throw error;
}
}
async getSuccessMessage(): Promise<string> {
testLogger.debug('获取成功消息');
try {
const successElement = this.page.locator(this.selectors.successMessage);
await successElement.waitFor({ state: 'visible', timeout: this.timeout.element });
const message = await successElement.textContent();
testLogger.debug(`成功消息: ${message}`);
return message || '';
} catch (error) {
testLogger.error('获取成功消息失败', error as Error);
return '';
}
}
async getErrorMessage(): Promise<string> {
testLogger.debug('获取错误消息');
try {
const errorElement = this.page.locator(this.selectors.errorMessage);
await errorElement.waitFor({ state: 'visible', timeout: this.timeout.element });
const message = await errorElement.textContent();
testLogger.debug(`错误消息: ${message}`);
return message || '';
} catch (error) {
testLogger.error('获取错误消息失败', error as Error);
return '';
}
}
async getMenuCount(): Promise<number> {
testLogger.debug('获取菜单数量');
try {
const count = await this.tableHelper.getRowCount(this.selectors.menuTable);
testLogger.debug(`菜单数量: ${count}`);
return count;
} catch (error) {
testLogger.error('获取菜单数量失败', error as Error);
return 0;
}
}
async getTreeNodeCount(): Promise<number> {
testLogger.debug('获取树节点数量');
try {
const treeNodes = this.page.locator(this.selectors.treeNode);
const count = await treeNodes.count();
testLogger.debug(`树节点数量: ${count}`);
return count;
} catch (error) {
testLogger.error('获取树节点数量失败', error as Error);
return 0;
}
}
}
@@ -0,0 +1,224 @@
import { Page } from '@playwright/test';
import { BasePage } from './base-page';
import { FormHelper } from '../helpers/form-helper';
import { TableHelper } from '../helpers/table-helper';
import { ScreenshotHelper } from '../helpers/screenshot-helper';
import { testLogger } from '../core/test-logger';
export class RoleManagementPage extends BasePage {
private formHelper: FormHelper;
private tableHelper: TableHelper;
private screenshotHelper: ScreenshotHelper;
private readonly selectors = {
roleTable: '.role-table',
addRoleButton: 'button:has-text("新增")',
editButton: 'button:has-text("编辑")',
deleteButton: 'button:has-text("删除")',
searchInput: 'input[placeholder*="搜索"]',
searchButton: 'button:has-text("查询")',
resetButton: 'button:has-text("重置")',
modal: '.ant-modal',
modalTitle: '.ant-modal-title',
modalConfirmButton: '.ant-modal-confirm-btn',
modalCancelButton: '.ant-modal-cancel-btn',
successMessage: '.ant-message-success',
errorMessage: '.ant-message-error',
roleForm: '.role-form',
roleNameInput: 'input[name="roleName"]',
roleKeyInput: 'input[name="roleKey"]',
roleSortInput: 'input[name="roleSort"]',
statusSelect: 'select[name="status"]',
remarkInput: 'textarea[name="remark"]',
permissionTree: '.permission-tree',
permissionCheckbox: '.ant-tree-checkbox'
};
constructor(page: Page) {
super(page);
this.formHelper = new FormHelper(page);
this.tableHelper = new TableHelper(page);
this.screenshotHelper = new ScreenshotHelper(page);
}
async navigate(): Promise<void> {
testLogger.info('导航到角色管理页面');
await super.navigate('/system/role');
}
async waitForLoad(): Promise<void> {
testLogger.info('等待角色管理页面加载');
try {
await this.page.waitForSelector(this.selectors.roleTable, {
state: 'visible',
timeout: this.timeout.default
});
testLogger.info('角色管理页面加载完成');
} catch (error) {
testLogger.error('角色管理页面加载超时', error as Error);
await this.screenshotHelper.takeScreenshot('role-management-load-error');
throw error;
}
}
async clickAddRole(): Promise<void> {
testLogger.info('点击新增角色按钮');
try {
await this.page.waitForSelector(this.selectors.addRoleButton, {
state: 'visible',
timeout: this.timeout.element
});
await this.page.click(this.selectors.addRoleButton);
await this.page.waitForSelector(this.selectors.modal, {
state: 'visible',
timeout: this.timeout.element
});
testLogger.info('新增角色对话框已打开');
} catch (error) {
testLogger.error('点击新增角色按钮失败', error as Error);
await this.screenshotHelper.takeScreenshot('click-add-role-error');
throw error;
}
}
async clickEditRole(rowIndex: number): Promise<void> {
testLogger.info(`点击编辑角色按钮,行索引: ${rowIndex}`);
try {
const editButtons = this.page.locator(this.selectors.editButton);
await editButtons.nth(rowIndex).waitFor({ state: 'visible', timeout: this.timeout.element });
await editButtons.nth(rowIndex).click();
await this.page.waitForSelector(this.selectors.modal, {
state: 'visible',
timeout: this.timeout.element
});
testLogger.info('编辑角色对话框已打开');
} catch (error) {
testLogger.error(`点击编辑角色按钮失败,行索引: ${rowIndex}`, error as Error);
await this.screenshotHelper.takeScreenshot(`click-edit-role-${rowIndex}-error`);
throw error;
}
}
async clickDeleteRole(rowIndex: number): Promise<void> {
testLogger.info(`点击删除角色按钮,行索引: ${rowIndex}`);
try {
const deleteButtons = this.page.locator(this.selectors.deleteButton);
await deleteButtons.nth(rowIndex).waitFor({ state: 'visible', timeout: this.timeout.element });
await deleteButtons.nth(rowIndex).click();
testLogger.info('删除确认对话框已打开');
} catch (error) {
testLogger.error(`点击删除角色按钮失败,行索引: ${rowIndex}`, error as Error);
await this.screenshotHelper.takeScreenshot(`click-delete-role-${rowIndex}-error`);
throw error;
}
}
async confirmDelete(): Promise<void> {
testLogger.info('确认删除角色');
try {
await this.page.waitForSelector(this.selectors.modalConfirmButton, {
state: 'visible',
timeout: this.timeout.element
});
await this.page.click(this.selectors.modalConfirmButton);
await this.page.waitForSelector(this.selectors.modal, {
state: 'hidden',
timeout: this.timeout.element
});
testLogger.info('角色删除确认成功');
} catch (error) {
testLogger.error('确认删除角色失败', error as Error);
await this.screenshotHelper.takeScreenshot('confirm-delete-error');
throw error;
}
}
async searchRole(keyword: string): Promise<void> {
testLogger.info(`搜索角色,关键词: ${keyword}`);
try {
await this.page.waitForSelector(this.selectors.searchInput, {
state: 'visible',
timeout: this.timeout.element
});
await this.page.fill(this.selectors.searchInput, keyword);
await this.page.click(this.selectors.searchButton);
await this.page.waitForLoadState('networkidle', {
timeout: this.timeout.network
});
testLogger.info('角色搜索完成');
} catch (error) {
testLogger.error(`搜索角色失败,关键词: ${keyword}`, error as Error);
await this.screenshotHelper.takeScreenshot('search-role-error');
throw error;
}
}
async getSuccessMessage(): Promise<string> {
testLogger.debug('获取成功消息');
try {
const successElement = this.page.locator(this.selectors.successMessage);
await successElement.waitFor({ state: 'visible', timeout: this.timeout.element });
const message = await successElement.textContent();
testLogger.debug(`成功消息: ${message}`);
return message || '';
} catch (error) {
testLogger.error('获取成功消息失败', error as Error);
return '';
}
}
async getErrorMessage(): Promise<string> {
testLogger.debug('获取错误消息');
try {
const errorElement = this.page.locator(this.selectors.errorMessage);
await errorElement.waitFor({ state: 'visible', timeout: this.timeout.element });
const message = await errorElement.textContent();
testLogger.debug(`错误消息: ${message}`);
return message || '';
} catch (error) {
testLogger.error('获取错误消息失败', error as Error);
return '';
}
}
async getRoleCount(): Promise<number> {
testLogger.debug('获取角色数量');
try {
const count = await this.tableHelper.getRowCount(this.selectors.roleTable);
testLogger.debug(`角色数量: ${count}`);
return count;
} catch (error) {
testLogger.error('获取角色数量失败', error as Error);
return 0;
}
}
}
@@ -0,0 +1,250 @@
import { Page } from '@playwright/test';
import { BasePage } from './base-page';
import { FormHelper } from '../helpers/form-helper';
import { TableHelper } from '../helpers/table-helper';
import { ScreenshotHelper } from '../helpers/screenshot-helper';
import { testLogger } from '../core/test-logger';
export class UserManagementPage extends BasePage {
private formHelper: FormHelper;
private tableHelper: TableHelper;
private screenshotHelper: ScreenshotHelper;
private readonly selectors = {
userTable: '[data-testid="user-table"]',
addUserButton: '[data-testid="add-user-button"]',
editButton: 'button:has-text("编辑")',
deleteButton: 'button:has-text("删除")',
searchInput: '[data-testid="username-search-input"]',
emailSearchInput: '[data-testid="email-search-input"]',
statusSelect: '[data-testid="status-select"]',
searchButton: '[data-testid="search-button"]',
resetButton: '[data-testid="reset-button"]',
modal: '.ant-modal',
modalTitle: '.ant-modal-title',
modalConfirmButton: '.ant-modal .ant-btn-primary',
modalCancelButton: '.ant-modal .ant-btn-default',
successMessage: '.ant-message-success',
errorMessage: '.ant-message-error',
pagination: '.ant-pagination',
userForm: '.user-form',
usernameInput: 'input[name="username"]',
passwordInput: 'input[name="password"]',
emailInput: 'input[name="email"]',
phoneInput: 'input[name="phone"]',
realNameInput: 'input[name="realName"]',
statusSelect: 'select[name="status"]',
roleSelect: 'select[name="roleIds"]'
};
constructor(page: Page) {
super(page);
this.formHelper = new FormHelper(page);
this.tableHelper = new TableHelper(page);
this.screenshotHelper = new ScreenshotHelper(page);
}
async navigate(): Promise<void> {
testLogger.info('导航到用户管理页面');
await super.navigate('/system/user');
}
async waitForLoad(): Promise<void> {
testLogger.info('等待用户管理页面加载');
try {
await this.page.waitForSelector(this.selectors.userTable, {
state: 'visible',
timeout: this.timeout.default
});
testLogger.info('用户管理页面加载完成');
} catch (error) {
testLogger.error('用户管理页面加载超时', error as Error);
await this.screenshotHelper.takeScreenshot('user-management-load-error');
throw error;
}
}
async clickAddUser(): Promise<void> {
testLogger.info('点击新增用户按钮');
try {
await this.page.waitForSelector(this.selectors.addUserButton, {
state: 'visible',
timeout: this.timeout.element
});
await this.page.click(this.selectors.addUserButton);
await this.page.waitForSelector(this.selectors.modal, {
state: 'visible',
timeout: this.timeout.element
});
testLogger.info('新增用户对话框已打开');
} catch (error) {
testLogger.error('点击新增用户按钮失败', error as Error);
await this.screenshotHelper.takeScreenshot('click-add-user-error');
throw error;
}
}
async clickEditUser(rowIndex: number): Promise<void> {
testLogger.info(`点击编辑用户按钮,行索引: ${rowIndex}`);
try {
const editButtons = this.page.locator(this.selectors.editButton);
await editButtons.nth(rowIndex).waitFor({ state: 'visible', timeout: this.timeout.element });
await editButtons.nth(rowIndex).click();
await this.page.waitForSelector(this.selectors.modal, {
state: 'visible',
timeout: this.timeout.element
});
testLogger.info('编辑用户对话框已打开');
} catch (error) {
testLogger.error(`点击编辑用户按钮失败,行索引: ${rowIndex}`, error as Error);
await this.screenshotHelper.takeScreenshot(`click-edit-user-${rowIndex}-error`);
throw error;
}
}
async clickDeleteUser(rowIndex: number): Promise<void> {
testLogger.info(`点击删除用户按钮,行索引: ${rowIndex}`);
try {
const deleteButtons = this.page.locator(this.selectors.deleteButton);
await deleteButtons.nth(rowIndex).waitFor({ state: 'visible', timeout: this.timeout.element });
await deleteButtons.nth(rowIndex).click();
testLogger.info('删除确认对话框已打开');
} catch (error) {
testLogger.error(`点击删除用户按钮失败,行索引: ${rowIndex}`, error as Error);
await this.screenshotHelper.takeScreenshot(`click-delete-user-${rowIndex}-error`);
throw error;
}
}
async confirmDelete(): Promise<void> {
testLogger.info('确认删除用户');
try {
await this.page.waitForSelector(this.selectors.modalConfirmButton, {
state: 'visible',
timeout: this.timeout.element
});
await this.page.click(this.selectors.modalConfirmButton);
await this.page.waitForSelector(this.selectors.modal, {
state: 'hidden',
timeout: this.timeout.element
});
testLogger.info('用户删除确认成功');
} catch (error) {
testLogger.error('确认删除用户失败', error as Error);
await this.screenshotHelper.takeScreenshot('confirm-delete-error');
throw error;
}
}
async searchUser(keyword: string): Promise<void> {
testLogger.info(`搜索用户,关键词: ${keyword}`);
try {
await this.page.waitForSelector(this.selectors.searchInput, {
state: 'visible',
timeout: this.timeout.element
});
await this.page.fill(this.selectors.searchInput, keyword);
await this.page.click(this.selectors.searchButton);
await this.page.waitForLoadState('networkidle', {
timeout: this.timeout.network
});
testLogger.info('用户搜索完成');
} catch (error) {
testLogger.error(`搜索用户失败,关键词: ${keyword}`, error as Error);
await this.screenshotHelper.takeScreenshot('search-user-error');
throw error;
}
}
async resetSearch(): Promise<void> {
testLogger.info('重置搜索条件');
try {
await this.page.waitForSelector(this.selectors.resetButton, {
state: 'visible',
timeout: this.timeout.element
});
await this.page.click(this.selectors.resetButton);
await this.page.waitForLoadState('networkidle', {
timeout: this.timeout.network
});
testLogger.info('搜索条件已重置');
} catch (error) {
testLogger.error('重置搜索条件失败', error as Error);
await this.screenshotHelper.takeScreenshot('reset-search-error');
throw error;
}
}
async getSuccessMessage(): Promise<string> {
testLogger.debug('获取成功消息');
try {
const successElement = this.page.locator(this.selectors.successMessage);
await successElement.waitFor({ state: 'visible', timeout: this.timeout.element });
const message = await successElement.textContent();
testLogger.debug(`成功消息: ${message}`);
return message || '';
} catch (error) {
testLogger.error('获取成功消息失败', error as Error);
return '';
}
}
async getErrorMessage(): Promise<string> {
testLogger.debug('获取错误消息');
try {
const errorElement = this.page.locator(this.selectors.errorMessage);
await errorElement.waitFor({ state: 'visible', timeout: this.timeout.element });
const message = await errorElement.textContent();
testLogger.debug(`错误消息: ${message}`);
return message || '';
} catch (error) {
testLogger.error('获取错误消息失败', error as Error);
return '';
}
}
async getUserCount(): Promise<number> {
testLogger.debug('获取用户数量');
try {
const count = await this.tableHelper.getRowCount(this.selectors.userTable);
testLogger.debug(`用户数量: ${count}`);
return count;
} catch (error) {
testLogger.error('获取用户数量失败', error as Error);
return 0;
}
}
}