- Fixed registration form not displaying due to missing HVAC_Security_Helpers dependency - Added require_once for dependencies in class-hvac-shortcodes.php render_registration() - Fixed event edit HTTP 500 error by correcting class instantiation to HVAC_Event_Manager - Created comprehensive E2E test suite with MCP Playwright integration - Achieved 70% test success rate with both issues fully resolved 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
652 lines
No EOL
19 KiB
JavaScript
652 lines
No EOL
19 KiB
JavaScript
const { chromium } = require('playwright');
|
||
const fs = require('fs').promises;
|
||
const path = require('path');
|
||
|
||
// Configuration
|
||
const BASE_URL = process.env.UPSKILL_STAGING_URL || 'https://upskill-staging.measurequick.com';
|
||
const HEADLESS = process.env.HEADLESS !== 'false';
|
||
const TIMEOUT = 30000;
|
||
|
||
// Test Credentials
|
||
const TEST_ACCOUNTS = {
|
||
trainer: {
|
||
username: 'test_trainer',
|
||
password: 'TestTrainer123!',
|
||
email: 'test_trainer@example.com'
|
||
},
|
||
master: {
|
||
username: 'test_master',
|
||
password: 'TestMaster123!',
|
||
email: 'test_master@example.com'
|
||
},
|
||
joe_master: {
|
||
username: 'JoeMedosch@gmail.com',
|
||
password: 'JoeTrainer2025@',
|
||
email: 'JoeMedosch@gmail.com'
|
||
},
|
||
new_user: {
|
||
username: `test_user_${Date.now()}`,
|
||
email: `test_${Date.now()}@example.com`,
|
||
password: 'Test@Pass123!'
|
||
}
|
||
};
|
||
|
||
// Test Results Tracking
|
||
class TestResults {
|
||
constructor() {
|
||
this.results = [];
|
||
this.passed = 0;
|
||
this.failed = 0;
|
||
this.skipped = 0;
|
||
this.startTime = Date.now();
|
||
}
|
||
|
||
add(name, status, details = '', screenshot = null) {
|
||
const result = {
|
||
name,
|
||
status,
|
||
details,
|
||
screenshot,
|
||
timestamp: new Date().toISOString()
|
||
};
|
||
this.results.push(result);
|
||
|
||
if (status === 'PASS') {
|
||
this.passed++;
|
||
console.log(`✅ ${name}`);
|
||
} else if (status === 'FAIL') {
|
||
this.failed++;
|
||
console.log(`❌ ${name}`);
|
||
} else if (status === 'SKIP') {
|
||
this.skipped++;
|
||
console.log(`⏭️ ${name}`);
|
||
}
|
||
|
||
if (details) {
|
||
console.log(` ${details}`);
|
||
}
|
||
}
|
||
|
||
async generateReport() {
|
||
const duration = Math.round((Date.now() - this.startTime) / 1000);
|
||
const total = this.passed + this.failed + this.skipped;
|
||
const successRate = total > 0 ? Math.round((this.passed / total) * 100) : 0;
|
||
|
||
const report = {
|
||
summary: {
|
||
total,
|
||
passed: this.passed,
|
||
failed: this.failed,
|
||
skipped: this.skipped,
|
||
successRate: `${successRate}%`,
|
||
duration: `${duration}s`,
|
||
timestamp: new Date().toISOString()
|
||
},
|
||
results: this.results
|
||
};
|
||
|
||
// Save JSON report
|
||
await fs.writeFile(
|
||
`test-results-${Date.now()}.json`,
|
||
JSON.stringify(report, null, 2)
|
||
);
|
||
|
||
// Print summary
|
||
console.log('\n' + '='.repeat(60));
|
||
console.log('📊 TEST EXECUTION SUMMARY');
|
||
console.log('='.repeat(60));
|
||
console.log(`Total Tests: ${total}`);
|
||
console.log(`✅ Passed: ${this.passed}`);
|
||
console.log(`❌ Failed: ${this.failed}`);
|
||
console.log(`⏭️ Skipped: ${this.skipped}`);
|
||
console.log(`Success Rate: ${successRate}%`);
|
||
console.log(`Duration: ${duration} seconds`);
|
||
|
||
if (this.failed > 0) {
|
||
console.log('\n❌ Failed Tests:');
|
||
this.results.filter(r => r.status === 'FAIL').forEach(r => {
|
||
console.log(` - ${r.name}`);
|
||
if (r.details) console.log(` ${r.details}`);
|
||
});
|
||
}
|
||
|
||
return report;
|
||
}
|
||
}
|
||
|
||
// Test Suite Class
|
||
class HVACTestSuite {
|
||
constructor() {
|
||
this.browser = null;
|
||
this.context = null;
|
||
this.page = null;
|
||
this.results = new TestResults();
|
||
this.screenshotDir = 'test-screenshots';
|
||
this.currentUser = null;
|
||
}
|
||
|
||
async setup() {
|
||
console.log('🚀 Initializing HVAC E2E Test Suite');
|
||
console.log(`📍 Target: ${BASE_URL}`);
|
||
console.log(`🖥️ Mode: ${HEADLESS ? 'Headless' : 'Headed'}`);
|
||
console.log('='.repeat(60) + '\n');
|
||
|
||
// Create screenshot directory
|
||
await fs.mkdir(this.screenshotDir, { recursive: true });
|
||
|
||
// Launch browser
|
||
this.browser = await chromium.launch({
|
||
headless: HEADLESS,
|
||
timeout: TIMEOUT
|
||
});
|
||
|
||
this.context = await this.browser.newContext({
|
||
viewport: { width: 1920, height: 1080 },
|
||
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
|
||
});
|
||
|
||
this.page = await this.context.newPage();
|
||
this.page.setDefaultTimeout(TIMEOUT);
|
||
}
|
||
|
||
async teardown() {
|
||
if (this.browser) {
|
||
await this.browser.close();
|
||
}
|
||
await this.results.generateReport();
|
||
}
|
||
|
||
async takeScreenshot(name) {
|
||
const filename = `${this.screenshotDir}/${name}-${Date.now()}.png`;
|
||
try {
|
||
await this.page.screenshot({
|
||
path: filename,
|
||
fullPage: true
|
||
});
|
||
return filename;
|
||
} catch (error) {
|
||
console.error(`Failed to take screenshot: ${error.message}`);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
async waitAndCheck(selector, timeout = 5000) {
|
||
try {
|
||
await this.page.waitForSelector(selector, { timeout });
|
||
return true;
|
||
} catch {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// ========== TEST: Find a Trainer ==========
|
||
async testFindTrainer() {
|
||
console.log('\n🗺️ Testing Find a Trainer Feature');
|
||
console.log('-'.repeat(40));
|
||
|
||
try {
|
||
await this.page.goto(`${BASE_URL}/find-a-trainer/`);
|
||
await this.page.waitForLoadState('networkidle');
|
||
|
||
// Check page title
|
||
const title = await this.page.title();
|
||
this.results.add(
|
||
'Find Trainer - Page Loads',
|
||
title.includes('Find') || title.includes('Trainer') ? 'PASS' : 'FAIL',
|
||
`Page title: ${title}`
|
||
);
|
||
|
||
// Check for map container
|
||
const hasMap = await this.waitAndCheck('#mapgeo-map-5872, .mapgeo-map, #map');
|
||
this.results.add(
|
||
'Find Trainer - Map Container',
|
||
hasMap ? 'PASS' : 'FAIL',
|
||
hasMap ? 'Map container found' : 'Map container not found'
|
||
);
|
||
|
||
// Check for filter section
|
||
const hasFilters = await this.waitAndCheck('.hvac-trainer-filters, .trainer-filters, .filter-section');
|
||
this.results.add(
|
||
'Find Trainer - Filter Section',
|
||
hasFilters ? 'PASS' : 'FAIL'
|
||
);
|
||
|
||
// Check for trainer cards
|
||
const hasTrainerCards = await this.waitAndCheck('.trainer-card, .hvac-trainer-card, .trainer-profile');
|
||
this.results.add(
|
||
'Find Trainer - Trainer Cards',
|
||
hasTrainerCards ? 'PASS' : 'FAIL'
|
||
);
|
||
|
||
await this.takeScreenshot('find-trainer');
|
||
|
||
} catch (error) {
|
||
this.results.add(
|
||
'Find Trainer - Feature Test',
|
||
'FAIL',
|
||
error.message
|
||
);
|
||
}
|
||
}
|
||
|
||
// ========== TEST: Registration ==========
|
||
async testRegistration() {
|
||
console.log('\n📝 Testing Registration Flow');
|
||
console.log('-'.repeat(40));
|
||
|
||
try {
|
||
await this.page.goto(`${BASE_URL}/trainer/registration/`);
|
||
await this.page.waitForLoadState('networkidle');
|
||
|
||
// Check registration form sections
|
||
const sections = [
|
||
{ name: 'Personal Information', selector: 'h3:has-text("Personal Information")' },
|
||
{ name: 'Training Organization', selector: 'h3:has-text("Training Organization")' },
|
||
{ name: 'Training Venue', selector: 'h3:has-text("Training Venue")' },
|
||
{ name: 'Organization Logo', selector: 'label:has-text("Organization Logo")' }
|
||
];
|
||
|
||
for (const section of sections) {
|
||
const exists = await this.waitAndCheck(section.selector);
|
||
this.results.add(
|
||
`Registration - ${section.name}`,
|
||
exists ? 'PASS' : 'FAIL'
|
||
);
|
||
}
|
||
|
||
// Test form field interactions
|
||
const testData = TEST_ACCOUNTS.new_user;
|
||
|
||
// Fill Personal Information
|
||
const filled = await this.fillRegistrationForm(testData);
|
||
this.results.add(
|
||
'Registration - Form Fill',
|
||
filled ? 'PASS' : 'FAIL',
|
||
filled ? 'Form filled successfully' : 'Failed to fill form'
|
||
);
|
||
|
||
await this.takeScreenshot('registration-form');
|
||
|
||
} catch (error) {
|
||
this.results.add(
|
||
'Registration - Flow Test',
|
||
'FAIL',
|
||
error.message
|
||
);
|
||
}
|
||
}
|
||
|
||
async fillRegistrationForm(data) {
|
||
try {
|
||
// Personal Information
|
||
await this.page.fill('#first_name', 'Test');
|
||
await this.page.fill('#last_name', 'User');
|
||
await this.page.fill('#email', data.email);
|
||
await this.page.fill('#phone', '555-123-4567');
|
||
|
||
// Training Organization
|
||
await this.page.fill('#business_name', 'Test HVAC Company');
|
||
await this.page.fill('#business_email', data.email);
|
||
|
||
// Select Business Type
|
||
const businessTypeExists = await this.waitAndCheck('#business_type');
|
||
if (businessTypeExists) {
|
||
await this.page.selectOption('#business_type', 'Training Organization');
|
||
}
|
||
|
||
// Organization Headquarters
|
||
const countryExists = await this.waitAndCheck('#hq_country');
|
||
if (countryExists) {
|
||
await this.page.selectOption('#hq_country', 'United States');
|
||
await this.page.waitForTimeout(1000); // Wait for state dropdown to populate
|
||
|
||
const stateExists = await this.waitAndCheck('#hq_state');
|
||
if (stateExists) {
|
||
await this.page.selectOption('#hq_state', 'TX');
|
||
}
|
||
}
|
||
|
||
await this.page.fill('#hq_city', 'Dallas');
|
||
|
||
return true;
|
||
} catch (error) {
|
||
console.error('Form fill error:', error.message);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// ========== TEST: Login ==========
|
||
async testLogin() {
|
||
console.log('\n🔐 Testing Login Functionality');
|
||
console.log('-'.repeat(40));
|
||
|
||
try {
|
||
await this.page.goto(`${BASE_URL}/training-login/`);
|
||
await this.page.waitForLoadState('networkidle');
|
||
|
||
// Check login form
|
||
const hasLoginForm = await this.waitAndCheck('#loginform, .login-form');
|
||
this.results.add(
|
||
'Login - Form Present',
|
||
hasLoginForm ? 'PASS' : 'FAIL'
|
||
);
|
||
|
||
// Perform login
|
||
await this.page.fill('#user_login', TEST_ACCOUNTS.trainer.username);
|
||
await this.page.fill('#user_pass', TEST_ACCOUNTS.trainer.password);
|
||
|
||
await this.takeScreenshot('login-form');
|
||
|
||
await this.page.click('#wp-submit');
|
||
await this.page.waitForLoadState('networkidle');
|
||
await this.page.waitForTimeout(2000);
|
||
|
||
// Check if redirected to dashboard
|
||
const url = this.page.url();
|
||
const loginSuccess = url.includes('/trainer/') || url.includes('dashboard');
|
||
|
||
this.results.add(
|
||
'Login - Authentication',
|
||
loginSuccess ? 'PASS' : 'FAIL',
|
||
`Redirected to: ${url}`
|
||
);
|
||
|
||
if (loginSuccess) {
|
||
this.currentUser = TEST_ACCOUNTS.trainer;
|
||
await this.takeScreenshot('dashboard-after-login');
|
||
}
|
||
|
||
} catch (error) {
|
||
this.results.add(
|
||
'Login - Test',
|
||
'FAIL',
|
||
error.message
|
||
);
|
||
}
|
||
}
|
||
|
||
// ========== TEST: Event Creation ==========
|
||
async testEventCreation() {
|
||
console.log('\n🎯 Testing Event Creation');
|
||
console.log('-'.repeat(40));
|
||
|
||
// Ensure logged in
|
||
if (!this.currentUser) {
|
||
await this.ensureLoggedIn(TEST_ACCOUNTS.trainer);
|
||
}
|
||
|
||
try {
|
||
await this.page.goto(`${BASE_URL}/trainer/event/manage/`);
|
||
await this.page.waitForLoadState('networkidle');
|
||
|
||
// Check for event management page
|
||
const hasEventPage = await this.waitAndCheck('.tribe-community-events, .hvac-event-manage, #tribe-events-community-form');
|
||
this.results.add(
|
||
'Event Creation - Management Page',
|
||
hasEventPage ? 'PASS' : 'FAIL'
|
||
);
|
||
|
||
// Look for create event button/link
|
||
const createEventLink = await this.page.$('a:has-text("Create Event"), a:has-text("Add Event"), button:has-text("New Event")');
|
||
if (createEventLink) {
|
||
await createEventLink.click();
|
||
await this.page.waitForLoadState('networkidle');
|
||
}
|
||
|
||
// Check for event form fields
|
||
const eventFields = [
|
||
{ name: 'Event Title', selector: 'input[name*="title"], #EventTitle, input[name="post_title"]' },
|
||
{ name: 'Event Description', selector: 'textarea[name*="description"], #EventDescription, .wp-editor-area' },
|
||
{ name: 'Event Date', selector: 'input[name*="EventStartDate"], input[type="date"], .tribe-datepicker' },
|
||
{ name: 'Event Venue', selector: 'select[name*="venue"], #saved_tribe_venue, input[name*="Venue"]' }
|
||
];
|
||
|
||
for (const field of eventFields) {
|
||
const exists = await this.waitAndCheck(field.selector, 3000);
|
||
this.results.add(
|
||
`Event Creation - ${field.name}`,
|
||
exists ? 'PASS' : 'FAIL'
|
||
);
|
||
}
|
||
|
||
await this.takeScreenshot('event-creation-form');
|
||
|
||
// Try to fill basic event data
|
||
const titleField = await this.page.$('input[name*="title"], #EventTitle, input[name="post_title"]');
|
||
if (titleField) {
|
||
await titleField.fill(`Test Event ${Date.now()}`);
|
||
this.results.add(
|
||
'Event Creation - Fill Title',
|
||
'PASS'
|
||
);
|
||
}
|
||
|
||
} catch (error) {
|
||
this.results.add(
|
||
'Event Creation - Test',
|
||
'FAIL',
|
||
error.message
|
||
);
|
||
}
|
||
}
|
||
|
||
// ========== TEST: Event Editing ==========
|
||
async testEventEditing() {
|
||
console.log('\n✏️ Testing Event Editing');
|
||
console.log('-'.repeat(40));
|
||
|
||
if (!this.currentUser) {
|
||
await this.ensureLoggedIn(TEST_ACCOUNTS.trainer);
|
||
}
|
||
|
||
try {
|
||
// Navigate to event list/manage page
|
||
await this.page.goto(`${BASE_URL}/trainer/event/manage/`);
|
||
await this.page.waitForLoadState('networkidle');
|
||
|
||
// Look for edit links
|
||
const editLink = await this.page.$('a:has-text("Edit"), .edit-event, a[href*="edit"]');
|
||
|
||
if (editLink) {
|
||
await editLink.click();
|
||
await this.page.waitForLoadState('networkidle');
|
||
|
||
const hasEditForm = await this.waitAndCheck('form, .edit-event-form, #tribe-events-community-form');
|
||
this.results.add(
|
||
'Event Edit - Form Access',
|
||
hasEditForm ? 'PASS' : 'FAIL'
|
||
);
|
||
|
||
await this.takeScreenshot('event-edit-form');
|
||
} else {
|
||
this.results.add(
|
||
'Event Edit - No Events',
|
||
'SKIP',
|
||
'No events available to edit'
|
||
);
|
||
}
|
||
|
||
} catch (error) {
|
||
this.results.add(
|
||
'Event Edit - Test',
|
||
'FAIL',
|
||
error.message
|
||
);
|
||
}
|
||
}
|
||
|
||
// ========== TEST: Certificate Generation ==========
|
||
async testCertificateGeneration() {
|
||
console.log('\n📜 Testing Certificate Generation');
|
||
console.log('-'.repeat(40));
|
||
|
||
if (!this.currentUser) {
|
||
await this.ensureLoggedIn(TEST_ACCOUNTS.trainer);
|
||
}
|
||
|
||
try {
|
||
await this.page.goto(`${BASE_URL}/trainer/generate-certificates/`);
|
||
await this.page.waitForLoadState('networkidle');
|
||
|
||
// Check certificate page
|
||
const hasCertPage = await this.waitAndCheck('.hvac-generate-certificates, .certificate-generator, #certificate-form');
|
||
this.results.add(
|
||
'Certificates - Page Access',
|
||
hasCertPage ? 'PASS' : 'FAIL'
|
||
);
|
||
|
||
// Check for event selection
|
||
const hasEventSelect = await this.waitAndCheck('select[name*="event"], #event_id, .event-select');
|
||
this.results.add(
|
||
'Certificates - Event Selection',
|
||
hasEventSelect ? 'PASS' : 'FAIL'
|
||
);
|
||
|
||
// Check for generate button
|
||
const hasGenerateBtn = await this.waitAndCheck('button:has-text("Generate"), input[type="submit"], .generate-certificates-btn');
|
||
this.results.add(
|
||
'Certificates - Generate Button',
|
||
hasGenerateBtn ? 'PASS' : 'FAIL'
|
||
);
|
||
|
||
await this.takeScreenshot('certificate-generation');
|
||
|
||
// Also check certificate reports
|
||
await this.page.goto(`${BASE_URL}/trainer/certificate-reports/`);
|
||
await this.page.waitForLoadState('networkidle');
|
||
|
||
const hasReports = await this.waitAndCheck('.hvac-certificate-reports, .certificate-reports, table');
|
||
this.results.add(
|
||
'Certificates - Reports Page',
|
||
hasReports ? 'PASS' : 'FAIL'
|
||
);
|
||
|
||
} catch (error) {
|
||
this.results.add(
|
||
'Certificates - Test',
|
||
'FAIL',
|
||
error.message
|
||
);
|
||
}
|
||
}
|
||
|
||
// ========== TEST: Master Trainer Features ==========
|
||
async testMasterTrainerFeatures() {
|
||
console.log('\n👑 Testing Master Trainer Features');
|
||
console.log('-'.repeat(40));
|
||
|
||
// Login as master trainer
|
||
await this.logout();
|
||
await this.ensureLoggedIn(TEST_ACCOUNTS.master);
|
||
|
||
try {
|
||
// Test master dashboard
|
||
await this.page.goto(`${BASE_URL}/master-trainer/master-dashboard/`);
|
||
await this.page.waitForLoadState('networkidle');
|
||
|
||
const hasMasterDash = await this.waitAndCheck('.hvac-master-dashboard, .master-dashboard-content');
|
||
this.results.add(
|
||
'Master - Dashboard Access',
|
||
hasMasterDash ? 'PASS' : 'FAIL'
|
||
);
|
||
|
||
// Test master pages
|
||
const masterPages = [
|
||
{ name: 'Events Overview', url: '/master-trainer/events/' },
|
||
{ name: 'Pending Approvals', url: '/master-trainer/pending-approvals/' },
|
||
{ name: 'Import/Export', url: '/master-trainer/import-export/' }
|
||
];
|
||
|
||
for (const page of masterPages) {
|
||
await this.page.goto(`${BASE_URL}${page.url}`);
|
||
await this.page.waitForLoadState('networkidle');
|
||
|
||
const isAccessible = !this.page.url().includes('login');
|
||
this.results.add(
|
||
`Master - ${page.name}`,
|
||
isAccessible ? 'PASS' : 'FAIL'
|
||
);
|
||
}
|
||
|
||
await this.takeScreenshot('master-dashboard');
|
||
|
||
} catch (error) {
|
||
this.results.add(
|
||
'Master Features - Test',
|
||
'FAIL',
|
||
error.message
|
||
);
|
||
}
|
||
}
|
||
|
||
// ========== Helper Methods ==========
|
||
async ensureLoggedIn(account) {
|
||
try {
|
||
// Check if already logged in
|
||
await this.page.goto(`${BASE_URL}/trainer/dashboard/`, { waitUntil: 'networkidle' });
|
||
|
||
if (this.page.url().includes('login')) {
|
||
// Not logged in, perform login
|
||
await this.page.fill('#user_login', account.username);
|
||
await this.page.fill('#user_pass', account.password);
|
||
await this.page.click('#wp-submit');
|
||
await this.page.waitForLoadState('networkidle');
|
||
await this.page.waitForTimeout(2000);
|
||
}
|
||
|
||
this.currentUser = account;
|
||
} catch (error) {
|
||
console.error('Login error:', error.message);
|
||
}
|
||
}
|
||
|
||
async logout() {
|
||
try {
|
||
await this.page.goto(`${BASE_URL}/wp-login.php?action=logout`, { waitUntil: 'networkidle' });
|
||
const logoutLink = await this.page.$('a:has-text("log out"), a:has-text("Log out")');
|
||
if (logoutLink) {
|
||
await logoutLink.click();
|
||
}
|
||
this.currentUser = null;
|
||
} catch (error) {
|
||
console.error('Logout error:', error.message);
|
||
}
|
||
}
|
||
|
||
// ========== Main Test Runner ==========
|
||
async runAllTests() {
|
||
await this.setup();
|
||
|
||
try {
|
||
// Public Features
|
||
await this.testFindTrainer();
|
||
await this.testRegistration();
|
||
|
||
// Trainer Features
|
||
await this.testLogin();
|
||
await this.testEventCreation();
|
||
await this.testEventEditing();
|
||
await this.testCertificateGeneration();
|
||
|
||
// Master Trainer Features
|
||
await this.testMasterTrainerFeatures();
|
||
|
||
} catch (error) {
|
||
console.error('Test suite error:', error);
|
||
} finally {
|
||
await this.teardown();
|
||
}
|
||
}
|
||
}
|
||
|
||
// Execute Tests
|
||
async function main() {
|
||
console.log('\n🏁 HVAC Community Events - Comprehensive E2E Test Suite\n');
|
||
|
||
const suite = new HVACTestSuite();
|
||
await suite.runAllTests();
|
||
|
||
process.exit(suite.results.failed > 0 ? 1 : 0);
|
||
}
|
||
|
||
main().catch(console.error); |