#!/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 [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 Add delay between test runs in milliseconds'); console.log(' --log 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;