204 lines
6.7 KiB
JavaScript
204 lines
6.7 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
const { spawn } = require('child_process');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
class TestLooper {
|
|
constructor(testFile, profile = 'Chrome', options = {}) {
|
|
this.testFile = testFile;
|
|
this.profile = profile;
|
|
this.options = options;
|
|
this.runCount = 0;
|
|
this.successCount = 0;
|
|
this.failureCount = 0;
|
|
this.startTime = Date.now();
|
|
this.logFile = options.logFile || `test-loop-${Date.now()}.log`;
|
|
}
|
|
|
|
log(message) {
|
|
const timestamp = new Date().toISOString();
|
|
const logMessage = `[${timestamp}] ${message}`;
|
|
console.log(logMessage);
|
|
|
|
// Also write to log file
|
|
fs.appendFileSync(this.logFile, logMessage + '\n');
|
|
}
|
|
|
|
async runSingleTest() {
|
|
return new Promise((resolve, reject) => {
|
|
this.runCount++;
|
|
const startTime = Date.now();
|
|
|
|
this.log(`Starting test run #${this.runCount}`);
|
|
|
|
const child = spawn('node', ['src/cli.js', this.testFile, this.profile], {
|
|
stdio: 'pipe',
|
|
env: process.env
|
|
});
|
|
|
|
let stdout = '';
|
|
let stderr = '';
|
|
|
|
child.stdout.on('data', (data) => {
|
|
const output = data.toString();
|
|
stdout += output;
|
|
// Show real-time progress by forwarding key output lines
|
|
const lines = output.split('\n').filter(line => line.trim());
|
|
for (const line of lines) {
|
|
if (line.includes('Executing:') || line.includes('✓ Completed') || line.includes('Running test file:')) {
|
|
console.log(` ${line}`);
|
|
}
|
|
}
|
|
});
|
|
|
|
child.stderr.on('data', (data) => {
|
|
const output = data.toString();
|
|
stderr += output;
|
|
// Show errors in real-time
|
|
const lines = output.split('\n').filter(line => line.trim());
|
|
for (const line of lines) {
|
|
console.log(` ERROR: ${line}`);
|
|
}
|
|
});
|
|
|
|
child.on('close', (code) => {
|
|
const duration = Date.now() - startTime;
|
|
const minutes = Math.floor(duration / 60000);
|
|
const seconds = ((duration % 60000) / 1000).toFixed(1);
|
|
|
|
if (code === 0) {
|
|
this.successCount++;
|
|
this.log(`✅ Test run #${this.runCount} PASSED (${minutes}m ${seconds}s) - Total passed: ${this.successCount}`);
|
|
resolve({ success: true, code, stdout, stderr, duration });
|
|
} else {
|
|
this.failureCount++;
|
|
this.log(`❌ Test run #${this.runCount} FAILED with exit code ${code} (${minutes}m ${seconds}s) - Total passed: ${this.successCount}`);
|
|
this.log(`STDOUT: ${stdout}`);
|
|
this.log(`STDERR: ${stderr}`);
|
|
resolve({ success: false, code, stdout, stderr, duration });
|
|
}
|
|
});
|
|
|
|
child.on('error', (error) => {
|
|
this.failureCount++;
|
|
this.log(`❌ Test run #${this.runCount} ERROR: ${error.message}`);
|
|
reject(error);
|
|
});
|
|
});
|
|
}
|
|
|
|
printStatistics() {
|
|
const totalTime = Date.now() - this.startTime;
|
|
const totalMinutes = Math.floor(totalTime / 60000);
|
|
const totalSeconds = ((totalTime % 60000) / 1000).toFixed(1);
|
|
|
|
this.log('\n=== FINAL STATISTICS ===');
|
|
this.log(`Total runs: ${this.runCount}`);
|
|
this.log(`Successful runs: ${this.successCount}`);
|
|
this.log(`Failed runs: ${this.failureCount}`);
|
|
this.log(`Success rate: ${this.runCount > 0 ? ((this.successCount / this.runCount) * 100).toFixed(1) : 0}%`);
|
|
this.log(`Total time: ${totalMinutes}m ${totalSeconds}s`);
|
|
this.log(`Average time per run: ${this.runCount > 0 ? (totalTime / this.runCount / 1000).toFixed(1) : 0}s`);
|
|
this.log(`Log file: ${this.logFile}`);
|
|
}
|
|
|
|
async loop() {
|
|
this.log(`Starting test loop for: ${this.testFile} with profile: ${this.profile}`);
|
|
this.log(`Log file: ${this.logFile}`);
|
|
this.log(`Press Ctrl+C to stop the loop\n`);
|
|
|
|
// Handle Ctrl+C gracefully
|
|
process.on('SIGINT', () => {
|
|
this.log('\n\n🛑 Loop interrupted by user');
|
|
this.printStatistics();
|
|
process.exit(0);
|
|
});
|
|
|
|
try {
|
|
while (true) {
|
|
const result = await this.runSingleTest();
|
|
|
|
if (!result.success) {
|
|
this.log('\n🔴 Test failed! Stopping loop...');
|
|
this.printStatistics();
|
|
process.exit(1);
|
|
}
|
|
|
|
// Optional: Add a small delay between runs
|
|
if (this.options.delay) {
|
|
await new Promise(resolve => setTimeout(resolve, this.options.delay));
|
|
}
|
|
|
|
// Print periodic statistics
|
|
if (this.runCount % 10 === 0) {
|
|
const currentTime = Date.now() - this.startTime;
|
|
const minutes = Math.floor(currentTime / 60000);
|
|
const seconds = ((currentTime % 60000) / 1000).toFixed(1);
|
|
this.log(`📊 Progress: ${this.runCount} runs completed, ${this.successCount} successful (${minutes}m ${seconds}s elapsed)`);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
this.log(`💥 Unexpected error: ${error.message}`);
|
|
this.printStatistics();
|
|
process.exit(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
// CLI handling
|
|
async function main() {
|
|
const args = process.argv.slice(2);
|
|
|
|
if (args.length === 0) {
|
|
console.log('Usage: node loop-test.js <test-file> [profile] [options]');
|
|
console.log('');
|
|
console.log('Arguments:');
|
|
console.log(' test-file Path to the test file (e.g., step1.test)');
|
|
console.log(' profile Browser profile (Chrome, Mobile, MobileSmall) [default: Chrome]');
|
|
console.log('');
|
|
console.log('Options:');
|
|
console.log(' --delay <ms> Add delay between test runs in milliseconds');
|
|
console.log(' --log <file> Custom log file name');
|
|
console.log('');
|
|
console.log('Examples:');
|
|
console.log(' node loop-test.js step1.test Chrome');
|
|
console.log(' node loop-test.js step1.test Chrome --delay 1000');
|
|
console.log(' node loop-test.js step1.test Chrome --log my-test-log.txt');
|
|
console.log('');
|
|
console.log('The script will run the test in a loop until it fails.');
|
|
console.log('Press Ctrl+C to stop the loop manually.');
|
|
process.exit(1);
|
|
}
|
|
|
|
const testFile = args[0];
|
|
const profile = args[1] || 'Chrome';
|
|
|
|
// Parse options
|
|
const options = {};
|
|
const delayIndex = args.indexOf('--delay');
|
|
if (delayIndex !== -1 && args[delayIndex + 1]) {
|
|
options.delay = parseInt(args[delayIndex + 1]);
|
|
}
|
|
|
|
const logIndex = args.indexOf('--log');
|
|
if (logIndex !== -1 && args[logIndex + 1]) {
|
|
options.logFile = args[logIndex + 1];
|
|
}
|
|
|
|
// Check if test file exists
|
|
if (!fs.existsSync(testFile)) {
|
|
console.error(`❌ Test file not found: ${testFile}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
const looper = new TestLooper(testFile, profile, options);
|
|
await looper.loop();
|
|
}
|
|
|
|
// Run if called directly
|
|
if (require.main === module) {
|
|
main().catch(console.error);
|
|
}
|
|
|
|
module.exports = TestLooper;
|