/** * Master Trainer Trainers Management Page Object * Handles interactions with the master trainer trainers management page */ const BasePage = require('../base/BasePage'); class MasterTrainerTrainers extends BasePage { constructor(page = null) { super(page); this.url = '/master-trainer/trainers/'; this.title = 'Trainers Management'; this.selectors = { // Main page elements pageTitle: 'h1, .page-title, .trainers-title', trainersContainer: '.trainers-container, .trainers-list, .trainers-table', // Trainers list and table trainersTable: 'table.trainers-table, .trainers-grid', trainerRows: 'tr.trainer-row, .trainer-item', trainerName: '.trainer-name, .trainer-title', trainerEmail: '.trainer-email, .email', trainerPhone: '.trainer-phone, .phone', trainerStatus: '.trainer-status, .status', trainerRole: '.trainer-role, .role', trainerJoinDate: '.trainer-join-date, .join-date', trainerLastActive: '.trainer-last-active, .last-active', trainerEventCount: '.trainer-events, .event-count', // Action buttons addTrainerBtn: 'a[href*="add"], .add-trainer, .create-trainer', editTrainerBtn: 'a[href*="edit"], .edit-trainer, .trainer-edit', deleteTrainerBtn: 'a[href*="delete"], .delete-trainer, .trainer-delete', viewTrainerBtn: 'a[href*="view"], .view-trainer, .trainer-view', profileTrainerBtn: 'a[href*="profile"], .trainer-profile, .profile-link', // Approval workflow buttons approveTrainerBtn: '.approve-trainer, .approve-btn, .btn-approve', rejectTrainerBtn: '.reject-trainer, .reject-btn, .btn-reject', suspendTrainerBtn: '.suspend-trainer, .suspend-btn', activateTrainerBtn: '.activate-trainer, .activate-btn', // Bulk actions bulkActionsSelect: 'select[name="bulk-action"], .bulk-actions select', bulkApplyBtn: '.bulk-apply, .apply-bulk-action', selectAllCheckbox: 'input[type="checkbox"].select-all', trainerCheckboxes: 'input[type="checkbox"].trainer-select', // Filters and search searchInput: 'input[type="search"], .search-trainers, #trainer-search', searchBtn: '.search-btn, .search-submit', statusFilter: 'select.status-filter, #status-filter', roleFilter: 'select.role-filter, #role-filter', locationFilter: 'select.location-filter, #location-filter', skillFilter: 'select.skill-filter, #skill-filter', clearFiltersBtn: '.clear-filters, .reset-filters', // Trainer statistics and overview trainerStats: '.trainer-stats, .trainers-statistics', totalTrainers: '.total-trainers, [data-metric="total"]', activeTrainers: '.active-trainers, [data-metric="active"]', pendingTrainers: '.pending-trainers, [data-metric="pending"]', suspendedTrainers: '.suspended-trainers, [data-metric="suspended"]', newTrainers: '.new-trainers, [data-metric="new"]', // Performance metrics performanceSection: '.trainer-performance, .performance-metrics', averageRating: '.average-rating, [data-metric="rating"]', eventCompletionRate: '.completion-rate, [data-metric="completion"]', trainerUtilization: '.trainer-utilization, [data-metric="utilization"]', // Communication features messageTrainerBtn: '.message-trainer, .send-message', emailTrainerBtn: '.email-trainer, .send-email', bulkMessageBtn: '.bulk-message, .message-all', // Import/Export features importTrainersBtn: '.import-trainers, .import-btn', exportTrainersBtn: '.export-trainers, .export-btn', // Pagination pagination: '.pagination, .page-navigation', prevPageBtn: '.prev-page, .previous', nextPageBtn: '.next-page, .next', pageNumbers: '.page-numbers a', // Loading and status indicators loadingIndicator: '.loading, .spinner', errorMessage: '.error-message, .notice-error', successMessage: '.success-message, .notice-success', emptyState: '.no-trainers, .trainers-empty', // Trainer details modal/panel trainerDetailsModal: '.trainer-details-modal, .trainer-popup', trainerDetailsPanel: '.trainer-details-panel, .details-panel', closeDetailsBtn: '.close-details, .modal-close', // Trainer history and logs trainerHistory: '.trainer-history, .activity-log', trainerEvents: '.trainer-events-list, .events-history', trainerCertificates: '.trainer-certificates, .certificates-list' }; } /** * Wait for trainers page to be fully loaded */ async waitForPageReady() { await this.waitForElement(this.selectors.pageTitle); await this.waitForAjaxComplete(); // Wait for trainers container or empty state try { await Promise.race([ this.waitForElement(this.selectors.trainersContainer, 10000), this.waitForElement(this.selectors.emptyState, 10000) ]); } catch (error) { console.warn('Trainers container or empty state not found, continuing...'); } } /** * Get all trainers from the current page */ async getTrainersList() { const trainers = []; const trainerRows = await this.page.$$(this.selectors.trainerRows); for (const row of trainerRows) { const trainer = { name: await this.getTextFromElement(row, this.selectors.trainerName), email: await this.getTextFromElement(row, this.selectors.trainerEmail), phone: await this.getTextFromElement(row, this.selectors.trainerPhone), status: await this.getTextFromElement(row, this.selectors.trainerStatus), role: await this.getTextFromElement(row, this.selectors.trainerRole), joinDate: await this.getTextFromElement(row, this.selectors.trainerJoinDate), lastActive: await this.getTextFromElement(row, this.selectors.trainerLastActive), eventCount: await this.getTextFromElement(row, this.selectors.trainerEventCount) }; trainers.push(trainer); } return trainers; } /** * Get trainer statistics from the page */ async getTrainerStatistics() { const stats = {}; try { if (await this.hasElement(this.selectors.totalTrainers)) { const totalText = await this.getElementText(this.selectors.totalTrainers); stats.total = this.extractNumber(totalText); } if (await this.hasElement(this.selectors.activeTrainers)) { const activeText = await this.getElementText(this.selectors.activeTrainers); stats.active = this.extractNumber(activeText); } if (await this.hasElement(this.selectors.pendingTrainers)) { const pendingText = await this.getElementText(this.selectors.pendingTrainers); stats.pending = this.extractNumber(pendingText); } if (await this.hasElement(this.selectors.suspendedTrainers)) { const suspendedText = await this.getElementText(this.selectors.suspendedTrainers); stats.suspended = this.extractNumber(suspendedText); } if (await this.hasElement(this.selectors.newTrainers)) { const newText = await this.getElementText(this.selectors.newTrainers); stats.new = this.extractNumber(newText); } } catch (error) { console.warn('Failed to get trainer statistics:', error.message); } return stats; } /** * Get performance metrics */ async getPerformanceMetrics() { const metrics = {}; try { if (await this.hasElement(this.selectors.averageRating)) { const ratingText = await this.getElementText(this.selectors.averageRating); metrics.averageRating = this.extractDecimal(ratingText); } if (await this.hasElement(this.selectors.eventCompletionRate)) { const completionText = await this.getElementText(this.selectors.eventCompletionRate); metrics.completionRate = this.extractNumber(completionText); } if (await this.hasElement(this.selectors.trainerUtilization)) { const utilizationText = await this.getElementText(this.selectors.trainerUtilization); metrics.utilization = this.extractNumber(utilizationText); } } catch (error) { console.warn('Failed to get performance metrics:', error.message); } return metrics; } /** * Search for trainers by term */ async searchTrainers(searchTerm) { if (await this.hasElement(this.selectors.searchInput)) { await this.fillField(this.selectors.searchInput, searchTerm); if (await this.hasElement(this.selectors.searchBtn)) { await this.clickElement(this.selectors.searchBtn); } else { await this.page.keyboard.press('Enter'); } await this.waitForAjaxComplete(); await this.waitForPageReady(); } } /** * Filter trainers by status */ async filterByStatus(status) { if (await this.hasElement(this.selectors.statusFilter)) { await this.selectOption(this.selectors.statusFilter, status); await this.waitForAjaxComplete(); await this.waitForPageReady(); } } /** * Filter trainers by role */ async filterByRole(role) { if (await this.hasElement(this.selectors.roleFilter)) { await this.selectOption(this.selectors.roleFilter, role); await this.waitForAjaxComplete(); await this.waitForPageReady(); } } /** * Approve a trainer by index or name */ async approveTrainer(identifier) { return await this.performTrainerAction(identifier, this.selectors.approveTrainerBtn); } /** * Reject a trainer by index or name */ async rejectTrainer(identifier) { return await this.performTrainerAction(identifier, this.selectors.rejectTrainerBtn); } /** * Suspend a trainer by index or name */ async suspendTrainer(identifier) { return await this.performTrainerAction(identifier, this.selectors.suspendTrainerBtn); } /** * Activate a trainer by index or name */ async activateTrainer(identifier) { return await this.performTrainerAction(identifier, this.selectors.activateTrainerBtn); } /** * Perform an action on a specific trainer */ async performTrainerAction(identifier, actionSelector) { const trainerRows = await this.page.$$(this.selectors.trainerRows); if (typeof identifier === 'number' && trainerRows[identifier]) { // Action by index const actionBtn = await trainerRows[identifier].$(actionSelector); if (actionBtn) { await actionBtn.click(); await this.waitForAjaxComplete(); return true; } } else if (typeof identifier === 'string') { // Action by trainer name for (const row of trainerRows) { const nameElement = await row.$(this.selectors.trainerName); if (nameElement) { const name = await nameElement.textContent(); if (name.trim().includes(identifier)) { const actionBtn = await row.$(actionSelector); if (actionBtn) { await actionBtn.click(); await this.waitForAjaxComplete(); return true; } } } } } return false; } /** * Navigate to add new trainer page */ async addNewTrainer() { if (await this.hasElement(this.selectors.addTrainerBtn)) { await this.clickElement(this.selectors.addTrainerBtn); await this.waitForPageLoad(); return true; } return false; } /** * Edit a trainer by identifier */ async editTrainer(identifier) { return await this.performTrainerAction(identifier, this.selectors.editTrainerBtn); } /** * View trainer profile */ async viewTrainerProfile(identifier) { return await this.performTrainerAction(identifier, this.selectors.profileTrainerBtn); } /** * Perform bulk actions on selected trainers */ async performBulkAction(action, trainerIndices = []) { // Select trainers by index const trainerRows = await this.page.$$(this.selectors.trainerRows); for (const index of trainerIndices) { if (trainerRows[index]) { const checkbox = await trainerRows[index].$(this.selectors.trainerCheckboxes); if (checkbox) { await checkbox.click(); } } } // Select bulk action if (await this.hasElement(this.selectors.bulkActionsSelect)) { await this.selectOption(this.selectors.bulkActionsSelect, action); if (await this.hasElement(this.selectors.bulkApplyBtn)) { await this.clickElement(this.selectors.bulkApplyBtn); await this.waitForAjaxComplete(); await this.waitForPageReady(); return true; } } return false; } /** * Select all trainers on current page */ async selectAllTrainers() { if (await this.hasElement(this.selectors.selectAllCheckbox)) { await this.clickElement(this.selectors.selectAllCheckbox); return true; } return false; } /** * Send message to trainer */ async messageTrainer(identifier) { return await this.performTrainerAction(identifier, this.selectors.messageTrainerBtn); } /** * Send bulk message to selected trainers */ async sendBulkMessage() { if (await this.hasElement(this.selectors.bulkMessageBtn)) { await this.clickElement(this.selectors.bulkMessageBtn); await this.waitForPageLoad(); return true; } return false; } /** * Import trainers data */ async importTrainers() { if (await this.hasElement(this.selectors.importTrainersBtn)) { await this.clickElement(this.selectors.importTrainersBtn); await this.waitForPageLoad(); return true; } return false; } /** * Export trainers data */ async exportTrainers() { if (await this.hasElement(this.selectors.exportTrainersBtn)) { await this.clickElement(this.selectors.exportTrainersBtn); await this.page.waitForTimeout(2000); return true; } return false; } /** * Get trainer details by opening details modal/panel */ async getTrainerDetails(identifier) { if (await this.performTrainerAction(identifier, this.selectors.viewTrainerBtn)) { // Wait for details to load try { await Promise.race([ this.waitForElement(this.selectors.trainerDetailsModal, 5000), this.waitForElement(this.selectors.trainerDetailsPanel, 5000) ]); // Extract details from modal/panel const details = await this.page.evaluate(() => { const modal = document.querySelector('.trainer-details-modal, .trainer-popup, .trainer-details-panel, .details-panel'); if (modal) { return { name: modal.querySelector('.trainer-name, .name')?.textContent || '', email: modal.querySelector('.trainer-email, .email')?.textContent || '', phone: modal.querySelector('.trainer-phone, .phone')?.textContent || '', status: modal.querySelector('.trainer-status, .status')?.textContent || '', joinDate: modal.querySelector('.join-date, .joined')?.textContent || '', eventCount: modal.querySelector('.event-count, .events-completed')?.textContent || '', rating: modal.querySelector('.trainer-rating, .rating')?.textContent || '' }; } return null; }); // Close details if (await this.hasElement(this.selectors.closeDetailsBtn)) { await this.clickElement(this.selectors.closeDetailsBtn); } return details; } catch (error) { console.warn('Failed to get trainer details:', error.message); return null; } } return null; } /** * Check if trainers page has any trainers */ async hasTrainers() { return await this.hasElement(this.selectors.trainerRows); } /** * Check if page is showing empty state */ async isEmptyState() { return await this.isElementVisible(this.selectors.emptyState); } /** * Get available trainer management features */ async getAvailableFeatures() { return { canAdd: await this.hasElement(this.selectors.addTrainerBtn), canSearch: await this.hasElement(this.selectors.searchInput), canFilter: await this.hasElement(this.selectors.statusFilter), canApprove: await this.hasElement(this.selectors.approveTrainerBtn), canBulkAction: await this.hasElement(this.selectors.bulkActionsSelect), canMessage: await this.hasElement(this.selectors.messageTrainerBtn), canImport: await this.hasElement(this.selectors.importTrainersBtn), canExport: await this.hasElement(this.selectors.exportTrainersBtn), hasStatistics: await this.hasElement(this.selectors.trainerStats), hasPerformance: await this.hasElement(this.selectors.performanceSection) }; } /** * Helper method to get text from child element */ async getTextFromElement(parentElement, selector) { try { const element = await parentElement.$(selector); return element ? await element.textContent() : ''; } catch (error) { return ''; } } /** * Extract number from text string */ extractNumber(text) { const match = text.match(/\d+/); return match ? parseInt(match[0], 10) : 0; } /** * Extract decimal number from text string */ extractDecimal(text) { const match = text.match(/\d+\.?\d*/); return match ? parseFloat(match[0]) : 0; } } module.exports = MasterTrainerTrainers;