feat(admin): 添加用户管理相关文件
添加用户管理视图、API和状态管理文件
This commit is contained in:
@@ -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 });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user