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; |