upskill-event-manager/comprehensive-docker-validation.js
Ben c3e7fe9140 feat: comprehensive HVAC plugin development framework and modernization
## Major Enhancements

### 🏗️ Architecture & Infrastructure
- Implement comprehensive Docker testing infrastructure with hermetic environment
- Add Forgejo Actions CI/CD pipeline for automated deployments
- Create Page Object Model (POM) testing architecture reducing test duplication by 90%
- Establish security-first development patterns with input validation and output escaping

### 🧪 Testing Framework Modernization
- Migrate 146+ tests from 80 duplicate files to centralized architecture
- Add comprehensive E2E test suites for all user roles and workflows
- Implement WordPress error detection with automatic site health monitoring
- Create robust browser lifecycle management with proper cleanup

### 📚 Documentation & Guides
- Add comprehensive development best practices guide
- Create detailed administrator setup documentation
- Establish user guides for trainers and master trainers
- Document security incident reports and migration guides

### 🔧 Core Plugin Features
- Enhance trainer profile management with certification system
- Improve find trainer functionality with advanced filtering
- Strengthen master trainer area with content management
- Add comprehensive venue and organizer management

### 🛡️ Security & Reliability
- Implement security-first patterns throughout codebase
- Add comprehensive input validation and output escaping
- Create secure credential management system
- Establish proper WordPress role-based access control

### 🎯 WordPress Integration
- Strengthen singleton pattern implementation across all classes
- Enhance template hierarchy with proper WordPress integration
- Improve page manager with hierarchical URL structure
- Add comprehensive shortcode and menu system

### 🔍 Developer Experience
- Add extensive debugging and troubleshooting tools
- Create comprehensive test data seeding scripts
- Implement proper error handling and logging
- Establish consistent code patterns and standards

### 📊 Performance & Optimization
- Optimize database queries and caching strategies
- Improve asset loading and script management
- Enhance template rendering performance
- Streamline user experience across all interfaces

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

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

431 lines
No EOL
15 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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 };