Files
playwrong/loop-test.js

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;