Files
novalon-manage-system/novalon-manage-web/e2e/qualityGate.js
T
张翔 e2ad1331cc feat: 添加测试框架和覆盖率报告功能
feat(测试): 新增Playwright和Vitest测试配置
feat(测试): 添加测试覆盖率报告生成功能
feat(测试): 实现前后端测试脚本集成

fix(测试): 修复测试密码不匹配问题
fix(测试): 修正URL等待策略
fix(测试): 调整错误消息选择器

refactor(测试): 重构测试目录结构
refactor(测试): 优化测试用例组织方式

docs: 更新测试报告文档
docs: 添加测试覆盖率报告模板

ci: 添加Docker测试环境配置
ci: 实现测试自动化脚本

chore: 更新依赖版本
chore: 添加测试相关配置文件
2026-03-25 09:03:37 +08:00

347 lines
10 KiB
JavaScript

#!/usr/bin/env node
/**
* 质量门禁检查工具
* 定义和执行自动化质量标准,阻止低质量代码合并
*/
const fs = require('fs');
const path = require('path');
class QualityGate {
constructor() {
this.qualityStandards = {
passRate: 95, // 通过率必须 >= 95%
flakyRate: 5, // 不稳定测试比例必须 <= 5%
maxDuration: 600000, // 总测试时间必须 <= 10分钟
maxFailedTests: 5, // 失败测试数量必须 <= 5
maxSlowTests: 10, // 慢速测试数量必须 <= 10
};
this.checks = [];
this.passed = true;
this.warnings = [];
this.errors = [];
}
checkPassRate(results) {
const passRate = results.summary?.passRate || 0;
const threshold = this.qualityStandards.passRate;
if (passRate < threshold) {
this.errors.push({
check: '通过率检查',
message: `测试通过率 ${passRate.toFixed(2)}% 低于标准 ${threshold}%`,
actual: passRate,
threshold: threshold,
status: 'failed',
});
this.passed = false;
} else {
this.checks.push({
check: '通过率检查',
message: `测试通过率 ${passRate.toFixed(2)}% 符合标准`,
actual: passRate,
threshold: threshold,
status: 'passed',
});
}
}
checkFlakyRate(results) {
const flakyRate = results.summary?.flakyRate || 0;
const threshold = this.qualityStandards.flakyRate;
if (flakyRate > threshold) {
this.warnings.push({
check: '不稳定测试检查',
message: `不稳定测试比例 ${flakyRate.toFixed(2)}% 超过标准 ${threshold}%`,
actual: flakyRate,
threshold: threshold,
status: 'warning',
});
} else {
this.checks.push({
check: '不稳定测试检查',
message: `不稳定测试比例 ${flakyRate.toFixed(2)}% 符合标准`,
actual: flakyRate,
threshold: threshold,
status: 'passed',
});
}
}
checkDuration(results) {
const duration = results.summary?.totalDuration || 0;
const threshold = this.qualityStandards.maxDuration;
if (duration > threshold) {
this.warnings.push({
check: '测试耗时检查',
message: `测试总耗时 ${this.formatDuration(duration)} 超过标准 ${this.formatDuration(threshold)}`,
actual: duration,
threshold: threshold,
status: 'warning',
});
} else {
this.checks.push({
check: '测试耗时检查',
message: `测试总耗时 ${this.formatDuration(duration)} 符合标准`,
actual: duration,
threshold: threshold,
status: 'passed',
});
}
}
checkFailedTests(results) {
const failedCount = results.failedTests?.length || 0;
const threshold = this.qualityStandards.maxFailedTests;
if (failedCount > threshold) {
this.errors.push({
check: '失败测试数量检查',
message: `失败测试数量 ${failedCount} 超过标准 ${threshold}`,
actual: failedCount,
threshold: threshold,
status: 'failed',
});
this.passed = false;
} else {
this.checks.push({
check: '失败测试数量检查',
message: `失败测试数量 ${failedCount} 符合标准`,
actual: failedCount,
threshold: threshold,
status: 'passed',
});
}
}
checkSlowTests(results) {
const slowCount = results.slowestTests?.length || 0;
const threshold = this.qualityStandards.maxSlowTests;
if (slowCount > threshold) {
this.warnings.push({
check: '慢速测试数量检查',
message: `慢速测试数量 ${slowCount} 超过标准 ${threshold}`,
actual: slowCount,
threshold: threshold,
status: 'warning',
});
} else {
this.checks.push({
check: '慢速测试数量检查',
message: `慢速测试数量 ${slowCount} 符合标准`,
actual: slowCount,
threshold: threshold,
status: 'passed',
});
}
}
checkCriticalTests(results) {
const criticalTests = results.failedTests?.filter(test => {
const title = test.title.toLowerCase();
return title.includes('登录') || title.includes('认证') || title.includes('安全');
}) || [];
if (criticalTests.length > 0) {
this.errors.push({
check: '关键功能测试检查',
message: `关键功能测试失败: ${criticalTests.map(t => t.title).join(', ')}`,
actual: criticalTests.length,
threshold: 0,
status: 'failed',
});
this.passed = false;
} else {
this.checks.push({
check: '关键功能测试检查',
message: '所有关键功能测试通过',
actual: 0,
threshold: 0,
status: 'passed',
});
}
}
execute(results) {
this.checkPassRate(results);
this.checkFlakyRate(results);
this.checkDuration(results);
this.checkFailedTests(results);
this.checkSlowTests(results);
this.checkCriticalTests(results);
return this.generateReport();
}
generateReport() {
const report = {
timestamp: new Date().toISOString(),
passed: this.passed,
summary: {
total: this.checks.length,
passed: this.checks.filter(c => c.status === 'passed').length,
warnings: this.warnings.length,
errors: this.errors.length,
},
checks: this.checks,
warnings: this.warnings,
errors: this.errors,
};
this.printReport(report);
this.saveReport(report);
return report;
}
printReport(report) {
console.log('');
console.log('═══════════════════════════════════════════');
console.log('🚪 质量门禁检查报告');
console.log('═══════════════════════════════════════════');
console.log('');
console.log(`📊 检查时间: ${new Date(report.timestamp).toLocaleString('zh-CN')}`);
console.log(`📈 检查结果: ${report.passed ? '✅ 通过' : '❌ 失败'}`);
console.log('');
console.log(`📋 检查统计:`);
console.log(` - 总检查项: ${report.summary.total}`);
console.log(` - 通过: ${report.summary.passed}`);
console.log(` - 警告: ${report.summary.warnings}`);
console.log(` - 错误: ${report.summary.errors}`);
console.log('');
if (report.checks.length > 0) {
console.log('✅ 通过的检查:');
report.checks.forEach(check => {
console.log(`${check.check}: ${check.message}`);
});
console.log('');
}
if (report.warnings.length > 0) {
console.log('⚠️ 警告:');
report.warnings.forEach(warning => {
console.log(` ⚠️ ${warning.check}: ${warning.message}`);
});
console.log('');
}
if (report.errors.length > 0) {
console.log('❌ 错误:');
report.errors.forEach(error => {
console.log(`${error.check}: ${error.message}`);
});
console.log('');
}
console.log('═══════════════════════════════════════════');
console.log('');
if (!report.passed) {
console.error('❌ 质量门禁检查失败!请修复错误后重试。');
process.exit(1);
}
}
saveReport(report) {
const dir = path.join(process.cwd(), 'test-results');
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
const reportPath = path.join(dir, 'quality-gate-report.json');
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2), 'utf-8');
console.log(`📄 质量门禁报告已保存: ${reportPath}`);
}
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`;
}
}
setStandard(standard, value) {
if (this.qualityStandards.hasOwnProperty(standard)) {
this.qualityStandards[standard] = value;
console.log(`✅ 质量标准已更新: ${standard} = ${value}`);
} else {
console.error(`❌ 错误: 未知的质量标准 ${standard}`);
process.exit(1);
}
}
getStandards() {
console.log('当前质量标准:');
console.log('');
console.log(` 通过率: >= ${this.qualityStandards.passRate}%`);
console.log(` 不稳定测试比例: <= ${this.qualityStandards.flakyRate}%`);
console.log(` 最大测试时间: <= ${this.formatDuration(this.qualityStandards.maxDuration)}`);
console.log(` 最大失败测试数: <= ${this.qualityStandards.maxFailedTests}`);
console.log(` 最大慢速测试数: <= ${this.qualityStandards.maxSlowTests}`);
console.log('');
}
}
// 命令行接口
if (require.main === module) {
const qualityGate = new QualityGate();
const command = process.argv[2];
switch (command) {
case 'check':
const resultsFile = process.argv[3];
if (resultsFile && fs.existsSync(resultsFile)) {
const results = JSON.parse(fs.readFileSync(resultsFile, 'utf-8'));
qualityGate.execute(results);
} else {
console.error('❌ 错误: 请提供有效的测试结果文件');
process.exit(1);
}
break;
case 'set':
const standard = process.argv[3];
const value = parseFloat(process.argv[4]);
if (standard && !isNaN(value)) {
qualityGate.setStandard(standard, value);
} else {
console.error('❌ 错误: 请提供有效的标准和数值');
console.error('用法: node qualityGate.js set <standard> <value>');
process.exit(1);
}
break;
case 'standards':
qualityGate.getStandards();
break;
default:
console.log('质量门禁检查工具');
console.log('');
console.log('用法:');
console.log(' node qualityGate.js check <results.json> - 执行质量门禁检查');
console.log(' node qualityGate.js set <standard> <value> - 设置质量标准');
console.log(' node qualityGate.js standards - 显示当前质量标准');
console.log('');
console.log('质量标准:');
console.log(' - passRate: 通过率 (%)');
console.log(' - flakyRate: 不稳定测试比例 (%)');
console.log(' - maxDuration: 最大测试时间 (ms)');
console.log(' - maxFailedTests: 最大失败测试数');
console.log(' - maxSlowTests: 最大慢速测试数');
console.log('');
break;
}
}
module.exports = QualityGate;