#!/usr/bin/env node /** * HVAC Community Events - Comprehensive Build System Test Runner * * Executes the complete test suite for the JavaScript build pipeline including: * - Build system validation * - Security vulnerability testing * - WordPress integration testing * - E2E functionality testing * - Performance and compatibility testing * * @package HVAC_Community_Events * @since 2.0.0 */ const { spawn } = require('child_process'); const fs = require('fs').promises; const path = require('path'); // Test configuration const TEST_CONFIG = { BASE_URL: process.env.BASE_URL || 'http://localhost:8080', HEADLESS: process.env.HEADLESS !== 'false', BROWSER: process.env.BROWSER || 'chromium', WORKERS: process.env.WORKERS || '1', // Test suites to run TEST_SUITES: [ { name: 'Build System Validation', file: './tests/build-system-validation.test.js', description: 'Webpack builds, bundle generation, performance validation', critical: true }, { name: 'Security Vulnerability Tests', file: './tests/build-system-security.test.js', description: 'Critical security vulnerability detection and testing', critical: true }, { name: 'E2E Bundled Assets Functionality', file: './tests/e2e-bundled-assets-functionality.test.js', description: 'End-to-end functionality with bundled assets', critical: true } ], // Test environments ENVIRONMENTS: { DOCKER: 'http://localhost:8080', STAGING: 'https://staging.upskillhvac.com', LOCAL: 'http://localhost:8080' } }; /** * Build System Test Runner */ class BuildSystemTestRunner { constructor() { this.results = { suites: [], summary: { total: 0, passed: 0, failed: 0, skipped: 0 }, startTime: new Date(), endTime: null, environment: process.env.NODE_ENV || 'test' }; } /** * Print colored console output */ log(message, color = 'white') { const colors = { red: '\x1b[31m', green: '\x1b[32m', yellow: '\x1b[33m', blue: '\x1b[34m', magenta: '\x1b[35m', cyan: '\x1b[36m', white: '\x1b[37m', reset: '\x1b[0m' }; console.log(`${colors[color]}${message}${colors.reset}`); } /** * Check prerequisites */ async checkPrerequisites() { this.log('\n๐Ÿ” Checking Prerequisites...', 'cyan'); const checks = []; // Check if build output exists try { await fs.access('./assets/js/dist'); checks.push({ name: 'Build output directory', status: 'pass' }); } catch (error) { checks.push({ name: 'Build output directory', status: 'fail', error: 'dist/ directory not found' }); } // Check webpack config try { await fs.access('./webpack.config.js'); checks.push({ name: 'Webpack configuration', status: 'pass' }); } catch (error) { checks.push({ name: 'Webpack configuration', status: 'fail', error: 'webpack.config.js not found' }); } // Check HVAC_Bundled_Assets class try { await fs.access('./includes/class-hvac-bundled-assets.php'); checks.push({ name: 'HVAC_Bundled_Assets class', status: 'pass' }); } catch (error) { checks.push({ name: 'HVAC_Bundled_Assets class', status: 'fail', error: 'class-hvac-bundled-assets.php not found' }); } // Check test environment try { const response = await fetch(TEST_CONFIG.BASE_URL); if (response.ok) { checks.push({ name: 'Test environment', status: 'pass', url: TEST_CONFIG.BASE_URL }); } else { checks.push({ name: 'Test environment', status: 'fail', error: `HTTP ${response.status}` }); } } catch (error) { checks.push({ name: 'Test environment', status: 'fail', error: error.message }); } // Print results checks.forEach(check => { if (check.status === 'pass') { this.log(` โœ… ${check.name}`, 'green'); if (check.url) this.log(` ${check.url}`, 'white'); } else { this.log(` โŒ ${check.name}: ${check.error}`, 'red'); } }); const failedChecks = checks.filter(c => c.status === 'fail'); if (failedChecks.length > 0) { this.log(`\nโš ๏ธ ${failedChecks.length} prerequisite checks failed`, 'yellow'); this.log('Some tests may fail or be skipped', 'yellow'); } return { checks, allPassed: failedChecks.length === 0 }; } /** * Run build system validation */ async runBuildValidation() { this.log('\n๐Ÿ—๏ธ Running Build System Validation...', 'blue'); try { // Check if we need to build const distExists = await fs.access('./assets/js/dist').then(() => true).catch(() => false); if (!distExists) { this.log('๐Ÿ“ฆ Building assets...', 'yellow'); await this.runCommand('npm run build'); } // Validate build output const distFiles = await fs.readdir('./assets/js/dist'); const bundleFiles = distFiles.filter(f => f.endsWith('.bundle.js')); this.log(`โœ… Found ${bundleFiles.length} bundle files:`, 'green'); bundleFiles.forEach(file => { this.log(` โ€ข ${file}`, 'white'); }); return { success: true, bundleCount: bundleFiles.length }; } catch (error) { this.log(`โŒ Build validation failed: ${error.message}`, 'red'); return { success: false, error: error.message }; } } /** * Run a shell command */ runCommand(command) { return new Promise((resolve, reject) => { const process = spawn('bash', ['-c', command], { stdio: ['pipe', 'pipe', 'pipe'] }); let output = ''; let errorOutput = ''; process.stdout.on('data', (data) => { output += data.toString(); }); process.stderr.on('data', (data) => { errorOutput += data.toString(); }); process.on('close', (code) => { if (code === 0) { resolve(output); } else { reject(new Error(`Command failed: ${command}\n${errorOutput}`)); } }); }); } /** * Run Playwright test suite */ async runTestSuite(suite) { this.log(`\n๐Ÿงช Running: ${suite.name}`, 'magenta'); this.log(` ${suite.description}`, 'white'); const startTime = Date.now(); try { // Build Playwright command const playwrightCmd = [ 'npx playwright test', `"${suite.file}"`, `--project=${TEST_CONFIG.BROWSER}`, `--workers=${TEST_CONFIG.WORKERS}`, TEST_CONFIG.HEADLESS ? '--headless' : '', '--reporter=json', '--reporter=line' ].filter(Boolean).join(' '); this.log(` Command: ${playwrightCmd}`, 'cyan'); const output = await this.runCommand(playwrightCmd); const duration = Date.now() - startTime; // Parse results (simplified) const passed = (output.match(/passed/g) || []).length; const failed = (output.match(/failed/g) || []).length; const skipped = (output.match(/skipped/g) || []).length; const result = { suite: suite.name, file: suite.file, passed, failed, skipped, duration: Math.round(duration / 1000), success: failed === 0, output: output.slice(-1000) // Last 1000 chars }; if (result.success) { this.log(` โœ… ${suite.name} completed successfully`, 'green'); this.log(` ๐Ÿ“Š ${passed} passed, ${failed} failed, ${skipped} skipped (${result.duration}s)`, 'green'); } else { this.log(` โŒ ${suite.name} had failures`, 'red'); this.log(` ๐Ÿ“Š ${passed} passed, ${failed} failed, ${skipped} skipped (${result.duration}s)`, 'red'); } return result; } catch (error) { const duration = Date.now() - startTime; this.log(` โŒ ${suite.name} failed to run: ${error.message}`, 'red'); return { suite: suite.name, file: suite.file, passed: 0, failed: 1, skipped: 0, duration: Math.round(duration / 1000), success: false, error: error.message }; } } /** * Generate test report */ async generateReport() { this.results.endTime = new Date(); const duration = Math.round((this.results.endTime - this.results.startTime) / 1000); this.log('\n๐Ÿ“‹ BUILD SYSTEM TEST REPORT', 'cyan'); this.log('โ•'.repeat(50), 'cyan'); this.log(`\n๐Ÿ• Duration: ${duration} seconds`, 'white'); this.log(`๐ŸŒ Environment: ${TEST_CONFIG.BASE_URL}`, 'white'); this.log(`๐Ÿ”ง Browser: ${TEST_CONFIG.BROWSER}`, 'white'); this.log(`๐Ÿ‘ฅ Workers: ${TEST_CONFIG.WORKERS}`, 'white'); this.log(`\n๐Ÿ“Š SUMMARY`, 'cyan'); this.log(` Total Suites: ${this.results.suites.length}`, 'white'); this.log(` Passed: ${this.results.summary.passed}`, 'green'); this.log(` Failed: ${this.results.summary.failed}`, this.results.summary.failed > 0 ? 'red' : 'white'); this.log(` Skipped: ${this.results.summary.skipped}`, this.results.summary.skipped > 0 ? 'yellow' : 'white'); this.log(`\n๐Ÿงช SUITE DETAILS`, 'cyan'); this.results.suites.forEach(suite => { const status = suite.success ? 'โœ…' : 'โŒ'; const color = suite.success ? 'green' : 'red'; this.log(` ${status} ${suite.suite}`, color); this.log(` ๐Ÿ“ ${path.basename(suite.file)}`, 'white'); this.log(` ๐Ÿ“Š ${suite.passed}P ${suite.failed}F ${suite.skipped}S (${suite.duration}s)`, 'white'); if (suite.error) { this.log(` โš ๏ธ ${suite.error}`, 'yellow'); } }); // Security vulnerabilities summary const securitySuite = this.results.suites.find(s => s.suite === 'Security Vulnerability Tests'); if (securitySuite) { this.log(`\n๐Ÿ”’ SECURITY ASSESSMENT`, 'red'); if (securitySuite.success) { this.log(' โœ… All security tests passed', 'green'); this.log(' โš ๏ธ However, tests may pass even with vulnerabilities', 'yellow'); } else { this.log(' โŒ Security test failures detected', 'red'); } this.log(' ๐Ÿ“‹ Review security test output for vulnerability details', 'white'); } // Overall status this.log(`\n๐ŸŽฏ OVERALL STATUS`, 'cyan'); const overallSuccess = this.results.summary.failed === 0; if (overallSuccess) { this.log(' โœ… All test suites passed', 'green'); this.log(' ๐Ÿš€ Build system ready for deployment validation', 'green'); } else { this.log(' โŒ Some test suites failed', 'red'); this.log(' โš ๏ธ Review failures before deployment', 'red'); } // Recommendations this.log(`\n๐Ÿ’ก RECOMMENDATIONS`, 'cyan'); if (securitySuite && securitySuite.failed > 0) { this.log(' ๐Ÿ”’ Fix all security vulnerabilities before production', 'red'); } this.log(' ๐Ÿงช Run tests in staging environment before production deployment', 'white'); this.log(' ๐Ÿ“Š Monitor bundle performance in production', 'white'); this.log(' ๐Ÿ”„ Re-run tests after any build system changes', 'white'); // Save report to file const reportPath = `./test-results/build-system-report-${Date.now()}.json`; await fs.mkdir('./test-results', { recursive: true }); await fs.writeFile(reportPath, JSON.stringify(this.results, null, 2)); this.log(`\n๐Ÿ’พ Report saved: ${reportPath}`, 'cyan'); return overallSuccess; } /** * Run all test suites */ async run() { this.log('๐Ÿงช HVAC BUILD SYSTEM COMPREHENSIVE TEST SUITE', 'cyan'); this.log('โ•'.repeat(60), 'cyan'); // Prerequisites check const prereqs = await this.checkPrerequisites(); if (!prereqs.allPassed) { this.log('\nโš ๏ธ Some prerequisites failed - continuing with available tests', 'yellow'); } // Build validation const buildValidation = await this.runBuildValidation(); if (!buildValidation.success) { this.log('\nโŒ Build validation failed - some tests may be skipped', 'red'); } // Run test suites for (const suite of TEST_CONFIG.TEST_SUITES) { const result = await this.runTestSuite(suite); this.results.suites.push(result); // Update summary this.results.summary.total += 1; if (result.success) { this.results.summary.passed += 1; } else { this.results.summary.failed += 1; } } // Generate final report const overallSuccess = await this.generateReport(); // Exit with appropriate code process.exit(overallSuccess ? 0 : 1); } } // Run the test suite if (require.main === module) { const runner = new BuildSystemTestRunner(); runner.run().catch(error => { console.error('Fatal error:', error.message); process.exit(1); }); } module.exports = BuildSystemTestRunner;