upskill-event-manager/tests/page-objects/event-management/EventEditing.js
Ben 7c9ca65cf2
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
feat: add comprehensive test framework and test files
- 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>
2025-08-29 23:23:26 -03:00

821 lines
No EOL
28 KiB
JavaScript

/**
* Event Editing Page Object Model
*
* Handles event editing and modification functionality including:
* - Event detail editing and updates
* - Status transitions (Draft → Pending → Published)
* - Event modification workflows
* - Version control and change tracking
*
* @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 EventEditing extends BasePage {
constructor(page) {
super(page);
// Event editing form selectors
this.selectors = {
// Main form containers
editEventForm: [
'[data-testid="edit-event-form"]',
'.hvac-edit-event-form',
'.tribe-events-community-form',
'.event-edit-form',
'form#edit-event'
],
// Form fields (similar to creation but for editing context)
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'
],
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'
],
status: [
'[data-testid="event-status"]',
'select[name="post_status"]',
'select[name="event_status"]',
'#event-status',
'.event-status-select'
]
},
// Event status indicators
status: {
draft: [
'[data-testid="status-draft"]',
'.status-draft',
'.event-status-draft',
'text=Draft'
],
pending: [
'[data-testid="status-pending"]',
'.status-pending',
'.event-status-pending',
'text=Pending Review'
],
published: [
'[data-testid="status-published"]',
'.status-published',
'.event-status-published',
'text=Published'
],
cancelled: [
'[data-testid="status-cancelled"]',
'.status-cancelled',
'.event-status-cancelled',
'text=Cancelled'
]
},
// Action buttons
buttons: {
update: [
'[data-testid="update-event"]',
'input[value="Update Event"]',
'button[name="update"]',
'#update-event',
'.update-event-btn'
],
publish: [
'[data-testid="publish-event"]',
'input[value="Publish"]',
'button[name="publish"]',
'#publish-event',
'.publish-event-btn'
],
submitForReview: [
'[data-testid="submit-for-review"]',
'input[value="Submit for Review"]',
'button[name="submit_review"]',
'#submit-for-review',
'.submit-review-btn'
],
saveDraft: [
'[data-testid="save-draft"]',
'input[value="Save Draft"]',
'button[name="save_draft"]',
'#save-draft',
'.save-draft-btn'
],
delete: [
'[data-testid="delete-event"]',
'button[name="delete"]',
'#delete-event',
'.delete-event-btn',
'text=Delete Event'
],
cancel: [
'[data-testid="cancel-event"]',
'button[name="cancel_event"]',
'#cancel-event',
'.cancel-event-btn',
'text=Cancel Event'
],
preview: [
'[data-testid="preview-event"]',
'button[name="preview"]',
'#preview-event',
'.preview-event-btn'
]
},
// Event info display
eventInfo: {
id: [
'[data-testid="event-id"]',
'.event-id',
'#event-id'
],
createdDate: [
'[data-testid="created-date"]',
'.created-date',
'.event-created'
],
lastModified: [
'[data-testid="last-modified"]',
'.last-modified',
'.event-modified'
],
author: [
'[data-testid="event-author"]',
'.event-author',
'.post-author'
]
},
// Attendee management
attendees: {
list: [
'[data-testid="attendees-list"]',
'.attendees-list',
'.event-attendees'
],
count: [
'[data-testid="attendee-count"]',
'.attendee-count',
'.event-attendee-count'
],
addAttendee: [
'[data-testid="add-attendee"]',
'.add-attendee-btn',
'button.add-attendee'
],
removeAttendee: [
'[data-testid="remove-attendee"]',
'.remove-attendee-btn',
'button.remove-attendee'
]
},
// Validation and messages
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'
],
warnings: [
'[data-testid="form-warnings"]',
'.form-warnings',
'.event-warnings',
'.warning-message'
]
},
// Version control and history
versions: {
history: [
'[data-testid="version-history"]',
'.version-history',
'.event-revisions'
],
compareLink: [
'[data-testid="compare-versions"]',
'.compare-versions',
'a.compare-revisions'
]
}
};
this.urls = {
editEvent: '/trainer/edit-event/',
tecEditEvent: '/trainer/tec-edit-event/',
customEditEvent: '/edit-event/',
communityEdit: '/events/community/edit/'
};
}
/**
* Navigate to event editing page
*/
async navigateToEdit(eventId, pageType = 'standard') {
let url;
switch (pageType) {
case 'tec':
url = `${this.urls.tecEditEvent}?event_id=${eventId}`;
break;
case 'community':
url = `${this.urls.communityEdit}${eventId}/`;
break;
case 'custom':
url = `${this.urls.customEditEvent}?id=${eventId}`;
break;
default:
url = `${this.urls.editEvent}?event_id=${eventId}`;
}
await this.goto(url);
await this.waitForEditFormLoad();
console.log(`✅ Navigated to event editing page: ${pageType} (ID: ${eventId})`);
}
/**
* Wait for edit form to load completely
*/
async waitForEditFormLoad() {
// Wait for form container
await this.waitForVisible(this.selectors.editEventForm, { 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 AJAX to complete
await this.waitForWordPressReady();
await this.waitForAjax();
console.log('✅ Event editing form loaded');
}
/**
* Get current event information
*/
async getEventInfo() {
const eventInfo = {};
// Get event ID if available
if (await this.isVisible(this.selectors.eventInfo.id)) {
eventInfo.id = await this.getText(this.selectors.eventInfo.id);
}
// Get current values from form fields
eventInfo.title = await this.getInputValue(this.selectors.fields.title);
eventInfo.description = await this.getInputValue(this.selectors.fields.description);
eventInfo.startDate = await this.getInputValue(this.selectors.fields.startDate);
eventInfo.endDate = await this.getInputValue(this.selectors.fields.endDate);
eventInfo.startTime = await this.getInputValue(this.selectors.fields.startTime);
eventInfo.endTime = await this.getInputValue(this.selectors.fields.endTime);
eventInfo.capacity = await this.getInputValue(this.selectors.fields.capacity);
eventInfo.cost = await this.getInputValue(this.selectors.fields.cost);
// Get status
eventInfo.status = await this.getCurrentStatus();
// Get created/modified dates if available
if (await this.isVisible(this.selectors.eventInfo.createdDate)) {
eventInfo.createdDate = await this.getText(this.selectors.eventInfo.createdDate);
}
if (await this.isVisible(this.selectors.eventInfo.lastModified)) {
eventInfo.lastModified = await this.getText(this.selectors.eventInfo.lastModified);
}
console.log('📋 Current event info:', eventInfo);
return eventInfo;
}
/**
* Update specific event fields
*/
async updateEventFields(updates) {
console.log('✏️ Updating event fields:', Object.keys(updates));
for (const [fieldName, newValue] of Object.entries(updates)) {
const fieldSelectors = this.selectors.fields[fieldName];
if (fieldSelectors && await this.isVisible(fieldSelectors)) {
await this.clear(fieldSelectors);
await this.fill(fieldSelectors, newValue);
console.log(` ✓ Updated ${fieldName}: ${newValue}`);
} else {
console.warn(` ⚠️ Field '${fieldName}' not found or not visible`);
}
}
// Wait for any auto-save or validation
await this.waitForAjax();
}
/**
* Get current event status
*/
async getCurrentStatus() {
// Check for status indicators
const statusChecks = [
{ status: 'published', selectors: this.selectors.status.published },
{ status: 'pending', selectors: this.selectors.status.pending },
{ status: 'draft', selectors: this.selectors.status.draft },
{ status: 'cancelled', selectors: this.selectors.status.cancelled }
];
for (const { status, selectors } of statusChecks) {
if (await this.isVisible(selectors)) {
return status;
}
}
// Check status dropdown if available
if (await this.isVisible(this.selectors.fields.status)) {
return await this.getSelectedValue(this.selectors.fields.status);
}
return 'unknown';
}
/**
* Change event status
*/
async changeStatus(newStatus) {
console.log(`🔄 Changing event status to: ${newStatus}`);
// If status dropdown is available
if (await this.isVisible(this.selectors.fields.status)) {
await this.selectByValue(this.selectors.fields.status, newStatus);
console.log(`✅ Status changed via dropdown: ${newStatus}`);
return;
}
// Use status-specific buttons
const statusButtonMap = {
'draft': this.selectors.buttons.saveDraft,
'pending': this.selectors.buttons.submitForReview,
'published': this.selectors.buttons.publish
};
const buttonSelectors = statusButtonMap[newStatus];
if (buttonSelectors && await this.isVisible(buttonSelectors)) {
await this.click(buttonSelectors);
console.log(`✅ Status changed via button: ${newStatus}`);
} else {
throw new Error(`Cannot change status to ${newStatus} - no available method found`);
}
}
/**
* Update event and save changes
*/
async updateEvent(options = {}) {
const {
waitForRedirect = true,
expectedOutcome = 'success',
timeout = 15000
} = options;
console.log('💾 Updating event');
// Take screenshot before update
await this.takeScreenshot('before-event-update');
// Click update button (priority order)
const updateButtons = [
this.selectors.buttons.update,
this.selectors.buttons.saveDraft,
this.selectors.buttons.publish
];
let updateClicked = false;
for (const buttonSelectors of updateButtons) {
if (await this.isVisible(buttonSelectors)) {
await this.click(buttonSelectors, { timeout });
updateClicked = true;
break;
}
}
if (!updateClicked) {
throw new Error('No update button found on event edit form');
}
if (waitForRedirect) {
// Wait for form processing
await this.waitForAjax();
// Wait for success/error message or URL change
if (expectedOutcome === 'success') {
await Promise.race([
this.waitForVisible(this.selectors.validation.success, { timeout }),
this.waitForUrlChange(timeout)
]);
console.log('✅ Event updated successfully');
} else if (expectedOutcome === 'error') {
await this.waitForVisible(this.selectors.validation.errors, { timeout });
console.log('⚠️ Event update resulted in expected error');
}
}
// Take screenshot after update
await this.takeScreenshot('after-event-update');
return await this.getUpdateResult();
}
/**
* Get update result with status and messages
*/
async getUpdateResult() {
// 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,
currentUrl: await this.page.url(),
newStatus: await this.getCurrentStatus()
};
}
// Check for error messages
if (await this.isVisible(this.selectors.validation.errors)) {
const errorMessage = await this.getText(this.selectors.validation.errors);
return {
success: false,
message: errorMessage,
currentUrl: await this.page.url(),
currentStatus: await this.getCurrentStatus()
};
}
// Check for warnings
if (await this.isVisible(this.selectors.validation.warnings)) {
const warningMessage = await this.getText(this.selectors.validation.warnings);
return {
success: true,
warning: true,
message: warningMessage,
currentUrl: await this.page.url(),
newStatus: await this.getCurrentStatus()
};
}
// Default success if no explicit messages
return {
success: true,
message: 'Event updated (no confirmation message displayed)',
currentUrl: await this.page.url(),
newStatus: await this.getCurrentStatus()
};
}
/**
* Delete event (with confirmation handling)
*/
async deleteEvent(confirmDelete = true) {
console.log('🗑️ Attempting to delete event');
if (!await this.isVisible(this.selectors.buttons.delete)) {
throw new Error('Delete button not available');
}
await this.click(this.selectors.buttons.delete);
// Handle confirmation dialog
if (confirmDelete) {
// Wait for browser confirmation dialog
this.page.on('dialog', async dialog => {
console.log(`🔔 Confirmation dialog: ${dialog.message()}`);
await dialog.accept();
});
}
await this.waitForAjax();
const currentUrl = await this.page.url();
const result = {
deleted: !currentUrl.includes('edit-event'),
currentUrl
};
console.log(result.deleted ? '✅ Event deleted' : '⚠️ Event deletion may have failed');
return result;
}
/**
* Cancel event (change status to cancelled)
*/
async cancelEvent(reason = '') {
console.log('❌ Cancelling event');
// If there's a specific cancel button
if (await this.isVisible(this.selectors.buttons.cancel)) {
await this.click(this.selectors.buttons.cancel);
// Handle reason input if available
const reasonInput = await this.getVisibleSelector([
'[data-testid="cancel-reason"]',
'textarea[name="cancel_reason"]',
'#cancel-reason'
]);
if (reasonInput && reason) {
await this.fill(reasonInput, reason);
}
await this.waitForAjax();
} else {
// Use status change
await this.changeStatus('cancelled');
}
return await this.updateEvent();
}
/**
* Get attendee information
*/
async getAttendeeInfo() {
const attendeeInfo = {
count: 0,
attendees: [],
hasAttendeeManagement: false
};
// Check if attendee management is available
if (await this.isVisible(this.selectors.attendees.list)) {
attendeeInfo.hasAttendeeManagement = true;
// Get attendee count
if (await this.isVisible(this.selectors.attendees.count)) {
const countText = await this.getText(this.selectors.attendees.count);
attendeeInfo.count = parseInt(countText.match(/\d+/)?.[0] || '0', 10);
}
// Get attendee list
const attendeeElements = await this.page.locator(`${this.selectors.attendees.list.join(', ')} .attendee`).all();
for (const attendeeElement of attendeeElements) {
const attendeeText = await attendeeElement.textContent();
attendeeInfo.attendees.push(attendeeText.trim());
}
}
console.log('👥 Attendee info:', attendeeInfo);
return attendeeInfo;
}
/**
* Test event status transitions
*/
async testStatusTransitions() {
console.log('🔄 Testing status transitions');
const transitions = [];
const currentStatus = await this.getCurrentStatus();
transitions.push({ from: null, to: currentStatus, timestamp: Date.now() });
// Test possible transitions based on current status
const transitionMap = {
'draft': ['pending', 'published'],
'pending': ['published', 'draft'],
'published': ['draft'],
'cancelled': ['draft']
};
const possibleTransitions = transitionMap[currentStatus] || [];
for (const targetStatus of possibleTransitions) {
try {
await this.changeStatus(targetStatus);
await this.updateEvent({ waitForRedirect: false });
const newStatus = await this.getCurrentStatus();
transitions.push({
from: currentStatus,
to: newStatus,
expected: targetStatus,
successful: newStatus === targetStatus,
timestamp: Date.now()
});
console.log(` ${currentStatus}${newStatus} ${newStatus === targetStatus ? '✅' : '❌'}`);
} catch (error) {
transitions.push({
from: currentStatus,
to: targetStatus,
expected: targetStatus,
successful: false,
error: error.message,
timestamp: Date.now()
});
console.log(` ${currentStatus}${targetStatus} ❌ (${error.message})`);
}
}
return transitions;
}
/**
* Compare current event data with expected data
*/
async compareEventData(expectedData) {
const currentData = await this.getEventInfo();
const comparison = {
matches: {},
differences: {},
missing: {}
};
for (const [field, expectedValue] of Object.entries(expectedData)) {
if (currentData.hasOwnProperty(field)) {
const matches = currentData[field] === expectedValue;
if (matches) {
comparison.matches[field] = expectedValue;
} else {
comparison.differences[field] = {
expected: expectedValue,
actual: currentData[field]
};
}
} else {
comparison.missing[field] = expectedValue;
}
}
const overallMatch = Object.keys(comparison.differences).length === 0 &&
Object.keys(comparison.missing).length === 0;
console.log('🔍 Event data comparison:', { overallMatch, ...comparison });
return { overallMatch, ...comparison };
}
/**
* Preview event
*/
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;
}
console.log('⚠️ Preview not available');
return false;
}
/**
* Get version history if available
*/
async getVersionHistory() {
if (!await this.isVisible(this.selectors.versions.history)) {
return { hasVersioning: false };
}
const versions = [];
const versionElements = await this.page.locator(`${this.selectors.versions.history.join(', ')} .revision`).all();
for (const versionElement of versionElements) {
const versionText = await versionElement.textContent();
versions.push(versionText.trim());
}
return {
hasVersioning: true,
versions,
count: versions.length
};
}
/**
* Take screenshot of edit form
*/
async screenshotEditForm(name = 'event-edit-form') {
return await this.takeScreenshot(name, { fullPage: true });
}
/**
* Verify edit form access and functionality
*/
async verifyEditAccess() {
const checks = {
formVisible: await this.isVisible(this.selectors.editEventForm),
fieldsEditable: await this.areFieldsEditable(),
updateButtonAvailable: await this.isVisible(this.selectors.buttons.update),
canChangeStatus: await this.canChangeStatus(),
eventInfoDisplayed: await this.isVisible(this.selectors.eventInfo.id)
};
const hasEditAccess = Object.values(checks).every(check => check === true);
console.log('🔐 Edit access verification:', { hasEditAccess, ...checks });
return { hasEditAccess, checks };
}
/**
* Check if form fields are editable
*/
async areFieldsEditable() {
const fieldsToCheck = ['title', 'description', 'startDate', 'capacity'];
for (const fieldName of fieldsToCheck) {
const fieldSelectors = this.selectors.fields[fieldName];
if (await this.isVisible(fieldSelectors)) {
const isDisabled = await this.page.locator(fieldSelectors.join(', ')).first().isDisabled();
if (isDisabled) return false;
}
}
return true;
}
/**
* Check if user can change event status
*/
async canChangeStatus() {
return await this.isVisible(this.selectors.fields.status) ||
await this.isVisible(this.selectors.buttons.publish) ||
await this.isVisible(this.selectors.buttons.submitForReview);
}
}
module.exports = EventEditing;