优化分批执行点击菜单
This commit is contained in:
parent
8d38687583
commit
c9dc8a83ee
@ -5,7 +5,14 @@ const testLifecycle = require('./testLifecycle');
|
|||||||
* 在所有测试结束后执行
|
* 在所有测试结束后执行
|
||||||
*/
|
*/
|
||||||
async function globalTeardown() {
|
async function globalTeardown() {
|
||||||
await testLifecycle.afterAll();
|
console.log('开始执行全局清理...');
|
||||||
|
try {
|
||||||
|
await testLifecycle.afterAll();
|
||||||
|
console.log('全局清理完成');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('全局清理出错:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = globalTeardown;
|
module.exports = globalTeardown;
|
||||||
@ -10,7 +10,7 @@ const FileUtils = require('../utils/FileUtils');
|
|||||||
*/
|
*/
|
||||||
class TestLifecycle {
|
class TestLifecycle {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.reportPath = process.env.PERFORMANCE_REPORT_PATH || './test-results/performance-report.json';
|
this.reportPath = process.env.PERFORMANCE_REPORT_PATH || './test-results/performance-report.html';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -25,95 +25,181 @@ class TestLifecycle {
|
|||||||
console.log('✨ 测试环境初始化完成');
|
console.log('✨ 测试环境初始化完成');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 测试结束后的处理
|
|
||||||
* 在所有测试结束后执行,用于生成报告和清理工作
|
|
||||||
*/
|
|
||||||
async afterAll() {
|
async afterAll() {
|
||||||
const performanceData = performanceService.getPerformanceData();
|
console.log('开始生成测试报告...');
|
||||||
const report = this.generateReport(performanceData);
|
try {
|
||||||
await this.saveReport(report);
|
const performanceData = performanceService.getPerformanceData();
|
||||||
console.log(`📊 测试报告已生成: ${this.reportPath}`);
|
console.log('获取到性能数据:', performanceData.length, '条记录');
|
||||||
|
const report = this.generateReport(performanceData);
|
||||||
|
await this.saveReport(report);
|
||||||
|
console.log(`📊 测试报告已生成: ${this.reportPath}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('生成测试报告出错:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成测试报告
|
* 生成测试报告
|
||||||
* @param {Array} performanceData 性能数据数组
|
* @param {Object} data 性能数据对象
|
||||||
* @returns {Object} 测试报告对象
|
* @returns {String} 测试报告字符串
|
||||||
*/
|
*/
|
||||||
generateReport(performanceData) {
|
generateReport(performanceData) {
|
||||||
// 按页面名称分组的统计
|
// 统计数据
|
||||||
|
const totalTests = performanceData.length;
|
||||||
|
const successTests = performanceData.filter(record => record.success).length;
|
||||||
|
const failedTests = totalTests - successTests;
|
||||||
|
const successRate = totalTests > 0 ? ((successTests / totalTests) * 100).toFixed(2) : '0.00';
|
||||||
|
const totalDuration = performanceData.reduce((sum, record) => sum + record.duration, 0);
|
||||||
|
|
||||||
|
// 按页面分组统计
|
||||||
const pageStats = {};
|
const pageStats = {};
|
||||||
let totalDuration = 0;
|
|
||||||
let totalSuccess = 0;
|
|
||||||
let totalFailure = 0;
|
|
||||||
|
|
||||||
// 统计每个页面的性能数据
|
|
||||||
performanceData.forEach(record => {
|
performanceData.forEach(record => {
|
||||||
const { pageName, duration, success } = record;
|
const { pageName, duration, success, errorMessage } = record;
|
||||||
|
|
||||||
if (!pageStats[pageName]) {
|
if (!pageStats[pageName]) {
|
||||||
pageStats[pageName] = {
|
pageStats[pageName] = {
|
||||||
totalTests: 0,
|
totalTests: 0,
|
||||||
successCount: 0,
|
successCount: 0,
|
||||||
failureCount: 0,
|
failureCount: 0,
|
||||||
totalDuration: 0,
|
totalDuration: 0,
|
||||||
averageDuration: 0,
|
|
||||||
minDuration: Infinity,
|
|
||||||
maxDuration: 0,
|
|
||||||
errors: []
|
errors: []
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const stats = pageStats[pageName];
|
const stats = pageStats[pageName];
|
||||||
stats.totalTests++;
|
stats.totalTests++;
|
||||||
success ? stats.successCount++ : stats.failureCount++;
|
success ? stats.successCount++ : stats.failureCount++;
|
||||||
stats.totalDuration += duration;
|
stats.totalDuration += duration;
|
||||||
stats.minDuration = Math.min(stats.minDuration, duration);
|
|
||||||
stats.maxDuration = Math.max(stats.maxDuration, duration);
|
if (!success && errorMessage) {
|
||||||
stats.averageDuration = stats.totalDuration / stats.totalTests;
|
stats.errors.push(errorMessage);
|
||||||
|
|
||||||
if (!success && record.errorMessage) {
|
|
||||||
stats.errors.push({
|
|
||||||
timestamp: record.timestamp,
|
|
||||||
error: record.errorMessage
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新总体统计
|
|
||||||
totalDuration += duration;
|
|
||||||
success ? totalSuccess++ : totalFailure++;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 生成报告
|
// 生成HTML报告
|
||||||
return {
|
return `
|
||||||
summary: {
|
<!DOCTYPE html>
|
||||||
totalTests: performanceData.length,
|
<html>
|
||||||
successCount: totalSuccess,
|
<head>
|
||||||
failureCount: totalFailure,
|
<meta charset="UTF-8">
|
||||||
successRate: (totalSuccess / performanceData.length * 100).toFixed(2) + '%',
|
<title>测试执行报告</title>
|
||||||
averageDuration: totalDuration / performanceData.length,
|
<style>
|
||||||
totalDuration: totalDuration
|
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; background: #f5f5f5; }
|
||||||
},
|
.container { max-width: 1200px; margin: 0 auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
|
||||||
pageStats,
|
.header { text-align: center; margin-bottom: 30px; }
|
||||||
timestamp: new Date().toISOString(),
|
.summary { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin-bottom: 30px; }
|
||||||
rawData: performanceData
|
.summary-item { background: #f8f9fa; padding: 15px; border-radius: 6px; text-align: center; }
|
||||||
};
|
.success { color: #28a745; }
|
||||||
|
.failure { color: #dc3545; }
|
||||||
|
table { width: 100%; border-collapse: collapse; margin-top: 20px; }
|
||||||
|
th, td { padding: 12px; text-align: left; border-bottom: 1px solid #dee2e6; }
|
||||||
|
th { background: #f8f9fa; }
|
||||||
|
tr:hover { background: #f8f9fa; }
|
||||||
|
.status-true { color: #28a745; }
|
||||||
|
.status-false { color: #dc3545; }
|
||||||
|
.error-list { color: #dc3545; margin-top: 5px; font-size: 0.9em; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h1>测试执行报告</h1>
|
||||||
|
<p>执行时间: ${new Date().toLocaleString()}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="summary">
|
||||||
|
<div class="summary-item">
|
||||||
|
<h3>总用例数</h3>
|
||||||
|
<p>${totalTests}</p>
|
||||||
|
</div>
|
||||||
|
<div class="summary-item success">
|
||||||
|
<h3>成功数</h3>
|
||||||
|
<p>${successTests}</p>
|
||||||
|
</div>
|
||||||
|
<div class="summary-item failure">
|
||||||
|
<h3>失败数</h3>
|
||||||
|
<p>${failedTests}</p>
|
||||||
|
</div>
|
||||||
|
<div class="summary-item">
|
||||||
|
<h3>成功率</h3>
|
||||||
|
<p>${successRate}%</p>
|
||||||
|
</div>
|
||||||
|
<div class="summary-item">
|
||||||
|
<h3>总耗时</h3>
|
||||||
|
<p>${(totalDuration / 1000).toFixed(2)}秒</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>页面测试详情</h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>页面名称</th>
|
||||||
|
<th>测试数</th>
|
||||||
|
<th>成功数</th>
|
||||||
|
<th>失败数</th>
|
||||||
|
<th>总耗时</th>
|
||||||
|
<th>错误信息</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
${Object.entries(pageStats).map(([pageName, stats]) => `
|
||||||
|
<tr>
|
||||||
|
<td>${pageName}</td>
|
||||||
|
<td>${stats.totalTests}</td>
|
||||||
|
<td class="status-true">${stats.successCount}</td>
|
||||||
|
<td class="status-false">${stats.failureCount}</td>
|
||||||
|
<td>${(stats.totalDuration / 1000).toFixed(2)}秒</td>
|
||||||
|
<td>
|
||||||
|
${stats.errors.length > 0 ?
|
||||||
|
`<div class="error-list">${stats.errors.join('<br>')}</div>` :
|
||||||
|
''}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`).join('')}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2>详细测试记录</h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>页面名称</th>
|
||||||
|
<th>状态</th>
|
||||||
|
<th>耗时</th>
|
||||||
|
<th>错误信息</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
${performanceData.map(record => `
|
||||||
|
<tr>
|
||||||
|
<td>${record.pageName}</td>
|
||||||
|
<td class="status-${record.success}">${record.success ? '通过' : '失败'}</td>
|
||||||
|
<td>${(record.duration / 1000).toFixed(2)}秒</td>
|
||||||
|
<td class="status-false">${record.errorMessage || ''}</td>
|
||||||
|
</tr>
|
||||||
|
`).join('')}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存测试报告
|
* 保存测试报告
|
||||||
* @param {Object} report 测试报告对象
|
* @param {String} report 测试报告字符串
|
||||||
|
* @returns {String} 保存后的报告路径
|
||||||
*/
|
*/
|
||||||
async saveReport(report) {
|
async saveReport(report) {
|
||||||
try {
|
try {
|
||||||
await FileUtils.writeJsonFile(this.reportPath, report);
|
await FileUtils.writeFile(this.reportPath, report);
|
||||||
|
return this.reportPath;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('保存测试报告失败:', error);
|
console.error('保存测试报告失败:', error);
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建单例实例
|
module.exports = new TestLifecycle();
|
||||||
const testLifecycle = new TestLifecycle();
|
|
||||||
module.exports = testLifecycle;
|
|
||||||
@ -99,7 +99,17 @@ class PerformanceService {
|
|||||||
* @returns {Array} 性能数据数组
|
* @returns {Array} 性能数据数组
|
||||||
*/
|
*/
|
||||||
getPerformanceData() {
|
getPerformanceData() {
|
||||||
return this.performanceData;
|
try {
|
||||||
|
// 从文件读取最新数据
|
||||||
|
const data = FileUtils.loadFromJsonFile(this.performanceDataPath);
|
||||||
|
if (data) {
|
||||||
|
this.performanceData = data;
|
||||||
|
}
|
||||||
|
return this.performanceData;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('读取性能数据失败:', error);
|
||||||
|
return this.performanceData;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* 提供文件读写、目录创建等常用操作
|
* 提供文件读写、目录创建等常用操作
|
||||||
*/
|
*/
|
||||||
const fs = require('fs').promises;
|
const fs = require('fs').promises;
|
||||||
|
const fsSync = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
class FileUtils {
|
class FileUtils {
|
||||||
@ -40,15 +41,18 @@ class FileUtils {
|
|||||||
* @param {Object} data 要写入的数据
|
* @param {Object} data 要写入的数据
|
||||||
*/
|
*/
|
||||||
static async writeJsonFile(filePath, data) {
|
static async writeJsonFile(filePath, data) {
|
||||||
try {
|
await this.ensureDirectoryExists(path.dirname(filePath));
|
||||||
// 确保目录存在
|
await fs.writeFile(filePath, JSON.stringify(data, null, 2), 'utf8');
|
||||||
await this.ensureDirectoryExists(path.dirname(filePath));
|
}
|
||||||
// 写入文件
|
|
||||||
await fs.writeFile(filePath, JSON.stringify(data, null, 2), 'utf8');
|
/**
|
||||||
} catch (error) {
|
* 写入文本文件
|
||||||
console.error(`写入JSON文件失败 [${filePath}]:`, error);
|
* @param {string} filePath 文件路径
|
||||||
throw error;
|
* @param {string} content 要写入的内容
|
||||||
}
|
*/
|
||||||
|
static async writeFile(filePath, content) {
|
||||||
|
await this.ensureDirectoryExists(path.dirname(filePath));
|
||||||
|
await fs.writeFile(filePath, content, 'utf8');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -84,15 +88,10 @@ class FileUtils {
|
|||||||
/**
|
/**
|
||||||
* 检查文件是否存在
|
* 检查文件是否存在
|
||||||
* @param {string} filePath 文件路径
|
* @param {string} filePath 文件路径
|
||||||
* @returns {Promise<boolean>} 文件是否存在
|
* @returns {boolean} 文件是否存在
|
||||||
*/
|
*/
|
||||||
static async fileExists(filePath) {
|
static fileExists(filePath) {
|
||||||
try {
|
return fsSync.existsSync(filePath);
|
||||||
await fs.access(filePath);
|
|
||||||
return true;
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -126,21 +125,17 @@ class FileUtils {
|
|||||||
/**
|
/**
|
||||||
* 从JSON文件加载对象
|
* 从JSON文件加载对象
|
||||||
* @param {string} filePath 文件路径
|
* @param {string} filePath 文件路径
|
||||||
* @param {Object} options 选项
|
|
||||||
* @param {string} options.encoding 文件编码 (默认: 'utf8')
|
|
||||||
* @returns {Object|null} 加载的对象,如果失败则返回null
|
* @returns {Object|null} 加载的对象,如果失败则返回null
|
||||||
*/
|
*/
|
||||||
static loadFromJsonFile(filePath, options = {}) {
|
static loadFromJsonFile(filePath) {
|
||||||
try {
|
try {
|
||||||
const {encoding = 'utf8'} = options;
|
if (!this.fileExists(filePath)) {
|
||||||
|
console.warn(`文件不存在: ${filePath}`);
|
||||||
if (!fs.existsSync(filePath)) {
|
|
||||||
console.error(`文件不存在: ${filePath}`);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = fs.readFileSync(filePath, encoding);
|
const content = fsSync.readFileSync(filePath, 'utf8');
|
||||||
return JSON.parse(data);
|
return JSON.parse(content);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`加载JSON文件失败: ${filePath}`, error);
|
console.error(`加载JSON文件失败: ${filePath}`, error);
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@ -1,8 +1,14 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"id": 7,
|
"id": 1,
|
||||||
"text": "电池投放计划",
|
"text": "主数据",
|
||||||
"path": "电池投放计划",
|
"path": "主数据",
|
||||||
"hasThirdMenu": true
|
"hasThirdMenu": true
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"text": "业务数据",
|
||||||
|
"path": "业务数据",
|
||||||
|
"hasThirdMenu": true
|
||||||
|
}
|
||||||
]
|
]
|
||||||
@ -1,3 +1 @@
|
|||||||
[
|
[]
|
||||||
7
|
|
||||||
]
|
|
||||||
Loading…
Reference in New Issue
Block a user