Files
张翔 08ea5fbe98 feat(admin): 添加用户管理相关文件
添加用户管理视图、API和状态管理文件
2026-03-28 14:37:29 +08:00

285 lines
6.9 KiB
TypeScript

/**
* 基础页面类
* 所有页面对象的基类,提供通用的页面操作方法
*/
import { Page, Locator, expect } from '@playwright/test';
import { testConfig } from '../config/test-config';
import { testLogger } from '../utils/test-logger';
export interface PageOptions {
baseURL?: string;
timeout?: number;
}
export class BasePage {
protected page: Page;
protected baseURL: string;
protected defaultTimeout: number;
constructor(page: Page, options: PageOptions = {}) {
this.page = page;
this.baseURL = options.baseURL || testConfig.getConfig().baseURL;
this.defaultTimeout = options.timeout || testConfig.getTimeout('default');
}
/**
* 导航到指定路径
*/
async navigate(path: string): Promise<void> {
testLogger.debug(`导航到: ${this.baseURL}${path}`);
await this.page.goto(`${this.baseURL}${path}`, {
timeout: testConfig.getTimeout('navigation'),
});
await this.waitForLoad();
}
/**
* 等待页面加载完成
*/
async waitForLoad(): Promise<void> {
await this.page.waitForLoadState('networkidle', {
timeout: this.defaultTimeout,
});
}
/**
* 等待元素可见
*/
async waitForVisible(selector: string, timeout?: number): Promise<Locator> {
const locator = this.page.locator(selector);
await locator.waitFor({
state: 'visible',
timeout: timeout || this.defaultTimeout,
});
return locator;
}
/**
* 等待元素隐藏
*/
async waitForHidden(selector: string, timeout?: number): Promise<void> {
const locator = this.page.locator(selector);
await locator.waitFor({
state: 'hidden',
timeout: timeout || this.defaultTimeout,
});
}
/**
* 点击元素
*/
async clickElement(selector: string, options?: { force?: boolean }): Promise<void> {
testLogger.debug(`点击元素: ${selector}`);
const locator = await this.waitForVisible(selector);
await locator.click({ force: options?.force });
}
/**
* 填写输入框
*/
async fillInput(selector: string, value: string, options?: { clear?: boolean }): Promise<void> {
testLogger.debug(`填写输入框: ${selector} = ${value}`);
const locator = await this.waitForVisible(selector);
if (options?.clear !== false) {
await locator.clear();
}
await locator.fill(value);
}
/**
* 获取元素文本
*/
async getElementText(selector: string): Promise<string> {
const locator = await this.waitForVisible(selector);
return await locator.textContent() || '';
}
/**
* 检查元素是否存在
*/
async elementExists(selector: string): Promise<boolean> {
const locator = this.page.locator(selector);
return await locator.count() > 0;
}
/**
* 检查元素是否可见
*/
async isElementVisible(selector: string): Promise<boolean> {
const locator = this.page.locator(selector);
return await locator.isVisible().catch(() => false);
}
/**
* 获取页面标题
*/
async getPageTitle(): Promise<string> {
return await this.page.title();
}
/**
* 获取当前URL
*/
async getCurrentURL(): Promise<string> {
return this.page.url();
}
/**
* 等待URL变化
*/
async waitForURL(pattern: string | RegExp, timeout?: number): Promise<void> {
await this.page.waitForURL(pattern, {
timeout: timeout || testConfig.getTimeout('navigation'),
});
}
/**
* 截图
*/
async takeScreenshot(name: string, fullPage: boolean = true): Promise<string> {
const path = `test-results/screenshots/${name}.png`;
await this.page.screenshot({ path, fullPage });
testLogger.addScreenshot(path);
return path;
}
/**
* 滚动到元素
*/
async scrollToElement(selector: string): Promise<void> {
const locator = this.page.locator(selector);
await locator.scrollIntoViewIfNeeded();
}
/**
* 滚动页面
*/
async scrollPage(x: number, y: number): Promise<void> {
await this.page.evaluate(([scrollX, scrollY]) => {
window.scrollTo(scrollX, scrollY);
}, [x, y]);
}
/**
* 等待指定时间
*/
async waitForTimeout(ms: number): Promise<void> {
await this.page.waitForTimeout(ms);
}
/**
* 选择下拉框选项
*/
async selectOption(selector: string, value: string): Promise<void> {
const locator = this.page.locator(selector);
await locator.selectOption(value);
}
/**
* 检查复选框
*/
async checkCheckbox(selector: string): Promise<void> {
const locator = this.page.locator(selector);
await locator.check();
}
/**
* 取消复选框
*/
async uncheckCheckbox(selector: string): Promise<void> {
const locator = this.page.locator(selector);
await locator.uncheck();
}
/**
* 获取元素数量
*/
async getElementCount(selector: string): Promise<number> {
return await this.page.locator(selector).count();
}
/**
* 悬停在元素上
*/
async hoverElement(selector: string): Promise<void> {
const locator = await this.waitForVisible(selector);
await locator.hover();
}
/**
* 拖拽元素
*/
async dragElement(sourceSelector: string, targetSelector: string): Promise<void> {
const source = this.page.locator(sourceSelector);
const target = this.page.locator(targetSelector);
await source.dragTo(target);
}
/**
* 刷新页面
*/
async reload(): Promise<void> {
await this.page.reload();
await this.waitForLoad();
}
/**
* 返回上一页
*/
async goBack(): Promise<void> {
await this.page.goBack();
await this.waitForLoad();
}
/**
* 前进到下一页
*/
async goForward(): Promise<void> {
await this.page.goForward();
await this.waitForLoad();
}
/**
* 执行键盘操作
*/
async pressKey(key: string): Promise<void> {
await this.page.keyboard.press(key);
}
/**
* 上传文件
*/
async uploadFile(selector: string, filePath: string): Promise<void> {
const locator = this.page.locator(selector);
await locator.setInputFiles(filePath);
}
/**
* 获取页面性能指标
*/
async getPerformanceMetrics(): Promise<Record<string, number>> {
return await this.page.evaluate(() => {
const navigation = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming;
return {
loadTime: navigation.loadEventEnd - navigation.startTime,
domContentLoaded: navigation.domContentLoadedEventEnd - navigation.startTime,
firstPaint: performance.getEntriesByName('first-paint')[0]?.startTime || 0,
firstContentfulPaint: performance.getEntriesByName('first-contentful-paint')[0]?.startTime || 0,
};
});
}
/**
* 验证页面加载性能
*/
async verifyPerformance(maxLoadTime: number = 3000): Promise<void> {
const metrics = await this.getPerformanceMetrics();
testLogger.info('页面性能指标', metrics);
expect(metrics.loadTime).toBeLessThan(maxLoadTime);
}
}