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>
533 lines
No EOL
20 KiB
JavaScript
533 lines
No EOL
20 KiB
JavaScript
/**
|
|
* 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; |