## 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>
414 lines
No EOL
15 KiB
JavaScript
414 lines
No EOL
15 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
/**
|
|
* COMPREHENSIVE VALIDATION TEST SUITE
|
|
*
|
|
* Tests all claimed fixes in the HVAC Community Events WordPress plugin
|
|
* - Missing trainer pages implementation
|
|
* - Master trainer layout fixes
|
|
* - Security fixes validation
|
|
* - Authentication and CRUD operations
|
|
*/
|
|
|
|
const { chromium } = require('playwright');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
// Import WordPress error detector
|
|
const WordPressErrorDetector = require(path.join(__dirname, 'tests', 'framework', 'utils', 'WordPressErrorDetector'));
|
|
|
|
// Test configuration
|
|
const BASE_URL = 'https://upskill-staging.measurequick.com';
|
|
const SCREENSHOTS_DIR = path.join(__dirname, 'test-evidence');
|
|
|
|
// Test credentials (if available)
|
|
const TEST_CREDENTIALS = {
|
|
trainer: {
|
|
username: process.env.TRAINER_USERNAME || 'test-trainer',
|
|
password: process.env.TRAINER_PASSWORD || 'test-password'
|
|
},
|
|
master: {
|
|
username: process.env.MASTER_USERNAME || 'test-master',
|
|
password: process.env.MASTER_PASSWORD || 'test-password'
|
|
}
|
|
};
|
|
|
|
// Test URLs claimed to be implemented
|
|
const TRAINER_PAGES = [
|
|
'/trainer/venue/list/',
|
|
'/trainer/venue/manage/',
|
|
'/trainer/organizer/manage/',
|
|
'/trainer/profile/training-leads/'
|
|
];
|
|
|
|
const MASTER_TRAINER_PAGES = [
|
|
'/master-trainer/google-sheets/',
|
|
'/master-trainer/announcements/',
|
|
'/master-trainer/pending-approvals/',
|
|
'/master-trainer/trainers/'
|
|
];
|
|
|
|
class ComprehensiveValidator {
|
|
constructor() {
|
|
this.browser = null;
|
|
this.page = null;
|
|
this.results = {
|
|
trainerPages: [],
|
|
masterPages: [],
|
|
security: [],
|
|
overall: {
|
|
passed: 0,
|
|
failed: 0,
|
|
errors: []
|
|
}
|
|
};
|
|
}
|
|
|
|
async init() {
|
|
// Create screenshots directory
|
|
if (!fs.existsSync(SCREENSHOTS_DIR)) {
|
|
fs.mkdirSync(SCREENSHOTS_DIR, { recursive: true });
|
|
}
|
|
|
|
// Launch browser
|
|
this.browser = await chromium.launch({
|
|
headless: true, // Headless mode for server environment
|
|
args: ['--no-sandbox', '--disable-dev-shm-usage']
|
|
});
|
|
this.page = await this.browser.newPage();
|
|
|
|
// Set viewport for consistent screenshots
|
|
await this.page.setViewportSize({ width: 1920, height: 1080 });
|
|
|
|
// Listen for console messages
|
|
this.page.on('console', msg => {
|
|
if (msg.type() === 'error') {
|
|
console.log('🔥 Console Error:', msg.text());
|
|
this.results.overall.errors.push(`Console Error: ${msg.text()}`);
|
|
}
|
|
});
|
|
}
|
|
|
|
async takeScreenshot(name) {
|
|
const filename = `${name}-${Date.now()}.png`;
|
|
const filepath = path.join(SCREENSHOTS_DIR, filename);
|
|
await this.page.screenshot({ path: filepath, fullPage: true });
|
|
console.log(`📸 Screenshot saved: ${filename}`);
|
|
return filename;
|
|
}
|
|
|
|
async testPageExists(url, pageName) {
|
|
console.log(`\n🧪 Testing: ${pageName}`);
|
|
console.log(` URL: ${BASE_URL}${url}`);
|
|
|
|
const result = {
|
|
url,
|
|
name: pageName,
|
|
exists: false,
|
|
authenticated: false,
|
|
functional: false,
|
|
errors: [],
|
|
screenshot: null
|
|
};
|
|
|
|
try {
|
|
const response = await this.page.goto(`${BASE_URL}${url}`, {
|
|
waitUntil: 'networkidle',
|
|
timeout: 30000
|
|
});
|
|
|
|
result.statusCode = response.status();
|
|
result.screenshot = await this.takeScreenshot(`${pageName.replace(/\s+/g, '-').toLowerCase()}`);
|
|
|
|
// Check if page actually loaded (not 404 or redirect)
|
|
if (response.status() === 200) {
|
|
result.exists = true;
|
|
|
|
// Check for WordPress login redirect
|
|
const currentUrl = this.page.url();
|
|
if (currentUrl.includes('wp-login') || currentUrl.includes('login')) {
|
|
result.authenticated = false;
|
|
result.errors.push('Page requires authentication - redirected to login');
|
|
} else {
|
|
result.authenticated = true;
|
|
|
|
// Check for actual content (not just empty page)
|
|
const bodyText = await this.page.textContent('body');
|
|
const hasContent = bodyText && bodyText.trim().length > 100;
|
|
|
|
if (hasContent) {
|
|
result.functional = true;
|
|
console.log(` ✅ PASS: Page loads with content`);
|
|
} else {
|
|
result.functional = false;
|
|
result.errors.push('Page exists but appears empty or minimal');
|
|
console.log(` ❌ FAIL: Page exists but no substantial content`);
|
|
}
|
|
}
|
|
} else if (response.status() === 404) {
|
|
result.errors.push(`Page returns 404 - Not Found`);
|
|
console.log(` ❌ FAIL: 404 - Page does not exist`);
|
|
} else {
|
|
result.errors.push(`Unexpected status code: ${response.status()}`);
|
|
console.log(` ⚠️ WARN: Status ${response.status()}`);
|
|
}
|
|
|
|
} catch (error) {
|
|
result.errors.push(`Navigation error: ${error.message}`);
|
|
console.log(` 💥 ERROR: ${error.message}`);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
async testLayoutAndResponsive(url, pageName) {
|
|
console.log(`\n🎨 Testing Layout: ${pageName}`);
|
|
|
|
const result = {
|
|
url,
|
|
name: pageName,
|
|
singleColumn: false,
|
|
hasNavigation: false,
|
|
responsive: false,
|
|
errors: []
|
|
};
|
|
|
|
try {
|
|
await this.page.goto(`${BASE_URL}${url}`, { waitUntil: 'networkidle' });
|
|
|
|
// Check for single column layout (look for main content container)
|
|
const mainContent = await this.page.$('.hvac-single-column, .single-column, main, .main-content');
|
|
if (mainContent) {
|
|
result.singleColumn = true;
|
|
console.log(` ✅ Single column layout detected`);
|
|
} else {
|
|
result.errors.push('Single column layout not detected');
|
|
console.log(` ❌ Single column layout not found`);
|
|
}
|
|
|
|
// Check for navigation/breadcrumbs
|
|
const navigation = await this.page.$('.breadcrumb, .navigation, nav, .hvac-navigation');
|
|
if (navigation) {
|
|
result.hasNavigation = true;
|
|
console.log(` ✅ Navigation found`);
|
|
} else {
|
|
result.errors.push('Navigation/breadcrumbs not found');
|
|
console.log(` ❌ Navigation not found`);
|
|
}
|
|
|
|
// Test responsive design (mobile viewport)
|
|
await this.page.setViewportSize({ width: 375, height: 667 }); // iPhone size
|
|
await this.page.waitForTimeout(1000);
|
|
|
|
const mobileScreenshot = await this.takeScreenshot(`${pageName.replace(/\s+/g, '-').toLowerCase()}-mobile`);
|
|
|
|
// Check if layout adapts to mobile (simplified check)
|
|
const bodyWidth = await this.page.evaluate(() => document.body.scrollWidth);
|
|
if (bodyWidth <= 400) { // Reasonable mobile width
|
|
result.responsive = true;
|
|
console.log(` ✅ Responsive design working`);
|
|
} else {
|
|
result.errors.push('Layout may not be mobile responsive');
|
|
console.log(` ⚠️ Layout width: ${bodyWidth}px (may not be responsive)`);
|
|
}
|
|
|
|
// Reset viewport
|
|
await this.page.setViewportSize({ width: 1920, height: 1080 });
|
|
|
|
} catch (error) {
|
|
result.errors.push(`Layout test error: ${error.message}`);
|
|
console.log(` 💥 ERROR: ${error.message}`);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
async testSecurityAndAJAX() {
|
|
console.log(`\n🔒 Testing Security & AJAX Endpoints`);
|
|
|
|
const securityResults = [];
|
|
|
|
// Test unauthenticated access to master trainer AJAX endpoints
|
|
const ajaxEndpoints = [
|
|
'/wp-admin/admin-ajax.php?action=hvac_get_trainer_stats',
|
|
'/wp-admin/admin-ajax.php?action=hvac_manage_announcement',
|
|
'/wp-admin/admin-ajax.php?action=hvac_approve_trainer'
|
|
];
|
|
|
|
for (const endpoint of ajaxEndpoints) {
|
|
const result = {
|
|
endpoint,
|
|
secure: false,
|
|
errors: []
|
|
};
|
|
|
|
try {
|
|
const response = await this.page.goto(`${BASE_URL}${endpoint}`);
|
|
const responseText = await this.page.textContent('body');
|
|
|
|
if (response.status() === 403 || responseText.includes('Authentication required') || responseText.includes('Access denied')) {
|
|
result.secure = true;
|
|
console.log(` ✅ ${endpoint} properly secured`);
|
|
} else {
|
|
result.secure = false;
|
|
result.errors.push(`Endpoint may be accessible without authentication`);
|
|
console.log(` ❌ ${endpoint} may not be properly secured`);
|
|
}
|
|
|
|
} catch (error) {
|
|
result.errors.push(`Security test error: ${error.message}`);
|
|
console.log(` 💥 ERROR testing ${endpoint}: ${error.message}`);
|
|
}
|
|
|
|
securityResults.push(result);
|
|
}
|
|
|
|
return securityResults;
|
|
}
|
|
|
|
async runComprehensiveTests() {
|
|
console.log('🚀 Starting Comprehensive Validation Tests');
|
|
console.log('=' * 60);
|
|
|
|
// Test trainer pages
|
|
console.log('\n📋 TESTING TRAINER PAGES');
|
|
console.log('-' * 30);
|
|
for (const url of TRAINER_PAGES) {
|
|
const pageName = url.split('/').filter(Boolean).join(' ').toUpperCase();
|
|
const result = await this.testPageExists(url, pageName);
|
|
this.results.trainerPages.push(result);
|
|
|
|
if (result.functional) {
|
|
this.results.overall.passed++;
|
|
} else {
|
|
this.results.overall.failed++;
|
|
}
|
|
}
|
|
|
|
// Test master trainer pages with layout validation
|
|
console.log('\n👑 TESTING MASTER TRAINER PAGES');
|
|
console.log('-' * 35);
|
|
for (const url of MASTER_TRAINER_PAGES) {
|
|
const pageName = url.split('/').filter(Boolean).join(' ').toUpperCase();
|
|
|
|
// Test existence first
|
|
const existsResult = await this.testPageExists(url, pageName);
|
|
this.results.masterPages.push(existsResult);
|
|
|
|
if (existsResult.functional) {
|
|
this.results.overall.passed++;
|
|
|
|
// Test layout if page is functional
|
|
const layoutResult = await this.testLayoutAndResponsive(url, pageName);
|
|
existsResult.layout = layoutResult;
|
|
} else {
|
|
this.results.overall.failed++;
|
|
}
|
|
}
|
|
|
|
// Test security
|
|
console.log('\n🔒 TESTING SECURITY FIXES');
|
|
console.log('-' * 25);
|
|
this.results.security = await this.testSecurityAndAJAX();
|
|
|
|
await this.generateReport();
|
|
}
|
|
|
|
async generateReport() {
|
|
console.log('\n📊 GENERATING COMPREHENSIVE TEST REPORT');
|
|
console.log('=' * 50);
|
|
|
|
const report = {
|
|
timestamp: new Date().toISOString(),
|
|
summary: {
|
|
totalTests: this.results.trainerPages.length + this.results.masterPages.length,
|
|
passed: this.results.overall.passed,
|
|
failed: this.results.overall.failed,
|
|
successRate: ((this.results.overall.passed / (this.results.overall.passed + this.results.overall.failed)) * 100).toFixed(1)
|
|
},
|
|
trainerPages: this.results.trainerPages,
|
|
masterPages: this.results.masterPages,
|
|
security: this.results.security,
|
|
errors: this.results.overall.errors,
|
|
evidenceLocation: SCREENSHOTS_DIR
|
|
};
|
|
|
|
// Save detailed JSON report
|
|
const reportPath = path.join(__dirname, 'validation-report.json');
|
|
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
|
|
|
|
// Generate human-readable summary
|
|
console.log('\n🎯 VALIDATION RESULTS SUMMARY');
|
|
console.log('=' * 35);
|
|
console.log(`Total Tests: ${report.summary.totalTests}`);
|
|
console.log(`Passed: ${report.summary.passed}`);
|
|
console.log(`Failed: ${report.summary.failed}`);
|
|
console.log(`Success Rate: ${report.summary.successRate}%`);
|
|
|
|
console.log('\n📋 TRAINER PAGES RESULTS:');
|
|
this.results.trainerPages.forEach(page => {
|
|
const status = page.functional ? '✅ PASS' : '❌ FAIL';
|
|
console.log(` ${status} ${page.name}: ${page.url}`);
|
|
if (page.errors.length > 0) {
|
|
page.errors.forEach(error => console.log(` ⚠️ ${error}`));
|
|
}
|
|
});
|
|
|
|
console.log('\n👑 MASTER TRAINER PAGES RESULTS:');
|
|
this.results.masterPages.forEach(page => {
|
|
const status = page.functional ? '✅ PASS' : '❌ FAIL';
|
|
console.log(` ${status} ${page.name}: ${page.url}`);
|
|
if (page.layout) {
|
|
console.log(` Layout: ${page.layout.singleColumn ? '✅' : '❌'} Single Column, ${page.layout.hasNavigation ? '✅' : '❌'} Navigation, ${page.layout.responsive ? '✅' : '❌'} Responsive`);
|
|
}
|
|
if (page.errors.length > 0) {
|
|
page.errors.forEach(error => console.log(` ⚠️ ${error}`));
|
|
}
|
|
});
|
|
|
|
console.log('\n🔒 SECURITY RESULTS:');
|
|
this.results.security.forEach(endpoint => {
|
|
const status = endpoint.secure ? '✅ SECURE' : '❌ INSECURE';
|
|
console.log(` ${status} ${endpoint.endpoint}`);
|
|
});
|
|
|
|
if (this.results.overall.errors.length > 0) {
|
|
console.log('\n💥 CONSOLE ERRORS DETECTED:');
|
|
this.results.overall.errors.forEach(error => console.log(` ⚠️ ${error}`));
|
|
}
|
|
|
|
console.log(`\n📸 Evidence saved to: ${SCREENSHOTS_DIR}`);
|
|
console.log(`📄 Detailed report saved to: ${reportPath}`);
|
|
|
|
return report;
|
|
}
|
|
|
|
async cleanup() {
|
|
if (this.browser) {
|
|
await this.browser.close();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Main execution
|
|
async function main() {
|
|
const validator = new ComprehensiveValidator();
|
|
|
|
try {
|
|
await validator.init();
|
|
await validator.runComprehensiveTests();
|
|
|
|
} catch (error) {
|
|
console.error('💥 Test execution failed:', error);
|
|
process.exit(1);
|
|
} finally {
|
|
await validator.cleanup();
|
|
}
|
|
}
|
|
|
|
// Run if called directly
|
|
if (require.main === module) {
|
|
main().catch(console.error);
|
|
}
|
|
|
|
module.exports = { ComprehensiveValidator }; |