#!/usr/bin/env node /** * COMPREHENSIVE DOCKER VALIDATION TEST SUITE * * Tests all implemented features in the Docker environment: * - New trainer pages: venue management, organizer management, training leads * - Master trainer pages: Google Sheets, announcements, pending approvals, trainers * - Core functionality: authentication, dashboards, event management * - Layout fixes and navigation */ const { chromium } = require('playwright'); const fs = require('fs').promises; const path = require('path'); // Configuration const CONFIG = { baseUrl: 'http://localhost:8080', timeout: 30000, screenshotDir: path.join(__dirname, 'test-evidence', 'docker-validation'), reportFile: path.join(__dirname, 'test-evidence', 'docker-validation-report.json') }; // Test accounts (Docker environment defaults) const TEST_ACCOUNTS = { admin: { username: 'admin', password: 'admin', role: 'administrator' }, trainer: { username: 'test_trainer', password: 'TestTrainer123!', role: 'hvac_trainer' }, master: { username: 'test_master', password: 'TestMaster123!', role: 'hvac_master_trainer' } }; // URLs to validate const TEST_URLS = { // New trainer pages (claimed to be implemented) trainer: [ '/trainer/dashboard/', '/trainer/venue/list/', '/trainer/venue/manage/', '/trainer/organizer/manage/', '/trainer/profile/training-leads/' ], // Master trainer pages (claimed to be fixed) master: [ '/master-trainer/master-dashboard/', '/master-trainer/events/', '/master-trainer/google-sheets/', '/master-trainer/announcements/', '/master-trainer/pending-approvals/', '/master-trainer/trainers/', '/master-trainer/communication-templates/' ], // Core pages public: [ '/', '/wp-login.php', '/training-login/', '/find-trainer/' ] }; class DockerValidationTester { constructor() { this.browser = null; this.page = null; this.results = { timestamp: new Date().toISOString(), environment: 'docker-local', baseUrl: CONFIG.baseUrl, testResults: [], summary: { total: 0, passed: 0, failed: 0, errors: [] } }; } async initialize() { console.log('๐Ÿš€ Starting Docker Environment Validation...'); console.log('๐Ÿ“ Base URL:', CONFIG.baseUrl); // Ensure screenshot directory exists await fs.mkdir(CONFIG.screenshotDir, { recursive: true }); this.browser = await chromium.launch({ headless: true, timeout: CONFIG.timeout }); this.page = await this.browser.newPage(); this.page.setDefaultTimeout(CONFIG.timeout); // Set viewport for consistent screenshots await this.page.setViewportSize({ width: 1280, height: 720 }); } async cleanup() { if (this.browser) { await this.browser.close(); } } async takeScreenshot(name, description) { const filename = `${name.replace(/[^a-zA-Z0-9]/g, '-')}-${Date.now()}.png`; const filepath = path.join(CONFIG.screenshotDir, filename); await this.page.screenshot({ path: filepath, fullPage: true }); return { filename, filepath, description }; } async testUrl(url, expectedTitle = null, description = null) { const testName = `URL: ${url}`; const result = { test: testName, url: url, description: description || `Testing ${url}`, passed: false, error: null, screenshot: null, details: {} }; this.results.summary.total++; try { console.log(`\n๐Ÿงช Testing: ${url}`); const response = await this.page.goto(CONFIG.baseUrl + url, { waitUntil: 'networkidle', timeout: CONFIG.timeout }); const status = response.status(); const title = await this.page.title(); result.details.httpStatus = status; result.details.pageTitle = title; // Take screenshot for evidence result.screenshot = await this.takeScreenshot( url.replace(/\//g, '-') || 'homepage', `Screenshot of ${url}` ); // Check for WordPress errors const hasWordPressError = await this.page.evaluate(() => { return document.body.innerHTML.includes('Fatal error') || document.body.innerHTML.includes('Parse error') || document.body.innerHTML.includes('Warning:') || document.body.innerHTML.includes('Notice:'); }); // Check for HTTP errors if (status >= 400) { result.error = `HTTP ${status} error`; } else if (hasWordPressError) { result.error = 'WordPress PHP error detected'; } else if (expectedTitle && !title.includes(expectedTitle)) { result.error = `Title mismatch. Expected: ${expectedTitle}, Got: ${title}`; } else { result.passed = true; this.results.summary.passed++; console.log(`โœ… ${url} - ${title}`); } } catch (error) { result.error = error.message; result.screenshot = await this.takeScreenshot( `error-${url.replace(/\//g, '-')}`, `Error screenshot for ${url}` ); console.log(`โŒ ${url} - ${error.message}`); } if (!result.passed) { this.results.summary.failed++; this.results.summary.errors.push({ url: url, error: result.error }); } this.results.testResults.push(result); return result; } async testAuthentication(account) { console.log(`\n๐Ÿ” Testing authentication for ${account.role}...`); try { // Go to wp-login.php await this.page.goto(CONFIG.baseUrl + '/wp-login.php'); // Fill login form await this.page.fill('#user_login', account.username); await this.page.fill('#user_pass', account.password); // Take screenshot before login await this.takeScreenshot(`login-form-${account.role}`, `Login form for ${account.role}`); // Click login await this.page.click('#wp-submit'); // Wait for navigation await this.page.waitForLoadState('networkidle'); // Check if logged in successfully const currentUrl = this.page.url(); const title = await this.page.title(); // Take screenshot after login await this.takeScreenshot(`after-login-${account.role}`, `After login for ${account.role}`); if (currentUrl.includes('wp-admin') || title.includes('Dashboard')) { console.log(`โœ… Authentication successful for ${account.role}`); return true; } else { console.log(`โŒ Authentication failed for ${account.role} - redirected to ${currentUrl}`); return false; } } catch (error) { console.log(`โŒ Authentication error for ${account.role}: ${error.message}`); return false; } } async testWordPressHealth() { console.log('\n๐Ÿฅ Testing WordPress Health...'); const healthResult = { test: 'WordPress Health Check', passed: false, details: {} }; try { // Test WordPress admin access await this.page.goto(CONFIG.baseUrl + '/wp-admin/', { timeout: 10000 }); const title = await this.page.title(); const hasLoginForm = await this.page.locator('#loginform').isVisible().catch(() => false); const hasAdminBar = await this.page.locator('#wpadminbar').isVisible().catch(() => false); healthResult.details.adminPageTitle = title; healthResult.details.hasLoginForm = hasLoginForm; healthResult.details.hasAdminBar = hasAdminBar; // Take screenshot healthResult.screenshot = await this.takeScreenshot('wp-health-check', 'WordPress health check'); // WordPress is healthy if we get login form or admin bar if (hasLoginForm || hasAdminBar || title.includes('WordPress')) { healthResult.passed = true; console.log('โœ… WordPress is responding correctly'); } else { console.log('โš ๏ธ WordPress health check inconclusive'); } } catch (error) { healthResult.error = error.message; console.log(`โŒ WordPress health check failed: ${error.message}`); } this.results.testResults.push(healthResult); return healthResult; } async runComprehensiveValidation() { try { await this.initialize(); // 1. WordPress Health Check await this.testWordPressHealth(); // 2. Test Public URLs console.log('\n๐Ÿ“„ Testing Public Pages...'); for (const url of TEST_URLS.public) { await this.testUrl(url); } // 3. Test Authentication console.log('\n๐Ÿ” Testing Authentication...'); const authResults = {}; for (const [role, account] of Object.entries(TEST_ACCOUNTS)) { authResults[role] = await this.testAuthentication(account); } // 4. Test Protected URLs (without authentication for now) console.log('\n๐Ÿ”’ Testing Protected Pages (access control)...'); // Test trainer pages console.log('\n๐Ÿ‘จโ€๐Ÿ”ง Testing New Trainer Pages...'); for (const url of TEST_URLS.trainer) { await this.testUrl(url, null, `New trainer page: ${url}`); } // Test master trainer pages console.log('\n๐Ÿ‘จโ€๐Ÿ’ผ Testing Master Trainer Pages...'); for (const url of TEST_URLS.master) { await this.testUrl(url, null, `Master trainer page: ${url}`); } // 5. Generate summary this.generateSummary(); } catch (error) { console.error('โŒ Critical test failure:', error); this.results.summary.errors.push({ type: 'critical', error: error.message }); } finally { await this.cleanup(); } } generateSummary() { console.log('\n๐Ÿ“Š TEST SUMMARY'); console.log('================'); console.log(`Total Tests: ${this.results.summary.total}`); console.log(`Passed: ${this.results.summary.passed}`); console.log(`Failed: ${this.results.summary.failed}`); console.log(`Success Rate: ${((this.results.summary.passed / this.results.summary.total) * 100).toFixed(1)}%`); if (this.results.summary.errors.length > 0) { console.log('\nโŒ ERRORS:'); this.results.summary.errors.forEach(error => { console.log(` โ€ข ${error.url || error.type}: ${error.error}`); }); } // Key findings console.log('\n๐Ÿ” KEY FINDINGS:'); const newTrainerPages = this.results.testResults.filter(r => r.url && TEST_URLS.trainer.includes(r.url) ); const newTrainerPagesWorking = newTrainerPages.filter(r => r.passed).length; console.log(` โ€ข New Trainer Pages: ${newTrainerPagesWorking}/${newTrainerPages.length} working`); const masterPages = this.results.testResults.filter(r => r.url && TEST_URLS.master.includes(r.url) ); const masterPagesWorking = masterPages.filter(r => r.passed).length; console.log(` โ€ข Master Trainer Pages: ${masterPagesWorking}/${masterPages.length} working`); const publicPages = this.results.testResults.filter(r => r.url && TEST_URLS.public.includes(r.url) ); const publicPagesWorking = publicPages.filter(r => r.passed).length; console.log(` โ€ข Public Pages: ${publicPagesWorking}/${publicPages.length} working`); // Recommendations console.log('\n๐Ÿ’ก RECOMMENDATIONS:'); if (this.results.summary.failed > 0) { console.log(' โ€ข Review failed tests and fix underlying issues'); console.log(' โ€ข Check WordPress error logs for PHP errors'); console.log(' โ€ข Verify plugin activation and configuration'); } if (newTrainerPagesWorking === newTrainerPages.length) { console.log(' โœ… All new trainer pages are accessible'); } else { console.log(' โš ๏ธ Some new trainer pages need attention'); } if (masterPagesWorking === masterPages.length) { console.log(' โœ… All master trainer pages are accessible'); } else { console.log(' โš ๏ธ Some master trainer pages need attention'); } console.log(`\n๐Ÿ“ธ Screenshots saved to: ${CONFIG.screenshotDir}`); console.log(`๐Ÿ“‹ Full report will be saved to: ${CONFIG.reportFile}`); } async saveReport() { try { await fs.writeFile( CONFIG.reportFile, JSON.stringify(this.results, null, 2) ); console.log(`\nโœ… Full report saved: ${CONFIG.reportFile}`); } catch (error) { console.error('โŒ Failed to save report:', error.message); } } } // Run the validation async function main() { const tester = new DockerValidationTester(); try { await tester.runComprehensiveValidation(); await tester.saveReport(); // Exit with appropriate code const success = tester.results.summary.failed === 0; process.exit(success ? 0 : 1); } catch (error) { console.error('โŒ Test suite failed:', error); process.exit(1); } } // Run if called directly if (require.main === module) { main().catch(console.error); } module.exports = { DockerValidationTester };