/** * Phase 2A Comprehensive Test Suite * * Tests event templates, bulk operations, and form builder integration * Validates all Phase 2A functionality end-to-end * * @package HVAC_Community_Events * @since 3.1.0 (Phase 2A) */ const { chromium } = require('playwright'); const path = require('path'); const fs = require('fs'); class Phase2ATestSuite { constructor(options = {}) { this.baseUrl = options.baseUrl || 'http://localhost:8080'; this.headless = options.headless !== false; // Default to headless this.timeout = options.timeout || 30000; this.browser = null; this.context = null; this.page = null; this.testResults = []; this.startTime = Date.now(); } async initialize() { console.log('\n๐Ÿš€ Phase 2A Comprehensive Test Suite'); console.log('====================================='); console.log(`Base URL: ${this.baseUrl}`); console.log(`Headless: ${this.headless}`); console.log(`Timeout: ${this.timeout}ms\n`); // Launch browser this.browser = await chromium.launch({ headless: this.headless, timeout: this.timeout }); this.context = await this.browser.newContext({ viewport: { width: 1280, height: 720 } }); this.page = await this.context.newPage(); // Set default timeout this.page.setDefaultTimeout(this.timeout); // Listen for console messages this.page.on('console', (msg) => { if (msg.type() === 'error') { console.log(`๐Ÿ”ด Console Error: ${msg.text()}`); } }); // Check if WordPress is accessible await this.checkWordPressHealth(); } async checkWordPressHealth() { try { await this.page.goto(this.baseUrl); await this.page.waitForLoadState('networkidle'); // Check for critical WordPress errors const errorSigns = [ 'Fatal error', 'Parse error', 'Database connection error', 'The site is temporarily unavailable' ]; const content = await this.page.content(); for (const error of errorSigns) { if (content.includes(error)) { throw new Error(`WordPress site has critical errors: ${error}`); } } console.log('โœ… WordPress site is healthy and accessible'); } catch (error) { console.error('โŒ WordPress health check failed:', error.message); throw error; } } async runAllTests() { try { console.log('๐Ÿงช Starting Phase 2A Test Suite...\n'); // Test 1: Database Schema Validation await this.testDatabaseSchema(); // Test 2: Event Template CRUD Operations await this.testEventTemplateCRUD(); // Test 3: Form Builder Template Integration await this.testFormBuilderIntegration(); // Test 4: Bulk Event Creation await this.testBulkEventCreation(); // Test 5: Template Application to Events await this.testTemplateApplication(); // Test 6: User Permission Validation await this.testUserPermissions(); // Test 7: Asset Loading Verification await this.testAssetLoading(); // Test 8: AJAX Security Testing await this.testAJAXSecurity(); // Test 9: Performance Validation await this.testPerformance(); // Test 10: Error Handling await this.testErrorHandling(); console.log('\n๐ŸŽ‰ All Phase 2A tests completed!'); await this.generateTestReport(); } catch (error) { console.error('\nโŒ Test suite failed:', error.message); await this.logError('Test Suite Failure', error); throw error; } } async testDatabaseSchema() { console.log('๐Ÿ“Š Testing Database Schema...'); try { // Navigate to a page that would trigger table creation await this.loginAsTrainer(); await this.page.goto(`${this.baseUrl}/trainer/dashboard/`); await this.page.waitForLoadState('networkidle'); // Check for database error indicators const content = await this.page.content(); const dbErrors = [ 'Table doesn\'t exist', 'Unknown column', 'Syntax error', 'Database error' ]; for (const error of dbErrors) { if (content.includes(error)) { throw new Error(`Database schema issue: ${error}`); } } this.logSuccess('Database Schema', 'Tables created and accessible'); } catch (error) { this.logFailure('Database Schema', error.message); throw error; } } async testEventTemplateCRUD() { console.log('๐Ÿ“ Testing Event Template CRUD...'); try { await this.loginAsTrainer(); // Navigate to event creation form await this.page.goto(`${this.baseUrl}/trainer/create-event/`); await this.page.waitForSelector('form[data-template-enabled="1"]', { timeout: 10000 }); // Test template creation await this.fillEventForm({ title: 'Phase 2A Test Event', description: 'Testing Phase 2A template functionality', startDate: '2025-02-01T09:00', endDate: '2025-02-01T17:00' }); // Save as template await this.page.click('.hvac-save-template'); await this.page.waitForSelector('#hvac-save-template-modal'); await this.page.fill('#template-name', 'Phase 2A Test Template'); await this.page.fill('#template-description', 'Test template for Phase 2A validation'); await this.page.selectOption('#template-category', 'training'); await this.page.click('#hvac-save-template-form button[type="submit"]'); // Wait for success message await this.page.waitForSelector('.hvac-message-success', { timeout: 5000 }); // Test template loading await this.page.selectOption('.hvac-template-selector', { label: 'Phase 2A Test Template' }); await this.page.waitForSelector('.template-info', { timeout: 5000 }); const templateInfo = await this.page.textContent('.template-info'); if (!templateInfo.includes('Phase 2A Test Template')) { throw new Error('Template loading failed - template info not displayed'); } this.logSuccess('Event Template CRUD', 'Template creation and loading successful'); } catch (error) { this.logFailure('Event Template CRUD', error.message); throw error; } } async testFormBuilderIntegration() { console.log('๐Ÿ”ง Testing Form Builder Integration...'); try { await this.loginAsTrainer(); await this.page.goto(`${this.baseUrl}/trainer/create-event/`); // Check for template selector await this.page.waitForSelector('.hvac-template-selector'); // Check for template-enabled form await this.page.waitForSelector('form[data-template-enabled="1"]'); // Check for template actions await this.page.waitForSelector('.hvac-save-template'); // Test form field tracking await this.page.fill('input[name="event_title"]', 'Integration Test Event'); // Verify template selector updates const selectorExists = await this.page.isVisible('.hvac-template-selector'); if (!selectorExists) { throw new Error('Template selector not properly integrated'); } this.logSuccess('Form Builder Integration', 'Template integration functioning correctly'); } catch (error) { this.logFailure('Form Builder Integration', error.message); throw error; } } async testBulkEventCreation() { console.log('๐Ÿ“ฆ Testing Bulk Event Creation...'); try { await this.loginAsTrainer(); await this.page.goto(`${this.baseUrl}/trainer/dashboard/`); // Check if bulk operations UI exists const bulkButtonExists = await this.page.isVisible('.hvac-bulk-create-btn'); if (bulkButtonExists) { await this.page.click('.hvac-bulk-create-btn'); // Check for bulk variations modal await this.page.waitForSelector('#hvac-bulk-variations-modal'); // Add variation row await this.page.click('.hvac-add-variation'); // Fill variation data await this.page.fill('input[name="variations[1][event_title]"]', 'Bulk Test Event 1'); await this.page.fill('input[name="variations[1][event_start_date]"]', '2025-02-05T09:00'); await this.page.fill('input[name="variations[1][event_end_date]"]', '2025-02-05T17:00'); this.logSuccess('Bulk Event Creation', 'Bulk operations UI functional'); } else { this.logInfo('Bulk Event Creation', 'Bulk operations UI not present (may be context-dependent)'); } } catch (error) { this.logFailure('Bulk Event Creation', error.message); throw error; } } async testTemplateApplication() { console.log('๐ŸŽจ Testing Template Application...'); try { await this.loginAsTrainer(); // Navigate to events list (if available) const eventsListUrl = `${this.baseUrl}/trainer/events/`; try { await this.page.goto(eventsListUrl); // Check for template application UI const applyTemplateExists = await this.page.isVisible('.hvac-apply-template-bulk-btn'); if (applyTemplateExists) { this.logSuccess('Template Application', 'Template application UI is available'); } else { this.logInfo('Template Application', 'Template application UI context-dependent'); } } catch (error) { this.logInfo('Template Application', 'Events list page not accessible - skipping UI test'); } } catch (error) { this.logFailure('Template Application', error.message); throw error; } } async testUserPermissions() { console.log('๐Ÿ” Testing User Permissions...'); try { // Test trainer permissions await this.loginAsTrainer(); await this.page.goto(`${this.baseUrl}/trainer/create-event/`); const trainerAccess = await this.page.isVisible('form[data-template-enabled="1"]'); if (!trainerAccess) { throw new Error('Trainer cannot access template-enabled forms'); } // Test master trainer permissions (if available) await this.loginAsMasterTrainer(); await this.page.goto(`${this.baseUrl}/master-trainer/master-dashboard/`); this.logSuccess('User Permissions', 'User role access controls functioning'); } catch (error) { this.logFailure('User Permissions', error.message); throw error; } } async testAssetLoading() { console.log('๐Ÿ“ฆ Testing Asset Loading...'); try { await this.loginAsTrainer(); await this.page.goto(`${this.baseUrl}/trainer/create-event/`); // Check if CSS is loaded const templateStyles = await this.page.evaluate(() => { const stylesheets = Array.from(document.styleSheets); return stylesheets.some(sheet => { try { return sheet.href && ( sheet.href.includes('hvac-event-form-templates.css') || sheet.href.includes('hvac-bulk-operations.css') ); } catch (e) { return false; } }); }); // Check if JavaScript is loaded const templateScripts = await this.page.evaluate(() => { return typeof window.HVACEventTemplates !== 'undefined' || typeof window.HVACBulkOperations !== 'undefined'; }); if (!templateStyles && !templateScripts) { throw new Error('Phase 2A assets not properly loaded'); } this.logSuccess('Asset Loading', 'Phase 2A assets loaded correctly'); } catch (error) { this.logFailure('Asset Loading', error.message); throw error; } } async testAJAXSecurity() { console.log('๐Ÿ”’ Testing AJAX Security...'); try { await this.loginAsTrainer(); await this.page.goto(`${this.baseUrl}/trainer/create-event/`); // Test AJAX endpoint security without proper nonce const response = await this.page.evaluate(async () => { const response = await fetch('/wp-admin/admin-ajax.php', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: 'action=hvac_save_as_template&template_name=SecurityTest' }); return response.status; }); // Should return 200 but with error message (proper WordPress AJAX handling) if (response !== 200) { throw new Error('AJAX endpoint not responding properly'); } this.logSuccess('AJAX Security', 'AJAX endpoints properly secured'); } catch (error) { this.logFailure('AJAX Security', error.message); throw error; } } async testPerformance() { console.log('โšก Testing Performance...'); try { const startTime = Date.now(); await this.loginAsTrainer(); await this.page.goto(`${this.baseUrl}/trainer/create-event/`); await this.page.waitForSelector('form[data-template-enabled="1"]'); const loadTime = Date.now() - startTime; if (loadTime > 10000) { // 10 seconds threshold throw new Error(`Page load time too slow: ${loadTime}ms`); } this.logSuccess('Performance', `Page loaded in ${loadTime}ms`); } catch (error) { this.logFailure('Performance', error.message); throw error; } } async testErrorHandling() { console.log('โŒ Testing Error Handling...'); try { await this.loginAsTrainer(); await this.page.goto(`${this.baseUrl}/trainer/create-event/`); // Test invalid template selection const invalidTemplateExists = await this.page.isVisible('.hvac-template-selector option[value="999999"]'); if (!invalidTemplateExists) { // Add invalid option for testing await this.page.evaluate(() => { const select = document.querySelector('.hvac-template-selector'); if (select) { const option = document.createElement('option'); option.value = '999999'; option.textContent = 'Invalid Template'; select.appendChild(option); } }); } // Try to select invalid template await this.page.selectOption('.hvac-template-selector', '999999'); // Wait for error handling await this.page.waitForTimeout(2000); this.logSuccess('Error Handling', 'Error conditions handled gracefully'); } catch (error) { this.logFailure('Error Handling', error.message); throw error; } } // Helper methods async fillEventForm(data) { await this.page.fill('input[name="event_title"]', data.title); await this.page.fill('textarea[name="event_description"]', data.description); await this.page.fill('input[name="event_start_date"]', data.startDate); await this.page.fill('input[name="event_end_date"]', data.endDate); } async loginAsTrainer() { await this.page.goto(`${this.baseUrl}/wp-login.php`); await this.page.fill('#user_login', 'test_trainer'); await this.page.fill('#user_pass', 'trainer123'); await this.page.click('#wp-submit'); await this.page.waitForURL(/dashboard|admin/); } async loginAsMasterTrainer() { await this.page.goto(`${this.baseUrl}/wp-login.php`); await this.page.fill('#user_login', 'test_master'); await this.page.fill('#user_pass', 'master123'); await this.page.click('#wp-submit'); await this.page.waitForURL(/dashboard|admin/); } // Logging methods logSuccess(testName, message) { console.log(`โœ… ${testName}: ${message}`); this.testResults.push({ test: testName, status: 'PASS', message, timestamp: new Date().toISOString() }); } logFailure(testName, message) { console.log(`โŒ ${testName}: ${message}`); this.testResults.push({ test: testName, status: 'FAIL', message, timestamp: new Date().toISOString() }); } logInfo(testName, message) { console.log(`โ„น๏ธ ${testName}: ${message}`); this.testResults.push({ test: testName, status: 'INFO', message, timestamp: new Date().toISOString() }); } logError(testName, error) { console.log(`๐Ÿ”ด ${testName}: ${error.message}`); this.testResults.push({ test: testName, status: 'ERROR', message: error.message, stack: error.stack, timestamp: new Date().toISOString() }); } async generateTestReport() { const endTime = Date.now(); const duration = endTime - this.startTime; const report = { suite: 'Phase 2A Comprehensive Test Suite', version: '3.1.0', timestamp: new Date().toISOString(), duration: `${(duration / 1000).toFixed(2)}s`, baseUrl: this.baseUrl, summary: { total: this.testResults.length, passed: this.testResults.filter(r => r.status === 'PASS').length, failed: this.testResults.filter(r => r.status === 'FAIL').length, errors: this.testResults.filter(r => r.status === 'ERROR').length, info: this.testResults.filter(r => r.status === 'INFO').length }, results: this.testResults }; // Write report to file const reportPath = path.join(__dirname, `phase2a-test-report-${Date.now()}.json`); fs.writeFileSync(reportPath, JSON.stringify(report, null, 2)); console.log('\n๐Ÿ“‹ Test Report Summary:'); console.log('======================'); console.log(`โœ… Passed: ${report.summary.passed}`); console.log(`โŒ Failed: ${report.summary.failed}`); console.log(`๐Ÿ”ด Errors: ${report.summary.errors}`); console.log(`โ„น๏ธ Info: ${report.summary.info}`); console.log(`โฑ๏ธ Duration: ${report.duration}`); console.log(`๐Ÿ“„ Report saved: ${reportPath}`); return report; } async cleanup() { if (this.browser) { await this.browser.close(); } } } // Main execution async function main() { const testSuite = new Phase2ATestSuite({ baseUrl: process.env.BASE_URL || 'http://localhost:8080', headless: process.env.HEADLESS !== 'false', timeout: parseInt(process.env.TIMEOUT) || 30000 }); try { await testSuite.initialize(); await testSuite.runAllTests(); console.log('\n๐ŸŽฏ Phase 2A validation completed successfully!'); process.exit(0); } catch (error) { console.error('\n๐Ÿ’ฅ Phase 2A validation failed:', error.message); process.exit(1); } finally { await testSuite.cleanup(); } } // Run if called directly if (require.main === module) { main(); } module.exports = Phase2ATestSuite;