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);