From 5e1d37134e6e6e1c7e97b46b8c80ffeb902ffe77 Mon Sep 17 00:00:00 2001 From: dengqichen Date: Thu, 6 Mar 2025 09:35:07 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AC=AC=E4=B8=80=E6=AC=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/pages/LongiMainPage.js | 662 +++++++++++++++++------------------ 1 file changed, 316 insertions(+), 346 deletions(-) diff --git a/tests/pages/LongiMainPage.js b/tests/pages/LongiMainPage.js index 8c63f43..61ed571 100644 --- a/tests/pages/LongiMainPage.js +++ b/tests/pages/LongiMainPage.js @@ -80,22 +80,16 @@ class LongiMainPage extends BasePage { * @returns {Promise} 是否执行了展开操作 */ async expandSideMenu() { - try { - // 检查菜单是否已展开 - const isExpanded = await this.isMenuExpanded(); + // 检查菜单是否已展开 + const isExpanded = await this.isMenuExpanded(); - if (!isExpanded) { - console.log('菜单未展开,点击展开'); - await this.clickExpandMenu(); - return true; - } else { - // console.log('菜单已经处于展开状态'); - return false; - } - } catch (error) { - console.error('展开菜单时出错:', error); - return false; + if (!isExpanded) { + console.log('菜单未展开,点击展开'); + await this.clickExpandMenu(); + return true; } + + return false; } /** @@ -115,6 +109,7 @@ class LongiMainPage extends BasePage { return await this.findAndSaveMenuItems(); } } catch (error) { + // 文件操作错误需要被捕获并处理 console.error(`检查并加载菜单项时出错: ${error}`); return null; } @@ -124,16 +119,16 @@ class LongiMainPage extends BasePage { * 查找菜单项并保存到文件 */ async findAndSaveMenuItems() { + // 查找菜单项 + const menuItems = await this.findMenuItems(); + + // 如果没有找到菜单项,则返回空数组 + if (!menuItems || menuItems.length === 0) { + console.warn('未找到任何菜单项,无法保存到文件'); + return []; + } + try { - // 查找菜单项 - const menuItems = await this.findMenuItems(); - - // 如果没有找到菜单项,则返回空数组 - if (!menuItems || menuItems.length === 0) { - console.warn('未找到任何菜单项,无法保存到文件'); - return []; - } - // 过滤掉不能序列化的element属性 const menuItemsForSave = menuItems.map(({element, ...rest}) => rest); @@ -144,8 +139,9 @@ class LongiMainPage extends BasePage { return menuItems; } catch (error) { - console.error('查找并保存菜单项时出错:', error); - return []; + // 文件操作错误需要被捕获 + console.error('保存菜单项到文件时出错:', error); + return menuItems; // 即使保存失败也返回找到的菜单项 } } @@ -154,79 +150,74 @@ class LongiMainPage extends BasePage { * @returns {Promise} 菜单项数组 */ async findMenuItems() { - try { - console.log('开始查找菜单项...'); + console.log('开始查找菜单项...'); - // 等待菜单加载完成 - await this.page.waitForSelector(this.selectors.sideNav, { - timeout: parseInt(process.env.MENU_TIME_OUT || '30000', 10) - }); + // 等待菜单加载完成 + await this.page.waitForSelector(this.selectors.sideNav, { + timeout: parseInt(process.env.MENU_TIME_OUT || '30000', 10) + }); - // 获取所有菜单项 - const items = await this.page.locator(this.selectors.menuItems).all(); - console.log(`找到 ${items.length} 个菜单项元素`); - const menuItems = []; - // 处理每个菜单项 - for (let i = 0; i < items.length; i++) { - const item = items[i]; - //过滤一级菜单 - let isTopMenu = (await item.locator(this.selectors.firstLevelIndicator).count()) > 0; - if (isTopMenu) { - continue; - } - // 获取菜单项文本 - const text = await item.textContent(); + // 获取所有菜单项 + const items = await this.page.locator(this.selectors.menuItems).all(); + console.log(`找到 ${items.length} 个菜单项元素`); + const menuItems = []; - // 检查是否是可见的菜单项 - const isVisible = await item.isVisible(); - - if (isVisible && text.trim()) { - // 检查是否有子菜单指示器 - const hasSubMenuIndicator = await item.evaluate(el => { - return el.querySelector('.el-submenu__icon-arrow') !== null || - el.querySelector('.el-icon-arrow-down') !== null; - }); - - // 检查是否有三级菜单指示器 - const hasThirdLevelIndicator = await item.evaluate(el => { - // 检查是否有特定的三级菜单指示器 - return el.classList.contains('is-opened') || - el.querySelector('.third-level-menu') !== null || - el.querySelector('.el-menu--inline') !== null; - }); - - // 检查是否是子菜单标题 - const isSubMenuTitle = await item.evaluate(el => el.classList.contains('el-sub-menu__title')); - - // 综合判断是否有三级菜单 - const hasThirdMenu = isSubMenuTitle || hasSubMenuIndicator || hasThirdLevelIndicator; - - console.log(`菜单项 "${text.trim()}" ${hasThirdMenu ? '有' : '没有'}三级菜单 (通过DOM结构判断)`); - - // 生成唯一标识符,结合索引和文本 - const uniqueId = `menu_${i}_${text.trim().replace(/\s+/g, '_')}`; - - // 获取菜单路径 - const menuPath = await this.getMenuPath(item); - - menuItems.push({ - index: i, - text: text.trim(), - element: item, - hasThirdMenu: hasThirdMenu, - uniqueId: uniqueId, - // 添加路径信息,帮助识别菜单层级 - path: menuPath - }); - } + // 处理每个菜单项 + for (let i = 0; i < items.length; i++) { + const item = items[i]; + //过滤一级菜单 + let isTopMenu = (await item.locator(this.selectors.firstLevelIndicator).count()) > 0; + if (isTopMenu) { + continue; } + // 获取菜单项文本 + const text = await item.textContent(); - console.log(`🔍 找到 ${menuItems.length} 个可测试的菜单项`); - return menuItems; - } catch (error) { - console.error('查找菜单项时出错:', error); - return []; + // 检查是否是可见的菜单项 + const isVisible = await item.isVisible(); + + if (isVisible && text.trim()) { + // 检查是否有子菜单指示器 + const hasSubMenuIndicator = await item.evaluate(el => { + return el.querySelector('.el-submenu__icon-arrow') !== null || + el.querySelector('.el-icon-arrow-down') !== null; + }); + + // 检查是否有三级菜单指示器 + const hasThirdLevelIndicator = await item.evaluate(el => { + // 检查是否有特定的三级菜单指示器 + return el.classList.contains('is-opened') || + el.querySelector('.third-level-menu') !== null || + el.querySelector('.el-menu--inline') !== null; + }); + + // 检查是否是子菜单标题 + const isSubMenuTitle = await item.evaluate(el => el.classList.contains('el-sub-menu__title')); + + // 综合判断是否有三级菜单 + const hasThirdMenu = isSubMenuTitle || hasSubMenuIndicator || hasThirdLevelIndicator; + + console.log(`菜单项 "${text.trim()}" ${hasThirdMenu ? '有' : '没有'}三级菜单 (通过DOM结构判断)`); + + // 生成唯一标识符,结合索引和文本 + const uniqueId = `menu_${i}_${text.trim().replace(/\s+/g, '_')}`; + + // 获取菜单路径 + const menuPath = await this.getMenuPath(item); + + menuItems.push({ + index: i, + text: text.trim(), + element: item, + hasThirdMenu: hasThirdMenu, + uniqueId: uniqueId, + path: menuPath + }); + } } + + console.log(`🔍 找到 ${menuItems.length} 个可测试的菜单项`); + return menuItems; } /** @@ -235,38 +226,62 @@ class LongiMainPage extends BasePage { * @returns {Promise} 菜单路径 */ async getMenuPath(menuItem) { - try { - // 尝试获取父级菜单的文本 - 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(); - } + // 尝试获取父级菜单的文本 + 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(); - - if (parentText) { - return `${parentText} > ${itemText}`; } - - return itemText; - } catch (error) { - console.error('获取菜单路径时出错:', error); return ''; + }); + + const itemText = await menuItem.textContent(); + return parentText ? `${parentText} > ${itemText}` : itemText; + } + + /** + * 处理所有菜单点击 + * @param {Array} menuItems 菜单项数组 + */ + async handleAllMenuClicks(menuItems) { + try { + for (let i = 0; i < menuItems.length; i++) { + await this.handleSingleMenuClick(menuItems[i]); + } + } catch (error) { + console.error('处理菜单点击时出错:', error); + throw error; // 向上传播错误 } } - async handleAllMenuClicks(menuItems) { - for (let i = 0; i < menuItems.length; i++) { - await this.handleSingleMenuClick(menuItems[i]); + /** + * 处理单个菜单点击 + * @param {Object} menu 菜单对象 + */ + async handleSingleMenuClick(menu) { + await this.expandSideMenu(); + // 在处理菜单前重新获取最新的element + menu = await this.restoreMenuElement(menu); + + if (!menu.element) { + console.error(`无法找到菜单项 "${menu.text}" 的element,跳过处理`); + return; } + + if (menu.hasThirdMenu) { + await this.handleThreeLevelMenu(menu); + } else { + // 处理二级菜单点击 + await this.handleMenuClick(menu); + } + + // 执行内存回收 + await this.cleanupMemory(menu.text); } /** @@ -276,57 +291,52 @@ class LongiMainPage extends BasePage { * @private */ async restoreMenuElement(menuItem) { - try { - // 获取当前页面上的所有菜单元素 - const currentMenuElements = await this.page.locator(this.selectors.menuItems).all(); - let foundElement = null; - let sameTextElements = []; + // 获取当前页面上的所有菜单元素 + const currentMenuElements = await this.page.locator(this.selectors.menuItems).all(); + let foundElement = null; + let sameTextElements = []; - // 首先找到所有文本匹配的元素 - for (const element of currentMenuElements) { - const elementText = await element.textContent(); - if (elementText.trim() === menuItem.text) { - // 检查是否是一级菜单 - const isTopMenu = await element.locator(this.selectors.firstLevelIndicator).count() > 0; - if (!isTopMenu) { - sameTextElements.push(element); - } + // 首先找到所有文本匹配的元素 + for (const element of currentMenuElements) { + const elementText = await element.textContent(); + if (elementText.trim() === menuItem.text) { + // 检查是否是一级菜单 + const isTopMenu = await element.locator(this.selectors.firstLevelIndicator).count() > 0; + if (!isTopMenu) { + sameTextElements.push(element); } } - - // 如果找到多个同名菜单,根据是否有三级菜单来区分 - if (sameTextElements.length > 0) { - for (const element of sameTextElements) { - // 检查是否有三级菜单指示器 - const hasThirdMenu = await element.evaluate(el => { - return el.classList.contains('el-sub-menu__title') || - el.querySelector('.el-submenu__icon-arrow') !== null; - }); - - if (hasThirdMenu === menuItem.hasThirdMenu) { - foundElement = element; - break; - } - } - - // 如果还没找到,就用第一个匹配的元素 - if (!foundElement && sameTextElements.length > 0) { - foundElement = sameTextElements[0]; - } - } - - if (foundElement) { - menuItem.element = foundElement; - console.log(`✅ 成功恢复菜单项 "${menuItem.text}" (${menuItem.hasThirdMenu ? '有' : '无'}三级菜单) 的element元素`); - } else { - console.warn(`⚠️ 无法恢复菜单项 "${menuItem.text}" (${menuItem.hasThirdMenu ? '有' : '无'}三级菜单) 的element元素`); - } - - return menuItem; - } catch (error) { - console.error(`恢复菜单项element时出错 [${menuItem.text}]:`, error.message); - return menuItem; } + + // 如果找到多个同名菜单,根据是否有三级菜单来区分 + if (sameTextElements.length > 0) { + for (const element of sameTextElements) { + // 检查是否有三级菜单指示器 + const hasThirdMenu = await element.evaluate(el => { + return el.classList.contains('el-sub-menu__title') || + el.querySelector('.el-submenu__icon-arrow') !== null; + }); + + if (hasThirdMenu === menuItem.hasThirdMenu) { + foundElement = element; + break; + } + } + + // 如果还没找到,就用第一个匹配的元素 + if (!foundElement && sameTextElements.length > 0) { + foundElement = sameTextElements[0]; + } + } + + if (foundElement) { + menuItem.element = foundElement; + console.log(`✅ 成功恢复菜单项 "${menuItem.text}" (${menuItem.hasThirdMenu ? '有' : '无'}三级菜单) 的element元素`); + } else { + console.warn(`⚠️ 无法恢复菜单项 "${menuItem.text}" (${menuItem.hasThirdMenu ? '有' : '无'}三级菜单) 的element元素`); + } + + return menuItem; } /** @@ -356,30 +366,155 @@ class LongiMainPage extends BasePage { } } - async handleSingleMenuClick(menu) { - try { - await this.expandSideMenu(); - // 在处理菜单前重新获取最新的element - menu = await this.restoreMenuElement(menu); + /** + * 获取三级菜单列表 + * @param {Object} parentMenu 父级菜单对象 + * @returns {Promise} 三级菜单项数组 + * @private + */ + async getThirdLevelMenus(parentMenu) { + // 检查三级菜单是否存在 + const elements = await this.page.locator('.el-popper.is-light.el-popover .menuTitle.canClick').all(); + if (elements.length === 0) { + return []; + } - if (!menu.element) { - console.error(`无法找到菜单项 "${menu.text}" 的element,跳过处理`); + // 收集所有三级菜单项 + const thirdMenus = []; + for (let i = 0; i < elements.length; i++) { + const element = elements[i]; + const text = await element.textContent(); + + thirdMenus.push({ + index: i, + text: text.trim(), + element: element, + hasThirdMenu: false, // 三级菜单项不会有下一级 + uniqueId: `menu_${parentMenu.uniqueId}_${i}_${text.trim().replace(/\s+/g, '_')}`, + path: `${parentMenu.text} > ${text.trim()}` + }); + } + + return thirdMenus; + } + + /** + * 处理三级菜单 + * @param {Object} menu 菜单对象 + */ + async handleThreeLevelMenu(menu) { + console.log(`处理三级菜单: ${menu.text}`); + + // 1. 点击展开三级菜单 + await menu.element.click(); + await this.page.waitForTimeout(500); + + // 2. 获取三级菜单列表 + const thirdMenus = await this.getThirdLevelMenus(menu); + if (thirdMenus.length === 0) { + console.log(`未找到三级菜单项`); + return; + } + + // 3. 处理每个三级菜单项 + for (let i = 0; i < thirdMenus.length; i++) { + const thirdMenu = thirdMenus[i]; + console.log(`处理第 ${i + 1}/${thirdMenus.length} 个三级菜单项: ${thirdMenu.text}`); + + await this.handleMenuClick(thirdMenu, menu); + + // 如果还有下一个菜单项,重新展开三级菜单 + if (i < thirdMenus.length - 1) { + await menu.element.click(); + await this.page.waitForTimeout(500); + } + } + } + + /** + * 处理菜单点击和页面加载 + * @param {Object} menuInfo 菜单信息对象 + * @param {Object} parentMenu 父级菜单(可选) + */ + async handleMenuClick(menuInfo, parentMenu = null) { + const menuPath = parentMenu ? `${parentMenu.text} > ${menuInfo.text}` : menuInfo.text; + console.log(`点击菜单: ${menuPath}`); + + // 1. 点击菜单并等待页面加载 + await menuInfo.element.click(); + const loadResult = await this.waitForPageLoadWithRetry(menuInfo); + + if (!loadResult) { + console.warn(`页面加载失败: ${menuPath}`); + return; + } + + // 2. 处理页面中的标签页 + await this.handleAllTabs(menuInfo); + + // 3. 关闭当前标签页 + await this.closeActiveTab(menuPath); + } + + /** + * 处理单个TAB页 + * @param {Object} tabInfo TAB页信息对象,包含text、isActive和element属性 + * @param {Object} parentMenu 父级菜单对象 + * @private + */ + async handleSingleTab(tabInfo, parentMenu) { + try { + const menuPath = parentMenu.path || parentMenu.text; + console.log(`🔹 处理TAB页: ${menuPath} > ${tabInfo.text}`); + + // 直接使用传入的element点击 + await tabInfo.element.click(); + await this.waitForPageLoadWithRetry(parentMenu, tabInfo.text) + } catch (error) { + console.error(`处理TAB页失败 [${parentMenu.text} > ${tabInfo.text}]:`, error.message); + } + } + + /** + * 处理所有TAB页 + * @param {Object} menu 菜单对象 + * @private + */ + async handleAllTabs(menu) { + try { + // 等待TAB容器加载 + await this.page.waitForTimeout(1000); + + // 使用更精确的选择器获取工作区的TAB页 + const tabs = await this.page.locator('.workSpaceBaseTab .el-tabs__item').all(); + if (tabs.length === 0) { + console.log(`📝 ${menu.text} 没有TAB页`); return; } - if (menu.hasThirdMenu) { - await this.handleThreeLevelMenu(menu); - } else { - // 处理二级菜单点击 - await this.handleMenuClick(menu); + console.log(`📑 ${menu.text} 找到 ${tabs.length} 个TAB页`); + + // 获取所有TAB页的完整信息(文本、激活状态和元素引用) + const tabInfos = await Promise.all( + tabs.map(async element => ({ + text: (await element.textContent()).trim(), + isActive: await element.evaluate(el => el.classList.contains('is-active')), + element: element // 保存元素引用 + })) + ); + + // 处理每个非激活的TAB页 + for (const tabInfo of tabInfos) { + // 跳过当前激活的TAB页,因为它已经是默认加载的 + if (!tabInfo.isActive) { + await this.handleSingleTab(tabInfo, menu); + } else { + console.log(`⏭️ 跳过当前激活的TAB页: ${menu.text} > ${tabInfo.text}`); + } } - // 执行内存回收 - await this.cleanupMemory(menu.text); - } catch (error) { - console.error(`处理菜单失败 [${menu.text}]:`, error.message); - throw error; + console.error(`处理TAB页失败 [${menu.text}]:`, error.message); } } @@ -477,171 +612,6 @@ class LongiMainPage extends BasePage { return false; } } - - /** - * 获取三级菜单列表 - * @param {Object} parentMenu 父级菜单对象 - * @returns {Promise} 三级菜单项数组 - * @private - */ - async getThirdLevelMenus(parentMenu) { - try { - // 检查三级菜单是否存在 - const elements = await this.page.locator('.el-popper.is-light.el-popover .menuTitle.canClick').all(); - if (elements.length === 0) { - return []; - } - // 收集所有三级菜单项 - const thirdMenus = []; - for (let i = 0; i < elements.length; i++) { - const element = elements[i]; - const text = await element.textContent(); - - thirdMenus.push({ - index: i, - text: text.trim(), - element: element, - hasThirdMenu: false, // 三级菜单项不会有下一级 - uniqueId: `menu_${parentMenu.uniqueId}_${i}_${text.trim().replace(/\s+/g, '_')}`, - path: `${parentMenu.text} > ${text.trim()}` - }); - } - - return thirdMenus; - } catch (error) { - console.error(`获取三级菜单失败: ${error.message}`); - return []; - } - } - - /** - * 处理三级菜单 - * @param {Object} menu 菜单对象 - */ - async handleThreeLevelMenu(menu) { - console.log(`处理三级菜单: ${menu.text}`); - - // 1. 点击展开三级菜单 - await menu.element.click(); - await this.page.waitForTimeout(500); - - // 2. 获取三级菜单列表 - const thirdMenus = await this.getThirdLevelMenus(menu); - if (thirdMenus.length === 0) { - console.log(`未找到三级菜单项`); - return; - } - - // 3. 处理每个三级菜单项 - for (let i = 0; i < thirdMenus.length; i++) { - const thirdMenu = thirdMenus[i]; - console.log(`处理第 ${i + 1}/${thirdMenus.length} 个三级菜单项: ${thirdMenu.text}`); - - await this.handleMenuClick(thirdMenu, menu); - - // 如果还有下一个菜单项,重新展开三级菜单 - if (i < thirdMenus.length - 1) { - await menu.element.click(); - await this.page.waitForTimeout(500); - } - } - } - - /** - * 处理菜单点击和页面加载 - * @param {Object} menuInfo 菜单信息对象 - * @param {Object} parentMenu 父级菜单(可选) - */ - async handleMenuClick(menuInfo, parentMenu = null) { - const menuPath = parentMenu ? `${parentMenu.text} > ${menuInfo.text}` : menuInfo.text; - console.log(`点击菜单: ${menuPath}`); - - // 1. 点击菜单并等待页面加载 - await menuInfo.element.click(); - const loadResult = await this.waitForPageLoadWithRetry(menuInfo); - - if (!loadResult) { - console.warn(`页面加载失败: ${menuPath}`); - return false; - } - - // 2. 处理页面中的标签页 - await this.handleAllTabs(menuInfo); - - // 3. 关闭当前标签页 - await this.closeActiveTab(menuPath); - - return true; - } - - /** - * 处理单个TAB页 - * @param {Object} tabInfo TAB页信息对象,包含text、isActive和element属性 - * @param {Object} parentMenu 父级菜单对象 - * @returns {Promise} 处理是否成功 - * @private - */ - async handleSingleTab(tabInfo, parentMenu) { - try { - const menuPath = parentMenu.path || parentMenu.text; - console.log(`🔹 处理TAB页: ${menuPath} > ${tabInfo.text}`); - - // 直接使用传入的element点击 - await tabInfo.element.click(); - - return !await this.waitForPageLoadWithRetry(parentMenu, tabInfo.text); - } catch (error) { - console.error(`处理TAB页失败 [${parentMenu.text} > ${tabInfo.text}]:`, error.message); - return false; - } - } - - /** - * 处理所有TAB页 - * @param {Object} menu 菜单对象 - * @returns {Promise} 处理是否成功 - * @private - */ - async handleAllTabs(menu) { - try { - // 等待TAB容器加载 - await this.page.waitForTimeout(1000); - - // 使用更精确的选择器获取工作区的TAB页 - const tabs = await this.page.locator('.workSpaceBaseTab .el-tabs__item').all(); - if (tabs.length === 0) { - console.log(`📝 ${menu.text} 没有TAB页`); - return true; - } - - console.log(`📑 ${menu.text} 找到 ${tabs.length} 个TAB页`); - - // 获取所有TAB页的完整信息(文本、激活状态和元素引用) - const tabInfos = await Promise.all( - tabs.map(async element => ({ - text: (await element.textContent()).trim(), - isActive: await element.evaluate(el => el.classList.contains('is-active')), - element: element // 保存元素引用 - })) - ); - - // 处理每个非激活的TAB页 - for (const tabInfo of tabInfos) { - // 跳过当前激活的TAB页,因为它已经是默认加载的 - if (!tabInfo.isActive) { - await this.handleSingleTab(tabInfo, menu); - } else { - console.log(`⏭️ 跳过当前激活的TAB页: ${menu.text} > ${tabInfo.text}`); - } - } - - return true; - } catch (error) { - console.error(`处理TAB页失败 [${menu.text}]:`, error.message); - return false; - } - } - } module.exports = LongiMainPage; \ No newline at end of file