From 1abc876fdc75cb31987f3fff90531f1861f4b60c Mon Sep 17 00:00:00 2001 From: dengqichen Date: Tue, 18 Feb 2025 17:02:30 +0800 Subject: [PATCH] first commit --- .idea/.gitignore | 5 + .idea/longi.iml | 12 + .idea/modules.xml | 8 + .idea/vcs.xml | 7 + cypress.config.js | 131 ++++ cypress/e2e/需求计划页面是否可用.cy.js | 919 +++++++++++++++++++++++++ cypress/fixtures/example.json | 5 + cypress/support/commands.js | 25 + cypress/support/e2e.js | 17 + 9 files changed, 1129 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/longi.iml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 cypress.config.js create mode 100644 cypress/e2e/需求计划页面是否可用.cy.js create mode 100644 cypress/fixtures/example.json create mode 100644 cypress/support/commands.js create mode 100644 cypress/support/e2e.js diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..10b731c --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,5 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ diff --git a/.idea/longi.iml b/.idea/longi.iml new file mode 100644 index 0000000..24643cc --- /dev/null +++ b/.idea/longi.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..02132c8 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..3bd95c9 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/cypress.config.js b/cypress.config.js new file mode 100644 index 0000000..82dffc6 --- /dev/null +++ b/cypress.config.js @@ -0,0 +1,131 @@ +module.exports = { + e2e: { + viewportWidth: 1920, + viewportHeight: 1080, + numTestsKeptInMemory: 0, + experimentalMemoryManagement: true, + video: false, + screenshotOnRunFailure: false, + defaultCommandTimeout: 10000, + pageLoadTimeout: 30000, + retries: { + runMode: 1, + openMode: 0 + }, + setupNodeEvents(on, config) { + const fs = require('fs'); + const path = require('path'); + + on('before:browser:launch', (browser = {}, launchOptions) => { + if (browser.name === 'chrome' || browser.name === 'electron') { + launchOptions.args.push( + '--js-flags="--expose-gc"', + '--disable-dev-shm-usage', + '--disable-background-timer-throttling', + '--disable-renderer-backgrounding', + '--disable-backgrounding-occluded-windows', + '--memory-pressure-off', + '--disable-site-isolation-trials', + '--js-flags="--max-old-space-size=8192"', + '--disable-gpu', + '--disable-notifications', + '--disable-extensions', + '--no-sandbox', + '--disable-software-rasterizer', + '--disable-logging', + '--disable-web-security', + '--disable-translate', + '--disable-features=site-per-process', + '--disable-features=TranslateUI', + '--disable-features=GlobalMediaControls' + ); + } + return launchOptions; + }); + + on('task', { + clearMemory() { + try { + if (global.gc) { + global.gc(); + } + return null; + } catch (e) { + console.error('GC failed:', e); + return null; + } + }, + + createReportDir(dirPath) { + try { + if (!fs.existsSync(dirPath)) { + fs.mkdirSync(dirPath, { recursive: true }); + } + return null; + } catch (e) { + console.error('Create report directory failed:', e); + return null; + } + }, + + readAllReports(environment) { + try { + const reportsDir = path.join(__dirname, 'cypress', 'reports'); + const reports = []; + const files = fs.readdirSync(reportsDir); + + files.forEach(file => { + if (file.startsWith(`${environment}-menu-test-report-batch-`) && file.endsWith('.json')) { + const filePath = path.join(reportsDir, file); + const content = fs.readFileSync(filePath, 'utf8'); + reports.push(JSON.parse(content)); + } + }); + + return reports; + } catch (e) { + console.error('Read reports failed:', e); + return []; + } + }, + + cleanupReports(environment) { + try { + const reportsDir = path.join(__dirname, 'cypress', 'reports'); + const files = fs.readdirSync(reportsDir); + + files.forEach(file => { + if (file.startsWith(`${environment}-menu-test-report-batch-`) && file.endsWith('.json')) { + fs.unlinkSync(path.join(reportsDir, file)); + } + }); + + return null; + } catch (e) { + console.error('Cleanup reports failed:', e); + return null; + } + }, + + saveReport({ data, filename }) { + try { + const reportsDir = path.join(__dirname, 'cypress', 'reports'); + if (!fs.existsSync(reportsDir)) { + fs.mkdirSync(reportsDir, { recursive: true }); + } + fs.writeFileSync( + path.join(reportsDir, filename), + JSON.stringify(data, null, 0) + ); + return null; + } catch (e) { + console.error('Save report failed:', e); + return null; + } + } + }); + + return config; + }, + }, +}; diff --git a/cypress/e2e/需求计划页面是否可用.cy.js b/cypress/e2e/需求计划页面是否可用.cy.js new file mode 100644 index 0000000..662b713 --- /dev/null +++ b/cypress/e2e/需求计划页面是否可用.cy.js @@ -0,0 +1,919 @@ +describe('隆基系统全部页面验证', () => { + // 格式化时间的辅助函数 + const formatTime = (date) => { + return date.toLocaleString('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + hour12: false + }); + }; + + // 计算时间差(返回秒数) + const calculateDuration = (startTime, endTime) => { + return ((endTime - startTime) / 1000).toFixed(2); + }; + + // 添加全局变量用于存储选择的环境 + let selectedEnvironment = null; + + // 添加环境配置 + const ENV_CONFIG = { + DEV: { + url: 'https://ibp-dev.longi.com/main/#/login', + needCredentials: false + }, + UAT: { + url: 'https://ibp-test.longi.com/main/#/login?debug=ly', + needCredentials: true, + username: 'qichenadmin', + password: '123456' + } + }; + + // 添加环境选择和登录处理函数 + const handleLogin = (environment) => { + const envConfig = ENV_CONFIG[environment]; + + cy.visit(envConfig.url); + // 忽略未捕获的异常 + Cypress.on('uncaught:exception', (err, runnable) => false); + + cy.get('#app', {timeout: 3000}).should('exist'); + + if (envConfig.needCredentials) { + // 清空输入框 + cy.get('input[name="username"]').clear(); + cy.get('input[name="passWord"]').clear(); + + // 输入账号密码 + cy.get('input[name="username"]').type(envConfig.username); + cy.get('input[name="passWord"]').type(envConfig.password); + } + + // 执行登录 + cy.get('.container-button').click(); + // 等待登录完成 + cy.wait(3000); + }; + + // 在整个测试套件开始前先选择环境 + before(() => { + // 添加一个变量来控制环境选择流程 + let envSelected = false; + + cy.visit('https://ibp-dev.longi.com/main/#/login'); + + // 忽略未捕获的异常 + Cypress.on('uncaught:exception', (err, runnable) => false); + + // 创建环境选择对话框 + const dialogHtml = ` +
+
+
+ 选择测试环境 +
+
+
+ 请选择要测试的环境 +
+
+ + +
+
+
+
+ `; + + // 添加对话框到页面 + cy.get('body').then($body => { + $body.append(dialogHtml); + }); + + // 等待环境选择 + cy.get('.env-btn').then($buttons => { + // 为每个按钮添加点击事件 + $buttons.each((_, button) => { + Cypress.$(button).on('click', () => { + const env = Cypress.$(button).data('env'); + selectedEnvironment = env; + envSelected = true; + Cypress.$('.env-select-dialog').remove(); + }); + }); + }); + + // 使用递归函数等待环境选择 + function waitForEnvSelection() { + if (envSelected) { + cy.log(`已选择 ${selectedEnvironment} 环境`); + // 如果是UAT环境,则需要重新访问对应的URL + if (selectedEnvironment === 'UAT') { + cy.visit(ENV_CONFIG.UAT.url); + } + // 执行登录 + handleLogin(selectedEnvironment); + } else { + cy.wait(1000).then(waitForEnvSelection); + } + } + + // 开始等待环境选择 + waitForEnvSelection(); + }); + + // 修改测试报告数据结构,使用更轻量的格式 + let testReport = { + summary: { + totalPages: 0, + totalTabs: 0, + failedCount: 0, + startTime: formatTime(new Date()), + endTime: '' + }, + currentBatch: { + pages: [], + failedPages: [] + } + }; + + // 每个测试用例前不再重新访问登录页 + // beforeEach(() => { + // // 只设置异常处理 + // Cypress.on('uncaught:exception', (err, runnable) => false); + // }); + + // 添加内存清理的公共方法 + const cleanupMemory = () => { + // 执行垃圾回收 + cy.task('clearMemory', null, {log: false}); + + // 清理DOM快照 + cy.get('body').then(() => { + Cypress.$(document).find('*').off(); + Cypress.$('.el-loading-mask').remove(); + Cypress.$('.el-message').remove(); + Cypress.$('.el-message-box').remove(); + + // 清理可能的内存泄漏 + Cypress.$('iframe').remove(); // 移除所有iframe + Cypress.$('img').remove(); // 移除所有图片 + Cypress.$('video').remove(); // 移除所有视频 + + // 清理事件监听器 + const win = Cypress.$(window); + win.off(); + win.find('*').off(); + }); + + // 手动清理一些可能的引用 + if (window.gc) { + window.gc(); + } + }; + + // 添加关闭tab的公共方法 + const closeActiveTab = (pageName) => { + cy.log(`🗑️ 正在关闭页面 "${pageName}" 的tab...`); + cy.get('body').then($body => { + // 检查是否存在活动的tab和关闭按钮 + const $activeTab = $body.find('.vab-tabs .el-tabs--card .el-tabs__item.is-active'); + const $closeButton = $activeTab.find('.el-icon.is-icon-close'); + + if ($activeTab.length > 0 && $closeButton.length > 0) { + cy.get('.vab-tabs .el-tabs--card .el-tabs__item.is-active .el-icon.is-icon-close') + .should('be.visible') + .click({force: true}) + .then(() => { + cy.wait(500); // 等待关闭动画完成 + cleanupMemory(); // 在关闭tab后清理内存 + }); + } else { + cy.log(`⚠️ [${pageName}] 没有找到可关闭的tab,继续执行...`); + } + }); + }; + + // 添加报告写入方法 + const writeReportBatch = () => { + const batchNumber = Math.floor(testReport.summary.totalPages / 20); // 每20个页面写入一次 + const fileName = `cypress/reports/${selectedEnvironment}-menu-test-report-batch-${batchNumber}.json`; + + cy.log(`📊 写入批次报告 ${batchNumber}:`); + cy.log(`当前批次页面数: ${testReport.currentBatch.pages.length}`); + cy.log(`当前批次失败页面数: ${testReport.currentBatch.failedPages.length}`); + + cy.writeFile(fileName, testReport.currentBatch).then(() => { + cy.log(`✅ 批次报告已写入: ${fileName}`); + // 清空当前批次数据,释放内存 + testReport.currentBatch = { + pages: [], + failedPages: [] + }; + cleanupMemory(); + }); + }; + + // 页面信息管理类 + class PageInfo { + constructor(name, isThirdMenu = false, parentMenu = null) { + this.name = isThirdMenu ? `${parentMenu} > ${name}` : name; + this.startTime = formatTime(new Date()); + this.endTime = ''; + this.tabs = []; + this.hasThirdMenu = false; + this.parentMenu = parentMenu; + } + + addToBatch() { + testReport.currentBatch.pages.push(this); + testReport.summary.totalPages++; + return this; + } + + setAsThirdMenu() { + this.hasThirdMenu = true; + return this; + } + + complete() { + this.endTime = formatTime(new Date()); + return this; + } + + addTab(tabInfo) { + if (!this.tabs) { + this.tabs = []; + } + this.tabs.push(tabInfo); + return this; + } + } + + // 处理单个tab页 + const processSingleTab = (currentPage, $allTabs) => { + const tabText = $allTabs.text().trim(); + cy.log(`📑 页面 "${currentPage.name}" 只有一个tab页: ${tabText}`); + + const tabStartTime = new Date(); + const tabInfo = { + name: tabText, + startTime: formatTime(tabStartTime), + endTime: formatTime(tabStartTime), + duration: '0.00', + status: 'success' + }; + + if (!currentPage.tabs) { + currentPage.tabs = []; + } + currentPage.tabs.push(tabInfo); + testReport.summary.totalTabs += 1; + }; + + // 更新tab信息 + const updateTabInfo = (tabInfo, startTime, status) => { + const endTime = new Date(); + tabInfo.endTime = formatTime(endTime); + tabInfo.duration = calculateDuration(startTime, endTime); + tabInfo.status = status; + }; + + // 处理多个tab页 + const processMultipleTabs = (currentPage, $inactiveTabs) => { + cy.log(`\n📑 页面 "${currentPage.name}" 发现 ${$inactiveTabs.length} 个待访问的tab页`); + testReport.summary.totalTabs += $inactiveTabs.length; + + const processTab = (index = 0) => { + if (index >= $inactiveTabs.length) { + cy.log(`✅ 页面 "${currentPage.name}" 的所有tab页处理完成`); + return; + } + + const tabProgress = ((index + 1) / $inactiveTabs.length * 100).toFixed(1); + const $currentTab = Cypress.$($inactiveTabs[index]); + const tabText = $currentTab.text().trim(); + + cy.log(`🔸 正在处理tab页 [${index + 1}/${$inactiveTabs.length}] (${tabProgress}%): ${currentPage.name} > ${tabText}`); + + const tabStartTime = new Date(); + const tabInfo = { + name: tabText, + startTime: formatTime(tabStartTime), + endTime: '', + duration: '', + status: 'pending' + }; + + if (!currentPage.tabs) { + currentPage.tabs = []; + } + currentPage.tabs.push(tabInfo); + + cy.wrap($currentTab).click({force: true}).then(() => { + cy.log(`⏳ [${currentPage.name}] 已点击tab页: ${tabText},等待加载...`); + + let tabLoadRetry = 0; + const waitForTabLoad = () => { + if (tabLoadRetry >= 30) { + cy.log(`❌ [${currentPage.name}] Tab页 ${tabText} 加载超时 [${tabLoadRetry}次重试],继续下一个`); + updateTabInfo(tabInfo, tabStartTime, 'timeout'); + processTab(index + 1); + return; + } + + cy.get('@pageBody').then($body => { + const isLoading = $body.find('.el-loading-mask').length > 0; + const hasTabError = $body.find('.el-message-box__message').length > 0 || + $body.find('.el-message--error').length > 0; + + if (hasTabError) { + cy.log(`❌ [${currentPage.name}] Tab页 ${tabText} 加载出现错误`); + updateTabInfo(tabInfo, tabStartTime, 'error'); + processTab(index + 1); + return; + } + + if (isLoading) { + tabLoadRetry++; + cy.log(`⏳ [${currentPage.name}] Tab页 ${tabText} 第 ${tabLoadRetry} 次检查加载状态...`); + cy.wait(500).then(waitForTabLoad); + } else { + updateTabInfo(tabInfo, tabStartTime, 'success'); + cy.log(`✅ [${currentPage.name}] Tab页 ${tabText} 加载完成 (耗时: ${tabInfo.duration}秒)`); + cy.wait(1000).then(() => processTab(index + 1)); + } + }); + }; + + waitForTabLoad(); + }); + }; + + processTab(); + }; + + // 处理页面中的tab页 + const processPageTabs = (currentPage, pageStartTime) => { + return cy.get('.workSpaceBaseTab').as('workTabs').then($tabs => { + if ($tabs.length > 0) { + return cy.get('@workTabs').find('.el-tabs__item').as('allTabs').then($allTabs => { + const $activeTab = $allTabs.filter('.is-active'); + const $inactiveTabs = $allTabs.filter(':not(.is-active)').toArray(); + + // 首先处理活动的Tab页,使用页面开始时间 + if ($activeTab.length > 0) { + const activeTabText = $activeTab.text().trim(); + const tabInfo = { + name: activeTabText, + startTime: formatTime(pageStartTime), // 使用页面开始时间作为Tab开始时间 + endTime: '', + duration: '', + status: 'pending' + }; + + if (!currentPage.tabs) { + currentPage.tabs = []; + } + currentPage.tabs.push(tabInfo); + testReport.summary.totalTabs += 1; + + // 检查活动Tab页的加载状态 + let activeTabLoadRetry = 0; + const waitForActiveTabLoad = () => { + if (activeTabLoadRetry >= 30) { + cy.log(`❌ [${currentPage.name}] 活动Tab页 ${activeTabText} 加载超时`); + updateTabInfo(tabInfo, pageStartTime, 'timeout'); + return; + } + + cy.get('@pageBody').then($body => { + const isLoading = $body.find('.el-loading-mask').length > 0; + const hasTabError = $body.find('.el-message-box__message').length > 0 || + $body.find('.el-message--error').length > 0; + + if (hasTabError) { + cy.log(`❌ [${currentPage.name}] 活动Tab页 ${activeTabText} 加载出现错误`); + updateTabInfo(tabInfo, pageStartTime, 'error'); + return; + } + + if (isLoading) { + activeTabLoadRetry++; + cy.log(`⏳ [${currentPage.name}] 活动Tab页 ${activeTabText} 第 ${activeTabLoadRetry} 次检查加载状态...`); + cy.wait(500).then(waitForActiveTabLoad); + } else { + updateTabInfo(tabInfo, pageStartTime, 'success'); + cy.log(`✅ [${currentPage.name}] 活动Tab页 ${activeTabText} 加载完成 (耗时: ${tabInfo.duration}秒)`); + } + }); + }; + + waitForActiveTabLoad(); + } + + // 然后处理其他Tab页 + if ($inactiveTabs.length > 0) { + processMultipleTabs(currentPage, $inactiveTabs); + } else if ($allTabs.length === 1) { + cy.log(`ℹ️ [${currentPage.name}] 只有一个活动的tab页`); + } + + closeActiveTab(currentPage.name); + }); + } else { + cy.log(`ℹ️ [${currentPage.name}] 页面中没有找到workSpaceBaseTab`); + } + }); + }; + + // 记录失败页面 + const recordFailedPage = (pageName, error, startTime) => { + testReport.currentBatch.failedPages.push({ + name: pageName, + time: formatTime(new Date()), + error: error, + duration: calculateDuration(startTime, new Date()) + }); + cleanupMemory(); + }; + + // 修改 waitForPageLoad 函数,接收开始时间参数 + const waitForPageLoad = (currentPage, pageStartTime) => { + cy.log('等待页面数据加载...'); + let retryCount = 0; + const maxRetries = 30; + + function check() { + if (retryCount >= maxRetries) { + cy.log('页面加载超时,记录失败并继续执行...'); + recordFailedPage(currentPage.name, '页面加载超时', pageStartTime); + return; + } + + cy.get('@pageBody').then($body => { + const hasLoadingMask = $body.find('.el-loading-mask').length > 0; + const hasError = $body.find('.el-message-box__message').length > 0 || + $body.find('.el-message--error').length > 0; + + if (hasError) { + cy.log('页面加载出现错误'); + recordFailedPage(currentPage.name, '页面加载出现错误', pageStartTime); + return; + } + + if (hasLoadingMask) { + retryCount++; + cy.wait(500).then(check); + } else { + cy.log('页面数据加载完成'); + cy.wait(1000).then(() => { + // 修改处理Tab页的逻辑,传入页面开始时间 + processPageTabs(currentPage, pageStartTime); + }); + } + }); + } + + check(); + }; + + // 处理有三级菜单的情况 + const processWithThirdMenu = (pageInfo, $thirdMenuItems, $menuItem, menuText, processMenuItems, index) => { + pageInfo.hasThirdMenu = true; + cy.log(`✓ ${menuText} 包含 ${$thirdMenuItems.length} 个三级菜单`); + + const processThirdMenu = (thirdIndex = 0) => { + if (thirdIndex >= $thirdMenuItems.length) { + pageInfo.endTime = formatTime(new Date()); + const thirdMenuProgress = (100).toFixed(1); + cy.log(`✅ 完成菜单 "${menuText}" 的所有三级菜单处理 (${thirdMenuProgress}%)`); + closeActiveTab(menuText); + cy.wait(1000).then(() => processMenuItems(index + 1)); + return; + } + + const thirdMenuProgress = ((thirdIndex + 1) / $thirdMenuItems.length * 100).toFixed(1); + + if (thirdIndex > 0) { + cy.wrap($menuItem).click(); + cy.wait(1000); + } + + cy.get('@thirdMenuItems') + .eq(thirdIndex) + .then($thirdMenu => { + const thirdMenuText = $thirdMenu.text().trim(); + cy.log(`\n🔸 处理三级菜单 [${thirdIndex + 1}/${$thirdMenuItems.length}] (${thirdMenuProgress}%): ${menuText} > ${thirdMenuText}`); + + const thirdMenuPage = new PageInfo(thirdMenuText, true, menuText).addToBatch(); + + cy.wrap($thirdMenu).click(); + waitForPageLoad(thirdMenuPage, new Date()); + cy.wait(1000).then(() => processThirdMenu(thirdIndex + 1)); + }); + }; + + processThirdMenu(); + }; + + // 处理没有三级菜单的情况 + const processWithoutThirdMenu = (pageInfo, menuText, processMenuItems, index) => { + cy.log(`× ${menuText} 点击后没有显示三级菜单`); + const pageStartTime = new Date(); // 记录页面开始加载的时间 + + // 修改 waitForPageLoad 函数的调用,传入开始时间 + waitForPageLoad(pageInfo, pageStartTime); + pageInfo.endTime = formatTime(new Date()); + closeActiveTab(menuText); + cy.wait(1000).then(() => processMenuItems(index + 1)); + }; + + it('验证菜单操作', () => { + // 初始化时清理内存 + cleanupMemory(); + + // 添加一个全局变量来控制测试流程 + let testStarted = false; + let selectedMenuItems = null; + + cy.wait(3000); + cy.log('开始菜单验证测试'); + + // 为常用元素创建别名 + cy.get('body').as('pageBody'); + cy.get('.ly-side-nav').as('sideNav'); + cy.get('.el-scrollbar__view > .el-menu').as('mainMenu'); + cy.get('.vab-content .toggle-icon').as('menuToggle'); + + // 确保菜单展开 + cy.get('@sideNav').then(($el) => { + cy.log(`菜单现在的偏移量是:${$el.css('left')}`) + const leftValue = $el.css('left'); + if (leftValue !== '0px') { + cy.log('菜单未展开,点击展开'); + cy.get('@menuToggle').click(); + cy.log('已点击展开菜单'); + } else { + cy.log('菜单已经处于展开状态'); + } + }); + + cy.log('开始获取菜单结构'); + cy.get('@mainMenu').then(($elMenu) => { + const allMenuItems = $elMenu.find('.el-sub-menu__title, .el-menu-item'); + const menuItems = []; + + // 过滤并整理菜单项 + allMenuItems.each((index, el) => { + const $item = Cypress.$(el); + const menuText = $item.find('.titleSpan').text().trim() || $item.text().trim(); + const isFirstLevel = $item.find('.menuIcon').length > 0; + + if (!isFirstLevel) { + menuItems.push({ + index, + text: menuText, + element: el + }); + } + }); + + cy.log(`🔍 找到 ${menuItems.length} 个可测试的菜单项`); + + // 创建菜单选择对话框 + const dialogHtml = ` + + `; + + // 添加对话框到页面 + Cypress.$('body').append(dialogHtml); + + // 更新开始测试按钮状态的函数 + const updateStartButtonState = () => { + const checkedCount = Cypress.$('.menu-list input[type="checkbox"]:checked').length; + Cypress.$('.start-test-btn').prop('disabled', checkedCount === 0); + Cypress.$('.menu-selection-status').text( + checkedCount === 0 ? + '请选择需要测试的菜单项,然后点击"开始测试"按钮开始测试。' : + `已选择 ${checkedCount} 个菜单项,点击"开始测试"按钮开始测试。` + ); + }; + + // 绑定事件 + Cypress.$('.select-all-btn').on('click', function () { + const checkboxes = Cypress.$('.menu-list input[type="checkbox"]'); + const allChecked = checkboxes.length === checkboxes.filter(':checked').length; + checkboxes.prop('checked', !allChecked); + updateStartButtonState(); + }); + + // 为每个复选框添加change事件 + Cypress.$('.menu-list input[type="checkbox"]').on('change', updateStartButtonState); + + // 绑定开始测试按钮事件 + Cypress.$('.start-test-btn').on('click', function () { + const selectedIndexes = []; + Cypress.$('.menu-list input[type="checkbox"]:checked').each(function () { + selectedIndexes.push(parseInt(Cypress.$(this).val())); + }); + + if (selectedIndexes.length > 0) { + selectedMenuItems = selectedIndexes.map(index => menuItems[index]); + testStarted = true; + Cypress.$('.menu-select-dialog').remove(); + } + }); + }); + + // 使用递归检查测试是否开始 + function checkTestStarted() { + if (testStarted) { + cy.log(`选中了 ${selectedMenuItems.length} 个菜单项进行测试`); + startTesting(selectedMenuItems); + } else { + cy.wait(1000).then(checkTestStarted); + } + } + + // 开始测试的函数 + function startTesting(selectedMenus) { + let processedCount = 0; + + const processMenuItems = (index = 0) => { + if (index >= selectedMenus.length) { + cy.log('🎉 所有菜单项处理完成!'); + writeReportBatch(); + testReport.summary.endTime = formatTime(new Date()); + + // 生成时间戳 + const timestamp = new Date().toLocaleString('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + hour12: false + }).replace(/[\/\s:]/g, ''); + + // 创建以时间戳命名的报告目录 + const reportDir = `cypress/reports/report-${timestamp}`; + + // 创建报告目录并生成最终报告 + cy.task('createReportDir', reportDir).then(() => { + // 将摘要信息写入到时间戳目录中 + cy.writeFile(`${reportDir}/test-summary-${timestamp}.json`, testReport.summary).then(() => { + generateFinalReport(reportDir, timestamp, selectedEnvironment); + }); + }); + return; + } + + if (testReport.currentBatch.pages.length >= 20) { + writeReportBatch(); + } + + if (index > 0 && index % 3 === 0) { + cleanupMemory(); + } + + const totalMenuItems = selectedMenus.length; + const progress = ((index + 1) / totalMenuItems * 100).toFixed(1); + const menuItem = selectedMenus[index]; + + cy.log(`\n===== 📍 正在处理菜单项 [${index + 1}/${totalMenuItems}] (${progress}%): ${menuItem.text} =====`); + + const pageInfo = new PageInfo(menuItem.text).addToBatch(); + + cy.wrap(menuItem.element).click().then(() => { + cy.wait(1000); + + cy.get('@pageBody').then($body => { + // 修改检查三级菜单的逻辑 + cy.get('body').then($body => { + const hasThirdMenu = $body.find('.el-popper.is-light.el-popover .menuTitle.canClick').length > 0; + + if (hasThirdMenu) { + cy.get('.el-popper.is-light.el-popover .menuTitle.canClick').as('thirdMenuItems').then($thirdMenuItems => { + processWithThirdMenu(pageInfo, $thirdMenuItems, Cypress.$(menuItem.element), menuItem.text, processMenuItems, index); + }); + } else { + processWithoutThirdMenu(pageInfo, menuItem.text, processMenuItems, index); + } + }); + }); + }); + }; + + cy.log('开始遍历选中的菜单项'); + processMenuItems(); + } + + // 开始检查是否可以开始测试 + checkTestStarted(); + }); + + // 在每个测试用例结束后清理内存 + afterEach(() => { + cleanupMemory(); + }); + + // 修改生成HTML报告的方法,接收环境参数 + const generateFinalReport = (reportDir, timestamp, environment) => { + // 读取所有批次报告 + cy.task('readAllReports', environment).then((allBatches) => { + cy.log(`📊 读取到 ${allBatches.length} 个批次报告`); + + // 合并所有批次数据 + const combinedReport = { + pages: [], + failedPages: [] + }; + + if (Array.isArray(allBatches)) { + allBatches.forEach(batch => { + if (batch.pages) { + cy.log(`合并批次报告: ${batch.pages.length} 个页面`); + combinedReport.pages.push(...batch.pages); + } + if (batch.failedPages) { + combinedReport.failedPages.push(...batch.failedPages); + } + }); + } + + // 添加当前批次的数据 + if (testReport.currentBatch.pages.length > 0) { + cy.log(`添加当前批次: ${testReport.currentBatch.pages.length} 个页面`); + combinedReport.pages.push(...testReport.currentBatch.pages); + } + if (testReport.currentBatch.failedPages.length > 0) { + combinedReport.failedPages.push(...testReport.currentBatch.failedPages); + } + + cy.log(`📊 总计: ${combinedReport.pages.length} 个页面, ${testReport.summary.totalTabs} 个Tab页`); + + const htmlReport = ` + + + + + 系统页面可用性测试报告 + + + +

系统页面可用性测试报告

+
+

测试摘要

+

测试环境: ${environment}

+

总页面数: ${combinedReport.pages.length}

+

总Tab页数: ${testReport.summary.totalTabs}

+

失败页面数: ${combinedReport.failedPages.length}

+

开始时间: ${testReport.summary.startTime}

+

结束时间: ${testReport.summary.endTime}

+

总耗时: ${calculateDuration(new Date(testReport.summary.startTime), new Date(testReport.summary.endTime))}秒

+
+ + ${combinedReport.failedPages.length > 0 ? ` +
+

失败页面列表

+ ${combinedReport.failedPages.map(page => ` +
+

${page.name}

+

失败时间: ${page.time}

+

错误信息: ${page.error}

+

耗时: ${page.duration}秒

+
+ `).join('')} +
+ ` : ''} + +

详细测试结果

+ + + + + + + + + + + ${combinedReport.pages.map(page => { + if (!page.tabs || page.tabs.length === 0) { + return ` + + + + + `; + } + return page.tabs.map((tab, index) => ` + + + + + + + + + `).join('') + }).join('')} +
页面名称开始时间Tab页名称Tab开始时间Tab结束时间加载时长(秒)状态
${page.name}${page.startTime}无Tab页
${index === 0 ? page.name : ''}${index === 0 ? page.startTime : ''}${tab.name}${tab.startTime}${tab.endTime}${tab.duration}${tab.status}
+ +`; + + // 将报告写入到时间戳目录中 + cy.writeFile(`${reportDir}/${environment}-test-report-${timestamp}.html`, htmlReport).then(() => { + cy.log(`✅ 已生成测试报告: ${reportDir}/${environment}-test-report-${timestamp}.html`); + + // 添加完成提示对话框 + const completionDialogHtml = ` +
+
+
+ ✅ 测试完成 +
+
+
+

测试报告已生成完成!

+

报告目录:
${reportDir}

+

报告文件:
${environment}-test-report-${timestamp}.html

+
+
+ +
+
+ `; + + // 添加对话框到页面 + cy.get('body').then($body => { + $body.append(completionDialogHtml); + + // 绑定关闭按钮事件 + Cypress.$('.close-dialog-btn').on('click', function() { + Cypress.$('.completion-dialog').remove(); + }); + }); + }); + + // 清理批次报告文件 + cy.task('cleanupReports', environment); + }); + }; + + // 删除重复的after钩子 + after(() => { + cleanupMemory(); + }); +}); \ No newline at end of file diff --git a/cypress/fixtures/example.json b/cypress/fixtures/example.json new file mode 100644 index 0000000..02e4254 --- /dev/null +++ b/cypress/fixtures/example.json @@ -0,0 +1,5 @@ +{ + "name": "Using fixtures to represent data", + "email": "hello@cypress.io", + "body": "Fixtures are a great way to mock data for responses to routes" +} diff --git a/cypress/support/commands.js b/cypress/support/commands.js new file mode 100644 index 0000000..66ea16e --- /dev/null +++ b/cypress/support/commands.js @@ -0,0 +1,25 @@ +// *********************************************** +// This example commands.js shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** +// +// +// -- This is a parent command -- +// Cypress.Commands.add('login', (email, password) => { ... }) +// +// +// -- This is a child command -- +// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This will overwrite an existing command -- +// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) \ No newline at end of file diff --git a/cypress/support/e2e.js b/cypress/support/e2e.js new file mode 100644 index 0000000..3eaffff --- /dev/null +++ b/cypress/support/e2e.js @@ -0,0 +1,17 @@ +// *********************************************************** +// This example support/e2e.js is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Import commands.js using ES2015 syntax: +import './commands' \ No newline at end of file