playwright/tests/pages/BasePage.js
2025-03-06 13:19:29 +08:00

290 lines
8.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 页面对象模型基类
* 提供所有页面共用的方法和属性
*/
class BasePage {
/**
* 创建页面对象
* @param {import('@playwright/test').Page} page Playwright页面对象
*/
constructor(page) {
this.page = page;
this.initializeSelectors();
this.initializeConfig();
}
/**
* 初始化选择器
* @protected
*/
initializeSelectors() {
this.selectors = {
// 通用加载状态
loadingMask: '.el-loading-mask',
// 通用错误提示
errorBox: '.el-message-box__message',
errorMessage: '.el-message--error',
// 通用对话框
dialog: '.el-dialog',
dialogTitle: '.el-dialog__title',
dialogClose: '.el-dialog__close',
// 通用按钮
submitButton: '.el-button--primary',
cancelButton: '.el-button--default',
// 通用表单元素
input: '.el-input__inner',
select: '.el-select',
checkbox: '.el-checkbox__input',
radio: '.el-radio__input',
// 通用消息提示
message: '.el-message',
// 通用分页
pagination: '.el-pagination',
// 通用表格
table: '.el-table',
tableHeader: '.el-table__header',
tableBody: '.el-table__body',
// 临时元素
temporaryElements: '.el-loading-mask, .el-message, .el-message-box'
};
}
/**
* 初始化配置
* @protected
*/
initializeConfig() {
this.config = {
timeout: {
page: 30000,
element: 30000,
navigate: 30000
},
pageLoad: {
maxRetries: 30,
retryInterval: 500,
stabilityDelay: 1000
}
};
}
/**
* 导航到指定URL
* @param {string} url 目标URL
*/
async navigate(url) {
await this.page.goto(url, {
waitUntil: 'networkidle',
timeout: this.config.timeout.navigate
});
}
/**
* 等待元素可见
* @param {string} selector 元素选择器
* @param {Object} options 选项
* @param {boolean} [options.returnBoolean=false] 是否返回布尔值而不是元素
* @param {boolean} [options.firstOnly=false] 是否只获取第一个元素
* @returns {Promise<import('@playwright/test').Locator|boolean>} 元素定位器或是否可见
*/
async waitForElement(selector, options = {}) {
try {
const element = options.firstOnly ? this.page.locator(selector).first() : this.page.locator(selector);
await element.waitFor({
state: 'visible',
timeout: this.config.timeout.element
});
return options.returnBoolean ? true : element;
} catch (error) {
if (options.returnBoolean) {
return false;
}
throw error;
}
}
/**
* 点击元素
* @param {string} selector 元素选择器
* @param {Object} options 选项
*/
async clickBySelector(selector, options = {}) {
const element = await this.waitForElement(selector, options);
await element.click(options);
}
/**
* 安全点击元素
* @param {Object} element Playwright元素对象
* @returns {Promise<boolean>} 是否点击成功
*/
async clickByElement(element) {
try {
await element.click();
return true;
} catch (error) {
console.error(`点击失败:`, error.message);
return false;
}
}
/**
* 点击元素并等待页面加载完成
* @param {string} selector 要点击的元素选择器
* @returns {Promise<boolean>} 操作是否成功
*/
async clickBySelectorAndWaitForLoad(selector) {
try {
const element = await this.waitForElement(selector);
await element.click();
await this.waitForPageLoad();
return true;
} catch (error) {
console.error(`点击元素并等待页面加载失败: ${error.message}`);
return false;
}
}
async clickByElementAndWaitForLoad(element) {
try {
await this.clickBySelector(element);
await this.waitForPageLoad();
return true;
} catch (error) {
console.error(`点击元素并等待页面加载失败: ${error.message}`);
return false;
}
}
/**
* 填写表单字段
* @param {string} selector 元素选择器
* @param {string} value 要填写的值
* @param {Object} options 选项
*/
async fill(selector, value, options = {}) {
const element = await this.waitForElement(selector, options);
await element.fill(value);
}
/**
* 等待页面加载完成
*/
async waitForPageLoad() {
await this.page.waitForLoadState('networkidle', {
timeout: this.config.timeout.page
});
}
/**
* 等待IBP系统特定页面加载完成带重试机制
* 此方法专门用于检查IBP系统的页面加载状态包括
* 1. 检查el-loading-mask是否存在
* 2. 检查系统特定的错误提示
* 3. 等待页面稳定
*
* @param {string} pageName 页面名称,用于日志显示
* @returns {Promise<boolean>} 页面是否加载成功
*/
async waitForIBPPageLoadWithRetry(pageName) {
console.log(`等待页面 ${pageName} 数据加载...`);
let retryCount = 0;
const {maxRetries, retryInterval, stabilityDelay} = this.config.pageLoad;
try {
while (retryCount < maxRetries) {
// 检查错误状态
const hasError = await this.checkPageError(pageName);
if (hasError) return false;
// 检查加载状态
const isLoading = await this.elementExistsBySelector(this.selectors.loadingMask);
if (!isLoading) {
await this.waitForTimeout(stabilityDelay);
console.log(`✅ 页面 ${pageName} 加载完成`);
return true;
}
retryCount++;
await this.waitForTimeout(retryInterval);
}
console.error(`页面加载超时: ${pageName}, 重试次数: ${maxRetries}`);
return false;
} catch (error) {
console.error(`页面加载出错: ${pageName}, 错误信息: ${error.message}`);
return false;
}
}
/**
* 检查页面是否有错误
* @param {string} pageName 页面名称
* @returns {Promise<boolean>} 是否有错误
* @private
*/
async checkPageError(pageName) {
const errorSelectors = [this.selectors.errorBox, this.selectors.errorMessage];
for (const selector of errorSelectors) {
const elements = await this.page.locator(selector).all();
if (elements.length > 0) {
const errorText = await this.getTextByElement(elements[0]);
console.error(`页面加载出现错误: ${pageName}, 错误信息: ${errorText}`);
return true;
}
}
return false;
}
/**
* 获取当前URL
* @returns {Promise<string>} 当前URL
*/
async getCurrentUrl() {
return this.page.url();
}
/**
* 等待指定时间
* @param {number} ms 等待时间(毫秒)
*/
async waitForTimeout(ms) {
await this.page.waitForTimeout(ms);
}
/**
* 检查元素是否存在
* @param {string} selector 元素选择器
* @returns {Promise<boolean>} 是否存在
*/
async elementExistsBySelector(selector) {
const count = await this.page.locator(selector).count();
return count > 0;
}
/**
* 获取元素文本
* @param {Object} element Playwright元素对象
* @returns {Promise<string>} 元素文本
*/
async getTextByElement(element) {
try {
const text = await element.textContent();
return text.trim();
} catch (error) {
console.error('获取元素文本失败:', error.message);
return '';
}
}
}
module.exports = BasePage;