feat(admin): 添加用户管理相关文件
添加用户管理视图、API和状态管理文件
This commit is contained in:
@@ -0,0 +1,284 @@
|
||||
/**
|
||||
* 基础页面类
|
||||
* 所有页面对象的基类,提供通用的页面操作方法
|
||||
*/
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user