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>
850 lines
No EOL
28 KiB
JavaScript
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; |