/** * 页面对象模型基类 * 提供所有页面共用的方法和属性 */ 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: 30000, // 默认超时时间 pageLoad: { maxRetries: 30, retryInterval: 500, stabilityDelay: 1000 }, menuTimeout: parseInt(process.env.MENU_TIME_OUT || '30000', 10) }; } /** * 导航到指定URL * @param {string} url 目标URL */ async navigate(url) { await this.page.goto(url, { waitUntil: 'networkidle', timeout: 30000 }); } /** * 等待元素可见 * @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: options.timeout || this.config.timeout }); return options.returnBoolean ? true : element; } catch (error) { if (options.returnBoolean) { return false; } throw error; } } /** * 点击元素 * @param {string} selector 元素选择器 * @param {Object} options 选项 */ async click(selector, options = {}) { const element = await this.waitForElement(selector, options); await element.click(options); } /** * 填写表单字段 * @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); } /** * 获取元素文本 * @param {string} selector 元素选择器 * @param {Object} options 选项 * @returns {Promise} 元素文本 */ async getText(selector, options = {}) { const element = await this.waitForElement(selector, options); return element.textContent(); } /** * 检查元素是否可见 * @param {string} selector 元素选择器 * @param {Object} options 选项 * @returns {Promise} 元素是否可见 */ async isVisible(selector, options = {}) { try { await this.waitForElement(selector, { timeout: options.timeout || 1000 }); return true; } catch (error) { return false; } } /** * 等待页面加载完成 */ async waitForPageLoad() { await this.page.waitForLoadState('networkidle', { timeout: 30000 }); } /** * 获取页面标题 * @returns {Promise} 页面标题 */ async getTitle() { return this.page.title(); } /** * 获取当前URL * @returns {Promise} 当前URL */ async getCurrentUrl() { return this.page.url(); } /** * 截取页面截图 * @param {string} name 截图名称 * @param {Object} options 截图选项 */ async takeScreenshot(name, options = {}) { const screenshotPath = options.path || `./screenshots/${name}_${Date.now()}.png`; await this.page.screenshot({ path: screenshotPath, fullPage: options.fullPage || false, ...options }); return screenshotPath; } /** * 选择下拉菜单选项 * @param {string} selector 下拉菜单选择器 * @param {string} value 要选择的值 */ async selectOption(selector, value) { const element = await this.waitForElement(selector); await element.selectOption(value); } /** * 获取元素属性值 * @param {string} selector 元素选择器 * @param {string} attributeName 属性名 * @returns {Promise} 属性值 */ async getAttribute(selector, attributeName) { const element = await this.waitForElement(selector); return element.getAttribute(attributeName); } /** * 安全点击元素 * @param {Object} element Playwright元素对象 * @param {string} description 元素描述,用于日志 * @returns {Promise} 是否点击成功 */ async safeClick(element, description) { try { await element.click(); return true; } catch (error) { console.error(`点击${description}失败:`, error.message); return false; } } /** * 等待指定时间 * @param {number} ms 等待时间(毫秒) */ async wait(ms) { await this.page.waitForTimeout(ms); } /** * 检查元素是否存在 * @param {string} selector 元素选择器 * @returns {Promise} 是否存在 */ async elementExists(selector) { const count = await this.page.locator(selector).count(); return count > 0; } /** * 获取元素文本 * @param {Object} element Playwright元素对象 * @returns {Promise} 元素文本 */ async getElementText(element) { try { const text = await element.textContent(); return text.trim(); } catch (error) { console.error('获取元素文本失败:', error.message); return ''; } } } module.exports = BasePage;