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>
384 lines
No EOL
13 KiB
JavaScript
384 lines
No EOL
13 KiB
JavaScript
/**
|
|
* Trainer Dashboard Page Object
|
|
* Handles all interactions with the regular trainer dashboard
|
|
*/
|
|
|
|
const BasePage = require('../base/BasePage');
|
|
|
|
class TrainerDashboard extends BasePage {
|
|
constructor(page = null) {
|
|
super(page);
|
|
this.url = '/trainer/dashboard/';
|
|
this.title = 'Trainer Dashboard';
|
|
|
|
this.selectors = {
|
|
// Main dashboard elements
|
|
dashboardTitle: 'h1, .dashboard-title, .page-title',
|
|
welcomeMessage: '.welcome-message, .dashboard-welcome',
|
|
userInfo: '.user-info, .current-user',
|
|
|
|
// Navigation menu
|
|
navigationMenu: '.trainer-navigation, .dashboard-nav',
|
|
menuItems: '.nav-item, .menu-item',
|
|
|
|
// Dashboard sections
|
|
eventsSection: '.events-section, [data-section="events"]',
|
|
profileSection: '.profile-section, [data-section="profile"]',
|
|
venueSection: '.venue-section, [data-section="venues"]',
|
|
organizerSection: '.organizer-section, [data-section="organizers"]',
|
|
|
|
// Quick action buttons
|
|
quickActions: '.quick-actions, .dashboard-actions',
|
|
createEventBtn: 'a[href*="create-event"], .create-event-btn',
|
|
manageEventsBtn: 'a[href*="events"], .manage-events-btn',
|
|
profileBtn: 'a[href*="profile"], .profile-btn',
|
|
venueBtn: 'a[href*="venue"], .venue-btn',
|
|
organizerBtn: 'a[href*="organizer"], .organizer-btn',
|
|
|
|
// Events related
|
|
upcomingEvents: '.upcoming-events, .events-list',
|
|
eventItems: '.event-item, .event-card',
|
|
noEventsMessage: '.no-events, .empty-events',
|
|
|
|
// Profile related
|
|
profileStatus: '.profile-status, .trainer-status',
|
|
profileCompletion: '.profile-completion, .completion-status',
|
|
editProfileBtn: 'a[href*="edit-profile"], .edit-profile-btn',
|
|
|
|
// Venue management
|
|
venueList: '.venue-list, .venues',
|
|
addVenueBtn: 'a[href*="add-venue"], .add-venue-btn',
|
|
manageVenuesBtn: 'a[href*="venue/manage"], .manage-venues-btn',
|
|
|
|
// Training leads
|
|
trainingLeadsBtn: 'a[href*="training-leads"], .training-leads-btn',
|
|
leadsSection: '.training-leads, [data-section="leads"]',
|
|
|
|
// Common elements
|
|
logoutBtn: '.logout, a[href*="logout"]',
|
|
settingsLink: 'a[href*="settings"], .settings-link',
|
|
helpLink: 'a[href*="help"], .help-link',
|
|
|
|
// Error and loading states
|
|
loadingIndicator: '.loading, .spinner, .loader',
|
|
errorMessage: '.error, .notice-error',
|
|
successMessage: '.success, .notice-success',
|
|
|
|
// Layout elements
|
|
mainContent: '.main-content, .dashboard-content',
|
|
sidebar: '.sidebar, .dashboard-sidebar',
|
|
breadcrumbs: '.breadcrumbs, .breadcrumb-nav'
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Wait for dashboard page to be fully loaded
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async waitForPageReady() {
|
|
// Wait for main dashboard elements
|
|
await this.waitForElement(this.selectors.dashboardTitle);
|
|
await this.waitForElement(this.selectors.mainContent);
|
|
|
|
// Wait for navigation to be loaded
|
|
await this.waitForElement(this.selectors.navigationMenu);
|
|
|
|
// Wait for any AJAX loading to complete
|
|
await this.waitForAjaxComplete();
|
|
}
|
|
|
|
/**
|
|
* Get dashboard title
|
|
* @returns {Promise<string>}
|
|
*/
|
|
async getDashboardTitle() {
|
|
return await this.getElementText(this.selectors.dashboardTitle);
|
|
}
|
|
|
|
/**
|
|
* Check if user is properly authenticated as trainer
|
|
* @returns {Promise<boolean>}
|
|
*/
|
|
async isAuthenticatedAsTrainer() {
|
|
try {
|
|
await this.waitForElement(this.selectors.dashboardTitle, 10000);
|
|
const title = await this.getDashboardTitle();
|
|
return title.toLowerCase().includes('trainer') &&
|
|
!title.toLowerCase().includes('master trainer');
|
|
} catch (error) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Navigate to Create Event page
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async navigateToCreateEvent() {
|
|
const createBtn = await this.page.$(this.selectors.createEventBtn);
|
|
if (createBtn) {
|
|
await this.clickElement(this.selectors.createEventBtn);
|
|
} else {
|
|
await this.page.goto(`${this.page.url().split('/trainer')[0]}/trainer/create-event/`);
|
|
}
|
|
await this.waitForPageLoad();
|
|
}
|
|
|
|
/**
|
|
* Navigate to Manage Events page
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async navigateToManageEvents() {
|
|
const manageBtn = await this.page.$(this.selectors.manageEventsBtn);
|
|
if (manageBtn) {
|
|
await this.clickElement(this.selectors.manageEventsBtn);
|
|
} else {
|
|
await this.page.goto(`${this.page.url().split('/trainer')[0]}/trainer/events/`);
|
|
}
|
|
await this.waitForPageLoad();
|
|
}
|
|
|
|
/**
|
|
* Navigate to Profile page
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async navigateToProfile() {
|
|
const profileBtn = await this.page.$(this.selectors.profileBtn);
|
|
if (profileBtn) {
|
|
await this.clickElement(this.selectors.profileBtn);
|
|
} else {
|
|
await this.page.goto(`${this.page.url().split('/trainer')[0]}/trainer/profile/`);
|
|
}
|
|
await this.waitForPageLoad();
|
|
}
|
|
|
|
/**
|
|
* Navigate to Venue Management
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async navigateToVenueManagement() {
|
|
const venueBtn = await this.page.$(this.selectors.venueBtn);
|
|
if (venueBtn) {
|
|
await this.clickElement(this.selectors.venueBtn);
|
|
} else {
|
|
await this.page.goto(`${this.page.url().split('/trainer')[0]}/trainer/venue/manage/`);
|
|
}
|
|
await this.waitForPageLoad();
|
|
}
|
|
|
|
/**
|
|
* Navigate to Organizer Management
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async navigateToOrganizerManagement() {
|
|
const organizerBtn = await this.page.$(this.selectors.organizerBtn);
|
|
if (organizerBtn) {
|
|
await this.clickElement(this.selectors.organizerBtn);
|
|
} else {
|
|
await this.page.goto(`${this.page.url().split('/trainer')[0]}/trainer/organizer/manage/`);
|
|
}
|
|
await this.waitForPageLoad();
|
|
}
|
|
|
|
/**
|
|
* Navigate to Training Leads
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async navigateToTrainingLeads() {
|
|
const leadsBtn = await this.page.$(this.selectors.trainingLeadsBtn);
|
|
if (leadsBtn) {
|
|
await this.clickElement(this.selectors.trainingLeadsBtn);
|
|
} else {
|
|
await this.page.goto(`${this.page.url().split('/trainer')[0]}/trainer/profile/training-leads/`);
|
|
}
|
|
await this.waitForPageLoad();
|
|
}
|
|
|
|
/**
|
|
* Get list of upcoming events
|
|
* @returns {Promise<Array>}
|
|
*/
|
|
async getUpcomingEvents() {
|
|
const events = [];
|
|
|
|
try {
|
|
if (await this.hasElement(this.selectors.noEventsMessage)) {
|
|
return events; // No events found
|
|
}
|
|
|
|
const eventItems = await this.page.$$(this.selectors.eventItems);
|
|
for (const item of eventItems) {
|
|
const title = await item.$eval('.event-title, .title, h3, h4', el => el.textContent.trim());
|
|
const date = await item.$eval('.event-date, .date', el => el.textContent.trim()).catch(() => null);
|
|
const location = await item.$eval('.event-location, .location', el => el.textContent.trim()).catch(() => null);
|
|
|
|
events.push({ title, date, location });
|
|
}
|
|
} catch (error) {
|
|
console.warn('Failed to get upcoming events:', error.message);
|
|
}
|
|
|
|
return events;
|
|
}
|
|
|
|
/**
|
|
* Check profile completion status
|
|
* @returns {Promise<Object>}
|
|
*/
|
|
async getProfileStatus() {
|
|
const status = {
|
|
isComplete: false,
|
|
completionPercentage: 0,
|
|
missingFields: []
|
|
};
|
|
|
|
try {
|
|
if (await this.hasElement(this.selectors.profileCompletion)) {
|
|
const completionText = await this.getElementText(this.selectors.profileCompletion);
|
|
const percentMatch = completionText.match(/(\d+)%/);
|
|
if (percentMatch) {
|
|
status.completionPercentage = parseInt(percentMatch[1], 10);
|
|
status.isComplete = status.completionPercentage === 100;
|
|
}
|
|
}
|
|
|
|
// Check for any missing field indicators
|
|
const missingFieldElements = await this.page.$$('.missing-field, .incomplete-field');
|
|
for (const element of missingFieldElements) {
|
|
const fieldName = await element.textContent();
|
|
status.missingFields.push(fieldName.trim());
|
|
}
|
|
} catch (error) {
|
|
console.warn('Failed to get profile status:', error.message);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* Check if navigation menu is present and functional
|
|
* @returns {Promise<boolean>}
|
|
*/
|
|
async hasNavigationMenu() {
|
|
try {
|
|
await this.waitForElement(this.selectors.navigationMenu, 5000);
|
|
const menuItems = await this.page.$$(this.selectors.menuItems);
|
|
return menuItems.length > 0;
|
|
} catch (error) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get all available navigation menu items
|
|
* @returns {Promise<Array>}
|
|
*/
|
|
async getNavigationMenuItems() {
|
|
const menuItems = [];
|
|
|
|
try {
|
|
const items = await this.page.$$(this.selectors.menuItems);
|
|
for (const item of items) {
|
|
const text = await item.textContent();
|
|
const href = await item.getAttribute('href');
|
|
menuItems.push({ text: text.trim(), href });
|
|
}
|
|
} catch (error) {
|
|
console.warn('Failed to get menu items:', error.message);
|
|
}
|
|
|
|
return menuItems;
|
|
}
|
|
|
|
/**
|
|
* Check if specific dashboard section is visible
|
|
* @param {string} sectionName - Name of section (events, profile, venue, etc.)
|
|
* @returns {Promise<boolean>}
|
|
*/
|
|
async isDashboardSectionVisible(sectionName) {
|
|
const sectionSelectors = {
|
|
'events': this.selectors.eventsSection,
|
|
'profile': this.selectors.profileSection,
|
|
'venue': this.selectors.venueSection,
|
|
'organizer': this.selectors.organizerSection,
|
|
'leads': this.selectors.leadsSection
|
|
};
|
|
|
|
const selector = sectionSelectors[sectionName];
|
|
if (!selector) return false;
|
|
|
|
return await this.isElementVisible(selector);
|
|
}
|
|
|
|
/**
|
|
* Wait for dashboard loading to complete
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async waitForDashboardLoaded() {
|
|
// Wait for loading indicators to disappear
|
|
try {
|
|
await this.waitForElementHidden(this.selectors.loadingIndicator, 10000);
|
|
} catch (error) {
|
|
// Loading indicator might not be present
|
|
}
|
|
|
|
// Ensure main content is visible
|
|
await this.waitForElement(this.selectors.mainContent);
|
|
|
|
// Wait for any AJAX to complete
|
|
await this.waitForAjaxComplete();
|
|
}
|
|
|
|
/**
|
|
* Check if trainer has access to specific functionality
|
|
* @param {string} functionality - Name of functionality to check
|
|
* @returns {Promise<boolean>}
|
|
*/
|
|
async hasAccessTo(functionality) {
|
|
const accessSelectors = {
|
|
'createEvents': this.selectors.createEventBtn,
|
|
'manageVenues': this.selectors.venueBtn,
|
|
'manageOrganizers': this.selectors.organizerBtn,
|
|
'trainingLeads': this.selectors.trainingLeadsBtn,
|
|
'editProfile': this.selectors.editProfileBtn
|
|
};
|
|
|
|
const selector = accessSelectors[functionality];
|
|
if (!selector) return false;
|
|
|
|
return await this.hasElement(selector);
|
|
}
|
|
|
|
/**
|
|
* Logout from trainer dashboard
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async logout() {
|
|
const logoutBtn = await this.page.$(this.selectors.logoutBtn);
|
|
if (logoutBtn) {
|
|
await this.clickElement(this.selectors.logoutBtn);
|
|
} else {
|
|
// Fallback logout
|
|
await this.page.goto('/wp-login.php?action=logout');
|
|
}
|
|
|
|
// Wait for redirect to login page
|
|
await this.page.waitForURL(url =>
|
|
url.includes('login') ||
|
|
url.includes('wp-login'),
|
|
{ timeout: 10000 }
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Take screenshot of dashboard
|
|
* @param {string} filename - Screenshot filename
|
|
* @returns {Promise<string>}
|
|
*/
|
|
async takeScreenshot(filename = 'trainer-dashboard.png') {
|
|
await this.waitForDashboardLoaded();
|
|
return await super.takeScreenshot(filename);
|
|
}
|
|
}
|
|
|
|
module.exports = TrainerDashboard; |