feat: add intelligent test scheduler
This commit is contained in:
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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');
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user