upskill-event-manager/tests/scripts/test-runner.js
Ben 7c9ca65cf2
Some checks are pending
HVAC Plugin CI/CD Pipeline / Security Analysis (push) Waiting to run
HVAC Plugin CI/CD Pipeline / Code Quality & Standards (push) Waiting to run
HVAC Plugin CI/CD Pipeline / Unit Tests (push) Waiting to run
HVAC Plugin CI/CD Pipeline / Integration Tests (push) Waiting to run
HVAC Plugin CI/CD Pipeline / Deploy to Staging (push) Blocked by required conditions
HVAC Plugin CI/CD Pipeline / Deploy to Production (push) Blocked by required conditions
HVAC Plugin CI/CD Pipeline / Notification (push) Blocked by required conditions
Security Monitoring & Compliance / Dependency Vulnerability Scan (push) Waiting to run
Security Monitoring & Compliance / Secrets & Credential Scan (push) Waiting to run
Security Monitoring & Compliance / WordPress Security Analysis (push) Waiting to run
Security Monitoring & Compliance / Static Code Security Analysis (push) Waiting to run
Security Monitoring & Compliance / Security Compliance Validation (push) Waiting to run
Security Monitoring & Compliance / Security Summary Report (push) Blocked by required conditions
Security Monitoring & Compliance / Security Team Notification (push) Blocked by required conditions
feat: add comprehensive test framework and test files
- Add 90+ test files including E2E, unit, and integration tests
- Implement Page Object Model (POM) architecture
- Add Docker testing environment with comprehensive services
- Include modernized test framework with error recovery
- Add specialized test suites for master trainer and trainer workflows
- Update .gitignore to properly track test infrastructure

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-29 23:23:26 -03:00

465 lines
No EOL
15 KiB
JavaScript

#!/usr/bin/env node
/**
* Comprehensive Test Runner for HVAC Testing Framework 2.0
* Orchestrates test execution with proper setup, cleanup, and reporting
*/
const { spawn } = require('child_process');
const fs = require('fs').promises;
const path = require('path');
class TestRunner {
constructor() {
this.config = {
environment: process.env.TEST_ENVIRONMENT || 'staging',
parallel: process.env.PARALLEL === 'true',
headless: process.env.HEADLESS !== 'false',
retries: parseInt(process.env.RETRIES) || 2,
timeout: parseInt(process.env.TIMEOUT) || 60000
};
this.results = {
total: 0,
passed: 0,
failed: 0,
skipped: 0,
duration: 0,
suites: []
};
}
/**
* Main test runner entry point
*/
async run() {
console.log('🚀 HVAC Testing Framework 2.0 - Test Runner');
console.log(`Environment: ${this.config.environment}`);
console.log(`Parallel: ${this.config.parallel}`);
console.log(`Headless: ${this.config.headless}`);
console.log('─'.repeat(60));
try {
await this.setupEnvironment();
await this.runTestSuites();
await this.generateReports();
await this.cleanup();
this.printSummary();
process.exit(this.results.failed > 0 ? 1 : 0);
} catch (error) {
console.error('❌ Test Runner Failed:', error.message);
process.exit(1);
}
}
/**
* Set up test environment
*/
async setupEnvironment() {
console.log('📋 Setting up test environment...');
// Create evidence directories
const evidenceDirs = [
'evidence/screenshots',
'evidence/videos',
'evidence/reports',
'evidence/logs'
];
for (const dir of evidenceDirs) {
await fs.mkdir(dir, { recursive: true });
}
// Validate framework dependencies
await this.validateFramework();
console.log('✅ Environment setup complete');
}
/**
* Validate framework dependencies
*/
async validateFramework() {
try {
// Check if core framework files exist
const coreFiles = [
'framework/base/BasePage.js',
'framework/base/BaseTest.js',
'framework/browser/BrowserManager.js',
'framework/authentication/AuthManager.js'
];
for (const file of coreFiles) {
const filePath = path.join(__dirname, '..', file);
await fs.access(filePath);
}
console.log(' ✅ Framework core files validated');
} catch (error) {
throw new Error(`Framework validation failed: ${error.message}`);
}
}
/**
* Run test suites based on arguments or configuration
*/
async runTestSuites() {
const args = process.argv.slice(2);
let suitesToRun = [];
if (args.length === 0) {
// Run all test suites
suitesToRun = await this.discoverTestSuites();
} else {
// Run specific suites
suitesToRun = args.map(suite => this.resolveTestSuite(suite));
}
console.log(`📊 Running ${suitesToRun.length} test suite(s):`);
suitesToRun.forEach(suite => console.log(` - ${suite}`));
console.log('─'.repeat(60));
for (const suite of suitesToRun) {
await this.runSingleSuite(suite);
}
}
/**
* Discover all available test suites
*/
async discoverTestSuites() {
const suites = [];
const suitesDir = path.join(__dirname, '..', 'suites');
try {
const entries = await fs.readdir(suitesDir, { withFileTypes: true });
for (const entry of entries) {
if (entry.isDirectory()) {
const suiteFiles = await fs.readdir(path.join(suitesDir, entry.name));
const testFiles = suiteFiles.filter(file =>
file.endsWith('.js') &&
(file.includes('test') || file.includes('Test'))
);
for (const testFile of testFiles) {
suites.push(`suites/${entry.name}/${testFile}`);
}
}
}
} catch (error) {
console.warn(`Warning: Could not discover test suites: ${error.message}`);
}
return suites.length > 0 ? suites : ['suites/master-trainer/MasterTrainerE2E.modernized.js'];
}
/**
* Resolve test suite path
*/
resolveTestSuite(suiteName) {
// Handle different suite name formats
if (suiteName.includes('/') && suiteName.endsWith('.js')) {
return suiteName; // Already a file path
}
if (suiteName === 'master-trainer' || suiteName === 'mt') {
return 'suites/master-trainer/MasterTrainerE2E.modernized.js';
}
if (suiteName === 'security') {
return 'suites/security/SecurityTests.js';
}
if (suiteName === 'trainer') {
return 'suites/trainer/TrainerTests.js';
}
// Default: assume it's a file in suites directory
return `suites/${suiteName}`;
}
/**
* Run a single test suite
*/
async runSingleSuite(suitePath) {
const fullPath = path.join(__dirname, '..', suitePath);
const suiteName = path.basename(suitePath, '.js');
console.log(`🧪 Running test suite: ${suiteName}`);
const startTime = Date.now();
let suiteResult = {
name: suiteName,
path: suitePath,
passed: 0,
failed: 0,
skipped: 0,
duration: 0,
error: null
};
try {
// Check if file exists
await fs.access(fullPath);
// Execute the test suite
const result = await this.executeTestFile(fullPath);
suiteResult.passed = result.passed || 0;
suiteResult.failed = result.failed || 0;
suiteResult.skipped = result.skipped || 0;
} catch (error) {
console.error(` ❌ Suite execution failed: ${error.message}`);
suiteResult.failed = 1;
suiteResult.error = error.message;
}
suiteResult.duration = Date.now() - startTime;
// Update overall results
this.results.total += suiteResult.passed + suiteResult.failed + suiteResult.skipped;
this.results.passed += suiteResult.passed;
this.results.failed += suiteResult.failed;
this.results.skipped += suiteResult.skipped;
this.results.suites.push(suiteResult);
// Print suite summary
const status = suiteResult.failed === 0 ? '✅' : '❌';
console.log(` ${status} ${suiteName}: ${suiteResult.passed} passed, ${suiteResult.failed} failed (${suiteResult.duration}ms)`);
}
/**
* Execute a test file
*/
async executeTestFile(filePath) {
return new Promise((resolve, reject) => {
const env = {
...process.env,
TEST_ENVIRONMENT: this.config.environment,
HEADLESS: this.config.headless.toString(),
PARALLEL: this.config.parallel.toString()
};
const childProcess = spawn('node', [filePath], {
env: env,
stdio: 'pipe'
});
let stdout = '';
let stderr = '';
childProcess.stdout.on('data', (data) => {
const text = data.toString();
stdout += text;
process.stdout.write(text);
});
childProcess.stderr.on('data', (data) => {
const text = data.toString();
stderr += text;
process.stderr.write(text);
});
childProcess.on('close', (code) => {
if (code === 0) {
// Parse results from stdout if available
const results = this.parseTestResults(stdout);
resolve(results);
} else {
reject(new Error(`Test suite exited with code ${code}: ${stderr}`));
}
});
childProcess.on('error', (error) => {
reject(new Error(`Failed to start test suite: ${error.message}`));
});
// Set timeout
setTimeout(() => {
childProcess.kill('SIGKILL');
reject(new Error(`Test suite timed out after ${this.config.timeout}ms`));
}, this.config.timeout);
});
}
/**
* Parse test results from stdout
*/
parseTestResults(stdout) {
const results = { passed: 0, failed: 0, skipped: 0 };
// Look for test completion patterns
const passedMatches = stdout.match(/✅.*passed/gi) || [];
const failedMatches = stdout.match(/❌.*failed/gi) || [];
results.passed = passedMatches.length;
results.failed = failedMatches.length;
// If no explicit results found, assume success if no errors
if (results.passed === 0 && results.failed === 0) {
if (stdout.includes('completed successfully') || stdout.includes('All tests passed')) {
results.passed = 1;
} else if (stdout.includes('failed') || stdout.includes('error')) {
results.failed = 1;
} else {
results.passed = 1; // Default to passed if no clear indication
}
}
return results;
}
/**
* Generate test reports
*/
async generateReports() {
console.log('📊 Generating test reports...');
const reportData = {
timestamp: new Date().toISOString(),
environment: this.config.environment,
configuration: this.config,
results: this.results,
summary: {
totalTests: this.results.total,
passRate: this.results.total > 0 ? ((this.results.passed / this.results.total) * 100).toFixed(2) : '0',
duration: this.results.duration
}
};
// Generate JSON report
const jsonReportPath = path.join(__dirname, '..', 'evidence', 'reports', 'test-results.json');
await fs.writeFile(jsonReportPath, JSON.stringify(reportData, null, 2));
// Generate HTML report (simple)
const htmlReport = this.generateHTMLReport(reportData);
const htmlReportPath = path.join(__dirname, '..', 'evidence', 'reports', 'test-results.html');
await fs.writeFile(htmlReportPath, htmlReport);
console.log(` ✅ Reports generated:`);
console.log(` 📄 JSON: ${jsonReportPath}`);
console.log(` 🌐 HTML: ${htmlReportPath}`);
}
/**
* Generate simple HTML report
*/
generateHTMLReport(reportData) {
const { results, summary, timestamp, environment } = reportData;
return `<!DOCTYPE html>
<html>
<head>
<title>HVAC Testing Framework - Test Results</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.header { background: #f5f5f5; padding: 20px; border-radius: 8px; margin-bottom: 20px; }
.summary { display: flex; gap: 20px; margin: 20px 0; }
.metric { background: #e8f4fd; padding: 15px; border-radius: 5px; text-align: center; }
.metric.passed { background: #d4edda; }
.metric.failed { background: #f8d7da; }
.suite { border: 1px solid #ddd; margin: 10px 0; padding: 15px; border-radius: 5px; }
.suite.passed { border-left: 5px solid #28a745; }
.suite.failed { border-left: 5px solid #dc3545; }
</style>
</head>
<body>
<div class="header">
<h1>🧪 HVAC Testing Framework - Test Results</h1>
<p><strong>Environment:</strong> ${environment}</p>
<p><strong>Timestamp:</strong> ${timestamp}</p>
<p><strong>Pass Rate:</strong> ${summary.passRate}%</p>
</div>
<div class="summary">
<div class="metric passed">
<h3>${results.passed}</h3>
<p>Passed</p>
</div>
<div class="metric failed">
<h3>${results.failed}</h3>
<p>Failed</p>
</div>
<div class="metric">
<h3>${results.skipped}</h3>
<p>Skipped</p>
</div>
<div class="metric">
<h3>${results.total}</h3>
<p>Total</p>
</div>
</div>
<h2>📋 Test Suites</h2>
${results.suites.map(suite => `
<div class="suite ${suite.failed === 0 ? 'passed' : 'failed'}">
<h3>${suite.name}</h3>
<p><strong>Path:</strong> ${suite.path}</p>
<p><strong>Results:</strong> ${suite.passed} passed, ${suite.failed} failed, ${suite.skipped} skipped</p>
<p><strong>Duration:</strong> ${suite.duration}ms</p>
${suite.error ? `<p><strong>Error:</strong> ${suite.error}</p>` : ''}
</div>
`).join('')}
<footer style="margin-top: 40px; padding-top: 20px; border-top: 1px solid #ddd; text-align: center; color: #666;">
<p>Generated by HVAC Testing Framework 2.0</p>
</footer>
</body>
</html>`;
}
/**
* Clean up test environment
*/
async cleanup() {
console.log('🧹 Cleaning up...');
// Clean up temporary files if needed
// Keep evidence for analysis
console.log('✅ Cleanup complete');
}
/**
* Print final test summary
*/
printSummary() {
console.log('─'.repeat(60));
console.log('📊 TEST SUMMARY');
console.log('─'.repeat(60));
console.log(`Environment: ${this.config.environment}`);
console.log(`Total Tests: ${this.results.total}`);
console.log(`✅ Passed: ${this.results.passed}`);
console.log(`❌ Failed: ${this.results.failed}`);
console.log(`⏭️ Skipped: ${this.results.skipped}`);
if (this.results.total > 0) {
const passRate = ((this.results.passed / this.results.total) * 100).toFixed(2);
console.log(`📈 Pass Rate: ${passRate}%`);
}
console.log('─'.repeat(60));
if (this.results.failed === 0) {
console.log('🎉 ALL TESTS PASSED!');
} else {
console.log('💥 SOME TESTS FAILED');
console.log('📄 Check reports in evidence/reports/ for details');
}
}
}
// Run the test runner if this file is executed directly
if (require.main === module) {
const runner = new TestRunner();
runner.run().catch(error => {
console.error('Fatal error:', error);
process.exit(1);
});
}
module.exports = TestRunner;