/** * 页面对象模型基类 * 提供所有页面共用的方法和属性 */ 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} 元素定位器或是否可见 */ 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} 是否点击成功 */ async clickByElement(element) { try { await element.click(); return true; } catch (error) { console.error(`点击失败:`, error.message); return false; } } /** * 点击元素并等待页面加载完成 * @param {string} selector 要点击的元素选择器 * @returns {Promise} 操作是否成功 */ 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} 页面是否加载成功 */ 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} 是否有错误 * @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} 当前URL */ async getCurrentUrl() { return this.page.url(); } /** * 等待指定时间 * @param {number} ms 等待时间(毫秒) */ async waitForTimeout(ms) { await this.page.waitForTimeout(ms); } /** * 检查元素是否存在 * @param {string} selector 元素选择器 * @returns {Promise} 是否存在 */ async elementExistsBySelector(selector) { const count = await this.page.locator(selector).count(); return count > 0; } /** * 获取元素文本 * @param {Object} element Playwright元素对象 * @returns {Promise} 元素文本 */ async getTextByElement(element) { try { const text = await element.textContent(); return text.trim(); } catch (error) { console.error('获取元素文本失败:', error.message); return ''; } } } module.exports = BasePage;