/** * Event Creation Page Object Model * * Handles event creation functionality including: * - Standard HVAC event creation * - TEC (The Events Calendar) integration * - Form validation and error handling * - Event data population and submission * * @package HVAC_Community_Events * @version 2.0.0 * @created 2025-08-27 * @author Agent-B-Event-Management */ const BasePage = require('../base/BasePage'); const { expect } = require('@playwright/test'); class EventCreation extends BasePage { constructor(page) { super(page); // Event creation form selectors this.selectors = { // Main form containers createEventForm: [ '[data-testid="create-event-form"]', '.hvac-create-event-form', '.tribe-events-community-form', '.event-creation-form', 'form#create-event' ], // Form fields - Standard HVAC fields: { title: [ '[data-testid="event-title"]', 'input[name="event_title"]', 'input[name="post_title"]', '#event-title', '.event-title-input' ], description: [ '[data-testid="event-description"]', 'textarea[name="event_description"]', 'textarea[name="post_content"]', '#event-description', '.event-description-textarea' ], startDate: [ '[data-testid="event-start-date"]', 'input[name="event_start_date"]', 'input[name="EventStartDate"]', '#event-start-date', '.event-start-date' ], endDate: [ '[data-testid="event-end-date"]', 'input[name="event_end_date"]', 'input[name="EventEndDate"]', '#event-end-date', '.event-end-date' ], startTime: [ '[data-testid="event-start-time"]', 'input[name="event_start_time"]', 'input[name="EventStartTime"]', '#event-start-time', '.event-start-time' ], endTime: [ '[data-testid="event-end-time"]', 'input[name="event_end_time"]', 'input[name="EventEndTime"]', '#event-end-time', '.event-end-time' ], venue: [ '[data-testid="event-venue"]', 'select[name="venue"]', 'select[name="event_venue"]', '#event-venue', '.venue-select' ], organizer: [ '[data-testid="event-organizer"]', 'select[name="organizer"]', 'select[name="event_organizer"]', '#event-organizer', '.organizer-select' ], category: [ '[data-testid="event-category"]', 'select[name="event_category"]', 'select[name="tax_input[tribe_events_cat][]"]', '#event-category', '.event-category-select' ], capacity: [ '[data-testid="event-capacity"]', 'input[name="event_capacity"]', 'input[name="_EventCapacity"]', '#event-capacity', '.event-capacity' ], cost: [ '[data-testid="event-cost"]', 'input[name="event_cost"]', 'input[name="_EventCost"]', '#event-cost', '.event-cost' ], website: [ '[data-testid="event-website"]', 'input[name="event_website"]', 'input[name="_EventURL"]', '#event-website', '.event-website' ] }, // TEC specific fields tecFields: { allDayEvent: [ '[data-testid="all-day-event"]', 'input[name="_EventAllDay"]', '#allDayCheckbox', '.all-day-checkbox' ], timezone: [ '[data-testid="event-timezone"]', 'select[name="_EventTimezone"]', '#event-timezone', '.timezone-select' ], showMapLink: [ '[data-testid="show-map-link"]', 'input[name="_EventShowMapLink"]', '#show-map-link', '.show-map-link' ], showMap: [ '[data-testid="show-map"]', 'input[name="_EventShowMap"]', '#show-map', '.show-map' ] }, // Action buttons buttons: { submit: [ '[data-testid="submit-event"]', 'input[type="submit"]', 'button[type="submit"]', '#submit-event', '.submit-event-btn', '.tribe-community-event-submit' ], preview: [ '[data-testid="preview-event"]', 'button[name="preview"]', '#preview-event', '.preview-event-btn' ], saveDraft: [ '[data-testid="save-draft"]', 'button[name="save_draft"]', '#save-draft', '.save-draft-btn' ], cancel: [ '[data-testid="cancel-event"]', 'button[name="cancel"]', '#cancel-event', '.cancel-btn' ] }, // Validation and error elements validation: { errors: [ '[data-testid="form-errors"]', '.form-errors', '.tribe-community-notice-error', '.event-validation-errors', '.error-message' ], success: [ '[data-testid="form-success"]', '.form-success', '.tribe-community-notice-success', '.event-success-message', '.success-message' ], fieldError: [ '.field-error', '.input-error', '.validation-error' ] }, // Loading states loading: [ '[data-testid="form-loading"]', '.form-loading', '.tribe-community-loading', '.loading-spinner' ] }; this.urls = { createEvent: '/trainer/create-event/', tecCreateEvent: '/trainer/tec-create-event/', customCreateEvent: '/create-event/', communityCreate: '/events/community/add/' }; // Test data templates this.testEventData = { basic: { title: 'Test HVAC Training Event', description: 'Comprehensive HVAC training covering heat pump installation and maintenance.', startDate: this.getFormattedDate(7), // 7 days from now endDate: this.getFormattedDate(7), startTime: '09:00', endTime: '17:00', capacity: '25', cost: '299.00' }, advanced: { title: 'Advanced Heat Pump Diagnostics Workshop', description: 'Deep dive into advanced heat pump diagnostic techniques using measureQuick tools and protocols.', startDate: this.getFormattedDate(14), endDate: this.getFormattedDate(15), startTime: '08:30', endTime: '16:30', capacity: '15', cost: '499.00', website: 'https://upskill-staging.measurequick.com/advanced-diagnostics' }, multiDay: { title: 'HVAC Certification Bootcamp', description: 'Intensive 3-day certification program covering all aspects of HVAC systems.', startDate: this.getFormattedDate(21), endDate: this.getFormattedDate(23), startTime: '09:00', endTime: '17:00', capacity: '20', cost: '799.00' } }; } /** * Navigate to event creation page */ async navigate(pageType = 'standard') { let url; switch (pageType) { case 'tec': url = this.urls.tecCreateEvent; break; case 'community': url = this.urls.communityCreate; break; case 'custom': url = this.urls.customCreateEvent; break; default: url = this.urls.createEvent; } await this.goto(url); await this.waitForFormLoad(); console.log(`βœ… Navigated to event creation page: ${pageType}`); } /** * Wait for event creation form to load completely */ async waitForFormLoad() { // Wait for form container await this.waitForVisible(this.selectors.createEventForm, { timeout: 10000 }); // Wait for essential fields await this.waitForVisible(this.selectors.fields.title, { timeout: 5000 }); await this.waitForVisible(this.selectors.fields.description, { timeout: 5000 }); // Wait for WordPress and any AJAX to complete await this.waitForWordPressReady(); await this.waitForAjax(); // Wait for loading indicators to disappear await this.waitForHidden(this.selectors.loading, { timeout: 3000 }); console.log('βœ… Event creation form loaded'); } /** * Fill event creation form with provided data */ async fillEventForm(eventData) { console.log('πŸ“ Filling event form with data:', eventData.title); // Fill basic event information if (eventData.title) { await this.fill(this.selectors.fields.title, eventData.title); } if (eventData.description) { await this.fill(this.selectors.fields.description, eventData.description); } // Fill date and time information if (eventData.startDate) { await this.fill(this.selectors.fields.startDate, eventData.startDate); } if (eventData.endDate) { await this.fill(this.selectors.fields.endDate, eventData.endDate); } if (eventData.startTime) { await this.fill(this.selectors.fields.startTime, eventData.startTime); } if (eventData.endTime) { await this.fill(this.selectors.fields.endTime, eventData.endTime); } // Fill additional event details if (eventData.capacity) { await this.fill(this.selectors.fields.capacity, eventData.capacity); } if (eventData.cost) { await this.fill(this.selectors.fields.cost, eventData.cost); } if (eventData.website) { await this.fill(this.selectors.fields.website, eventData.website); } // Handle venue selection if provided if (eventData.venue) { await this.selectVenue(eventData.venue); } // Handle organizer selection if provided if (eventData.organizer) { await this.selectOrganizer(eventData.organizer); } // Handle category selection if provided if (eventData.category) { await this.selectCategory(eventData.category); } // Wait for form validation await this.waitForAjax(); console.log('βœ… Event form filled successfully'); } /** * Submit event creation form */ async submitEvent(options = {}) { const { waitForRedirect = true, expectedOutcome = 'success', timeout = 15000 } = options; console.log('πŸš€ Submitting event creation form'); // Take screenshot before submission await this.takeScreenshot('before-event-submission'); // Click submit button await this.click(this.selectors.buttons.submit, { timeout }); if (waitForRedirect) { // Wait for form processing await this.waitForAjax(); // Wait for either success or error message if (expectedOutcome === 'success') { await Promise.race([ this.waitForVisible(this.selectors.validation.success, { timeout }), this.waitForUrlChange(timeout) ]); console.log('βœ… Event submitted successfully'); } else if (expectedOutcome === 'error') { await this.waitForVisible(this.selectors.validation.errors, { timeout }); console.log('⚠️ Event submission resulted in expected error'); } } // Take screenshot after submission await this.takeScreenshot('after-event-submission'); return await this.getSubmissionResult(); } /** * Get form submission result */ async getSubmissionResult() { // Check for success messages if (await this.isVisible(this.selectors.validation.success)) { const successMessage = await this.getText(this.selectors.validation.success); return { success: true, message: successMessage, redirectUrl: await this.page.url() }; } // Check for error messages if (await this.isVisible(this.selectors.validation.errors)) { const errorMessage = await this.getText(this.selectors.validation.errors); const fieldErrors = await this.getFieldErrors(); return { success: false, message: errorMessage, fieldErrors, currentUrl: await this.page.url() }; } // Check if URL changed (successful redirect) const currentUrl = await this.page.url(); const isRedirected = !currentUrl.includes('create-event'); return { success: isRedirected, redirected: isRedirected, currentUrl }; } /** * Get field-specific validation errors */ async getFieldErrors() { const fieldErrors = {}; const errorElements = await this.page.locator(this.selectors.validation.fieldError.join(', ')).all(); for (const errorElement of errorElements) { const fieldName = await errorElement.getAttribute('data-field') || await errorElement.getAttribute('for') || 'unknown'; const errorText = await errorElement.textContent(); fieldErrors[fieldName] = errorText.trim(); } return fieldErrors; } /** * Select venue from dropdown */ async selectVenue(venueName) { if (await this.isVisible(this.selectors.fields.venue)) { // If venue is a dropdown await this.selectByText(this.selectors.fields.venue, venueName); } else { // If venue needs to be created or searched const venueInput = await this.getVisibleSelector([ 'input[name="venue_name"]', '#venue-search', '.venue-input' ]); if (venueInput) { await this.fill(venueInput, venueName); await this.waitForAjax(); } } console.log(`πŸ“ Selected venue: ${venueName}`); } /** * Select organizer from dropdown */ async selectOrganizer(organizerName) { if (await this.isVisible(this.selectors.fields.organizer)) { await this.selectByText(this.selectors.fields.organizer, organizerName); } else { // Handle organizer creation if needed const organizerInput = await this.getVisibleSelector([ 'input[name="organizer_name"]', '#organizer-search', '.organizer-input' ]); if (organizerInput) { await this.fill(organizerInput, organizerName); await this.waitForAjax(); } } console.log(`πŸ‘€ Selected organizer: ${organizerName}`); } /** * Select event category */ async selectCategory(categoryName) { if (await this.isVisible(this.selectors.fields.category)) { await this.selectByText(this.selectors.fields.category, categoryName); console.log(`🏷️ Selected category: ${categoryName}`); } } /** * Create event with test data */ async createTestEvent(dataType = 'basic', options = {}) { const eventData = { ...this.testEventData[dataType], ...options.data }; console.log(`πŸ§ͺ Creating test event: ${dataType}`); await this.fillEventForm(eventData); const result = await this.submitEvent(options); return { ...result, eventData, testType: dataType }; } /** * Validate form fields */ async validateForm() { const validationResults = { requiredFields: [], validationErrors: [], fieldsPresent: {} }; // Check required fields const requiredFields = ['title', 'description', 'startDate', 'startTime']; for (const fieldName of requiredFields) { const fieldSelectors = this.selectors.fields[fieldName]; const isPresent = await this.isVisible(fieldSelectors); validationResults.fieldsPresent[fieldName] = isPresent; if (!isPresent) { validationResults.requiredFields.push(fieldName); } } // Check for existing validation errors if (await this.isVisible(this.selectors.validation.errors)) { const errors = await this.getText(this.selectors.validation.errors); validationResults.validationErrors.push(errors); } console.log('πŸ” Form validation results:', validationResults); return validationResults; } /** * Test form validation by submitting empty form */ async testValidation() { console.log('πŸ§ͺ Testing form validation'); // Clear any existing data await this.clearForm(); // Attempt to submit empty form await this.click(this.selectors.buttons.submit); // Wait for validation errors await this.waitForVisible(this.selectors.validation.errors, { timeout: 5000 }); return await this.getFieldErrors(); } /** * Clear all form fields */ async clearForm() { const fieldsToCheck = ['title', 'description', 'startDate', 'endDate', 'startTime', 'endTime', 'capacity', 'cost']; for (const fieldName of fieldsToCheck) { const fieldSelectors = this.selectors.fields[fieldName]; if (await this.isVisible(fieldSelectors)) { await this.clear(fieldSelectors); } } console.log('🧹 Form cleared'); } /** * Handle TEC-specific functionality */ async configureTECOptions(options = {}) { if (options.allDay) { await this.check(this.selectors.tecFields.allDayEvent); console.log('β˜‘οΈ Set as all-day event'); } if (options.timezone) { await this.selectByText(this.selectors.tecFields.timezone, options.timezone); console.log(`🌍 Set timezone: ${options.timezone}`); } if (options.showMap !== undefined) { if (options.showMap) { await this.check(this.selectors.tecFields.showMap); } else { await this.uncheck(this.selectors.tecFields.showMap); } console.log(`πŸ—ΊοΈ Show map: ${options.showMap}`); } } /** * Preview event before submission */ async previewEvent() { if (await this.isVisible(this.selectors.buttons.preview)) { await this.click(this.selectors.buttons.preview); await this.waitForAjax(); console.log('πŸ‘οΈ Event preview opened'); return true; } return false; } /** * Save event as draft */ async saveDraft() { if (await this.isVisible(this.selectors.buttons.saveDraft)) { await this.click(this.selectors.buttons.saveDraft); await this.waitForAjax(); const result = await this.getSubmissionResult(); console.log('πŸ“ Event saved as draft'); return result; } throw new Error('Save draft button not available'); } /** * Get formatted date for form input */ getFormattedDate(daysFromNow = 0) { const date = new Date(); date.setDate(date.getDate() + daysFromNow); return date.toISOString().split('T')[0]; // YYYY-MM-DD format } /** * Take screenshot of form state */ async screenshotForm(name = 'event-creation-form') { return await this.takeScreenshot(name, { fullPage: true }); } /** * Verify form is accessible and ready for input */ async verifyFormAccessibility() { const checks = { formVisible: await this.isVisible(this.selectors.createEventForm), titleFieldAccessible: await this.isVisible(this.selectors.fields.title), descriptionFieldAccessible: await this.isVisible(this.selectors.fields.description), submitButtonAccessible: await this.isVisible(this.selectors.buttons.submit), hasNoLoadingIndicators: !await this.isVisible(this.selectors.loading) }; const isAccessible = Object.values(checks).every(check => check === true); console.log('β™Ώ Form accessibility check:', { isAccessible, ...checks }); return { isAccessible, checks }; } } module.exports = EventCreation;