Files
everything-is-suitable/everything-is-suitable-test/e2e/reporters/progress-bar-reporter.ts
T
张翔 08ea5fbe98 feat(admin): 添加用户管理相关文件
添加用户管理视图、API和状态管理文件
2026-03-28 14:37:29 +08:00

251 lines
7.5 KiB
TypeScript

import type { FullConfig, FullResult, Reporter, Suite, TestCase, TestResult } from '@playwright/test/reporter';
import * as colors from 'ansi-colors';
import * as readline from 'readline';
interface TestProgress {
total: number;
passed: number;
failed: number;
skipped: number;
started: number;
startTime: number;
tests: Map<string, TestResultInfo>;
}
interface TestResultInfo {
status: 'passed' | 'failed' | 'skipped' | 'running';
startTime: number;
duration: number;
file: string;
title: string[];
}
export class ProgressReporter implements Reporter {
private progress: TestProgress;
private interval: NodeJS.Timeout | null = null;
private lastUpdate: number = 0;
private updateInterval: number = 1000;
private showProgressBar: boolean = true;
private quietMode: boolean = false;
constructor(options?: { showProgressBar?: boolean; quietMode?: boolean; updateInterval?: number }) {
this.showProgressBar = options?.showProgressBar ?? true;
this.quietMode = options?.quietMode ?? false;
this.updateInterval = options?.updateInterval ?? 1000;
this.progress = {
total: 0,
passed: 0,
failed: 0,
skipped: 0,
started: 0,
startTime: 0,
tests: new Map()
};
}
onBegin(config: FullConfig, suite: Suite) {
this.progress.startTime = Date.now();
this.progress.total = this.countTotalTests(suite);
if (!this.quietMode) {
console.log('\n' + colors.bold('🧪 开始执行测试'));
console.log(colors.gray(`总测试数: ${this.progress.total}`));
console.log(colors.gray(`并行数: ${config.workers}`));
console.log('');
if (this.showProgressBar) {
this.startProgressUpdate();
}
}
}
onTestBegin(test: TestCase, result: TestResult) {
this.progress.started++;
this.progress.tests.set(test.id, {
status: 'running',
startTime: Date.now(),
duration: 0,
file: test.location.file,
title: test.titlePath()
});
if (!this.quietMode && this.showProgressBar) {
this.updateProgress();
}
}
onTestEnd(test: TestCase, result: TestResult) {
const testInfo = this.progress.tests.get(test.id);
if (testInfo) {
testInfo.status = result.status === 'passed' ? 'passed' :
result.status === 'failed' ? 'failed' : 'skipped';
testInfo.duration = result.duration;
if (result.status === 'passed') {
this.progress.passed++;
} else if (result.status === 'failed') {
this.progress.failed++;
} else {
this.progress.skipped++;
}
}
if (!this.quietMode && this.showProgressBar) {
this.updateProgress();
}
}
onEnd(result: FullResult) {
if (this.interval) {
clearInterval(this.interval);
this.interval = null;
}
const duration = Date.now() - this.progress.startTime;
if (!this.quietMode) {
if (this.showProgressBar) {
this.clearProgress();
}
this.printSummary(result, duration);
}
}
private countTotalTests(suite: Suite): number {
let count = 0;
for (const child of suite.suites) {
count += this.countTotalTests(child);
}
count += suite.tests.length;
return count;
}
private startProgressUpdate(): void {
this.interval = setInterval(() => {
this.updateProgress();
}, this.updateInterval);
}
private updateProgress(): void {
const now = Date.now();
if (now - this.lastUpdate < this.updateInterval) {
return;
}
this.lastUpdate = now;
this.printProgressBar();
}
private printProgressBar(): void {
const { total, passed, failed, skipped, started, startTime } = this.progress;
const completed = passed + failed + skipped;
const percentage = total > 0 ? (completed / total) * 100 : 0;
const elapsed = (now() - startTime) / 1000;
const avgTime = completed > 0 ? elapsed / completed : 0;
const remaining = (total - completed) * avgTime;
const barWidth = 40;
const filledWidth = Math.round((completed / total) * barWidth);
const emptyWidth = barWidth - filledWidth;
const bar = colors.green('█'.repeat(filledWidth)) +
colors.gray('░'.repeat(emptyWidth));
const statusLine = [
colors.bold(`[${percentage.toFixed(1)}%]`),
bar,
colors.green(`${passed}`),
failed > 0 ? colors.red(`${failed}`) : colors.gray(`${failed}`),
colors.yellow(`${skipped}`),
colors.gray(`${elapsed.toFixed(1)}s`),
remaining > 0 ? colors.gray(`${remaining.toFixed(1)}s`) : ''
].filter(Boolean).join(' ');
readline.cursorTo(process.stdout, 0);
process.stdout.write(statusLine);
}
private clearProgress(): void {
readline.cursorTo(process.stdout, 0);
readline.clearLine(process.stdout, 0);
}
private printSummary(result: FullResult, duration: number): void {
const { total, passed, failed, skipped } = this.progress;
const passRate = total > 0 ? (passed / total) * 100 : 0;
console.log('\n' + '='.repeat(60));
console.log(colors.bold('📊 测试执行完成'));
console.log('='.repeat(60));
console.log(colors.gray(`执行时间: ${(duration / 1000).toFixed(2)}`));
console.log('');
console.log(colors.bold('📈 测试结果:'));
console.log(` 总测试数: ${colors.bold(total)}`);
console.log(` 通过测试: ${colors.green(passed)} (${(passed / total * 100).toFixed(2)}%)`);
console.log(` 失败测试: ${failed > 0 ? colors.red(failed) : colors.gray(failed)} (${(failed / total * 100).toFixed(2)}%)`);
console.log(` 跳过测试: ${colors.yellow(skipped)} (${(skipped / total * 100).toFixed(2)}%)`);
console.log(` 通过率: ${passRate >= 80 ? colors.green : passRate >= 60 ? colors.yellow : colors.red}${passRate.toFixed(2)}%`);
console.log('');
if (failed > 0) {
console.log(colors.bold('❌ 失败的测试:'));
this.printFailedTests();
console.log('');
}
if (skipped > 0) {
console.log(colors.bold('⚠️ 跳过的测试:'));
this.printSkippedTests();
console.log('');
}
console.log('='.repeat(60));
if (result.status === 'passed') {
console.log(colors.green.bold('✅ 所有测试通过'));
} else {
console.log(colors.red.bold('❌ 存在失败的测试'));
}
console.log('='.repeat(60) + '\n');
}
private printFailedTests(): void {
const failedTests = Array.from(this.progress.tests.entries())
.filter(([_, info]) => info.status === 'failed')
.slice(0, 10);
failedTests.forEach(([testId, info]) => {
const fileName = info.file.split('/').pop();
console.log(` ${colors.red('✗')} ${colors.bold(fileName)}: ${info.title.join(' > ')}`);
});
if (failedTests.length >= 10) {
console.log(` ${colors.gray(`... 还有 ${this.progress.failed - 10} 个失败的测试`)}`);
}
}
private printSkippedTests(): void {
const skippedTests = Array.from(this.progress.tests.entries())
.filter(([_, info]) => info.status === 'skipped')
.slice(0, 5);
skippedTests.forEach(([testId, info]) => {
const fileName = info.file.split('/').pop();
console.log(` ${colors.yellow('⊘')} ${colors.bold(fileName)}: ${info.title.join(' > ')}`);
});
if (skippedTests.length >= 5) {
console.log(` ${colors.gray(`... 还有 ${this.progress.skipped - 5} 个跳过的测试`)}`);
}
}
}
function now(): number {
return Date.now();
}
export default ProgressReporter;