Some checks are pending
HVAC Plugin CI/CD Pipeline / Security Analysis (push) Waiting to run
HVAC Plugin CI/CD Pipeline / Code Quality & Standards (push) Waiting to run
HVAC Plugin CI/CD Pipeline / Unit Tests (push) Waiting to run
HVAC Plugin CI/CD Pipeline / Integration Tests (push) Waiting to run
HVAC Plugin CI/CD Pipeline / Deploy to Staging (push) Blocked by required conditions
HVAC Plugin CI/CD Pipeline / Deploy to Production (push) Blocked by required conditions
HVAC Plugin CI/CD Pipeline / Notification (push) Blocked by required conditions
Security Monitoring & Compliance / Dependency Vulnerability Scan (push) Waiting to run
Security Monitoring & Compliance / Secrets & Credential Scan (push) Waiting to run
Security Monitoring & Compliance / WordPress Security Analysis (push) Waiting to run
Security Monitoring & Compliance / Static Code Security Analysis (push) Waiting to run
Security Monitoring & Compliance / Security Compliance Validation (push) Waiting to run
Security Monitoring & Compliance / Security Summary Report (push) Blocked by required conditions
Security Monitoring & Compliance / Security Team Notification (push) Blocked by required conditions
- Add 90+ test files including E2E, unit, and integration tests - Implement Page Object Model (POM) architecture - Add Docker testing environment with comprehensive services - Include modernized test framework with error recovery - Add specialized test suites for master trainer and trainer workflows - Update .gitignore to properly track test infrastructure 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
694 lines
No EOL
23 KiB
JavaScript
694 lines
No EOL
23 KiB
JavaScript
/**
|
|
* 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; |