cypress-longi/cypress/e2e/需求计划页面是否可用.cy.js
2025-03-03 12:30:01 +08:00

1123 lines
47 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

describe('隆基系统全部页面验证', () => {
// 在所有测试开始前清除localStorage
beforeEach(() => {
// 清除localStorage
cy.window().then((win) => {
win.localStorage.clear();
});
});
// 每个测试用例结束后清理内存
afterEach(() => {
cleanupMemory();
});
// 创建通用对话框模板函数
const createDialogTemplate = (options) => {
const {
dialogClass,
title,
width,
bodyContent,
footerContent
} = options;
return `
<div class="el-dialog__wrapper ${dialogClass}" style="position: fixed; top: 0; right: 0; bottom: 0; left: 0; overflow: auto; margin: 0; z-index: 2000; background: rgba(0, 0, 0, 0.5);">
<div class="el-dialog" style="margin: 15vh auto 50px; width: ${width || '500px'}; background: #fff; border-radius: 4px; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);">
<div class="el-dialog__header" style="padding: 20px; border-bottom: 1px solid #eee;">
<span class="el-dialog__title" style="font-size: 18px; color: #303133;">${title}</span>
</div>
<div class="el-dialog__body" style="padding: 20px; max-height: 50vh; overflow-y: auto;">
${bodyContent}
</div>
<div class="el-dialog__footer" style="padding: 20px; text-align: right; border-top: 1px solid #eee;">
${footerContent}
</div>
</div>
</div>
`;
};
// 格式化时间的辅助函数
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 envDialogBodyContent = `
<div class="env-selection-status" style="margin-bottom: 15px; padding: 10px; background-color: #f0f9eb; border-radius: 4px; color: #67c23a;">
请选择要测试的环境
</div>
<div class="env-list" style="display: flex; gap: 10px;">
<button class="env-btn el-button el-button--primary" data-env="DEV" style="flex: 1; padding: 12px; height: 40px; line-height: 1;">
DEV环境
</button>
<button class="env-btn el-button el-button--success" data-env="UAT" style="flex: 1; padding: 12px; height: 40px; line-height: 1;">
UAT环境
</button>
</div>
`;
const dialogHtml = createDialogTemplate({
dialogClass: 'env-select-dialog',
title: '选择测试环境',
width: '400px',
bodyContent: envDialogBodyContent,
footerContent: ''
});
// 添加对话框到页面
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: []
}
};
// 优化内存清理函数确保与Cypress命令链兼容
const cleanupMemory = () => {
cy.log('🧹 执行内存清理...');
// 记录清理前的内存使用情况
cy.window({log: false}).then(win => {
if (win.performance && win.performance.memory) {
const beforeMemory = Math.round(win.performance.memory.usedJSHeapSize / 1024 / 1024);
cy.log(`清理前内存使用: ${beforeMemory}MB`);
}
// 执行内存清理操作
try {
// 1. 清理DOM元素
const elementsToRemove = [
'.el-loading-mask',
'.el-message',
'.el-message-box:not(.is-message)',
'.el-notification',
'.el-drawer__wrapper:not(.active)',
'.el-tooltip__popper',
'.el-select-dropdown',
'.el-popover',
'.el-dropdown-menu',
'.v-modal'
];
elementsToRemove.forEach(selector => {
if (Cypress.$(selector).length > 0) {
Cypress.$(selector).remove();
}
});
// 2. 清理iframe内容
Cypress.$('iframe').each((_, el) => {
try {
el.src = 'about:blank';
} catch (e) {
// 忽略错误
}
});
// 3. 清理大型表格内容
Cypress.$('.el-table__body tr').each((index, el) => {
if (index > 20) {
Cypress.$(el).remove();
}
});
// 4. 清理事件监听器(保留菜单相关元素)
Cypress.$('*:not(.el-menu):not(.el-menu-item):not(.el-submenu):not(.sidebar-menu):not(.el-scrollbar__view)').each((_, el) => {
try {
const element = Cypress.$(el);
// 只清理非菜单元素的事件
if (!element.closest('.el-menu, .sidebar-menu').length) {
element.off();
}
} catch (e) {
// 忽略错误
}
});
// 5. 清理定时器
let id = window.setTimeout(() => {}, 0);
while (id--) {
window.clearTimeout(id);
window.clearInterval(id);
}
// 6. 清理Vue相关引用
if (win.__VUE_DEVTOOLS_GLOBAL_HOOK__) {
win.__VUE_DEVTOOLS_GLOBAL_HOOK__.Vue = null;
}
// 7. 手动触发一些可能的内存释放
win.dispatchEvent(new Event('beforeunload'));
// 8. 清理控制台
console.clear();
} catch (e) {
console.error('清理DOM时出错:', e);
}
}).then(() => {
// 等待一段时间让垃圾回收生效
return cy.wait(1000, {log: false});
}).then(() => {
// 再次检查内存使用情况
return cy.window({log: false}).then(win => {
if (win.performance && win.performance.memory) {
const afterMemory = Math.round(win.performance.memory.usedJSHeapSize / 1024 / 1024);
cy.log(`清理后内存使用: ${afterMemory}MB`);
}
cy.log('🧹 内存清理完成');
});
});
};
// 添加关闭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 = () => {
if (testReport.currentBatch.pages.length === 0) {
return;
}
const batchNumber = Math.floor(Math.random() * 10000);
cy.log(`📊 写入批次报告 ${batchNumber}:`);
cy.log(`当前批次页面数: ${testReport.currentBatch.pages.length}`);
cy.log(`当前批次失败页面数: ${testReport.currentBatch.failedPages.length}`);
cy.task('saveReport', {
data: testReport.currentBatch,
filename: `${selectedEnvironment}-menu-test-report-batch-${batchNumber}.json`
}).then(() => {
cy.log(`✅ 批次报告已写入`);
// 清空当前批次
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;
// 使用迭代方式处理Tab页避免深度递归
const processTabsSequentially = (tabs, startIndex = 0) => {
if (startIndex >= tabs.length) {
cy.log(`✅ 页面 "${currentPage.name}" 的所有tab页处理完成`);
return cy.wrap(null);
}
const $currentTab = Cypress.$(tabs[startIndex]);
const tabText = $currentTab.text().trim();
const tabProgress = ((startIndex + 1) / tabs.length * 100).toFixed(1);
cy.log(`🔸 正在处理tab页 [${startIndex + 1}/${tabs.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);
// 处理单个Tab然后链式处理下一个
return cy.wrap($currentTab)
.click({force: true})
.then(() => {
cy.log(`⏳ [${currentPage.name}] 已点击tab页: ${tabText},等待加载...`);
return waitForTabLoadWithTimeout(currentPage, tabInfo, tabStartTime, 30);
})
.then(() => {
// 处理下一个Tab
return processTabsSequentially(tabs, startIndex + 1);
});
};
// 等待Tab加载的函数使用超时机制
const waitForTabLoadWithTimeout = (currentPage, tabInfo, tabStartTime, maxRetries, retryCount = 0) => {
if (retryCount >= maxRetries) {
cy.log(`❌ [${currentPage.name}] Tab页 ${tabInfo.name} 加载超时 [${retryCount}次重试]`);
updateTabInfo(tabInfo, tabStartTime, 'timeout');
return cy.wrap(null);
}
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页 ${tabInfo.name} 加载出现错误`);
updateTabInfo(tabInfo, tabStartTime, 'error');
return cy.wrap(null);
}
if (isLoading) {
cy.log(`⏳ [${currentPage.name}] Tab页 ${tabInfo.name}${retryCount + 1} 次检查加载状态...`);
return cy.wait(500).then(() => {
return waitForTabLoadWithTimeout(currentPage, tabInfo, tabStartTime, maxRetries, retryCount + 1);
});
} else {
updateTabInfo(tabInfo, tabStartTime, 'success');
cy.log(`✅ [${currentPage.name}] Tab页 ${tabInfo.name} 加载完成 (耗时: ${tabInfo.duration}秒)`);
return cy.wait(1000);
}
});
};
// 开始处理所有Tab
return processTabsSequentially($inactiveTabs);
};
// 处理页面中的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 processThirdMenuSequentially = (items, startIndex = 0) => {
if (startIndex >= items.length) {
pageInfo.endTime = formatTime(new Date());
const thirdMenuProgress = (100).toFixed(1);
cy.log(`✅ 完成菜单 "${menuText}" 的所有三级菜单处理 (${thirdMenuProgress}%)`);
closeActiveTab(menuText);
return cy.wait(1000).then(() => processMenuItems(index + 1));
}
const thirdMenuProgress = ((startIndex + 1) / items.length * 100).toFixed(1);
if (startIndex > 0) {
return cy.wrap($menuItem).click()
.wait(1000)
.then(() => {
return cy.get('@thirdMenuItems')
.eq(startIndex)
.then(processCurrentThirdMenuItem);
});
} else {
return cy.get('@thirdMenuItems')
.eq(startIndex)
.then(processCurrentThirdMenuItem);
}
function processCurrentThirdMenuItem($thirdMenu) {
const thirdMenuText = $thirdMenu.text().trim();
cy.log(`\n🔸 处理三级菜单 [${startIndex + 1}/${items.length}] (${thirdMenuProgress}%): ${menuText} > ${thirdMenuText}`);
const thirdMenuPage = new PageInfo(thirdMenuText, true, menuText).addToBatch();
return cy.wrap($thirdMenu).click()
.then(() => {
waitForPageLoad(thirdMenuPage, new Date());
return cy.wait(1000);
})
.then(() => {
return processThirdMenuSequentially(items, startIndex + 1);
});
}
};
return processThirdMenuSequentially($thirdMenuItems);
};
// 处理没有三级菜单的情况
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 menuDialogBodyContent = `
<div class="menu-selection-status" style="margin-bottom: 15px; padding: 10px; background-color: #f0f9eb; border-radius: 4px; color: #67c23a;">
请选择需要测试的菜单项,然后点击"开始测试"按钮开始测试。
</div>
<div class="menu-list">
${menuItems.map((item, idx) => `
<div class="menu-item" style="margin-bottom: 10px;">
<label style="display: flex; align-items: center;">
<input type="checkbox" value="${idx}" style="margin-right: 8px;">
<span>${item.text}</span>
</label>
</div>
`).join('')}
</div>
`;
const menuDialogFooterContent = `
<button class="select-all-btn el-button el-button--default" style="margin-right: 10px;">全选</button>
<button class="start-test-btn el-button el-button--primary" disabled>开始测试</button>
`;
const dialogHtml = createDialogTemplate({
dialogClass: 'menu-select-dialog',
title: '选择要测试的菜单',
width: '600px',
bodyContent: menuDialogBodyContent,
footerContent: menuDialogFooterContent
});
// 添加对话框到页面
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 batchSize = 10; // 每批处理10个菜单项
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/${selectedEnvironment}-report-${timestamp}`;
// 创建报告目录并生成最终报告
cy.task('createReportDir', reportDir).then(() => {
// 将摘要信息写入到时间戳目录中
cy.writeFile(`${reportDir}/${selectedEnvironment}-test-summary-${timestamp}.json`, testReport.summary).then(() => {
generateFinalReport(reportDir, timestamp, selectedEnvironment);
});
});
return;
}
if (testReport.currentBatch.pages.length >= 20) {
writeReportBatch();
}
// 每处理一定数量的菜单项,重新加载页面以释放内存
if (index > 0 && index % batchSize === 0) {
cy.log(`已处理 ${index} 个菜单项,重新加载页面以释放内存...`);
// 保存当前进度
writeReportBatch();
// 清理内存
cleanupMemory();
// 重新加载页面
cy.reload();
// 等待页面加载完成
cy.get('.ly-side-nav', { timeout: 30000 }).should('be.visible');
// 确保菜单展开
cy.get('.ly-side-nav').then($nav => {
if ($nav.css('left') !== '0px') {
cy.get('.vab-content .toggle-icon').click();
}
});
}
// 每处理3个菜单项清理一次内存
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();
});
// 修改生成HTML报告的方法接收环境参数
const generateFinalReport = (reportDir, timestamp, environment) => {
// 读取所有批次报告
cy.task('readAllReports', environment).then((allBatches) => {
cy.log(`📊 读取到 ${allBatches.length} 个批次报告`);
// 使用更高效的方式合并批次数据
const combinedReport = {
pages: [],
failedPages: []
};
// 预计算总页面数和失败页面数,提高性能
let totalPages = 0;
let totalFailedPages = 0;
// 使用for循环替代forEach减少函数调用和闭包
if (Array.isArray(allBatches)) {
for (let i = 0; i < allBatches.length; i++) {
const batch = allBatches[i];
if (batch.pages && Array.isArray(batch.pages)) {
totalPages += batch.pages.length;
}
if (batch.failedPages && Array.isArray(batch.failedPages)) {
totalFailedPages += batch.failedPages.length;
}
}
}
// 添加当前批次
if (testReport.currentBatch.pages && Array.isArray(testReport.currentBatch.pages)) {
totalPages += testReport.currentBatch.pages.length;
}
if (testReport.currentBatch.failedPages && Array.isArray(testReport.currentBatch.failedPages)) {
totalFailedPages += testReport.currentBatch.failedPages.length;
}
// 预分配数组大小,避免动态扩容
combinedReport.pages = new Array(totalPages);
combinedReport.failedPages = new Array(totalFailedPages);
// 填充数据使用for循环替代forEach
let pageIndex = 0;
let failedPageIndex = 0;
if (Array.isArray(allBatches)) {
for (let i = 0; i < allBatches.length; i++) {
const batch = allBatches[i];
if (batch.pages && Array.isArray(batch.pages)) {
for (let j = 0; j < batch.pages.length; j++) {
combinedReport.pages[pageIndex++] = batch.pages[j];
}
}
if (batch.failedPages && Array.isArray(batch.failedPages)) {
for (let j = 0; j < batch.failedPages.length; j++) {
combinedReport.failedPages[failedPageIndex++] = batch.failedPages[j];
}
}
}
}
// 添加当前批次使用for循环
if (testReport.currentBatch.pages && Array.isArray(testReport.currentBatch.pages)) {
for (let i = 0; i < testReport.currentBatch.pages.length; i++) {
combinedReport.pages[pageIndex++] = testReport.currentBatch.pages[i];
}
}
if (testReport.currentBatch.failedPages && Array.isArray(testReport.currentBatch.failedPages)) {
for (let i = 0; i < testReport.currentBatch.failedPages.length; i++) {
combinedReport.failedPages[failedPageIndex++] = testReport.currentBatch.failedPages[i];
}
}
cy.log(`📊 总计: ${combinedReport.pages.length} 个页面, ${testReport.summary.totalTabs} 个Tab页`);
// 使用字符串拼接而不是模板字符串生成HTML提高性能
let htmlReport = '<!DOCTYPE html>\n<html>\n<head>\n';
htmlReport += ' <meta charset="UTF-8">\n';
htmlReport += ' <title>系统页面可用性测试报告</title>\n';
htmlReport += ' <style>\n';
htmlReport += ' body { font-family: Arial, sans-serif; margin: 20px; }\n';
htmlReport += ' .summary { margin-bottom: 20px; }\n';
htmlReport += ' table { border-collapse: collapse; width: 100%; margin-bottom: 20px; }\n';
htmlReport += ' th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }\n';
htmlReport += ' th { background-color: #f4f4f4; }\n';
htmlReport += ' .success { color: green; }\n';
htmlReport += ' .error { color: red; }\n';
htmlReport += ' .timeout { color: orange; }\n';
htmlReport += ' .pending { color: gray; }\n';
htmlReport += ' .tab-row { background-color: #f9f9f9; }\n';
htmlReport += ' .failed-pages { margin-top: 20px; }\n';
htmlReport += ' .failed-page { background-color: #fff0f0; margin-bottom: 10px; padding: 10px; border-left: 4px solid #ff0000; }\n';
htmlReport += ' </style>\n';
htmlReport += '</head>\n<body>\n';
htmlReport += ' <h1>系统页面可用性测试报告</h1>\n';
htmlReport += ' <div class="summary">\n';
htmlReport += ' <h2>测试摘要</h2>\n';
htmlReport += ' <p>测试环境: ' + environment + '</p>\n';
htmlReport += ' <p>总页面数: ' + combinedReport.pages.length + '</p>\n';
htmlReport += ' <p>总Tab页数: ' + testReport.summary.totalTabs + '</p>\n';
htmlReport += ' <p>失败页面数: ' + combinedReport.failedPages.length + '</p>\n';
htmlReport += ' <p>开始时间: ' + testReport.summary.startTime + '</p>\n';
htmlReport += ' <p>结束时间: ' + testReport.summary.endTime + '</p>\n';
htmlReport += ' <p>总耗时: ' + calculateDuration(new Date(testReport.summary.startTime), new Date(testReport.summary.endTime)) + '秒</p>\n';
htmlReport += ' </div>\n\n';
// 生成失败页面列表使用for循环
if (combinedReport.failedPages.length > 0) {
htmlReport += ' <div class="failed-pages">\n';
htmlReport += ' <h2>失败页面列表</h2>\n';
for (let i = 0; i < combinedReport.failedPages.length; i++) {
const page = combinedReport.failedPages[i];
htmlReport += ' <div class="failed-page">\n';
htmlReport += ' <h3>' + page.name + '</h3>\n';
htmlReport += ' <p>失败时间: ' + page.time + '</p>\n';
htmlReport += ' <p>错误信息: ' + page.error + '</p>\n';
htmlReport += ' <p>耗时: ' + page.duration + '秒</p>\n';
htmlReport += ' </div>\n';
}
htmlReport += ' </div>\n';
}
// 生成详细测试结果表格使用for循环
htmlReport += ' <h2>详细测试结果</h2>\n';
htmlReport += ' <table>\n';
htmlReport += ' <tr>\n';
htmlReport += ' <th>页面名称</th>\n';
htmlReport += ' <th>开始时间</th>\n';
htmlReport += ' <th>Tab页名称</th>\n';
htmlReport += ' <th>Tab开始时间</th>\n';
htmlReport += ' <th>Tab结束时间</th>\n';
htmlReport += ' <th>加载时长(秒)</th>\n';
htmlReport += ' <th>状态</th>\n';
htmlReport += ' </tr>\n';
// 生成表格内容使用for循环
for (let i = 0; i < combinedReport.pages.length; i++) {
const page = combinedReport.pages[i];
if (!page.tabs || page.tabs.length === 0) {
htmlReport += ' <tr>\n';
htmlReport += ' <td>' + page.name + '</td>\n';
htmlReport += ' <td>' + page.startTime + '</td>\n';
htmlReport += ' <td colspan="5">无Tab页</td>\n';
htmlReport += ' </tr>\n';
} else {
for (let j = 0; j < page.tabs.length; j++) {
const tab = page.tabs[j];
htmlReport += ' <tr class="tab-row">\n';
htmlReport += ' <td>' + (j === 0 ? page.name : '') + '</td>\n';
htmlReport += ' <td>' + (j === 0 ? page.startTime : '') + '</td>\n';
htmlReport += ' <td>' + tab.name + '</td>\n';
htmlReport += ' <td>' + tab.startTime + '</td>\n';
htmlReport += ' <td>' + tab.endTime + '</td>\n';
htmlReport += ' <td>' + tab.duration + '</td>\n';
htmlReport += ' <td class="' + tab.status + '">' + tab.status + '</td>\n';
htmlReport += ' </tr>\n';
}
}
}
htmlReport += ' </table>\n';
htmlReport += '</body>\n</html>';
// 将报告写入到时间戳目录中
const reportFile = `${environment}-test-report-${timestamp}.html`;
cy.writeFile(`${reportDir}/${reportFile}`, htmlReport).then(() => {
cy.log(`✅ 已生成测试报告: ${reportDir}/${reportFile}`);
// 显示完成对话框
showCompletionDialog(reportDir, reportFile);
});
// 清理批次报告文件
cy.task('cleanupReports', environment);
});
};
// 删除重复的after钩子
after(() => {
cleanupMemory();
});
// 添加完成对话框
const showCompletionDialog = (reportDir, reportFile) => {
// 创建完成对话框的HTML
const completionDialogBodyContent = `
<div style="margin-bottom: 15px;">
<p style="margin: 0 0 15px 0;">测试报告已生成完成!</p>
<p style="margin: 0; color: #666;">报告目录:<br><span style="color: #409EFF; word-break: break-all;">${reportDir}</span></p>
<p style="margin: 10px 0 0 0; color: #666;">报告文件:<br><span style="color: #409EFF; word-break: break-all;">${reportFile}</span></p>
</div>
`;
const completionDialogFooterContent = `
<button class="close-dialog-btn el-button el-button--primary" style="padding: 9px 15px;">确定</button>
`;
const completionDialogHtml = createDialogTemplate({
dialogClass: 'completion-dialog',
title: '✅ 测试完成',
width: '500px',
bodyContent: completionDialogBodyContent,
footerContent: completionDialogFooterContent
});
// 添加对话框到页面
cy.get('body').then($body => {
$body.append(completionDialogHtml);
// 绑定关闭按钮事件
Cypress.$('.close-dialog-btn').on('click', function() {
Cypress.$('.completion-dialog').remove();
});
});
};
});