Files
novalon-website/scripts/generate-test-report.js
T
2026-03-06 10:09:04 +08:00

384 lines
10 KiB
JavaScript

const fs = require('fs');
const path = require('path');
function loadResults(filename) {
const filePath = path.join('test-results', filename);
if (!fs.existsSync(filePath)) {
return null;
}
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
}
function generateHTMLReport() {
const performance = loadResults('performance-summary.json');
const seo = loadResults('seo-summary.json');
const accessibility = loadResults('accessibility-summary.json');
const forms = loadResults('form-validation-summary.json');
const timestamp = new Date().toLocaleString('zh-CN');
const html = `<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>开发环境测试报告 - ${timestamp}</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
background: #f5f5f5;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 30px;
border-radius: 10px;
margin-bottom: 30px;
}
.header h1 {
margin: 0 0 10px 0;
}
.header p {
margin: 0;
opacity: 0.9;
}
.section {
background: white;
padding: 25px;
border-radius: 10px;
margin-bottom: 20px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.section h2 {
margin-top: 0;
color: #333;
border-bottom: 3px solid #667eea;
padding-bottom: 10px;
}
.metric {
display: inline-block;
padding: 10px 20px;
margin: 5px;
border-radius: 5px;
font-weight: bold;
}
.metric.success {
background: #10b981;
color: white;
}
.metric.warning {
background: #f59e0b;
color: white;
}
.metric.danger {
background: #ef4444;
color: white;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 15px;
}
th, td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #e5e7eb;
}
th {
background: #f9fafb;
font-weight: bold;
}
.summary {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin-top: 20px;
}
.summary-card {
background: #f9fafb;
padding: 20px;
border-radius: 8px;
border-left: 4px solid #667eea;
}
.summary-card h3 {
margin-top: 0;
color: #667eea;
}
.summary-card .value {
font-size: 24px;
font-weight: bold;
color: #333;
}
ul {
line-height: 1.8;
}
</style>
</head>
<body>
<div class="header">
<h1>🚀 开发环境测试报告</h1>
<p>生成时间: ${timestamp}</p>
</div>
${performance ? generatePerformanceSection(performance) : ''}
${seo ? generateSEOSection(seo) : ''}
${accessibility ? generateAccessibilitySection(accessibility) : ''}
${forms ? generateFormSection(forms) : ''}
<div class="section">
<h2>📊 总体评估</h2>
<div class="summary">
${generateOverallSummary(performance, seo, accessibility, forms)}
</div>
</div>
<div class="section">
<h2>📝 建议和后续步骤</h2>
${generateRecommendations(performance, seo, accessibility, forms)}
</div>
</body>
</html>`;
const outputPath = 'test-results/test-report.html';
fs.writeFileSync(outputPath, html);
return outputPath;
}
function generatePerformanceSection(data) {
const avgScore = data.reduce((sum, page) =>
sum + (page.metrics?.performance || 0), 0) / data.length;
return `
<div class="section">
<h2>⚡ 性能测试结果</h2>
<div class="summary">
<div class="summary-card">
<h3>平均分数</h3>
<div class="value">${avgScore.toFixed(1)}</div>
</div>
<div class="summary-card">
<h3>测试页面数</h3>
<div class="value">${data.length}</div>
</div>
</div>
<table>
<thead>
<tr>
<th>页面</th>
<th>性能</th>
<th>可访问性</th>
<th>最佳实践</th>
<th>SEO</th>
</tr>
</thead>
<tbody>
${data.map(page => `
<tr>
<td>${page.page}</td>
<td><span class="metric ${page.metrics?.performance >= 80 ? 'success' : 'warning'}">${page.metrics?.performance?.toFixed(0) || 'N/A'}</span></td>
<td><span class="metric ${page.metrics?.accessibility >= 80 ? 'success' : 'warning'}">${page.metrics?.accessibility?.toFixed(0) || 'N/A'}</span></td>
<td><span class="metric ${page.metrics?.bestPractices >= 80 ? 'success' : 'warning'}">${page.metrics?.bestPractices?.toFixed(0) || 'N/A'}</span></td>
<td><span class="metric ${page.metrics?.seo >= 80 ? 'success' : 'warning'}">${page.metrics?.seo?.toFixed(0) || 'N/A'}</span></td>
</tr>
`).join('')}
</tbody>
</table>
</div>`;
}
function generateSEOSection(data) {
const passRate = ((data.totalPages - data.pagesWithIssues) / data.totalPages * 100);
return `
<div class="section">
<h2>🔍 SEO检查结果</h2>
<div class="summary">
<div class="summary-card">
<h3>通过率</h3>
<div class="value">${passRate.toFixed(1)}%</div>
</div>
<div class="summary-card">
<h3>总问题数</h3>
<div class="value">${data.totalIssues}</div>
</div>
</div>
<table>
<thead>
<tr>
<th>严重程度</th>
<th>数量</th>
</tr>
</thead>
<tbody>
<tr>
<td>🔴 高</td>
<td>${data.severity.high}</td>
</tr>
<tr>
<td>🟡 中</td>
<td>${data.severity.medium}</td>
</tr>
<tr>
<td>🟢 低</td>
<td>${data.severity.low}</td>
</tr>
</tbody>
</table>
</div>`;
}
function generateAccessibilitySection(data) {
const avgScore = data.summary.averageScore;
const passRate = ((data.totalPages - data.pagesWithIssues) / data.totalPages * 100);
return `
<div class="section">
<h2>♿ 可访问性测试结果</h2>
<div class="summary">
<div class="summary-card">
<h3>平均分数</h3>
<div class="value">${avgScore}</div>
</div>
<div class="summary-card">
<h3>达标率</h3>
<div class="value">${passRate.toFixed(1)}%</div>
</div>
</div>
<p>标准: WCAG 2.1 AA</p>
<p>总违规数: ${data.summary.totalViolations}</p>
</div>`;
}
function generateFormSection(data) {
return `
<div class="section">
<h2>📝 表单验证结果</h2>
<div class="summary">
<div class="summary-card">
<h3>通过率</h3>
<div class="value">${data.summary.passRate}%</div>
</div>
<div class="summary-card">
<h3>总测试数</h3>
<div class="value">${data.summary.totalTests}</div>
</div>
</div>
</div>`;
}
function generateOverallSummary(performance, seo, accessibility, forms) {
const sections = [];
if (performance) {
const avgScore = performance.reduce((sum, page) =>
sum + (page.metrics?.performance || 0), 0) / performance.length;
sections.push(`
<div class="summary-card">
<h3>性能</h3>
<div class="value">${avgScore.toFixed(1)}</div>
</div>`);
}
if (seo) {
const passRate = ((seo.totalPages - seo.pagesWithIssues) / seo.totalPages * 100);
sections.push(`
<div class="summary-card">
<h3>SEO</h3>
<div class="value">${passRate.toFixed(1)}%</div>
</div>`);
}
if (accessibility) {
sections.push(`
<div class="summary-card">
<h3>可访问性</h3>
<div class="value">${accessibility.summary.averageScore}</div>
</div>`);
}
if (forms) {
sections.push(`
<div class="summary-card">
<h3>表单</h3>
<div class="value">${forms.summary.passRate}%</div>
</div>`);
}
return sections.join('');
}
function generateRecommendations(performance, seo, accessibility, forms) {
const recommendations = [];
if (performance) {
const lowPerfPages = performance.filter(p => p.metrics?.performance < 80);
if (lowPerfPages.length > 0) {
recommendations.push(`
<h3>⚡ 性能优化</h3>
<ul>
<li>优化以下页面的加载速度: ${lowPerfPages.map(p => p.page).join(', ')}</li>
<li>实施图片懒加载和压缩</li>
<li>优化CSS和JavaScript资源</li>
<li>配置CDN加速</li>
</ul>`);
}
}
if (seo) {
if (seo.totalIssues > 0) {
recommendations.push(`
<h3>🔍 SEO改进</h3>
<ul>
<li>修复 ${seo.severity.high} 个高优先级SEO问题</li>
<li>完善所有页面的Meta标签</li>
<li>添加Open Graph标签</li>
<li>生成并提交sitemap.xml</li>
<li>配置robots.txt</li>
</ul>`);
}
}
if (accessibility) {
const lowAccessPages = accessibility.pages.filter(p => parseFloat(p.score) < 80);
if (lowAccessPages.length > 0) {
recommendations.push(`
<h3>♿ 可访问性改进</h3>
<ul>
<li>修复以下页面的可访问性问题: ${lowAccessPages.map(p => p.page).join(', ')}</li>
<li>添加ARIA标签</li>
<li>改进键盘导航</li>
<li>优化颜色对比度</li>
<li>确保所有交互元素可访问</li>
</ul>`);
}
}
if (forms) {
if (parseFloat(forms.summary.passRate) < 100) {
recommendations.push(`
<h3>📝 表单改进</h3>
<ul>
<li>修复表单验证逻辑</li>
<li>改进错误提示信息</li>
<li>添加提交成功反馈</li>
<li>确保表单在移动端正常工作</li>
</ul>`);
}
}
return recommendations.join('') || '<p>✅ 所有测试都通过了!网站已准备好上线。</p>';
}
async function main() {
console.log('📊 生成综合测试报告...\n');
const reportPath = generateHTMLReport();
console.log(`✅ 报告已生成: ${reportPath}`);
console.log(`💡 在浏览器中打开查看详细报告`);
}
main().catch(console.error);