feat(e2e): 添加完整的E2E测试框架和测试用例

添加Playwright测试框架配置和基础页面对象
实现冒烟测试用例覆盖首页和联系页面核心功能
更新导航组件以支持滚动高亮功能
添加BackButton组件统一返回按钮行为
配置Woodpecker CI集成和测试报告生成
This commit is contained in:
张翔
2026-02-27 10:30:33 +08:00
parent 4a616fe96e
commit 5d5b7feb0a
50 changed files with 6765 additions and 46 deletions
+147
View File
@@ -0,0 +1,147 @@
#!/usr/bin/env node
const { spawn } = require('child_process');
const fs = require('fs');
const { glob } = require('glob');
const testTypes = [
{ name: '冒烟测试', script: 'test:smoke', pattern: 'src/tests/smoke/**/*.spec.ts' },
{ name: '回归测试', script: 'test:regression', pattern: 'src/tests/regression/**/*.spec.ts' },
{ name: '性能测试', script: 'test:performance', pattern: 'src/tests/performance/**/*.spec.ts' },
{ name: '响应式测试', script: 'test:responsive', pattern: 'src/tests/responsive/**/*.spec.ts' }
];
const TIMEOUT_SECONDS = 600;
async function runTests() {
console.log('🧪 开始运行E2E测试...\n');
const results = {
total: 0,
passed: 0,
failed: 0,
byType: {}
};
for (const testType of testTypes) {
console.log(`\n${'='.repeat(60)}`);
console.log(`📋 ${testType.name}`);
console.log(`${'='.repeat(60)}`);
await new Promise((resolve) => {
const startTime = Date.now();
let lastUpdateTime = startTime;
let currentTest = 0;
let passedCount = 0;
let failedCount = 0;
let isComplete = false;
let lastTestName = '';
const testProcess = spawn('npm', ['run', testType.script], {
cwd: __dirname,
shell: true,
stdio: ['pipe', 'pipe', 'pipe']
});
testProcess.stdout.on('data', (data) => {
const output = data.toString();
if (output.includes('')) {
currentTest++;
const progress = Math.min(100, Math.round((currentTest / 100) * 100));
const elapsed = Math.round((Date.now() - startTime) / 1000);
const barLength = Math.floor(progress / 2);
const bar = '█'.repeat(barLength) + '░'.repeat(50 - barLength);
const testNameMatch = output.match(/\s+(.+)/);
if (testNameMatch) {
lastTestName = testNameMatch[1].trim();
}
process.stdout.write(`\r⏳ 进度: [${bar}] ${progress}% - ${elapsed}s - ${lastTestName}`);
lastUpdateTime = Date.now();
}
if (output.includes('passed')) {
const match = output.match(/(\d+)\s+passed/);
if (match) {
passedCount = parseInt(match[1]);
}
}
if (output.includes('failed')) {
const match = output.match(/(\d+)\s+failed/);
if (match) {
failedCount = parseInt(match[1]);
}
}
});
testProcess.stderr.on('data', (data) => {
const output = data.toString();
if (output.includes('Error') || output.includes('error')) {
process.stdout.write('\n❌ 错误: ' + output);
}
});
testProcess.on('close', (code) => {
isComplete = true;
const elapsed = Math.round((Date.now() - startTime) / 1000);
process.stdout.write(`\r✅ 完成: [${'█'.repeat(50)}] 100% - ${elapsed}s\n`);
results.total += passedCount + failedCount;
results.passed += passedCount;
results.failed += failedCount;
results.byType[testType.name] = {
total: passedCount + failedCount,
passed: passedCount,
failed: failedCount,
elapsed: elapsed
};
resolve();
});
const progressInterval = setInterval(() => {
if (!isComplete) {
const elapsed = Math.round((Date.now() - startTime) / 1000);
const timeSinceLastUpdate = Date.now() - lastUpdateTime;
if (timeSinceLastUpdate > 10000 && timeSinceLastUpdate < 30000) {
process.stdout.write(`\r⏳ 等待测试... (${elapsed}s) - ${lastTestName}`);
} else if (timeSinceLastUpdate >= 30000) {
process.stdout.write(`\r⚠️ 测试可能卡住 (${elapsed}s) - ${lastTestName}`);
}
if (elapsed > TIMEOUT_SECONDS) {
console.log(`\n❌ 测试超时 (${TIMEOUT_SECONDS}s),正在停止...`);
testProcess.kill();
clearInterval(progressInterval);
isComplete = true;
resolve();
}
}
}, 5000);
testProcess.on('close', () => {
clearInterval(progressInterval);
});
});
}
console.log(`\n${'='.repeat(60)}`);
console.log('📊 测试结果汇总');
console.log(`${'='.repeat(60)}`);
console.log(`总测试数: ${results.total}`);
console.log(`通过: ${results.passed} (${((results.passed / results.total) * 100).toFixed(1)}%)`);
console.log(`失败: ${results.failed} (${((results.failed / results.total) * 100).toFixed(1)}%)`);
console.log('\n分类结果:');
for (const [name, result] of Object.entries(results.byType)) {
const passRate = ((result.passed / result.total) * 100).toFixed(1);
const status = passRate >= 80 ? '✅' : passRate >= 50 ? '⚠️' : '❌';
console.log(` ${status} ${name}: ${result.passed}/${result.total} (${passRate}%) - ${result.elapsed}s`);
}
}
runTests().catch(console.error);