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
				
			- 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>
		
			
				
	
	
		
			465 lines
		
	
	
		
			No EOL
		
	
	
		
			15 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			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; |