feat: 添加测试框架和覆盖率报告功能
feat(测试): 新增Playwright和Vitest测试配置 feat(测试): 添加测试覆盖率报告生成功能 feat(测试): 实现前后端测试脚本集成 fix(测试): 修复测试密码不匹配问题 fix(测试): 修正URL等待策略 fix(测试): 调整错误消息选择器 refactor(测试): 重构测试目录结构 refactor(测试): 优化测试用例组织方式 docs: 更新测试报告文档 docs: 添加测试覆盖率报告模板 ci: 添加Docker测试环境配置 ci: 实现测试自动化脚本 chore: 更新依赖版本 chore: 添加测试相关配置文件
This commit is contained in:
@@ -0,0 +1,339 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* 性能监控工具
|
||||
* 收集和分析测试性能数据,识别性能瓶颈
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
class PerformanceMonitor {
|
||||
constructor() {
|
||||
this.performanceDataPath = path.join(process.cwd(), 'test-results', 'performance-data.json');
|
||||
this.performanceData = this.loadPerformanceData();
|
||||
this.currentSession = {
|
||||
startTime: Date.now(),
|
||||
tests: [],
|
||||
metrics: {}
|
||||
};
|
||||
}
|
||||
|
||||
loadPerformanceData() {
|
||||
try {
|
||||
if (fs.existsSync(this.performanceDataPath)) {
|
||||
return JSON.parse(fs.readFileSync(this.performanceDataPath, 'utf-8'));
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('加载性能数据失败:', error.message);
|
||||
}
|
||||
return {
|
||||
sessions: [],
|
||||
summary: {
|
||||
avgTestTime: 0,
|
||||
avgPageLoadTime: 0,
|
||||
avgApiTime: 0,
|
||||
totalTests: 0,
|
||||
slowTests: [],
|
||||
fastTests: []
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
savePerformanceData() {
|
||||
const dir = path.dirname(this.performanceDataPath);
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
fs.writeFileSync(this.performanceDataPath, JSON.stringify(this.performanceData, null, 2), 'utf-8');
|
||||
}
|
||||
|
||||
startTest(testName) {
|
||||
const test = {
|
||||
name: testName,
|
||||
startTime: Date.now(),
|
||||
metrics: {
|
||||
pageLoads: [],
|
||||
apiCalls: [],
|
||||
domOperations: []
|
||||
}
|
||||
};
|
||||
this.currentSession.tests.push(test);
|
||||
return test;
|
||||
}
|
||||
|
||||
endTest(test) {
|
||||
test.endTime = Date.now();
|
||||
test.duration = test.endTime - test.startTime;
|
||||
return test;
|
||||
}
|
||||
|
||||
recordPageLoad(test, url, loadTime) {
|
||||
test.metrics.pageLoads.push({
|
||||
url,
|
||||
loadTime,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
}
|
||||
|
||||
recordApiCall(test, endpoint, duration) {
|
||||
test.metrics.apiCalls.push({
|
||||
endpoint,
|
||||
duration,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
}
|
||||
|
||||
recordDomOperation(test, operation, duration) {
|
||||
test.metrics.domOperations.push({
|
||||
operation,
|
||||
duration,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
}
|
||||
|
||||
endSession() {
|
||||
this.currentSession.endTime = Date.now();
|
||||
this.currentSession.duration = this.currentSession.endTime - this.currentSession.startTime;
|
||||
|
||||
this.performanceData.sessions.push(this.currentSession);
|
||||
this.updateSummary();
|
||||
this.savePerformanceData();
|
||||
|
||||
return this.currentSession;
|
||||
}
|
||||
|
||||
updateSummary() {
|
||||
const sessions = this.performanceData.sessions;
|
||||
const allTests = sessions.flatMap(s => s.tests);
|
||||
|
||||
if (allTests.length === 0) return;
|
||||
|
||||
const totalDuration = allTests.reduce((sum, t) => sum + t.duration, 0);
|
||||
const avgTestTime = totalDuration / allTests.length;
|
||||
|
||||
const allPageLoads = allTests.flatMap(t => t.metrics.pageLoads);
|
||||
const avgPageLoadTime = allPageLoads.length > 0
|
||||
? allPageLoads.reduce((sum, p) => sum + p.loadTime, 0) / allPageLoads.length
|
||||
: 0;
|
||||
|
||||
const allApiCalls = allTests.flatMap(t => t.metrics.apiCalls);
|
||||
const avgApiTime = allApiCalls.length > 0
|
||||
? allApiCalls.reduce((sum, a) => sum + a.duration, 0) / allApiCalls.length
|
||||
: 0;
|
||||
|
||||
const sortedTests = [...allTests].sort((a, b) => b.duration - a.duration);
|
||||
const slowTests = sortedTests.slice(0, 10);
|
||||
const fastTests = sortedTests.slice(-10).reverse();
|
||||
|
||||
this.performanceData.summary = {
|
||||
avgTestTime,
|
||||
avgPageLoadTime,
|
||||
avgApiTime,
|
||||
totalTests: allTests.length,
|
||||
slowTests: slowTests.map(t => ({
|
||||
name: t.name,
|
||||
duration: t.duration
|
||||
})),
|
||||
fastTests: fastTests.map(t => ({
|
||||
name: t.name,
|
||||
duration: t.duration
|
||||
}))
|
||||
};
|
||||
}
|
||||
|
||||
generateReport() {
|
||||
const summary = this.performanceData.summary;
|
||||
const sessions = this.performanceData.sessions;
|
||||
|
||||
console.log('');
|
||||
console.log('═══════════════════════════════════════════');
|
||||
console.log('📊 性能监控报告');
|
||||
console.log('═══════════════════════════════════════════');
|
||||
console.log('');
|
||||
console.log(`📈 总测试数: ${summary.totalTests}`);
|
||||
console.log(`⏱️ 平均测试时间: ${this.formatDuration(summary.avgTestTime)}`);
|
||||
console.log(`🌐 平均页面加载时间: ${this.formatDuration(summary.avgPageLoadTime)}`);
|
||||
console.log(`📡 平均API响应时间: ${this.formatDuration(summary.avgApiTime)}`);
|
||||
console.log('');
|
||||
|
||||
if (summary.slowTests.length > 0) {
|
||||
console.log('🐌 最慢的10个测试:');
|
||||
summary.slowTests.forEach((test, index) => {
|
||||
console.log(` ${index + 1}. ${test.name} - ${this.formatDuration(test.duration)}`);
|
||||
});
|
||||
console.log('');
|
||||
}
|
||||
|
||||
if (summary.fastTests.length > 0) {
|
||||
console.log('⚡ 最快的10个测试:');
|
||||
summary.fastTests.forEach((test, index) => {
|
||||
console.log(` ${index + 1}. ${test.name} - ${this.formatDuration(test.duration)}`);
|
||||
});
|
||||
console.log('');
|
||||
}
|
||||
|
||||
this.analyzePerformanceTrends();
|
||||
this.generateRecommendations();
|
||||
}
|
||||
|
||||
analyzePerformanceTrends() {
|
||||
const sessions = this.performanceData.sessions;
|
||||
if (sessions.length < 2) return;
|
||||
|
||||
const recentSessions = sessions.slice(-5);
|
||||
const avgDurations = recentSessions.map(s => {
|
||||
const tests = s.tests;
|
||||
if (tests.length === 0) return 0;
|
||||
return tests.reduce((sum, t) => sum + t.duration, 0) / tests.length;
|
||||
});
|
||||
|
||||
const trend = this.calculateTrend(avgDurations);
|
||||
|
||||
console.log('📈 性能趋势:');
|
||||
console.log(` 趋势: ${this.getTrendEmoji(trend)} ${trend.toUpperCase()}`);
|
||||
console.log(` 最近5次平均测试时间: ${avgDurations.map(d => this.formatDuration(d)).join(', ')}`);
|
||||
console.log('');
|
||||
}
|
||||
|
||||
calculateTrend(values) {
|
||||
if (values.length < 2) return 'stable';
|
||||
|
||||
const firstHalf = values.slice(0, Math.floor(values.length / 2));
|
||||
const secondHalf = values.slice(Math.floor(values.length / 2));
|
||||
|
||||
const firstAvg = firstHalf.reduce((a, b) => a + b, 0) / firstHalf.length;
|
||||
const secondAvg = secondHalf.reduce((a, b) => a + b, 0) / secondHalf.length;
|
||||
|
||||
const change = ((secondAvg - firstAvg) / firstAvg) * 100;
|
||||
|
||||
if (change < -10) return 'improving';
|
||||
if (change > 10) return 'degrading';
|
||||
return 'stable';
|
||||
}
|
||||
|
||||
generateRecommendations() {
|
||||
const summary = this.performanceData.summary;
|
||||
const recommendations = [];
|
||||
|
||||
if (summary.avgTestTime > 5000) {
|
||||
recommendations.push('⚠️ 平均测试时间超过5秒,建议优化测试执行效率');
|
||||
}
|
||||
|
||||
if (summary.avgPageLoadTime > 2000) {
|
||||
recommendations.push('⚠️ 平均页面加载时间超过2秒,建议优化页面性能');
|
||||
}
|
||||
|
||||
if (summary.avgApiTime > 1000) {
|
||||
recommendations.push('⚠️ 平均API响应时间超过1秒,建议优化API性能');
|
||||
}
|
||||
|
||||
const slowTestsCount = summary.slowTests.filter(t => t.duration > 10000).length;
|
||||
if (slowTestsCount > 5) {
|
||||
recommendations.push(`⚠️ 有${slowTestsCount}个测试执行时间超过10秒,建议重点优化`);
|
||||
}
|
||||
|
||||
if (recommendations.length > 0) {
|
||||
console.log('💡 性能优化建议:');
|
||||
recommendations.forEach(rec => {
|
||||
console.log(` ${rec}`);
|
||||
});
|
||||
console.log('');
|
||||
}
|
||||
}
|
||||
|
||||
getTrendEmoji(trend) {
|
||||
switch (trend) {
|
||||
case 'improving':
|
||||
return '📈';
|
||||
case 'degrading':
|
||||
return '📉';
|
||||
default:
|
||||
return '➡️';
|
||||
}
|
||||
}
|
||||
|
||||
formatDuration(ms) {
|
||||
if (ms < 1000) {
|
||||
return `${ms}ms`;
|
||||
} else if (ms < 60000) {
|
||||
return `${(ms / 1000).toFixed(1)}s`;
|
||||
} else {
|
||||
return `${(ms / 60000).toFixed(1)}m`;
|
||||
}
|
||||
}
|
||||
|
||||
exportData(filePath) {
|
||||
const exportPath = filePath || 'performance-data-export.json';
|
||||
fs.writeFileSync(exportPath, JSON.stringify(this.performanceData, null, 2), 'utf-8');
|
||||
console.log(`✅ 性能数据已导出到: ${exportPath}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 命令行接口
|
||||
if (require.main === module) {
|
||||
const monitor = new PerformanceMonitor();
|
||||
const command = process.argv[2];
|
||||
|
||||
switch (command) {
|
||||
case 'report':
|
||||
monitor.generateReport();
|
||||
break;
|
||||
|
||||
case 'export':
|
||||
const exportFile = process.argv[3];
|
||||
monitor.exportData(exportFile);
|
||||
break;
|
||||
|
||||
case 'start':
|
||||
const testName = process.argv[3];
|
||||
if (testName) {
|
||||
const test = monitor.startTest(testName);
|
||||
console.log(`✅ 测试已启动: ${testName}`);
|
||||
console.log(`测试ID: ${monitor.currentSession.tests.length - 1}`);
|
||||
} else {
|
||||
console.error('❌ 错误: 请提供测试名称');
|
||||
process.exit(1);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'end':
|
||||
const testId = parseInt(process.argv[3]);
|
||||
if (!isNaN(testId)) {
|
||||
const test = monitor.currentSession.tests[testId];
|
||||
if (test) {
|
||||
monitor.endTest(test);
|
||||
console.log(`✅ 测试已结束: ${test.name}`);
|
||||
console.log(`执行时间: ${monitor.formatDuration(test.duration)}`);
|
||||
} else {
|
||||
console.error('❌ 错误: 测试ID不存在');
|
||||
process.exit(1);
|
||||
}
|
||||
} else {
|
||||
console.error('❌ 错误: 请提供有效的测试ID');
|
||||
process.exit(1);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'session':
|
||||
monitor.endSession();
|
||||
console.log('✅ 测试会话已结束');
|
||||
console.log(`会话时长: ${monitor.formatDuration(monitor.currentSession.duration)}`);
|
||||
console.log(`测试数量: ${monitor.currentSession.tests.length}`);
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log('性能监控工具');
|
||||
console.log('');
|
||||
console.log('用法:');
|
||||
console.log(' node performanceMonitor.js report - 生成性能报告');
|
||||
console.log(' node performanceMonitor.js export [file.json] - 导出性能数据');
|
||||
console.log(' node performanceMonitor.js start <testName> - 启动测试监控');
|
||||
console.log(' node performanceMonitor.js end <testId> - 结束测试监控');
|
||||
console.log(' node performanceMonitor.js session - 结束测试会话');
|
||||
console.log('');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PerformanceMonitor;
|
||||
Reference in New Issue
Block a user