first commit
This commit is contained in:
commit
1abc876fdc
5
.idea/.gitignore
vendored
Normal file
5
.idea/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# 默认忽略的文件
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# 基于编辑器的 HTTP 客户端请求
|
||||||
|
/httpRequests/
|
||||||
12
.idea/longi.iml
Normal file
12
.idea/longi.iml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="WEB_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
8
.idea/modules.xml
Normal file
8
.idea/modules.xml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/longi.iml" filepath="$PROJECT_DIR$/.idea/longi.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
7
.idea/vcs.xml
Normal file
7
.idea/vcs.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
<mapping directory="$PROJECT_DIR$/cypress/e2e" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
131
cypress.config.js
Normal file
131
cypress.config.js
Normal file
@ -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;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
919
cypress/e2e/需求计划页面是否可用.cy.js
Normal file
919
cypress/e2e/需求计划页面是否可用.cy.js
Normal file
@ -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 = `
|
||||||
|
<div class="el-dialog__wrapper env-select-dialog" 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: 400px; 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;">选择测试环境</span>
|
||||||
|
</div>
|
||||||
|
<div class="el-dialog__body" style="padding: 20px;">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// 添加对话框到页面
|
||||||
|
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 = `
|
||||||
|
<div class="el-dialog__wrapper menu-select-dialog" 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: 600px; 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;">选择要测试的菜单</span>
|
||||||
|
</div>
|
||||||
|
<div class="el-dialog__body" style="padding: 20px; max-height: 50vh; overflow-y: auto;">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
<div class="el-dialog__footer" style="padding: 20px; text-align: right; border-top: 1px solid #eee;">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// 添加对话框到页面
|
||||||
|
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 = `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>系统页面可用性测试报告</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: Arial, sans-serif; margin: 20px; }
|
||||||
|
.summary { margin-bottom: 20px; }
|
||||||
|
table { border-collapse: collapse; width: 100%; margin-bottom: 20px; }
|
||||||
|
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
|
||||||
|
th { background-color: #f4f4f4; }
|
||||||
|
.success { color: green; }
|
||||||
|
.error { color: red; }
|
||||||
|
.timeout { color: orange; }
|
||||||
|
.pending { color: gray; }
|
||||||
|
.tab-row { background-color: #f9f9f9; }
|
||||||
|
.failed-pages { margin-top: 20px; }
|
||||||
|
.failed-page { background-color: #fff0f0; margin-bottom: 10px; padding: 10px; border-left: 4px solid #ff0000; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>系统页面可用性测试报告</h1>
|
||||||
|
<div class="summary">
|
||||||
|
<h2>测试摘要</h2>
|
||||||
|
<p>测试环境: ${environment}</p>
|
||||||
|
<p>总页面数: ${combinedReport.pages.length}</p>
|
||||||
|
<p>总Tab页数: ${testReport.summary.totalTabs}</p>
|
||||||
|
<p>失败页面数: ${combinedReport.failedPages.length}</p>
|
||||||
|
<p>开始时间: ${testReport.summary.startTime}</p>
|
||||||
|
<p>结束时间: ${testReport.summary.endTime}</p>
|
||||||
|
<p>总耗时: ${calculateDuration(new Date(testReport.summary.startTime), new Date(testReport.summary.endTime))}秒</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${combinedReport.failedPages.length > 0 ? `
|
||||||
|
<div class="failed-pages">
|
||||||
|
<h2>失败页面列表</h2>
|
||||||
|
${combinedReport.failedPages.map(page => `
|
||||||
|
<div class="failed-page">
|
||||||
|
<h3>${page.name}</h3>
|
||||||
|
<p>失败时间: ${page.time}</p>
|
||||||
|
<p>错误信息: ${page.error}</p>
|
||||||
|
<p>耗时: ${page.duration}秒</p>
|
||||||
|
</div>
|
||||||
|
`).join('')}
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
|
||||||
|
<h2>详细测试结果</h2>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>页面名称</th>
|
||||||
|
<th>开始时间</th>
|
||||||
|
<th>Tab页名称</th>
|
||||||
|
<th>Tab开始时间</th>
|
||||||
|
<th>Tab结束时间</th>
|
||||||
|
<th>加载时长(秒)</th>
|
||||||
|
<th>状态</th>
|
||||||
|
</tr>
|
||||||
|
${combinedReport.pages.map(page => {
|
||||||
|
if (!page.tabs || page.tabs.length === 0) {
|
||||||
|
return `
|
||||||
|
<tr>
|
||||||
|
<td>${page.name}</td>
|
||||||
|
<td>${page.startTime}</td>
|
||||||
|
<td colspan="5">无Tab页</td>
|
||||||
|
</tr>`;
|
||||||
|
}
|
||||||
|
return page.tabs.map((tab, index) => `
|
||||||
|
<tr class="tab-row">
|
||||||
|
<td>${index === 0 ? page.name : ''}</td>
|
||||||
|
<td>${index === 0 ? page.startTime : ''}</td>
|
||||||
|
<td>${tab.name}</td>
|
||||||
|
<td>${tab.startTime}</td>
|
||||||
|
<td>${tab.endTime}</td>
|
||||||
|
<td>${tab.duration}</td>
|
||||||
|
<td class="${tab.status}">${tab.status}</td>
|
||||||
|
</tr>`).join('')
|
||||||
|
}).join('')}
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>`;
|
||||||
|
|
||||||
|
// 将报告写入到时间戳目录中
|
||||||
|
cy.writeFile(`${reportDir}/${environment}-test-report-${timestamp}.html`, htmlReport).then(() => {
|
||||||
|
cy.log(`✅ 已生成测试报告: ${reportDir}/${environment}-test-report-${timestamp}.html`);
|
||||||
|
|
||||||
|
// 添加完成提示对话框
|
||||||
|
const completionDialogHtml = `
|
||||||
|
<div class="el-dialog__wrapper completion-dialog" style="position: fixed; top: 0; right: 0; bottom: 0; left: 0; overflow: auto; margin: 0; z-index: 2001; background: rgba(0, 0, 0, 0.5);">
|
||||||
|
<div class="el-dialog" style="margin: 15vh auto 50px; 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;">✅ 测试完成</span>
|
||||||
|
</div>
|
||||||
|
<div class="el-dialog__body" style="padding: 30px 20px;">
|
||||||
|
<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;">${environment}-test-report-${timestamp}.html</span></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="el-dialog__footer" style="padding: 20px; text-align: right; border-top: 1px solid #eee;">
|
||||||
|
<button class="close-dialog-btn el-button el-button--primary" style="padding: 9px 15px;">确定</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// 添加对话框到页面
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
});
|
||||||
5
cypress/fixtures/example.json
Normal file
5
cypress/fixtures/example.json
Normal file
@ -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"
|
||||||
|
}
|
||||||
25
cypress/support/commands.js
Normal file
25
cypress/support/commands.js
Normal file
@ -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) => { ... })
|
||||||
17
cypress/support/e2e.js
Normal file
17
cypress/support/e2e.js
Normal file
@ -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'
|
||||||
Loading…
Reference in New Issue
Block a user