08ea5fbe98
添加用户管理视图、API和状态管理文件
415 lines
13 KiB
TypeScript
415 lines
13 KiB
TypeScript
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.getEnvironment().baseURL;
|
|
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 });
|
|
}
|
|
}
|