upskill-event-manager/tests/page-objects/event-management/EventManagement.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

850 lines
No EOL
28 KiB
JavaScript

/**
* Event Management Dashboard Page Object Model
*
* Handles event management dashboard functionality including:
* - Event listing and filtering
* - Bulk operations on events
* - Event status management
* - Attendee management dashboard
* - Event analytics and reporting
*
* @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 EventManagement extends BasePage {
constructor(page) {
super(page);
// Event management dashboard selectors
this.selectors = {
// Main dashboard containers
managementDashboard: [
'[data-testid="event-management-dashboard"]',
'.hvac-event-management',
'.event-management-dashboard',
'.manage-events-dashboard'
],
// Event listing
eventsList: [
'[data-testid="events-list"]',
'.events-list',
'.hvac-events-list',
'.manage-events-list',
'table.events-table'
],
eventRow: [
'[data-testid="event-row"]',
'.event-row',
'tr.event-item',
'.event-listing-item'
],
// Event actions
actions: {
createNew: [
'[data-testid="create-event-btn"]',
'.create-event-btn',
'a[href*="create-event"]',
'button.new-event'
],
bulkActions: [
'[data-testid="bulk-actions"]',
'select[name="bulk_action"]',
'.bulk-actions-select'
],
applyBulk: [
'[data-testid="apply-bulk"]',
'button[name="apply_bulk"]',
'.apply-bulk-btn'
],
export: [
'[data-testid="export-events"]',
'.export-events-btn',
'button.export'
],
import: [
'[data-testid="import-events"]',
'.import-events-btn',
'button.import'
]
},
// Event row actions
rowActions: {
edit: [
'[data-testid="edit-event"]',
'.edit-event',
'a.edit-link',
'text=Edit'
],
view: [
'[data-testid="view-event"]',
'.view-event',
'a.view-link',
'text=View'
],
delete: [
'[data-testid="delete-event"]',
'.delete-event',
'button.delete-link',
'text=Delete'
],
duplicate: [
'[data-testid="duplicate-event"]',
'.duplicate-event',
'button.duplicate-link',
'text=Duplicate'
],
cancel: [
'[data-testid="cancel-event"]',
'.cancel-event',
'button.cancel-link',
'text=Cancel'
]
},
// Filtering and search
filters: {
search: [
'[data-testid="events-search"]',
'input[name="search"]',
'#events-search',
'.search-events'
],
status: [
'[data-testid="filter-status"]',
'select[name="status_filter"]',
'.status-filter'
],
dateRange: [
'[data-testid="date-range-filter"]',
'input[name="date_range"]',
'.date-range-filter'
],
category: [
'[data-testid="category-filter"]',
'select[name="category_filter"]',
'.category-filter'
],
venue: [
'[data-testid="venue-filter"]',
'select[name="venue_filter"]',
'.venue-filter'
]
},
// Sorting
sorting: {
title: [
'[data-testid="sort-title"]',
'th.sort-title',
'.sort-by-title'
],
date: [
'[data-testid="sort-date"]',
'th.sort-date',
'.sort-by-date'
],
status: [
'[data-testid="sort-status"]',
'th.sort-status',
'.sort-by-status'
],
attendees: [
'[data-testid="sort-attendees"]',
'th.sort-attendees',
'.sort-by-attendees'
]
},
// Pagination
pagination: {
container: [
'[data-testid="pagination"]',
'.pagination',
'.events-pagination'
],
previous: [
'[data-testid="prev-page"]',
'.prev-page',
'a.prev'
],
next: [
'[data-testid="next-page"]',
'.next-page',
'a.next'
],
pageNumbers: [
'[data-testid="page-numbers"]',
'.page-numbers',
'.pagination-numbers'
]
},
// Event details in list view
eventDetails: {
title: [
'[data-testid="event-title"]',
'.event-title',
'td.title'
],
date: [
'[data-testid="event-date"]',
'.event-date',
'td.date'
],
status: [
'[data-testid="event-status"]',
'.event-status',
'td.status'
],
attendeeCount: [
'[data-testid="attendee-count"]',
'.attendee-count',
'td.attendees'
],
venue: [
'[data-testid="event-venue"]',
'.event-venue',
'td.venue'
],
organizer: [
'[data-testid="event-organizer"]',
'.event-organizer',
'td.organizer'
]
},
// Dashboard statistics
stats: {
totalEvents: [
'[data-testid="total-events"]',
'.total-events-count',
'.stat-total-events'
],
upcomingEvents: [
'[data-testid="upcoming-events"]',
'.upcoming-events-count',
'.stat-upcoming'
],
draftEvents: [
'[data-testid="draft-events"]',
'.draft-events-count',
'.stat-drafts'
],
publishedEvents: [
'[data-testid="published-events"]',
'.published-events-count',
'.stat-published'
],
totalAttendees: [
'[data-testid="total-attendees"]',
'.total-attendees-count',
'.stat-attendees'
]
},
// Quick actions panel
quickActions: {
panel: [
'[data-testid="quick-actions"]',
'.quick-actions-panel',
'.management-quick-actions'
],
bulkPublish: [
'[data-testid="bulk-publish"]',
'.bulk-publish-btn',
'button.quick-publish'
],
bulkCancel: [
'[data-testid="bulk-cancel"]',
'.bulk-cancel-btn',
'button.quick-cancel'
],
exportSelected: [
'[data-testid="export-selected"]',
'.export-selected-btn',
'button.export-selected'
]
},
// Messages and notifications
messages: {
success: [
'[data-testid="success-message"]',
'.success-message',
'.notice-success'
],
error: [
'[data-testid="error-message"]',
'.error-message',
'.notice-error'
],
warning: [
'[data-testid="warning-message"]',
'.warning-message',
'.notice-warning'
]
},
// Loading states
loading: [
'[data-testid="loading"]',
'.loading-events',
'.management-loading'
]
};
this.urls = {
management: '/trainer/manage-event/',
events: '/trainer/events/',
dashboard: '/trainer/dashboard/',
tecMyEvents: '/trainer/tec-my-events/'
};
}
/**
* Navigate to event management dashboard
*/
async navigate(dashboardType = 'standard') {
let url;
switch (dashboardType) {
case 'tec':
url = this.urls.tecMyEvents;
break;
case 'events':
url = this.urls.events;
break;
default:
url = this.urls.management;
}
await this.goto(url);
await this.waitForDashboardLoad();
console.log(`✅ Navigated to event management: ${dashboardType}`);
}
/**
* Wait for management dashboard to load
*/
async waitForDashboardLoad() {
// Wait for main dashboard container
await this.waitForVisible(this.selectors.managementDashboard, { timeout: 10000 });
// Wait for events list to load
await this.waitForVisible(this.selectors.eventsList, { timeout: 8000 });
// Wait for WordPress and AJAX
await this.waitForWordPressReady();
await this.waitForAjax();
// Wait for any loading indicators to disappear
await this.waitForHidden(this.selectors.loading, { timeout: 5000 });
console.log('✅ Event management dashboard loaded');
}
/**
* Get dashboard statistics
*/
async getDashboardStats() {
const stats = {};
// Get various statistics if available
const statFields = [
'totalEvents',
'upcomingEvents',
'draftEvents',
'publishedEvents',
'totalAttendees'
];
for (const statField of statFields) {
const statSelectors = this.selectors.stats[statField];
if (await this.isVisible(statSelectors)) {
const statText = await this.getText(statSelectors);
stats[statField] = this.extractNumber(statText);
}
}
console.log('📊 Dashboard statistics:', stats);
return stats;
}
/**
* Get list of events with their details
*/
async getEventsList() {
const events = [];
// Find all event rows
const eventRows = await this.page.locator(this.selectors.eventRow.join(', ')).all();
for (const row of eventRows) {
const event = {};
// Extract event details from each row
try {
const titleElement = await row.locator(this.selectors.eventDetails.title.join(', ')).first();
event.title = await titleElement.textContent();
const dateElement = await row.locator(this.selectors.eventDetails.date.join(', ')).first();
event.date = await dateElement.textContent();
const statusElement = await row.locator(this.selectors.eventDetails.status.join(', ')).first();
event.status = await statusElement.textContent();
// Optional fields
if (await row.locator(this.selectors.eventDetails.attendeeCount.join(', ')).count() > 0) {
const attendeeElement = await row.locator(this.selectors.eventDetails.attendeeCount.join(', ')).first();
event.attendees = await attendeeElement.textContent();
}
if (await row.locator(this.selectors.eventDetails.venue.join(', ')).count() > 0) {
const venueElement = await row.locator(this.selectors.eventDetails.venue.join(', ')).first();
event.venue = await venueElement.textContent();
}
events.push(event);
} catch (error) {
console.warn('Could not extract event details from row:', error.message);
}
}
console.log(`📋 Found ${events.length} events in list`);
return events;
}
/**
* Search for events
*/
async searchEvents(searchTerm) {
console.log(`🔍 Searching for events: "${searchTerm}"`);
if (!await this.isVisible(this.selectors.filters.search)) {
throw new Error('Search functionality not available');
}
// Clear existing search and enter new term
await this.clear(this.selectors.filters.search);
await this.fill(this.selectors.filters.search, searchTerm);
await this.page.keyboard.press('Enter');
// Wait for results to update
await this.waitForAjax();
const results = await this.getEventsList();
console.log(`📋 Search returned ${results.length} results`);
return results;
}
/**
* Filter events by status
*/
async filterByStatus(status) {
console.log(`🎯 Filtering events by status: ${status}`);
if (await this.isVisible(this.selectors.filters.status)) {
await this.selectByValue(this.selectors.filters.status, status);
await this.waitForAjax();
const results = await this.getEventsList();
console.log(`📋 Status filter returned ${results.length} results`);
return results;
}
throw new Error('Status filter not available');
}
/**
* Filter events by date range
*/
async filterByDateRange(startDate, endDate) {
console.log(`📅 Filtering events by date range: ${startDate} to ${endDate}`);
if (await this.isVisible(this.selectors.filters.dateRange)) {
const dateRange = `${startDate} - ${endDate}`;
await this.fill(this.selectors.filters.dateRange, dateRange);
await this.waitForAjax();
const results = await this.getEventsList();
console.log(`📋 Date filter returned ${results.length} results`);
return results;
}
throw new Error('Date range filter not available');
}
/**
* Sort events by specified field
*/
async sortEvents(sortBy, direction = 'asc') {
console.log(`⬆️ Sorting events by ${sortBy} (${direction})`);
const sortSelectors = this.selectors.sorting[sortBy];
if (!sortSelectors || !await this.isVisible(sortSelectors)) {
throw new Error(`Sort option '${sortBy}' not available`);
}
// Click sort header
await this.click(sortSelectors);
// Click again if we need descending order
if (direction === 'desc') {
await this.click(sortSelectors);
}
await this.waitForAjax();
console.log(`✅ Events sorted by ${sortBy} (${direction})`);
}
/**
* Select events for bulk operations
*/
async selectEvents(eventTitles) {
console.log(`☑️ Selecting events for bulk operation:`, eventTitles);
const selectedCount = 0;
const eventRows = await this.page.locator(this.selectors.eventRow.join(', ')).all();
for (const row of eventRows) {
try {
const titleElement = await row.locator(this.selectors.eventDetails.title.join(', ')).first();
const title = await titleElement.textContent();
if (eventTitles.includes(title.trim())) {
// Find and click checkbox in this row
const checkbox = await row.locator('input[type="checkbox"]').first();
await checkbox.click();
selectedCount++;
}
} catch (error) {
console.warn('Could not select event in row:', error.message);
}
}
console.log(`✅ Selected ${selectedCount} events`);
return selectedCount;
}
/**
* Perform bulk action on selected events
*/
async performBulkAction(action) {
console.log(`⚡ Performing bulk action: ${action}`);
if (!await this.isVisible(this.selectors.actions.bulkActions)) {
throw new Error('Bulk actions not available');
}
// Select the bulk action
await this.selectByValue(this.selectors.actions.bulkActions, action);
// Apply the bulk action
await this.click(this.selectors.actions.applyBulk);
// Wait for action to complete
await this.waitForAjax();
// Check for success/error messages
const result = await this.getBulkActionResult();
console.log(`${result.success ? '✅' : '❌'} Bulk action result:`, result.message);
return result;
}
/**
* Get bulk action result
*/
async getBulkActionResult() {
// Check for success message
if (await this.isVisible(this.selectors.messages.success)) {
return {
success: true,
message: await this.getText(this.selectors.messages.success)
};
}
// Check for error message
if (await this.isVisible(this.selectors.messages.error)) {
return {
success: false,
message: await this.getText(this.selectors.messages.error)
};
}
// Default success if no messages
return {
success: true,
message: 'Bulk action completed (no confirmation message)'
};
}
/**
* Edit specific event
*/
async editEvent(eventTitle) {
console.log(`✏️ Editing event: ${eventTitle}`);
const eventRow = await this.findEventRow(eventTitle);
if (!eventRow) {
throw new Error(`Event '${eventTitle}' not found`);
}
// Find and click edit button in this row
const editButton = await eventRow.locator(this.selectors.rowActions.edit.join(', ')).first();
await editButton.click();
// Wait for navigation to edit page
await this.waitForUrlChange();
console.log(`✅ Navigated to edit page for: ${eventTitle}`);
}
/**
* View specific event
*/
async viewEvent(eventTitle) {
console.log(`👁️ Viewing event: ${eventTitle}`);
const eventRow = await this.findEventRow(eventTitle);
if (!eventRow) {
throw new Error(`Event '${eventTitle}' not found`);
}
// Find and click view button in this row
const viewButton = await eventRow.locator(this.selectors.rowActions.view.join(', ')).first();
await viewButton.click();
// Wait for navigation or modal to open
await this.waitForAjax();
console.log(`✅ Viewing event: ${eventTitle}`);
}
/**
* Delete specific event
*/
async deleteEvent(eventTitle, confirmDelete = true) {
console.log(`🗑️ Deleting event: ${eventTitle}`);
const eventRow = await this.findEventRow(eventTitle);
if (!eventRow) {
throw new Error(`Event '${eventTitle}' not found`);
}
// Find and click delete button
const deleteButton = await eventRow.locator(this.selectors.rowActions.delete.join(', ')).first();
await deleteButton.click();
// Handle confirmation dialog if it appears
if (confirmDelete) {
this.page.on('dialog', async dialog => {
console.log(`🔔 Confirmation: ${dialog.message()}`);
await dialog.accept();
});
}
await this.waitForAjax();
// Verify deletion
const stillExists = await this.findEventRow(eventTitle);
const deleted = !stillExists;
console.log(`${deleted ? '✅' : '❌'} Event deletion: ${eventTitle}`);
return { deleted, eventTitle };
}
/**
* Duplicate specific event
*/
async duplicateEvent(eventTitle) {
console.log(`📋 Duplicating event: ${eventTitle}`);
const eventRow = await this.findEventRow(eventTitle);
if (!eventRow) {
throw new Error(`Event '${eventTitle}' not found`);
}
const duplicateButton = await eventRow.locator(this.selectors.rowActions.duplicate.join(', ')).first();
await duplicateButton.click();
await this.waitForAjax();
// Look for success message or new duplicate event
const result = await this.getBulkActionResult();
console.log(`✅ Event duplicated: ${eventTitle}`);
return result;
}
/**
* Find event row by title
*/
async findEventRow(eventTitle) {
const eventRows = await this.page.locator(this.selectors.eventRow.join(', ')).all();
for (const row of eventRows) {
try {
const titleElement = await row.locator(this.selectors.eventDetails.title.join(', ')).first();
const title = await titleElement.textContent();
if (title.trim() === eventTitle.trim()) {
return row;
}
} catch (error) {
continue;
}
}
return null;
}
/**
* Navigate through pagination
*/
async navigateToPage(pageNumber) {
console.log(`📄 Navigating to page: ${pageNumber}`);
if (!await this.isVisible(this.selectors.pagination.container)) {
console.log('⚠️ Pagination not available');
return false;
}
// Look for specific page number link
const pageLink = await this.getVisibleSelector([
`text=${pageNumber}`,
`[data-page="${pageNumber}"]`,
`.page-${pageNumber}`
]);
if (pageLink) {
await this.click(pageLink);
await this.waitForAjax();
console.log(`✅ Navigated to page ${pageNumber}`);
return true;
}
console.log(`⚠️ Page ${pageNumber} not found`);
return false;
}
/**
* Export events
*/
async exportEvents(format = 'csv') {
console.log(`📤 Exporting events in ${format} format`);
if (!await this.isVisible(this.selectors.actions.export)) {
throw new Error('Export functionality not available');
}
// Click export button
await this.click(this.selectors.actions.export);
// Handle format selection if needed
const formatSelector = await this.getVisibleSelector([
`select[name="export_format"]`,
`input[value="${format}"]`,
`.format-${format}`
]);
if (formatSelector) {
if (formatSelector.includes('select')) {
await this.selectByValue(formatSelector, format);
} else {
await this.click(formatSelector);
}
}
// Wait for export to process
await this.waitForAjax();
console.log(`✅ Events export initiated (${format})`);
return { format, initiated: true };
}
/**
* Get event management metrics
*/
async getManagementMetrics() {
const metrics = {
totalEvents: 0,
eventsByStatus: {},
averageAttendeesPerEvent: 0,
upcomingEventsCount: 0
};
// Get dashboard stats
const stats = await this.getDashboardStats();
metrics.totalEvents = stats.totalEvents || 0;
metrics.upcomingEventsCount = stats.upcomingEvents || 0;
// Get events list for detailed analysis
const events = await this.getEventsList();
// Analyze events by status
events.forEach(event => {
const status = event.status?.toLowerCase() || 'unknown';
metrics.eventsByStatus[status] = (metrics.eventsByStatus[status] || 0) + 1;
});
// Calculate average attendees (if available)
const eventsWithAttendees = events.filter(event => event.attendees && !isNaN(parseInt(event.attendees)));
if (eventsWithAttendees.length > 0) {
const totalAttendees = eventsWithAttendees.reduce((sum, event) => {
return sum + parseInt(event.attendees);
}, 0);
metrics.averageAttendeesPerEvent = Math.round(totalAttendees / eventsWithAttendees.length);
}
console.log('📊 Management metrics:', metrics);
return metrics;
}
/**
* Verify management dashboard functionality
*/
async verifyDashboardFunctionality() {
const functionality = {
canViewEvents: await this.isVisible(this.selectors.eventsList),
canCreateEvents: await this.isVisible(this.selectors.actions.createNew),
canSearch: await this.isVisible(this.selectors.filters.search),
canFilter: await this.isVisible(this.selectors.filters.status),
canSort: await this.isVisible(this.selectors.sorting.date),
hasBulkActions: await this.isVisible(this.selectors.actions.bulkActions),
canExport: await this.isVisible(this.selectors.actions.export),
hasPagination: await this.isVisible(this.selectors.pagination.container),
hasStats: await this.isVisible(this.selectors.stats.totalEvents)
};
const functionalityScore = Object.values(functionality).filter(Boolean).length;
const totalFeatures = Object.keys(functionality).length;
console.log(`🔧 Dashboard functionality: ${functionalityScore}/${totalFeatures} features available`);
return { ...functionality, score: functionalityScore, total: totalFeatures };
}
/**
* Extract number from text
*/
extractNumber(text) {
const match = text.match(/\d+/);
return match ? parseInt(match[0], 10) : 0;
}
/**
* Take screenshot of management dashboard
*/
async screenshotDashboard(name = 'event-management-dashboard') {
return await this.takeScreenshot(name, { fullPage: true });
}
}
module.exports = EventManagement;