## 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>
431 lines
No EOL
15 KiB
JavaScript
431 lines
No EOL
15 KiB
JavaScript
#!/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 }; |