feat(e2e): 添加完整的E2E测试框架和测试用例
添加Playwright测试框架配置和基础页面对象 实现冒烟测试用例覆盖首页和联系页面核心功能 更新导航组件以支持滚动高亮功能 添加BackButton组件统一返回按钮行为 配置Woodpecker CI集成和测试报告生成
This commit is contained in:
@@ -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);
|
||||
Reference in New Issue
Block a user