diff --git a/tests/pages/BasePage.js b/tests/pages/BasePage.js index 1ac8fa6..2fb6c37 100644 --- a/tests/pages/BasePage.js +++ b/tests/pages/BasePage.js @@ -87,12 +87,12 @@ class BasePage { */ async waitForElement(selector, options = {}) { try { - const element = options.firstOnly ? + const element = options.firstOnly ? this.page.locator(selector).first() : this.page.locator(selector); await element.waitFor({ - state: 'visible', + state: 'visible', timeout: options.timeout || this.config.timeout }); @@ -156,6 +156,7 @@ class BasePage { /** * 等待页面加载完成 + * @deprecated 请使用 waitForPageLoadWithRetry 方法替代 */ async waitForPageLoad() { await this.page.waitForLoadState('networkidle', { @@ -163,6 +164,83 @@ class BasePage { }); } + /** + * 等待页面加载完成,带重试机制 + * @param {Object} context 上下文信息对象 + * @param {string} [context.text] 页面名称或描述 + * @param {string} [context.path] 页面路径 + * @param {string} [subContext] 子上下文信息(可选) + * @returns {Promise} 页面是否加载成功 + */ + async waitForPageLoadWithRetry(context, subContext = '') { + const pageName = this.getContextName(context, subContext); + 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.elementExists(this.selectors.loadingMask); + if (!isLoading) { + await this.wait(stabilityDelay); + console.log(`✅ 页面 ${pageName} 加载完成`); + return true; + } + + retryCount++; + await this.wait(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.getElementText(elements[0]); + console.error(`页面加载出现错误: ${pageName}, 错误信息: ${errorText}`); + return true; + } + } + + return false; + } + + /** + * 获取上下文名称 + * @param {Object} context 上下文信息对象 + * @param {string} [subContext] 子上下文信息 + * @returns {string} 格式化的上下文名称 + * @private + */ + getContextName(context, subContext = '') { + if (typeof context === 'string') { + return context; + } + const mainText = context.text || context.path || 'Unknown'; + return subContext ? `${mainText} > ${subContext}` : mainText; + } + /** * 获取页面标题 * @returns {Promise} 页面标题 @@ -218,15 +296,14 @@ class BasePage { /** * 安全点击元素 * @param {Object} element Playwright元素对象 - * @param {string} description 元素描述,用于日志 * @returns {Promise} 是否点击成功 */ - async safeClick(element, description) { + async safeClick(element) { try { await element.click(); return true; } catch (error) { - console.error(`点击${description}失败:`, error.message); + console.error(`点击失败:`, error.message); return false; } } diff --git a/tests/pages/LongiLoginPage.js b/tests/pages/LongiLoginPage.js index a02685f..4114927 100644 --- a/tests/pages/LongiLoginPage.js +++ b/tests/pages/LongiLoginPage.js @@ -18,7 +18,7 @@ class LongiLoginPage extends BasePage { initializeSelectors() { // 调用父类的选择器初始化 super.initializeSelectors(); - + // 添加或覆盖特定于 LongiLoginPage 的选择器 Object.assign(this.selectors, { // 登录表单元素 @@ -40,7 +40,7 @@ class LongiLoginPage extends BasePage { initializeConfig() { // 调用父类的配置初始化 super.initializeConfig(); - + // 添加或覆盖特定于 LongiLoginPage 的配置 Object.assign(this.config, { loginTimeout: 10000 @@ -63,14 +63,14 @@ class LongiLoginPage extends BasePage { try { // 方法1: 使用更精确的选择器,指定包含特定类的按钮 await this.click(this.selectors.loginButton); - console.log('使用 container-button 类选择器成功点击登录按钮'); + // console.log('使用 container-button 类选择器成功点击登录按钮'); await this.waitForPageLoad(); return true; } catch (error) { console.log('第一种方法失败,尝试备用方法...'); try { // 方法2: 使用精确文本匹配 - await this.safeClick(await this.page.locator(this.selectors.loginButtonByText), '登录按钮(精确文本匹配)'); + await this.safeClick(await this.page.locator(this.selectors.loginButtonByText)); // console.log('使用精确文本匹配成功点击登录按钮'); await this.waitForPageLoad(); return true; @@ -78,7 +78,7 @@ class LongiLoginPage extends BasePage { console.log('第二种方法也失败,尝试第三种方法...'); try { // 方法3: 使用first()选择第一个匹配的按钮 - await this.safeClick(await this.page.locator(this.selectors.loginButtonFirst).first(), '登录按钮(first方法)'); + await this.safeClick(await this.page.locator(this.selectors.loginButtonFirst).first()); // console.log('使用first()方法成功点击登录按钮'); await this.waitForPageLoad(); return true; @@ -144,7 +144,7 @@ class LongiLoginPage extends BasePage { await this.enterUsername(username); await this.enterPassword(password); await this.enterCaptcha(captcha); - + if (await this.clickLoginButton()) { return await this.isLoginSuccessful(); } diff --git a/tests/pages/LongiMainPage.js b/tests/pages/LongiMainPage.js index 65fbc6d..4e31d04 100644 --- a/tests/pages/LongiMainPage.js +++ b/tests/pages/LongiMainPage.js @@ -19,7 +19,7 @@ class LongiMainPage extends BasePage { initializeSelectors() { // 调用父类的选择器初始化 super.initializeSelectors(); - + // 添加或覆盖特定于 LongiMainPage 的选择器 Object.assign(this.selectors, { // 侧边导航菜单 @@ -46,7 +46,7 @@ class LongiMainPage extends BasePage { initializeConfig() { // 调用父类的配置初始化 super.initializeConfig(); - + // 添加或覆盖特定于 LongiMainPage 的配置 Object.assign(this.config, { menuTimeout: parseInt(process.env.MENU_TIME_OUT || '30000', 10) @@ -210,7 +210,7 @@ class LongiMainPage extends BasePage { const uniqueId = `menu_${i}_${text.trim().replace(/\s+/g, '_')}`; // 获取菜单路径 - const menuPath = await this.getMenuPath(item); + const menuPath = this.getMenuPath(item); menuItems.push({ index: i, @@ -228,27 +228,14 @@ class LongiMainPage extends BasePage { } /** - * 获取菜单项的路径信息 - * @param {Object} menuItem 菜单项元素 - * @returns {Promise} 菜单路径 + * 获取菜单路径 + * @param {Object} menuInfo 菜单信息对象 + * @param {Object} parentMenu 父级菜单(可选) + * @returns {string} 菜单路径 + * @private */ - async getMenuPath(menuItem) { - // 尝试获取父级菜单的文本 - const parentText = await menuItem.evaluate(el => { - // 查找最近的父级菜单项 - const parent = el.closest('.el-submenu'); - if (parent) { - const parentTitle = parent.querySelector('.el-submenu__title'); - if (parentTitle) { - const titleSpan = parentTitle.querySelector('.titleSpan'); - return titleSpan ? titleSpan.textContent.trim() : parentTitle.textContent.trim(); - } - } - return ''; - }); - - const itemText = await menuItem.textContent(); - return parentText ? `${parentText} > ${itemText}` : itemText; + async getMenuPath(menuInfo, parentMenu = null) { + return parentMenu ? `${parentMenu.text} > ${menuInfo.text}` : menuInfo.text; } /** @@ -420,7 +407,7 @@ class LongiMainPage extends BasePage { const menuPath = this.getMenuPath(menuInfo, parentMenu); console.log(`点击菜单: ${menuPath}`); - if (!await this.safeClick(menuInfo.element, menuPath)) { + if (!await this.safeClick(menuInfo.element)) { return; } @@ -430,18 +417,7 @@ class LongiMainPage extends BasePage { } await this.handleAllTabs(menuInfo); - await this.closeActiveTab(menuPath); - } - - /** - * 获取菜单路径 - * @param {Object} menuInfo 菜单信息对象 - * @param {Object} parentMenu 父级菜单(可选) - * @returns {string} 菜单路径 - * @private - */ - getMenuPath(menuInfo, parentMenu = null) { - return parentMenu ? `${parentMenu.text} > ${menuInfo.text}` : menuInfo.text; + await this.closeActiveTab(menuInfo); } /** @@ -506,86 +482,25 @@ class LongiMainPage extends BasePage { ); } - /** - * 等待页面加载完成,带重试机制 - * @param {Object} menu 菜单对象 - * @param {string} subMenuText 子菜单文本(可选) - * @returns {Promise} 页面是否加载成功 - */ - async waitForPageLoadWithRetry(menu, subMenuText = '') { - const pageName = this.getMenuPath(menu, {text: subMenuText}); - 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.elementExists(this.selectors.loadingMask); - if (!isLoading) { - await this.wait(stabilityDelay); - console.log(`✅ 页面 ${pageName} 加载完成`); - return true; - } - - retryCount++; - await this.wait(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.getElementText(elements[0]); - console.error(`页面加载出现错误: ${pageName}, 错误信息: ${errorText}`); - return true; - } - } - - return false; - } - /** * 关闭当前活动的标签页 - * @param {string} pageName 页面名称,用于日志显示 */ - async closeActiveTab(pageName) { + async closeActiveTab(parentMenu) { try { - console.log(`🗑️ 正在关闭页面 "${pageName}" 的tab...`); + console.log(`🗑️ 正在关闭页面 "${parentMenu.text}" 的tab...`); const activeTab = this.page.locator(this.selectors.activeTab); const closeButton = activeTab.locator(this.selectors.closeButton); if (await this.canCloseTab(activeTab, closeButton)) { await closeButton.waitFor({state: 'visible', timeout: 5000}); - await this.safeClick(closeButton, `${pageName}的关闭按钮`); + await this.safeClick(closeButton); await this.wait(500); } else { - console.log(`⚠️ [${pageName}] 没有找到可关闭的tab,继续执行...`); + console.log(`⚠️ [${parentMenu.text}] 没有找到可关闭的tab,继续执行...`); } } catch (error) { - console.error(`关闭标签页时出错 [${pageName}]:`, error.message); + console.error(`关闭标签页时出错 [${parentMenu.text}]:`, error.message); } }