feat: add intelligent test scheduler

This commit is contained in:
张翔
2026-03-13 11:31:53 +08:00
parent a3e7114349
commit dd1ea3f9a9
2 changed files with 141 additions and 0 deletions
+104
View File
@@ -0,0 +1,104 @@
import { TestHistoryManager } from './test-history';
interface TestSchedule {
testId: string;
file: string;
title: string;
priority: number;
estimatedDuration: number;
dependencies: string[];
}
export class TestScheduler {
private historyManager: TestHistoryManager;
constructor() {
this.historyManager = new TestHistoryManager();
}
scheduleTests(testFiles: string[]): TestSchedule[] {
const schedules: TestSchedule[] = [];
for (const file of testFiles) {
const testId = this.generateTestId(file);
const priority = this.calculatePriority(file);
const estimatedDuration = this.historyManager.getAverageDuration(testId) || 60000;
const dependencies = this.analyzeDependencies(file);
schedules.push({
testId,
file,
title: this.extractTestTitle(file),
priority,
estimatedDuration,
dependencies,
});
}
return schedules.sort((a, b) => {
if (a.priority !== b.priority) {
return a.priority - b.priority;
}
return a.estimatedDuration - b.estimatedDuration;
});
}
private generateTestId(file: string): string {
return file.replace(/[^a-zA-Z0-9]/g, '-');
}
private calculatePriority(file: string): number {
if (file.includes('smoke')) return 1;
if (file.includes('api')) return 2;
if (file.includes('admin')) return 3;
if (file.includes('regression')) return 4;
return 5;
}
private extractTestTitle(file: string): string {
const parts = file.split('/');
return parts[parts.length - 1].replace('.spec.ts', '');
}
private analyzeDependencies(file: string): string[] {
const dependencies: string[] = [];
if (file.includes('admin') && !file.includes('login')) {
dependencies.push('admin-login');
}
return dependencies;
}
optimizeExecutionOrder(schedules: TestSchedule[]): TestSchedule[] {
const optimized: TestSchedule[] = [];
const executed = new Set<string>();
const noDeps = schedules.filter(s => s.dependencies.length === 0);
optimized.push(...noDeps);
noDeps.forEach(s => executed.add(s.testId));
let remaining = schedules.filter(s => !executed.has(s.testId));
let iterations = 0;
while (remaining.length > 0 && iterations < 100) {
const canExecute = remaining.filter(s =>
s.dependencies.every(dep => executed.has(dep))
);
if (canExecute.length === 0) {
optimized.push(remaining[0]);
executed.add(remaining[0].testId);
remaining = remaining.slice(1);
} else {
optimized.push(...canExecute);
canExecute.forEach(s => executed.add(s.testId));
remaining = remaining.filter(s => !executed.has(s.testId));
}
iterations++;
}
return optimized;
}
}
+37
View File
@@ -0,0 +1,37 @@
const fs = require('fs');
const path = require('path');
const HISTORY_FILE = path.join(__dirname, 'test-history.json');
const testFiles = [
'smoke/navigation.smoke.spec.ts',
'admin/news-management.spec.ts',
'api/admin.api.spec.ts',
];
console.log('📊 Testing test scheduler...');
console.log('Test files:', testFiles);
if (fs.existsSync(HISTORY_FILE)) {
const data = fs.readFileSync(HISTORY_FILE, 'utf-8');
const history = JSON.parse(data);
console.log('✅ History loaded');
console.log('Records:', history.records.length);
const schedule = testFiles.map(file => ({
file,
testId: file.replace(/[^a-zA-Z0-9]/g, '-'),
priority: file.includes('smoke') ? 1 : file.includes('api') ? 2 : 3,
estimatedDuration: 60000,
dependencies: [],
}));
console.log('\n📋 Scheduled tests:');
schedule.forEach((test, index) => {
console.log(` ${index + 1}. ${test.file} (Priority: ${test.priority})`);
});
console.log('\n✅ Scheduler test completed');
} else {
console.log('❌ History file not found');
}