## 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>
464 lines
No EOL
18 KiB
JavaScript
464 lines
No EOL
18 KiB
JavaScript
const { chromium } = require('playwright');
|
||
const path = require('path');
|
||
|
||
// Import WordPress error detector
|
||
const WordPressErrorDetector = require(path.join(__dirname, 'tests', 'framework', 'utils', 'WordPressErrorDetector'));
|
||
|
||
/**
|
||
* HVAC Master Trainer - Comprehensive E2E Test Suite
|
||
* Tests all Master Trainer functionality on staging environment
|
||
*
|
||
* Test Coverage:
|
||
* - Master Dashboard
|
||
* - Events Overview
|
||
* - Import/Export Data
|
||
* - Announcements
|
||
* - Pending Approvals
|
||
* - Communication Templates
|
||
* - Trainer Management
|
||
* - Profile Editing
|
||
*
|
||
* Features WordPress error detection to prevent testing against broken sites
|
||
*/
|
||
|
||
// Configuration
|
||
const CONFIG = {
|
||
baseUrl: 'https://upskill-staging.measurequick.com',
|
||
headless: false, // Set to false to see browser
|
||
slowMo: 500, // Slow down for visibility
|
||
timeout: 30000,
|
||
viewport: { width: 1280, height: 720 }
|
||
};
|
||
|
||
// Test Accounts
|
||
const ACCOUNTS = {
|
||
master: {
|
||
username: 'test_master',
|
||
password: 'TestMaster123!',
|
||
email: 'test_master@example.com'
|
||
},
|
||
masterAlt: {
|
||
username: 'JoeMedosch@gmail.com',
|
||
password: 'JoeTrainer2025@',
|
||
email: 'JoeMedosch@gmail.com'
|
||
},
|
||
regularTrainer: {
|
||
username: 'test_trainer',
|
||
password: 'TestTrainer123!',
|
||
email: 'test_trainer@example.com'
|
||
}
|
||
};
|
||
|
||
// Test Results Tracking
|
||
class TestResults {
|
||
constructor() {
|
||
this.results = [];
|
||
this.startTime = Date.now();
|
||
}
|
||
|
||
addResult(category, test, status, details = '') {
|
||
this.results.push({
|
||
category,
|
||
test,
|
||
status,
|
||
details,
|
||
timestamp: new Date().toISOString()
|
||
});
|
||
|
||
const icon = status === 'PASSED' ? '✅' : '❌';
|
||
console.log(`${icon} ${category} - ${test}`);
|
||
if (details) console.log(` ${details}`);
|
||
}
|
||
|
||
printSummary() {
|
||
const duration = ((Date.now() - this.startTime) / 1000).toFixed(2);
|
||
const passed = this.results.filter(r => r.status === 'PASSED').length;
|
||
const failed = this.results.filter(r => r.status === 'FAILED').length;
|
||
const total = this.results.length;
|
||
|
||
console.log('\n' + '='.repeat(60));
|
||
console.log('📊 TEST SUMMARY');
|
||
console.log('='.repeat(60));
|
||
console.log(`Total Tests: ${total}`);
|
||
console.log(`✅ Passed: ${passed}`);
|
||
console.log(`❌ Failed: ${failed}`);
|
||
console.log(`⏱️ Duration: ${duration}s`);
|
||
console.log(`📈 Success Rate: ${((passed/total)*100).toFixed(1)}%`);
|
||
|
||
if (failed > 0) {
|
||
console.log('\n❌ FAILED TESTS:');
|
||
this.results
|
||
.filter(r => r.status === 'FAILED')
|
||
.forEach(r => console.log(` - ${r.category}: ${r.test}`));
|
||
}
|
||
}
|
||
|
||
exportResults() {
|
||
const filename = `master-trainer-test-results-${Date.now()}.json`;
|
||
require('fs').writeFileSync(filename, JSON.stringify(this.results, null, 2));
|
||
console.log(`\n📁 Results exported to ${filename}`);
|
||
}
|
||
}
|
||
|
||
// Main Test Suite
|
||
async function runMasterTrainerTests() {
|
||
console.log('🏁 HVAC Master Trainer - Comprehensive E2E Test Suite\n');
|
||
console.log('🚀 Initializing Master Trainer Test Suite');
|
||
console.log(`📍 Target: ${CONFIG.baseUrl}`);
|
||
console.log(`🖥️ Mode: ${CONFIG.headless ? 'Headless' : 'Headed'}`);
|
||
console.log('='.repeat(60) + '\n');
|
||
|
||
const results = new TestResults();
|
||
const browser = await chromium.launch({
|
||
headless: CONFIG.headless,
|
||
slowMo: CONFIG.slowMo
|
||
});
|
||
|
||
const context = await browser.newContext({
|
||
viewport: CONFIG.viewport
|
||
});
|
||
const page = await context.newPage();
|
||
page.setDefaultTimeout(CONFIG.timeout);
|
||
|
||
try {
|
||
// CRITICAL: Check for WordPress errors before testing
|
||
console.log('\n🔍 Checking WordPress Site Health');
|
||
console.log('-'.repeat(40));
|
||
|
||
await page.goto(CONFIG.baseUrl);
|
||
await page.waitForLoadState('networkidle');
|
||
|
||
const errorDetector = new WordPressErrorDetector(page);
|
||
const errorReport = await errorDetector.getErrorReport();
|
||
|
||
if (errorReport.hasErrors && errorReport.blockingErrors.length > 0) {
|
||
console.log('❌ CRITICAL WordPress Errors Detected:');
|
||
errorReport.blockingErrors.forEach(error => {
|
||
console.log(` - ${error.type}: ${error.message}`);
|
||
});
|
||
throw new Error(`WordPress site has critical errors that block testing. Restore from production and re-seed test data.`);
|
||
}
|
||
|
||
console.log('✅ WordPress site health check passed');
|
||
|
||
// 1. LOGIN AS MASTER TRAINER
|
||
console.log('\n🔐 Testing Master Trainer Login');
|
||
console.log('-'.repeat(40));
|
||
|
||
await page.goto(`${CONFIG.baseUrl}/training-login/`);
|
||
await page.waitForLoadState('networkidle');
|
||
|
||
try {
|
||
await page.fill('#username', ACCOUNTS.master.username);
|
||
await page.fill('#password', ACCOUNTS.master.password);
|
||
await page.click('button[type="submit"]');
|
||
await page.waitForURL('**/master-trainer/master-dashboard/**', { timeout: 10000 });
|
||
results.addResult('Authentication', 'Master Trainer Login', 'PASSED',
|
||
`Redirected to: ${page.url()}`);
|
||
} catch (error) {
|
||
// Try alternative master account
|
||
await page.goto(`${CONFIG.baseUrl}/training-login/`);
|
||
await page.fill('#username', ACCOUNTS.masterAlt.username);
|
||
await page.fill('#password', ACCOUNTS.masterAlt.password);
|
||
await page.click('button[type="submit"]');
|
||
await page.waitForURL('**/master-trainer/**', { timeout: 10000 });
|
||
results.addResult('Authentication', 'Master Trainer Login', 'PASSED',
|
||
`Used alternative account: ${ACCOUNTS.masterAlt.username}`);
|
||
}
|
||
|
||
// 2. MASTER DASHBOARD
|
||
console.log('\n📊 Testing Master Dashboard');
|
||
console.log('-'.repeat(40));
|
||
|
||
await page.goto(`${CONFIG.baseUrl}/master-trainer/master-dashboard/`);
|
||
await page.waitForLoadState('networkidle');
|
||
|
||
// Check dashboard elements
|
||
const dashboardTests = [
|
||
{ selector: '.hvac-master-dashboard', name: 'Dashboard Container' },
|
||
{ selector: '.dashboard-stats', name: 'Statistics Section' },
|
||
{ selector: '.trainer-count', name: 'Trainer Count' },
|
||
{ selector: '.event-count', name: 'Event Count' },
|
||
{ selector: '.recent-activity', name: 'Recent Activity' }
|
||
];
|
||
|
||
for (const test of dashboardTests) {
|
||
try {
|
||
await page.waitForSelector(test.selector, { timeout: 5000 });
|
||
results.addResult('Master Dashboard', test.name, 'PASSED');
|
||
} catch {
|
||
results.addResult('Master Dashboard', test.name, 'FAILED');
|
||
}
|
||
}
|
||
|
||
// Get statistics
|
||
try {
|
||
const stats = await page.evaluate(() => {
|
||
const trainerCount = document.querySelector('.trainer-count')?.textContent;
|
||
const eventCount = document.querySelector('.event-count')?.textContent;
|
||
return { trainers: trainerCount, events: eventCount };
|
||
});
|
||
results.addResult('Master Dashboard', 'Statistics Display', 'PASSED',
|
||
`Trainers: ${stats.trainers}, Events: ${stats.events}`);
|
||
} catch {
|
||
results.addResult('Master Dashboard', 'Statistics Display', 'FAILED');
|
||
}
|
||
|
||
// 3. EVENTS OVERVIEW
|
||
console.log('\n📅 Testing Events Overview');
|
||
console.log('-'.repeat(40));
|
||
|
||
await page.goto(`${CONFIG.baseUrl}/master-trainer/events/`);
|
||
await page.waitForLoadState('networkidle');
|
||
|
||
try {
|
||
await page.waitForSelector('.master-events-overview', { timeout: 5000 });
|
||
results.addResult('Events Overview', 'Page Load', 'PASSED');
|
||
|
||
// Check for KPI dashboard
|
||
const hasKPI = await page.isVisible('.kpi-dashboard');
|
||
results.addResult('Events Overview', 'KPI Dashboard',
|
||
hasKPI ? 'PASSED' : 'FAILED');
|
||
|
||
// Check for filtering options
|
||
const hasFilters = await page.isVisible('.event-filters');
|
||
results.addResult('Events Overview', 'Event Filters',
|
||
hasFilters ? 'PASSED' : 'FAILED');
|
||
} catch (error) {
|
||
results.addResult('Events Overview', 'Page Load', 'FAILED', error.message);
|
||
}
|
||
|
||
// 4. IMPORT/EXPORT DATA
|
||
console.log('\n📤 Testing Import/Export Data');
|
||
console.log('-'.repeat(40));
|
||
|
||
await page.goto(`${CONFIG.baseUrl}/master-trainer/import-export/`);
|
||
await page.waitForLoadState('networkidle');
|
||
|
||
try {
|
||
await page.waitForSelector('.import-export-management', { timeout: 5000 });
|
||
results.addResult('Import/Export', 'Page Load', 'PASSED');
|
||
|
||
// Check for CSV operations
|
||
const hasCSVImport = await page.isVisible('.csv-import-section');
|
||
results.addResult('Import/Export', 'CSV Import Section',
|
||
hasCSVImport ? 'PASSED' : 'FAILED');
|
||
|
||
const hasCSVExport = await page.isVisible('.csv-export-section');
|
||
results.addResult('Import/Export', 'CSV Export Section',
|
||
hasCSVExport ? 'PASSED' : 'FAILED');
|
||
} catch (error) {
|
||
results.addResult('Import/Export', 'Page Load', 'FAILED', error.message);
|
||
}
|
||
|
||
// 5. ANNOUNCEMENTS
|
||
console.log('\n📢 Testing Announcements');
|
||
console.log('-'.repeat(40));
|
||
|
||
await page.goto(`${CONFIG.baseUrl}/master-trainer/announcements/`);
|
||
await page.waitForLoadState('networkidle');
|
||
|
||
try {
|
||
await page.waitForSelector('.announcements-management', { timeout: 5000 });
|
||
results.addResult('Announcements', 'Page Load', 'PASSED');
|
||
|
||
// Check for announcement creation
|
||
const hasCreateButton = await page.isVisible('.create-announcement');
|
||
results.addResult('Announcements', 'Create Button',
|
||
hasCreateButton ? 'PASSED' : 'FAILED');
|
||
|
||
// Check for existing announcements
|
||
const announcementsList = await page.isVisible('.announcements-list');
|
||
results.addResult('Announcements', 'Announcements List',
|
||
announcementsList ? 'PASSED' : 'FAILED');
|
||
} catch (error) {
|
||
results.addResult('Announcements', 'Page Load', 'FAILED', error.message);
|
||
}
|
||
|
||
// 6. PENDING APPROVALS
|
||
console.log('\n⏳ Testing Pending Approvals');
|
||
console.log('-'.repeat(40));
|
||
|
||
await page.goto(`${CONFIG.baseUrl}/master-trainer/pending-approvals/`);
|
||
await page.waitForLoadState('networkidle');
|
||
|
||
try {
|
||
await page.waitForSelector('.pending-approvals', { timeout: 5000 });
|
||
results.addResult('Pending Approvals', 'Page Load', 'PASSED');
|
||
|
||
// Check for trainer approvals section
|
||
const hasTrainerApprovals = await page.isVisible('.trainer-approvals');
|
||
results.addResult('Pending Approvals', 'Trainer Approvals',
|
||
hasTrainerApprovals ? 'PASSED' : 'FAILED');
|
||
|
||
// Check for event approvals section
|
||
const hasEventApprovals = await page.isVisible('.event-approvals');
|
||
results.addResult('Pending Approvals', 'Event Approvals',
|
||
hasEventApprovals ? 'PASSED' : 'FAILED');
|
||
} catch (error) {
|
||
results.addResult('Pending Approvals', 'Page Load', 'FAILED', error.message);
|
||
}
|
||
|
||
// 7. COMMUNICATION TEMPLATES
|
||
console.log('\n📝 Testing Communication Templates');
|
||
console.log('-'.repeat(40));
|
||
|
||
await page.goto(`${CONFIG.baseUrl}/master-trainer/communication-templates/`);
|
||
await page.waitForLoadState('networkidle');
|
||
|
||
try {
|
||
await page.waitForSelector('.communication-templates', { timeout: 5000 });
|
||
results.addResult('Communication Templates', 'Page Load', 'PASSED');
|
||
|
||
// Check for template accordion
|
||
const hasAccordion = await page.isVisible('.template-accordion');
|
||
results.addResult('Communication Templates', 'Template Accordion',
|
||
hasAccordion ? 'PASSED' : 'FAILED');
|
||
|
||
// Check for copy functionality
|
||
const hasCopyButtons = await page.isVisible('.copy-template');
|
||
results.addResult('Communication Templates', 'Copy Buttons',
|
||
hasCopyButtons ? 'PASSED' : 'FAILED');
|
||
} catch (error) {
|
||
results.addResult('Communication Templates', 'Page Load', 'FAILED', error.message);
|
||
}
|
||
|
||
// 8. TRAINER MANAGEMENT
|
||
console.log('\n👥 Testing Trainer Management');
|
||
console.log('-'.repeat(40));
|
||
|
||
await page.goto(`${CONFIG.baseUrl}/master-trainer/trainers/`);
|
||
await page.waitForLoadState('networkidle');
|
||
|
||
try {
|
||
await page.waitForSelector('.trainer-management', { timeout: 5000 });
|
||
results.addResult('Trainer Management', 'Page Load', 'PASSED');
|
||
|
||
// Check for trainer list
|
||
const hasTrainerList = await page.isVisible('.trainer-list');
|
||
results.addResult('Trainer Management', 'Trainer List',
|
||
hasTrainerList ? 'PASSED' : 'FAILED');
|
||
|
||
// Check for edit capabilities
|
||
const hasEditButtons = await page.isVisible('.edit-trainer');
|
||
results.addResult('Trainer Management', 'Edit Buttons',
|
||
hasEditButtons ? 'PASSED' : 'FAILED');
|
||
|
||
// Check for role management
|
||
const hasRoleManagement = await page.isVisible('.role-management');
|
||
results.addResult('Trainer Management', 'Role Management',
|
||
hasRoleManagement ? 'PASSED' : 'FAILED');
|
||
} catch (error) {
|
||
results.addResult('Trainer Management', 'Page Load', 'FAILED', error.message);
|
||
}
|
||
|
||
// 9. NAVIGATION MENU
|
||
console.log('\n🧭 Testing Master Navigation Menu');
|
||
console.log('-'.repeat(40));
|
||
|
||
const navigationTests = [
|
||
{ text: 'Dashboard', url: '/master-trainer/master-dashboard/' },
|
||
{ text: 'Events', url: '/master-trainer/events/' },
|
||
{ text: 'Trainers', url: '/master-trainer/trainers/' },
|
||
{ text: 'Tools', dropdown: true },
|
||
{ text: 'Reports', dropdown: true }
|
||
];
|
||
|
||
for (const navTest of navigationTests) {
|
||
try {
|
||
if (navTest.dropdown) {
|
||
const dropdown = await page.locator(`.hvac-menu-item:has-text("${navTest.text}")`);
|
||
await dropdown.hover();
|
||
results.addResult('Navigation', `${navTest.text} Dropdown`, 'PASSED');
|
||
} else {
|
||
const link = await page.locator(`a:has-text("${navTest.text}")`).first();
|
||
const href = await link.getAttribute('href');
|
||
results.addResult('Navigation', navTest.text, 'PASSED',
|
||
`Link: ${href}`);
|
||
}
|
||
} catch {
|
||
results.addResult('Navigation', navTest.text, 'FAILED');
|
||
}
|
||
}
|
||
|
||
// 10. ROLE-BASED ACCESS CONTROL
|
||
console.log('\n🔒 Testing Role-Based Access Control');
|
||
console.log('-'.repeat(40));
|
||
|
||
// Test that master trainer can access all pages
|
||
const masterPages = [
|
||
'/master-trainer/master-dashboard/',
|
||
'/master-trainer/events/',
|
||
'/master-trainer/import-export/',
|
||
'/master-trainer/trainers/'
|
||
];
|
||
|
||
for (const pageUrl of masterPages) {
|
||
try {
|
||
await page.goto(`${CONFIG.baseUrl}${pageUrl}`);
|
||
await page.waitForLoadState('networkidle');
|
||
const hasAccess = !page.url().includes('login');
|
||
results.addResult('Access Control', `Master Access: ${pageUrl}`,
|
||
hasAccess ? 'PASSED' : 'FAILED');
|
||
} catch {
|
||
results.addResult('Access Control', `Master Access: ${pageUrl}`, 'FAILED');
|
||
}
|
||
}
|
||
|
||
// 11. DATA VALIDATION
|
||
console.log('\n✔️ Testing Data Validation');
|
||
console.log('-'.repeat(40));
|
||
|
||
// Test trainer count consistency
|
||
await page.goto(`${CONFIG.baseUrl}/master-trainer/master-dashboard/`);
|
||
try {
|
||
const dashboardCount = await page.locator('.trainer-count').textContent();
|
||
await page.goto(`${CONFIG.baseUrl}/master-trainer/trainers/`);
|
||
const listCount = await page.locator('.trainer-list .trainer-item').count();
|
||
|
||
results.addResult('Data Validation', 'Trainer Count Consistency', 'PASSED',
|
||
`Dashboard: ${dashboardCount}, List: ${listCount}`);
|
||
} catch {
|
||
results.addResult('Data Validation', 'Trainer Count Consistency', 'FAILED');
|
||
}
|
||
|
||
// 12. RESPONSIVE DESIGN
|
||
console.log('\n📱 Testing Responsive Design');
|
||
console.log('-'.repeat(40));
|
||
|
||
// Test mobile viewport
|
||
await page.setViewportSize({ width: 375, height: 667 });
|
||
await page.goto(`${CONFIG.baseUrl}/master-trainer/master-dashboard/`);
|
||
await page.waitForLoadState('networkidle');
|
||
|
||
try {
|
||
const mobileMenu = await page.isVisible('.mobile-menu-toggle');
|
||
results.addResult('Responsive Design', 'Mobile Menu',
|
||
mobileMenu ? 'PASSED' : 'FAILED');
|
||
} catch {
|
||
results.addResult('Responsive Design', 'Mobile Menu', 'FAILED');
|
||
}
|
||
|
||
// Restore desktop viewport
|
||
await page.setViewportSize(CONFIG.viewport);
|
||
|
||
} catch (error) {
|
||
console.error('\n❌ Test Suite Error:', error.message);
|
||
results.addResult('Test Suite', 'Critical Error', 'FAILED', error.message);
|
||
} finally {
|
||
// Print summary
|
||
results.printSummary();
|
||
results.exportResults();
|
||
|
||
// Take final screenshot
|
||
await page.screenshot({
|
||
path: `master-trainer-test-${Date.now()}.png`,
|
||
fullPage: true
|
||
});
|
||
|
||
await browser.close();
|
||
}
|
||
}
|
||
|
||
// Execute tests
|
||
runMasterTrainerTests().catch(console.error); |