290 lines
8.4 KiB
JavaScript
290 lines
8.4 KiB
JavaScript
/**
|
||
* 页面对象模型基类
|
||
* 提供所有页面共用的方法和属性
|
||
*/
|
||
|
||
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;
|