/** * HVAC Trainer Events E2E Test Suite * * This comprehensive test suite validates the complete trainer event creation and editing workflow * using The Events Calendar (TEC) Community Events integration. * * Test Coverage: * - Trainer authentication and access control * - Event creation through TEC integration * - Event editing and updates * - Event management navigation * - Data persistence and validation * - Form field validation and error handling * - Dashboard integration * - Complete trainer workflow */ const { chromium } = require('playwright'); const fs = require('fs'); const path = require('path'); // Test configuration const CONFIG = { baseUrl: 'https://upskill-staging.measurequick.com', credentials: { username: 'test_trainer', password: 'TestTrainer123!' }, timeouts: { navigation: 30000, element: 10000, wait: 3000 }, headless: true, screenshots: true }; // Test data for event creation const TEST_EVENT_DATA = { title: `Test HVAC Training Event ${Date.now()}`, description: 'This is a comprehensive test event for HVAC training with detailed description covering all aspects of the training program.', excerpt: 'Brief summary of the test HVAC training event', startDate: '2025-09-01', startTime: '09:00', endDate: '2025-09-01', endTime: '17:00', venue: 'Test Training Center', venueAddress: '123 Training Street, Test City, Test State 12345', category: 'Installation Training', tags: 'hvac, installation, test', cost: '299.00', maxAttendees: '25' }; // Test results tracking const testResults = { passed: 0, failed: 0, total: 0, details: [] }; /** * Utility function to log test results */ function logTest(testName, passed, details = '', screenshot = null) { const status = passed ? 'āœ… PASS' : 'āŒ FAIL'; const timestamp = new Date().toISOString(); console.log(`[${timestamp}] ${status} ${testName}`); if (details) { console.log(` ${details}`); } testResults.total++; if (passed) { testResults.passed++; } else { testResults.failed++; } testResults.details.push({ name: testName, passed, details, screenshot, timestamp }); } /** * Utility function to take screenshots for debugging */ async function takeScreenshot(page, filename, description = '') { if (!CONFIG.screenshots) return null; try { const screenshotPath = path.join(__dirname, '../../screenshots', `${filename}-${Date.now()}.png`); // Ensure screenshots directory exists const screenshotsDir = path.dirname(screenshotPath); if (!fs.existsSync(screenshotsDir)) { fs.mkdirSync(screenshotsDir, { recursive: true }); } await page.screenshot({ path: screenshotPath, fullPage: true }); console.log(` šŸ“ø Screenshot saved: ${path.basename(screenshotPath)}`); if (description) { console.log(` šŸ“ ${description}`); } return screenshotPath; } catch (error) { console.log(` āš ļø Screenshot failed: ${error.message}`); return null; } } /** * Wait for element with better error handling */ async function waitForElement(page, selector, timeout = CONFIG.timeouts.element) { try { await page.waitForSelector(selector, { timeout, state: 'visible' }); return true; } catch (error) { console.log(` āš ļø Element not found: ${selector}`); return false; } } /** * Login as trainer with comprehensive validation */ async function loginAsTrainer(page) { console.log('\nšŸ” AUTHENTICATION TEST'); console.log('─'.repeat(50)); try { // Navigate to login page await page.goto(`${CONFIG.baseUrl}/trainer/training-login/`, { waitUntil: 'networkidle', timeout: CONFIG.timeouts.navigation }); const loginPageLoaded = await page.locator('form').isVisible(); logTest('Login page loads', loginPageLoaded, page.url()); if (!loginPageLoaded) { await takeScreenshot(page, 'login-page-failed', 'Login page failed to load'); return false; } // Fill login credentials await page.fill('#username, #user_login, input[name="log"]', CONFIG.credentials.username); await page.fill('#password, #user_pass, input[name="pwd"]', CONFIG.credentials.password); // Submit login form await page.click('input[type="submit"], button[type="submit"]'); await page.waitForTimeout(CONFIG.timeouts.wait); // Verify login success const currentUrl = page.url(); const isLoggedIn = currentUrl.includes('/trainer/') && !currentUrl.includes('login'); logTest('Login authentication', isLoggedIn, `Final URL: ${currentUrl}`); if (isLoggedIn) { await takeScreenshot(page, 'login-success', 'Successfully logged in as trainer'); return true; } else { await takeScreenshot(page, 'login-failed', 'Login failed - not redirected to trainer area'); return false; } } catch (error) { logTest('Login process', false, `Login error: ${error.message}`); await takeScreenshot(page, 'login-error', 'Login process encountered an error'); return false; } } /** * Test trainer dashboard access and navigation */ async function testTrainerDashboard(page) { console.log('\nšŸ“Š TRAINER DASHBOARD TEST'); console.log('─'.repeat(50)); try { await page.goto(`${CONFIG.baseUrl}/trainer/dashboard/`, { waitUntil: 'networkidle', timeout: CONFIG.timeouts.navigation }); // Check dashboard loads correctly const dashboardTitle = await page.title(); const hasDashboardContent = await page.locator('h1, h2').first().isVisible(); logTest('Dashboard page loads', hasDashboardContent, `Title: ${dashboardTitle}`); // Check navigation menu - use first() to avoid strict mode violation const hasNavigation = await page.locator('.hvac-trainer-nav, .hvac-trainer-menu').first().isVisible(); logTest('Dashboard navigation menu', hasNavigation); // Check for event management links const hasEventManageLink = await page.locator('a[href*="event/manage"], a[href*="events"]').first().isVisible(); logTest('Event management link present', hasEventManageLink); await takeScreenshot(page, 'dashboard-loaded', 'Trainer dashboard successfully loaded'); return true; } catch (error) { logTest('Dashboard access', false, `Dashboard error: ${error.message}`); await takeScreenshot(page, 'dashboard-error', 'Dashboard access failed'); return false; } } /** * Test event management page access */ async function testEventManagePage(page) { console.log('\nšŸ“‹ EVENT MANAGEMENT PAGE TEST'); console.log('─'.repeat(50)); try { await page.goto(`${CONFIG.baseUrl}/trainer/event/manage/`, { waitUntil: 'networkidle', timeout: CONFIG.timeouts.navigation }); // Check page loads const pageTitle = await page.title(); const hasContent = await page.locator('h1, h2, .hvac-event-manage').first().isVisible(); logTest('Event management page loads', hasContent, `Title: ${pageTitle}`); // Check for create event button/link const hasCreateButton = await page.locator('a[href*="add"], a[href*="create"], button:has-text("Create")').first().isVisible(); logTest('Create event button present', hasCreateButton); // Check for my events link const hasMyEventsLink = await page.locator('a[href*="events"], a:has-text("My Events")').first().isVisible(); logTest('My Events link present', hasMyEventsLink); await takeScreenshot(page, 'event-manage-page', 'Event management page loaded'); return true; } catch (error) { logTest('Event management page', false, `Event management error: ${error.message}`); await takeScreenshot(page, 'event-manage-error', 'Event management page failed'); return false; } } /** * Test event creation workflow */ async function testEventCreation(page) { console.log('\nāž• EVENT CREATION TEST'); console.log('─'.repeat(50)); try { // Navigate to event creation page (TEC Community Events) const createUrls = [ `${CONFIG.baseUrl}/events/network/add/`, `${CONFIG.baseUrl}/trainer/event/create/`, `${CONFIG.baseUrl}/events/community/add/` ]; let createPageLoaded = false; let workingUrl = ''; // Try different URLs for event creation for (const url of createUrls) { try { await page.goto(url, { waitUntil: 'networkidle', timeout: CONFIG.timeouts.navigation }); const hasForm = await page.locator('form').first().isVisible({ timeout: 5000 }); if (hasForm) { createPageLoaded = true; workingUrl = url; break; } } catch (error) { console.log(` āš ļø URL not accessible: ${url}`); } } logTest('Event creation page access', createPageLoaded, `Working URL: ${workingUrl}`); if (!createPageLoaded) { await takeScreenshot(page, 'create-event-no-access', 'Cannot access event creation page'); return null; } await takeScreenshot(page, 'create-event-form', 'Event creation form loaded'); // Fill out event creation form - using exact TEC field selectors from form analysis const formFields = { title: '#post_title', startDate: '#EventStartDate', startTime: '#EventStartTime', endDate: '#EventEndDate', endTime: '#EventEndTime', venue: '#saved_tribe_venue', organizer: '#saved_tribe_organizer' }; const filledFields = {}; // Fill form fields with better error handling - use .first() to avoid strict mode violations for (const [fieldName, selector] of Object.entries(formFields)) { try { const element = page.locator(selector).first(); const isVisible = await element.isVisible({ timeout: 3000 }); if (isVisible) { let value; switch (fieldName) { case 'title': value = TEST_EVENT_DATA.title; break; case 'startDate': value = TEST_EVENT_DATA.startDate; break; case 'startTime': value = TEST_EVENT_DATA.startTime; break; case 'endDate': value = TEST_EVENT_DATA.endDate; break; case 'endTime': value = TEST_EVENT_DATA.endTime; break; case 'venue': // For select fields, select by index if it's a dropdown try { await element.selectOption({ index: 1 }); filledFields[fieldName] = true; console.log(` āœ“ ${fieldName} selected`); continue; } catch (e) { value = 'Test Venue'; } break; case 'organizer': // For select fields, select by index if it's a dropdown try { await element.selectOption({ index: 1 }); filledFields[fieldName] = true; console.log(` āœ“ ${fieldName} selected`); continue; } catch (e) { value = 'Test Organizer'; } break; default: value = TEST_EVENT_DATA[fieldName] || 'Test Value'; } await element.fill(value); filledFields[fieldName] = true; console.log(` āœ“ ${fieldName} filled: ${value}`); } else { console.log(` āš ļø Field not visible: ${fieldName} (${selector})`); } } catch (error) { console.log(` āš ļø Could not fill field: ${fieldName} - ${error.message}`); } } // Try to fill the description field using WordPress rich text editor try { // Try to interact with TinyMCE editor await page.evaluate(() => { // Try to set content via TinyMCE if available if (typeof tinymce !== 'undefined' && tinymce.get('tcepostcontent')) { tinymce.get('tcepostcontent').setContent('Test event description for automated testing'); return true; } // Otherwise try to set via textarea directly const textarea = document.getElementById('tcepostcontent'); if (textarea) { textarea.value = 'Test event description for automated testing'; textarea.dispatchEvent(new Event('input', { bubbles: true })); return true; } return false; }); filledFields.description = true; console.log(' āœ“ Description filled via editor'); } catch (error) { console.log(' āš ļø Could not fill description field'); } const fieldsFilledCount = Object.keys(filledFields).length; logTest('Form fields populated', fieldsFilledCount > 0, `${fieldsFilledCount} fields filled`); // Handle special fields (dropdowns, etc.) try { // Try to set event category const categorySelect = page.locator('select[name*="category"], #tribe_events_cat'); if (await categorySelect.isVisible({ timeout: 2000 })) { await categorySelect.selectOption({ index: 1 }); console.log(' āœ“ Category selected'); } } catch (error) { console.log(' āš ļø Category field not available'); } await takeScreenshot(page, 'create-event-filled', 'Event creation form filled out'); // Submit the form const submitButton = page.locator('input[type="submit"], button[type="submit"], button:has-text("Create"), button:has-text("Publish")'); const hasSubmitButton = await submitButton.first().isVisible(); logTest('Submit button present', hasSubmitButton); if (hasSubmitButton) { await submitButton.first().click(); await page.waitForTimeout(CONFIG.timeouts.wait); // Check for success or errors - use first() to avoid strict mode violations const currentUrl = page.url(); const hasErrorMessage = await page.locator('.tribe-community-notice-error').first().isVisible({ timeout: 3000 }); const hasSuccessMessage = await page.locator('.tribe-community-notice-success, .success').first().isVisible({ timeout: 3000 }); const submissionSuccessful = !hasErrorMessage && (hasSuccessMessage || currentUrl !== workingUrl); logTest('Event creation submission', submissionSuccessful, hasErrorMessage ? 'Form has errors' : `Redirected to: ${currentUrl}`); await takeScreenshot(page, 'create-event-submitted', 'Event creation form submitted'); // Extract event ID or URL for editing test if (submissionSuccessful) { const eventId = currentUrl.match(/event\/(\d+)/)?.[1] || currentUrl.match(/post=(\d+)/)?.[1] || Date.now().toString(); return eventId; } } return null; } catch (error) { logTest('Event creation process', false, `Creation error: ${error.message}`); await takeScreenshot(page, 'create-event-error', 'Event creation process failed'); return null; } } /** * Test event editing workflow */ async function testEventEditing(page, eventId) { console.log('\nāœļø EVENT EDITING TEST'); console.log('─'.repeat(50)); if (!eventId) { logTest('Event editing setup', false, 'No event ID available for editing'); return false; } try { // Navigate to edit event page const editUrls = [ `${CONFIG.baseUrl}/events/network/edit/?event_id=${eventId}`, `${CONFIG.baseUrl}/events/community/edit/${eventId}/`, `${CONFIG.baseUrl}/wp-admin/post.php?post=${eventId}&action=edit` ]; let editPageLoaded = false; let workingUrl = ''; for (const url of editUrls) { try { await page.goto(url, { waitUntil: 'networkidle', timeout: CONFIG.timeouts.navigation }); const hasEditForm = await page.locator('form, #post').first().isVisible({ timeout: 5000 }); if (hasEditForm) { editPageLoaded = true; workingUrl = url; break; } } catch (error) { console.log(` āš ļø Edit URL not accessible: ${url}`); } } logTest('Event edit page access', editPageLoaded, `Working URL: ${workingUrl}`); if (!editPageLoaded) { await takeScreenshot(page, 'edit-event-no-access', 'Cannot access event edit page'); return false; } await takeScreenshot(page, 'edit-event-form', 'Event edit form loaded'); // Modify event data const updatedTitle = `${TEST_EVENT_DATA.title} - EDITED`; const titleField = page.locator('#post_title, input[name="post_title"], #title'); if (await titleField.isVisible({ timeout: 3000 })) { await titleField.fill(updatedTitle); console.log(' āœ“ Event title updated'); } // Update description if available const descriptionField = page.locator('#post_content, textarea[name="post_content"]'); if (await descriptionField.isVisible({ timeout: 3000 })) { await descriptionField.fill(TEST_EVENT_DATA.description + ' - This event has been edited.'); console.log(' āœ“ Event description updated'); } await takeScreenshot(page, 'edit-event-modified', 'Event edit form modified'); // Submit changes const updateButton = page.locator('input[type="submit"], button[type="submit"], button:has-text("Update"), button:has-text("Save")'); const hasUpdateButton = await updateButton.first().isVisible(); logTest('Update button present', hasUpdateButton); if (hasUpdateButton) { await updateButton.first().click(); await page.waitForTimeout(CONFIG.timeouts.wait); const currentUrl = page.url(); const hasErrorMessage = await page.locator('.tribe-community-notice-error').first().isVisible({ timeout: 3000 }); const updateSuccessful = !hasErrorMessage; logTest('Event update submission', updateSuccessful, hasErrorMessage ? 'Update has errors' : 'Update completed'); await takeScreenshot(page, 'edit-event-submitted', 'Event edit form submitted'); return updateSuccessful; } return false; } catch (error) { logTest('Event editing process', false, `Editing error: ${error.message}`); await takeScreenshot(page, 'edit-event-error', 'Event editing process failed'); return false; } } /** * Test My Events page functionality */ async function testMyEventsPage(page) { console.log('\nšŸ“… MY EVENTS PAGE TEST'); console.log('─'.repeat(50)); try { const myEventsUrls = [ `${CONFIG.baseUrl}/events/network/`, `${CONFIG.baseUrl}/trainer/events/`, `${CONFIG.baseUrl}/events/community/my-events/` ]; let myEventsPageLoaded = false; let workingUrl = ''; for (const url of myEventsUrls) { try { await page.goto(url, { waitUntil: 'networkidle', timeout: CONFIG.timeouts.navigation }); const pageTitle = await page.title(); const hasEventsList = await page.locator('.tribe-events-list, .events-list, table').first().isVisible({ timeout: 5000 }); if (pageTitle.toLowerCase().includes('events') || hasEventsList) { myEventsPageLoaded = true; workingUrl = url; break; } } catch (error) { console.log(` āš ļø My Events URL not accessible: ${url}`); } } logTest('My Events page access', myEventsPageLoaded, `Working URL: ${workingUrl}`); if (myEventsPageLoaded) { // Count events displayed const eventCount = await page.locator('.tribe-events-list-event, .event-row, tr').count(); logTest('Events list displayed', eventCount >= 0, `${eventCount} events found`); // Check for event management actions const hasEditLinks = await page.locator('a:has-text("Edit"), a[href*="edit"]').first().isVisible({ timeout: 3000 }); logTest('Event edit links present', hasEditLinks); await takeScreenshot(page, 'my-events-page', 'My Events page loaded'); return true; } return false; } catch (error) { logTest('My Events page', false, `My Events error: ${error.message}`); await takeScreenshot(page, 'my-events-error', 'My Events page failed'); return false; } } /** * Test navigation between event pages */ async function testEventNavigation(page) { console.log('\n🧭 EVENT NAVIGATION TEST'); console.log('─'.repeat(50)); try { // Test navigation from dashboard to event management await page.goto(`${CONFIG.baseUrl}/trainer/dashboard/`, { waitUntil: 'networkidle', timeout: CONFIG.timeouts.navigation }); const eventManagementLink = page.locator('a[href*="event"], a:has-text("Event"), a:has-text("Manage")').first(); const hasEventManagementLink = await eventManagementLink.isVisible({ timeout: 5000 }); logTest('Dashboard to Event Management navigation', hasEventManagementLink); if (hasEventManagementLink) { await eventManagementLink.click(); await page.waitForTimeout(CONFIG.timeouts.wait); const navigationSuccessful = page.url().includes('event') || page.url().includes('manage'); logTest('Event Management navigation successful', navigationSuccessful, page.url()); } // Test breadcrumb navigation if available const hasBreadcrumbs = await page.locator('.breadcrumb, .hvac-breadcrumb, nav[aria-label*="breadcrumb"]').isVisible({ timeout: 3000 }); logTest('Breadcrumb navigation present', hasBreadcrumbs); await takeScreenshot(page, 'event-navigation', 'Event navigation testing'); return true; } catch (error) { logTest('Event navigation', false, `Navigation error: ${error.message}`); await takeScreenshot(page, 'navigation-error', 'Event navigation failed'); return false; } } /** * Test form validation and error handling */ async function testFormValidation(page) { console.log('\nšŸ” FORM VALIDATION TEST'); console.log('─'.repeat(50)); try { // Navigate to event creation page await page.goto(`${CONFIG.baseUrl}/events/network/add/`, { waitUntil: 'networkidle', timeout: CONFIG.timeouts.navigation }); const hasForm = await page.locator('form').first().isVisible({ timeout: 5000 }); if (hasForm) { // Test empty form submission const submitButton = page.locator('input[type="submit"], button[type="submit"]').first(); if (await submitButton.isVisible()) { await submitButton.click(); await page.waitForTimeout(CONFIG.timeouts.wait); // Check for validation errors - be more specific to avoid strict mode violations const hasValidationErrors = await page.locator('.tribe-community-notice-error, .required').first().isVisible({ timeout: 3000 }); logTest('Form validation on empty submission', hasValidationErrors, 'Required field validation'); // Test field-specific validation const titleField = page.locator('#post_title, input[name="post_title"]').first(); if (await titleField.isVisible()) { await titleField.fill('Test'); await titleField.fill(''); // Clear field await titleField.blur(); const hasFieldError = await page.locator('.error, .invalid').first().isVisible({ timeout: 2000 }); logTest('Individual field validation', hasFieldError, 'Field-level validation working'); } } } await takeScreenshot(page, 'form-validation', 'Form validation testing'); return hasForm; } catch (error) { logTest('Form validation', false, `Validation error: ${error.message}`); await takeScreenshot(page, 'validation-error', 'Form validation failed'); return false; } } /** * Main test execution function */ async function runHVACTrainerEventsTest() { console.log('šŸš€ HVAC TRAINER EVENTS E2E TEST SUITE'); console.log('═'.repeat(70)); console.log(`šŸ“… Test Started: ${new Date().toLocaleString()}`); console.log(`🌐 Base URL: ${CONFIG.baseUrl}`); console.log(`šŸ‘¤ Test User: ${CONFIG.credentials.username}`); console.log('═'.repeat(70)); // Browser setup const browser = await chromium.launch({ headless: CONFIG.headless, args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage'] }); const context = await browser.newContext({ ignoreHTTPSErrors: true, viewport: { width: 1920, height: 1080 }, userAgent: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36' }); const page = await context.newPage(); // Enable request/response logging for debugging page.on('requestfailed', request => { console.log(` āš ļø Request failed: ${request.url()}`); }); let eventId = null; try { // Test Sequence const loginSuccessful = await loginAsTrainer(page); if (loginSuccessful) { await testTrainerDashboard(page); await testEventManagePage(page); // Event creation and editing workflow eventId = await testEventCreation(page); if (eventId) { await testEventEditing(page, eventId); } await testMyEventsPage(page); await testEventNavigation(page); await testFormValidation(page); } else { console.log('āš ļø Skipping remaining tests due to authentication failure'); } } catch (error) { console.error('āŒ Test suite error:', error.message); await takeScreenshot(page, 'test-suite-error', 'Critical test suite error'); logTest('Test suite execution', false, `Critical error: ${error.message}`); } finally { await browser.close(); } // Generate comprehensive test report console.log('\n' + '═'.repeat(70)); console.log('šŸ“Š COMPREHENSIVE TEST REPORT'); console.log('═'.repeat(70)); console.log(`šŸ“… Test Completed: ${new Date().toLocaleString()}`); console.log(`ā±ļø Total Tests: ${testResults.total}`); console.log(`āœ… Passed: ${testResults.passed}`); console.log(`āŒ Failed: ${testResults.failed}`); if (testResults.total > 0) { const successRate = Math.round((testResults.passed / testResults.total) * 100); console.log(`šŸ“ˆ Success Rate: ${successRate}%`); if (successRate >= 80) { console.log('šŸŽ‰ TEST SUITE: EXCELLENT PERFORMANCE'); } else if (successRate >= 60) { console.log('āš ļø TEST SUITE: NEEDS ATTENTION'); } else { console.log('🚨 TEST SUITE: CRITICAL ISSUES'); } } // Detailed results if (testResults.failed > 0) { console.log('\nāŒ FAILED TESTS:'); console.log('─'.repeat(50)); testResults.details.filter(t => !t.passed).forEach((test, index) => { console.log(`${index + 1}. ${test.name}`); if (test.details) console.log(` Details: ${test.details}`); if (test.screenshot) console.log(` Screenshot: ${path.basename(test.screenshot)}`); }); } if (testResults.passed > 0) { console.log('\nāœ… PASSED TESTS:'); console.log('─'.repeat(50)); testResults.details.filter(t => t.passed).forEach((test, index) => { console.log(`${index + 1}. ${test.name}`); if (test.details) console.log(` Details: ${test.details}`); }); } // Recommendations console.log('\nšŸ” RECOMMENDATIONS:'); console.log('─'.repeat(50)); const authTest = testResults.details.find(t => t.name.includes('Login authentication')); const createTest = testResults.details.find(t => t.name.includes('Event creation')); const editTest = testResults.details.find(t => t.name.includes('Event update')); if (!authTest?.passed) { console.log('• Fix trainer authentication system'); console.log('• Verify test user credentials and permissions'); } if (!createTest?.passed) { console.log('• Debug TEC Community Events integration'); console.log('• Check event creation form accessibility'); console.log('• Verify required plugins are active'); } if (eventId && !editTest?.passed) { console.log('• Fix event editing permissions'); console.log('• Debug event update workflow'); } if (testResults.failed === 0) { console.log('• All tests passed! System is working correctly'); console.log('• Consider adding more edge case tests'); console.log('• Monitor performance and user experience'); } console.log('\n' + '═'.repeat(70)); console.log('šŸ TEST SUITE COMPLETE'); console.log('═'.repeat(70)); // Return exit code based on results process.exit(testResults.failed > 0 ? 1 : 0); } // Execute the test suite if (require.main === module) { runHVACTrainerEventsTest().catch(error => { console.error('Fatal test error:', error); process.exit(1); }); } module.exports = { runHVACTrainerEventsTest, CONFIG, TEST_EVENT_DATA };