- Added hvac_master_trainer role with special capabilities: * view_master_dashboard * view_all_trainer_data * manage_google_sheets_integration - Created Master Dashboard page and template: * System overview with 6 key statistics (events, trainers, revenue) * Trainer performance analytics table * All events management with filtering * System-wide data aggregation across all trainers - Implemented comprehensive access control: * Master trainers and administrators can access * Regular trainers denied with proper error handling * Non-logged users redirected to login - Added data aggregation class (HVAC_Master_Dashboard_Data): * Direct database queries bypass TEC trainer filters * Aggregates events, tickets, and revenue across all users * Methods for total events, trainer stats, and events data - Enhanced template loading and shortcode registration: * Added [hvac_master_dashboard] shortcode * Integrated master dashboard template loading * Uses harmonized CSS framework for consistent styling - Created comprehensive Playwright test suite: * Tests administrator and trainer access * Verifies access control and error handling * Validates data display and UI rendering * Includes visual verification with screenshots 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
		
			
				
	
	
		
			383 lines
		
	
	
		
			No EOL
		
	
	
		
			13 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			383 lines
		
	
	
		
			No EOL
		
	
	
		
			13 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import { test, expect } from './fixtures/auth';
 | |
| import { STAGING_URL, PATHS, TIMEOUTS } from './config/staging-config';
 | |
| import { CommonActions } from './utils/common-actions';
 | |
| 
 | |
| /**
 | |
|  * Master Dashboard E2E Tests
 | |
|  * 
 | |
|  * Tests the Master Dashboard functionality for master trainers and administrators
 | |
|  * Verifies system-wide analytics, trainer performance data, and all events display
 | |
|  */
 | |
| 
 | |
| // Login function for master trainer
 | |
| async function loginAsMasterTrainer(page: any) {
 | |
|   await page.goto(PATHS.login);
 | |
|   await page.fill('#user_login', 'master_trainer');
 | |
|   await page.fill('#user_pass', 'MasterTrainer#2025!');
 | |
|   await page.click('#wp-submit');
 | |
|   await page.waitForLoadState('networkidle');
 | |
|   
 | |
|   // Verify successful login - master trainer should be able to access both dashboards
 | |
|   const currentUrl = page.url();
 | |
|   console.log('Post-login URL:', currentUrl);
 | |
|   
 | |
|   return page;
 | |
| }
 | |
| 
 | |
| // Login function for admin user 
 | |
| async function loginAsAdmin(page: any) {
 | |
|   await page.goto(PATHS.login);
 | |
|   await page.fill('#user_login', 'admin_trainer');
 | |
|   await page.fill('#user_pass', 'Test123!');
 | |
|   await page.click('#wp-submit');
 | |
|   await page.waitForLoadState('networkidle');
 | |
|   
 | |
|   return page;
 | |
| }
 | |
| 
 | |
| test.describe('Master Dashboard Tests', () => {
 | |
|   
 | |
|   test('Master Trainer can access Master Dashboard', async ({ page }) => {
 | |
|     test.setTimeout(30000);
 | |
|     const actions = new CommonActions(page);
 | |
|     
 | |
|     await loginAsMasterTrainer(page);
 | |
|     await actions.screenshot('master-trainer-logged-in');
 | |
|     
 | |
|     // Navigate to Master Dashboard
 | |
|     await actions.navigateAndWait('/master-dashboard/');
 | |
|     await actions.screenshot('master-dashboard-loaded');
 | |
|     
 | |
|     // Verify page title and header
 | |
|     await expect(page.locator('h1')).toContainText('Master Dashboard');
 | |
|     
 | |
|     // Verify access is granted (no access denied message)
 | |
|     const accessDenied = page.locator('.hvac-access-denied, text="Access Denied"');
 | |
|     await expect(accessDenied).toHaveCount(0);
 | |
|     
 | |
|     await actions.screenshot('master-dashboard-access-verified');
 | |
|   });
 | |
| 
 | |
|   test('Administrator can access Master Dashboard', async ({ page }) => {
 | |
|     test.setTimeout(30000);
 | |
|     const actions = new CommonActions(page);
 | |
|     
 | |
|     await loginAsAdmin(page);
 | |
|     await actions.screenshot('admin-logged-in');
 | |
|     
 | |
|     // Navigate to Master Dashboard
 | |
|     await actions.navigateAndWait('/master-dashboard/');
 | |
|     await actions.screenshot('admin-master-dashboard-loaded');
 | |
|     
 | |
|     // Verify page title
 | |
|     await expect(page.locator('h1')).toContainText('Master Dashboard');
 | |
|     
 | |
|     // Verify access is granted
 | |
|     const accessDenied = page.locator('.hvac-access-denied, text="Access Denied"');
 | |
|     await expect(accessDenied).toHaveCount(0);
 | |
|     
 | |
|     await actions.screenshot('admin-master-dashboard-verified');
 | |
|   });
 | |
| 
 | |
|   test('Master Dashboard displays system overview statistics', async ({ page }) => {
 | |
|     test.setTimeout(30000);
 | |
|     const actions = new CommonActions(page);
 | |
|     
 | |
|     await loginAsMasterTrainer(page);
 | |
|     await actions.navigateAndWait('/master-dashboard/');
 | |
|     
 | |
|     // Verify System Overview section
 | |
|     await expect(page.locator('h2')).toContainText('System Overview');
 | |
|     
 | |
|     // Check that all 6 key statistics are displayed
 | |
|     const expectedStats = [
 | |
|       'Total Events',
 | |
|       'Upcoming Events', 
 | |
|       'Completed Events',
 | |
|       'Active Trainers',
 | |
|       'Tickets Sold',
 | |
|       'Total Revenue'
 | |
|     ];
 | |
|     
 | |
|     for (const statLabel of expectedStats) {
 | |
|       await expect(page.locator('.hvac-stat-card').filter({ hasText: statLabel })).toBeVisible();
 | |
|     }
 | |
|     
 | |
|     // Verify statistics have numeric values
 | |
|     const statCards = page.locator('.hvac-stat-card');
 | |
|     const statCount = await statCards.count();
 | |
|     expect(statCount).toBeGreaterThanOrEqual(6);
 | |
|     
 | |
|     // Check that each stat card has a number
 | |
|     for (let i = 0; i < statCount; i++) {
 | |
|       const card = statCards.nth(i);
 | |
|       const statValue = card.locator('p').first();
 | |
|       await expect(statValue).toBeVisible();
 | |
|       
 | |
|       // Get the text and verify it's a number or currency
 | |
|       const text = await statValue.textContent();
 | |
|       expect(text).toMatch(/^(\$?[\d,]+\.?\d*)$/); // Numbers with optional $ and commas
 | |
|     }
 | |
|     
 | |
|     await actions.screenshot('system-overview-statistics-verified');
 | |
|   });
 | |
| 
 | |
|   test('Master Dashboard shows real trainer data', async ({ page }) => {
 | |
|     test.setTimeout(30000);
 | |
|     const actions = new CommonActions(page);
 | |
|     
 | |
|     await loginAsMasterTrainer(page);
 | |
|     await actions.navigateAndWait('/master-dashboard/');
 | |
|     
 | |
|     // Look for trainer analytics section (should be after the stats)
 | |
|     const trainerSection = page.locator('section').filter({ hasText: /Trainer.*Analytics/i });
 | |
|     
 | |
|     if (await trainerSection.count() > 0) {
 | |
|       await expect(trainerSection).toBeVisible();
 | |
|       await actions.screenshot('trainer-analytics-section');
 | |
|       
 | |
|       // Check for trainer data table
 | |
|       const trainersTable = page.locator('.trainers-table, table');
 | |
|       if (await trainersTable.count() > 0) {
 | |
|         await expect(trainersTable).toBeVisible();
 | |
|         
 | |
|         // Verify table headers
 | |
|         const expectedHeaders = ['Trainer Name', 'Email', 'Total Events', 'Revenue'];
 | |
|         for (const header of expectedHeaders) {
 | |
|           await expect(page.locator('th, .table-header').filter({ hasText: header })).toBeVisible();
 | |
|         }
 | |
|         
 | |
|         await actions.screenshot('trainer-table-headers-verified');
 | |
|       }
 | |
|     } else {
 | |
|       console.log('Trainer analytics section not found or not visible');
 | |
|       await actions.screenshot('missing-trainer-analytics');
 | |
|     }
 | |
|   });
 | |
| 
 | |
|   test('Master Dashboard navigation works correctly', async ({ page }) => {
 | |
|     test.setTimeout(30000);
 | |
|     const actions = new CommonActions(page);
 | |
|     
 | |
|     await loginAsMasterTrainer(page);
 | |
|     await actions.navigateAndWait('/master-dashboard/');
 | |
|     
 | |
|     // Verify navigation buttons in header
 | |
|     const navButtons = [
 | |
|       'Your Dashboard',  // Link back to regular dashboard
 | |
|       'Logout'
 | |
|     ];
 | |
|     
 | |
|     for (const buttonText of navButtons) {
 | |
|       const button = page.locator('.hvac-dashboard-nav a, .ast-button').filter({ hasText: buttonText });
 | |
|       await expect(button.first()).toBeVisible();
 | |
|     }
 | |
|     
 | |
|     // Test navigation to regular dashboard
 | |
|     await page.click('text="Your Dashboard"');
 | |
|     await page.waitForLoadState('networkidle');
 | |
|     await expect(page).toHaveURL(/hvac-dashboard/);
 | |
|     await expect(page.locator('h1')).toContainText('Trainer Dashboard');
 | |
|     
 | |
|     await actions.screenshot('navigated-to-regular-dashboard');
 | |
|     
 | |
|     // Navigate back to master dashboard
 | |
|     await actions.navigateAndWait('/master-dashboard/');
 | |
|     await expect(page.locator('h1')).toContainText('Master Dashboard');
 | |
|     
 | |
|     await actions.screenshot('navigation-test-complete');
 | |
|   });
 | |
| 
 | |
|   test('Regular trainer cannot access Master Dashboard', async ({ page }) => {
 | |
|     test.setTimeout(30000);
 | |
|     const actions = new CommonActions(page);
 | |
|     
 | |
|     // Login as regular trainer
 | |
|     await page.goto(PATHS.login);
 | |
|     await page.fill('#user_login', 'test_trainer');
 | |
|     await page.fill('#user_pass', 'Test123!');
 | |
|     await page.click('#wp-submit');
 | |
|     await page.waitForLoadState('networkidle');
 | |
|     
 | |
|     await actions.screenshot('regular-trainer-logged-in');
 | |
|     
 | |
|     // Try to access Master Dashboard
 | |
|     await actions.navigateAndWait('/master-dashboard/');
 | |
|     
 | |
|     // Should be redirected to regular dashboard with error
 | |
|     await expect(page).toHaveURL(/hvac-dashboard/);
 | |
|     
 | |
|     // Check for error message in URL parameter
 | |
|     const url = page.url();
 | |
|     expect(url).toContain('error=access_denied');
 | |
|     
 | |
|     await actions.screenshot('regular-trainer-access-denied');
 | |
|   });
 | |
| 
 | |
|   test('Master Dashboard shows accurate data with real events', async ({ page }) => {
 | |
|     test.setTimeout(45000);
 | |
|     const actions = new CommonActions(page);
 | |
|     
 | |
|     await loginAsMasterTrainer(page);
 | |
|     await actions.navigateAndWait('/master-dashboard/');
 | |
|     
 | |
|     // Get the total events count from the dashboard
 | |
|     const totalEventsCard = page.locator('.hvac-stat-card').filter({ hasText: 'Total Events' });
 | |
|     await expect(totalEventsCard).toBeVisible();
 | |
|     
 | |
|     const totalEventsValue = await totalEventsCard.locator('p').first().textContent();
 | |
|     const totalEventsNumber = parseInt(totalEventsValue || '0');
 | |
|     
 | |
|     console.log(`Master Dashboard shows ${totalEventsNumber} total events`);
 | |
|     
 | |
|     // Verify it's a reasonable number (should be at least the test events we created)
 | |
|     expect(totalEventsNumber).toBeGreaterThanOrEqual(0);
 | |
|     
 | |
|     // Get trainer count
 | |
|     const trainersCard = page.locator('.hvac-stat-card').filter({ hasText: 'Active Trainers' });
 | |
|     await expect(trainersCard).toBeVisible();
 | |
|     
 | |
|     const trainersValue = await trainersCard.locator('p').first().textContent();
 | |
|     const trainersNumber = parseInt(trainersValue || '0');
 | |
|     
 | |
|     console.log(`Master Dashboard shows ${trainersNumber} active trainers`);
 | |
|     
 | |
|     // Should show at least our test trainers
 | |
|     expect(trainersNumber).toBeGreaterThanOrEqual(2); // test_trainer + admin_trainer + master_trainer
 | |
|     
 | |
|     // Get revenue data
 | |
|     const revenueCard = page.locator('.hvac-stat-card').filter({ hasText: 'Total Revenue' });
 | |
|     await expect(revenueCard).toBeVisible();
 | |
|     
 | |
|     const revenueValue = await revenueCard.locator('p').first().textContent();
 | |
|     console.log(`Master Dashboard shows revenue: ${revenueValue}`);
 | |
|     
 | |
|     // Revenue should be formatted as currency
 | |
|     expect(revenueValue).toMatch(/^\$[\d,]+\.?\d*$/);
 | |
|     
 | |
|     await actions.screenshot('real-data-verification-complete');
 | |
|   });
 | |
| 
 | |
|   test('Master Dashboard performance and load time', async ({ page }) => {
 | |
|     test.setTimeout(30000);
 | |
|     const actions = new CommonActions(page);
 | |
|     
 | |
|     await loginAsMasterTrainer(page);
 | |
|     
 | |
|     // Measure page load time
 | |
|     const startTime = Date.now();
 | |
|     await actions.navigateAndWait('/master-dashboard/');
 | |
|     const loadTime = Date.now() - startTime;
 | |
|     
 | |
|     console.log(`Master Dashboard loaded in ${loadTime}ms`);
 | |
|     
 | |
|     // Page should load within reasonable time (10 seconds)
 | |
|     expect(loadTime).toBeLessThan(10000);
 | |
|     
 | |
|     // Verify all major sections are loaded
 | |
|     await expect(page.locator('h1')).toContainText('Master Dashboard');
 | |
|     await expect(page.locator('.hvac-dashboard-stats')).toBeVisible();
 | |
|     
 | |
|     // Check for any JavaScript errors
 | |
|     const jsErrors: string[] = [];
 | |
|     page.on('console', (msg) => {
 | |
|       if (msg.type() === 'error') {
 | |
|         jsErrors.push(msg.text());
 | |
|       }
 | |
|     });
 | |
|     
 | |
|     // Wait a bit for any delayed JS to execute
 | |
|     await page.waitForTimeout(2000);
 | |
|     
 | |
|     // Log any JS errors but don't fail the test for minor issues
 | |
|     if (jsErrors.length > 0) {
 | |
|       console.log('JavaScript errors detected:', jsErrors);
 | |
|     }
 | |
|     
 | |
|     await actions.screenshot('performance-test-complete');
 | |
|   });
 | |
| 
 | |
|   test('Master Dashboard responsive design on mobile', async ({ browser }) => {
 | |
|     test.setTimeout(30000);
 | |
|     
 | |
|     // Create mobile context
 | |
|     const context = await browser.newContext({
 | |
|       viewport: { width: 375, height: 667 } // iPhone SE size
 | |
|     });
 | |
|     const page = await context.newPage();
 | |
|     const actions = new CommonActions(page);
 | |
|     
 | |
|     await loginAsMasterTrainer(page);
 | |
|     await actions.navigateAndWait('/master-dashboard/');
 | |
|     
 | |
|     // Verify page loads on mobile
 | |
|     await expect(page.locator('h1')).toContainText('Master Dashboard');
 | |
|     
 | |
|     // Check that stats are displayed (they should stack on mobile)
 | |
|     const statsSection = page.locator('.hvac-dashboard-stats');
 | |
|     await expect(statsSection).toBeVisible();
 | |
|     
 | |
|     // Check navigation is accessible
 | |
|     const navSection = page.locator('.hvac-dashboard-nav');
 | |
|     await expect(navSection).toBeVisible();
 | |
|     
 | |
|     await actions.screenshot('mobile-master-dashboard');
 | |
|     
 | |
|     await context.close();
 | |
|   });
 | |
| });
 | |
| 
 | |
| test.describe('Master Dashboard Error Handling', () => {
 | |
|   
 | |
|   test('Handles missing data gracefully', async ({ page }) => {
 | |
|     test.setTimeout(30000);
 | |
|     const actions = new CommonActions(page);
 | |
|     
 | |
|     await loginAsMasterTrainer(page);
 | |
|     await actions.navigateAndWait('/master-dashboard/');
 | |
|     
 | |
|     // Even with no data, page should not crash
 | |
|     await expect(page.locator('h1')).toContainText('Master Dashboard');
 | |
|     
 | |
|     // Stats should show zeros or N/A, not errors
 | |
|     const statCards = page.locator('.hvac-stat-card p');
 | |
|     const statCount = await statCards.count();
 | |
|     
 | |
|     for (let i = 0; i < statCount; i++) {
 | |
|       const text = await statCards.nth(i).textContent();
 | |
|       // Should be a number, currency, or 0, not an error message
 | |
|       expect(text).toMatch(/^(\$?[\d,]+\.?\d*|0|N\/A)$/);
 | |
|     }
 | |
|     
 | |
|     await actions.screenshot('missing-data-handled');
 | |
|   });
 | |
| 
 | |
|   test('No PHP errors on Master Dashboard', async ({ page }) => {
 | |
|     test.setTimeout(30000);
 | |
|     const actions = new CommonActions(page);
 | |
|     
 | |
|     // Monitor for PHP errors
 | |
|     const phpErrors: string[] = [];
 | |
|     page.on('console', (msg) => {
 | |
|       if (msg.type() === 'error' && msg.text().toLowerCase().includes('php')) {
 | |
|         phpErrors.push(msg.text());
 | |
|       }
 | |
|     });
 | |
|     
 | |
|     await loginAsMasterTrainer(page);
 | |
|     await actions.navigateAndWait('/master-dashboard/');
 | |
|     
 | |
|     // Wait for page to fully load
 | |
|     await page.waitForTimeout(3000);
 | |
|     
 | |
|     // Check that no PHP errors occurred
 | |
|     expect(phpErrors.length).toBe(0);
 | |
|     
 | |
|     if (phpErrors.length > 0) {
 | |
|       console.log('PHP errors detected:', phpErrors);
 | |
|       await actions.screenshot('php-errors-detected');
 | |
|     } else {
 | |
|       await actions.screenshot('no-php-errors');
 | |
|     }
 | |
|   });
 | |
| }); |