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>
		
			
				
	
	
		
			433 lines
		
	
	
		
			No EOL
		
	
	
		
			17 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			433 lines
		
	
	
		
			No EOL
		
	
	
		
			17 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /**
 | |
|  * Performance & Resource Optimization Test Suite
 | |
|  * 
 | |
|  * Tests the major performance improvements after architectural refactoring:
 | |
|  * - CSS consolidation (250+ files → 5 bundles)
 | |
|  * - HTTP request reduction (85% target)
 | |
|  * - Page load performance (85% faster)
 | |
|  * - Safari browser stability
 | |
|  * - Memory usage optimization
 | |
|  * 
 | |
|  * @package HVAC_Community_Events
 | |
|  * @version 3.0.0
 | |
|  * @created 2025-08-20
 | |
|  */
 | |
| 
 | |
| const { test, expect, authHelpers, LoginPage } = require('../helpers/auth-fixtures');
 | |
| const path = require('path');
 | |
| 
 | |
| // Test configuration
 | |
| const BASE_URL = process.env.UPSKILL_STAGING_URL || 'https://upskill-staging.measurequick.com';
 | |
| const TEST_TIMEOUT = 120000; // Extended for performance tests
 | |
| 
 | |
| // Performance thresholds (based on 85% improvement target)
 | |
| const PERFORMANCE_TARGETS = {
 | |
|     maxCssFiles: 5,           // Down from 250+
 | |
|     maxJsFiles: 10,           // Reasonable limit
 | |
|     maxPageLoadTime: 3000,    // 3 seconds (85% faster)
 | |
|     maxHttpRequests: 25,      // 85% reduction target
 | |
|     maxMemoryUsage: 100,      // MB threshold
 | |
|     maxDomNodes: 5000         // DOM complexity limit
 | |
| };
 | |
| 
 | |
| async function measurePagePerformance(page, url) {
 | |
|     const startTime = Date.now();
 | |
|     
 | |
|     // Clear cache and storage
 | |
|     await page.evaluate(() => {
 | |
|         if (window.performance && window.performance.clearResourceTimings) {
 | |
|             window.performance.clearResourceTimings();
 | |
|         }
 | |
|         if (window.caches) {
 | |
|             caches.keys().then(names => {
 | |
|                 names.forEach(name => caches.delete(name));
 | |
|             });
 | |
|         }
 | |
|     });
 | |
| 
 | |
|     // Navigate and measure
 | |
|     await page.goto(url, { waitUntil: 'networkidle' });
 | |
|     const loadTime = Date.now() - startTime;
 | |
|     
 | |
|     // Get resource counts
 | |
|     const resources = await page.evaluate(() => {
 | |
|         const entries = performance.getEntriesByType('resource');
 | |
|         const cssFiles = entries.filter(e => e.name.includes('.css')).length;
 | |
|         const jsFiles = entries.filter(e => e.name.includes('.js')).length;
 | |
|         const totalRequests = entries.length;
 | |
|         
 | |
|         return { cssFiles, jsFiles, totalRequests, loadTime: performance.timing.loadEventEnd - performance.timing.navigationStart };
 | |
|     });
 | |
| 
 | |
|     // Get DOM complexity
 | |
|     const domStats = await page.evaluate(() => {
 | |
|         return {
 | |
|             nodeCount: document.querySelectorAll('*').length,
 | |
|             depth: Math.max(...Array.from(document.querySelectorAll('*')).map(el => {
 | |
|                 let depth = 0;
 | |
|                 let parent = el.parentElement;
 | |
|                 while (parent) {
 | |
|                     depth++;
 | |
|                     parent = parent.parentElement;
 | |
|                 }
 | |
|                 return depth;
 | |
|             }))
 | |
|         };
 | |
|     });
 | |
| 
 | |
|     return {
 | |
|         loadTime,
 | |
|         clientLoadTime: resources.loadTime,
 | |
|         ...resources,
 | |
|         ...domStats
 | |
|     };
 | |
| }
 | |
| 
 | |
| async function monitorMemoryUsage(page) {
 | |
|     const memoryInfo = await page.evaluate(() => {
 | |
|         if (performance.memory) {
 | |
|             return {
 | |
|                 usedJSHeapSize: performance.memory.usedJSHeapSize / 1024 / 1024, // MB
 | |
|                 totalJSHeapSize: performance.memory.totalJSHeapSize / 1024 / 1024, // MB
 | |
|                 jsHeapSizeLimit: performance.memory.jsHeapSizeLimit / 1024 / 1024 // MB
 | |
|             };
 | |
|         }
 | |
|         return null;
 | |
|     });
 | |
|     
 | |
|     return memoryInfo;
 | |
| }
 | |
| 
 | |
| async function takePerformanceScreenshot(page, name, metrics) {
 | |
|     const screenshotDir = path.join(__dirname, '../../screenshots/performance');
 | |
|     await require('fs').promises.mkdir(screenshotDir, { recursive: true });
 | |
|     
 | |
|     await page.screenshot({ 
 | |
|         path: path.join(screenshotDir, `${name}-${Date.now()}.png`),
 | |
|         fullPage: true 
 | |
|     });
 | |
|     
 | |
|     // Log metrics for debugging
 | |
|     console.log(`Performance metrics for ${name}:`, metrics);
 | |
| }
 | |
| 
 | |
| test.describe('Performance & Resource Optimization Tests', () => {
 | |
|     test.setTimeout(TEST_TIMEOUT);
 | |
|     
 | |
|     test.beforeEach(async ({ page }) => {
 | |
|         await page.setViewportSize({ width: 1280, height: 720 });
 | |
|         // Login before each performance test
 | |
|         await authHelpers.loginAs(page, 'trainer');
 | |
|     });
 | |
| 
 | |
|     test.describe('CSS Consolidation Tests', () => {
 | |
|         test('should load maximum 5 CSS files on dashboard', async ({ page }) => {
 | |
|             const metrics = await measurePagePerformance(page, `${BASE_URL}/trainer/dashboard/`);
 | |
|             
 | |
|             console.log(`CSS files loaded: ${metrics.cssFiles}`);
 | |
|             expect(metrics.cssFiles).toBeLessThanOrEqual(PERFORMANCE_TARGETS.maxCssFiles);
 | |
|             
 | |
|             await takePerformanceScreenshot(page, 'css-consolidation-dashboard', metrics);
 | |
|         });
 | |
| 
 | |
|         test('should maintain CSS consolidation across all trainer pages', async ({ page }) => {
 | |
|             const pages = [
 | |
|                 '/trainer/dashboard/',
 | |
|                 '/trainer/profile/',
 | |
|                 '/trainer/certificate-reports/',
 | |
|                 '/trainer/events/',
 | |
|                 '/trainer/events/create/'
 | |
|             ];
 | |
|             
 | |
|             for (const pagePath of pages) {
 | |
|                 const metrics = await measurePagePerformance(page, `${BASE_URL}${pagePath}`);
 | |
|                 
 | |
|                 console.log(`CSS files on ${pagePath}: ${metrics.cssFiles}`);
 | |
|                 expect(metrics.cssFiles).toBeLessThanOrEqual(PERFORMANCE_TARGETS.maxCssFiles);
 | |
|             }
 | |
|         });
 | |
| 
 | |
|         test('should not load foreign CSS files', async ({ page }) => {
 | |
|             await page.goto(`${BASE_URL}/trainer/dashboard/`);
 | |
|             
 | |
|             // Check for unwanted CSS files
 | |
|             const foreignCss = await page.evaluate(() => {
 | |
|                 const links = Array.from(document.querySelectorAll('link[rel="stylesheet"]'));
 | |
|                 return links.filter(link => {
 | |
|                     const href = link.href;
 | |
|                     return !href.includes('hvac-') && 
 | |
|                            !href.includes('wordpress') && 
 | |
|                            !href.includes('wp-') &&
 | |
|                            !href.includes('astra') &&
 | |
|                            href.includes('.css');
 | |
|                 }).map(link => link.href);
 | |
|             });
 | |
|             
 | |
|             console.log('Foreign CSS files detected:', foreignCss);
 | |
|             expect(foreignCss.length).toBeLessThan(10); // Allow some theme/plugin CSS
 | |
|         });
 | |
|     });
 | |
| 
 | |
|     test.describe('Page Load Performance Tests', () => {
 | |
|         test('should load dashboard within performance target', async ({ page }) => {
 | |
|             // Dashboard is already loaded from beforeEach login, measure reload performance
 | |
|             const dashboardStartTime = Date.now();
 | |
|             await page.goto(`${BASE_URL}/trainer/dashboard/`, { waitUntil: 'networkidle' });
 | |
|             const dashboardLoadTime = Date.now() - dashboardStartTime;
 | |
|             
 | |
|             console.log(`Dashboard load time: ${dashboardLoadTime}ms`);
 | |
|             expect(dashboardLoadTime).toBeLessThan(PERFORMANCE_TARGETS.maxPageLoadTime);
 | |
|             
 | |
|             await takePerformanceScreenshot(page, 'dashboard-performance', { loadTime: dashboardLoadTime });
 | |
|         });
 | |
| 
 | |
|         test('should maintain fast load times on subsequent pages', async ({ page }) => {
 | |
|             const pages = [
 | |
|                 '/trainer/profile/',
 | |
|                 '/trainer/certificate-reports/',
 | |
|                 '/trainer/events/'
 | |
|             ];
 | |
|             
 | |
|             for (const pagePath of pages) {
 | |
|                 const startTime = Date.now();
 | |
|                 await page.goto(`${BASE_URL}${pagePath}`, { waitUntil: 'networkidle' });
 | |
|                 const loadTime = Date.now() - startTime;
 | |
|                 
 | |
|                 console.log(`Load time for ${pagePath}: ${loadTime}ms`);
 | |
|                 expect(loadTime).toBeLessThan(PERFORMANCE_TARGETS.maxPageLoadTime);
 | |
|             }
 | |
|         });
 | |
| 
 | |
|         test('should handle concurrent page loads without degradation', async ({ browser }) => {
 | |
|             const contexts = await Promise.all([
 | |
|                 browser.newContext(),
 | |
|                 browser.newContext(),
 | |
|                 browser.newContext()
 | |
|             ]);
 | |
|             
 | |
|             const pages = await Promise.all(contexts.map(ctx => ctx.newPage()));
 | |
|             
 | |
|             // Simulate concurrent users
 | |
|             const loadPromises = pages.map(async (page, index) => {
 | |
|                 const startTime = Date.now();
 | |
|                 await page.goto(`${BASE_URL}/trainer/dashboard/`);
 | |
|                 const loadTime = Date.now() - startTime;
 | |
|                 
 | |
|                 return { page: index, loadTime };
 | |
|             });
 | |
|             
 | |
|             const results = await Promise.all(loadPromises);
 | |
|             
 | |
|             // All concurrent loads should complete within acceptable time
 | |
|             results.forEach(result => {
 | |
|                 console.log(`Concurrent load ${result.page}: ${result.loadTime}ms`);
 | |
|                 expect(result.loadTime).toBeLessThan(PERFORMANCE_TARGETS.maxPageLoadTime * 2); // Allow 2x for concurrent
 | |
|             });
 | |
|             
 | |
|             // Cleanup
 | |
|             await Promise.all(contexts.map(ctx => ctx.close()));
 | |
|         });
 | |
|     });
 | |
| 
 | |
|     test.describe('HTTP Request Optimization Tests', () => {
 | |
|         test('should minimize HTTP requests on dashboard', async ({ page }) => {
 | |
|             const metrics = await measurePagePerformance(page, `${BASE_URL}/trainer/dashboard/`);
 | |
|             
 | |
|             console.log(`Total HTTP requests: ${metrics.totalRequests}`);
 | |
|             expect(metrics.totalRequests).toBeLessThan(PERFORMANCE_TARGETS.maxHttpRequests);
 | |
|             
 | |
|             await takePerformanceScreenshot(page, 'http-requests-dashboard', metrics);
 | |
|         });
 | |
| 
 | |
|         test('should use resource caching effectively', async ({ page }) => {
 | |
|             // First load
 | |
|             const firstLoad = await measurePagePerformance(page, `${BASE_URL}/trainer/dashboard/`);
 | |
|             
 | |
|             // Second load (should use cache)
 | |
|             const secondLoad = await measurePagePerformance(page, `${BASE_URL}/trainer/dashboard/`);
 | |
|             
 | |
|             console.log(`First load requests: ${firstLoad.totalRequests}, Second load: ${secondLoad.totalRequests}`);
 | |
|             
 | |
|             // Second load should have fewer requests due to caching
 | |
|             expect(secondLoad.totalRequests).toBeLessThanOrEqual(firstLoad.totalRequests);
 | |
|         });
 | |
|     });
 | |
| 
 | |
|     test.describe('Safari Browser Stability Tests', () => {
 | |
|         test('should load without hanging in Safari/WebKit', async ({ page, browserName }) => {
 | |
|             test.skip(browserName !== 'webkit', 'Safari-specific test');
 | |
|             
 | |
|             // Extended timeout for Safari
 | |
|             test.setTimeout(180000);
 | |
|             
 | |
|             const startTime = Date.now();
 | |
|             
 | |
|             try {
 | |
|                 await page.goto(`${BASE_URL}/find-trainer/`, { 
 | |
|                     waitUntil: 'networkidle',
 | |
|                     timeout: 60000 
 | |
|                 });
 | |
|                 
 | |
|                 const loadTime = Date.now() - startTime;
 | |
|                 console.log(`Safari load time: ${loadTime}ms`);
 | |
|                 
 | |
|                 // Verify page actually loaded
 | |
|                 await expect(page.locator('body')).toBeVisible();
 | |
|                 
 | |
|                 // Check for interactive elements
 | |
|                 const interactiveElements = await page.$$('button, a, input, select');
 | |
|                 expect(interactiveElements.length).toBeGreaterThan(0);
 | |
|                 
 | |
|                 await takePerformanceScreenshot(page, 'safari-stability', { loadTime });
 | |
|                 
 | |
|             } catch (error) {
 | |
|                 console.error('Safari loading failed:', error);
 | |
|                 await page.screenshot({ 
 | |
|                     path: path.join(__dirname, '../../screenshots/safari-failure.png'),
 | |
|                     fullPage: true 
 | |
|                 });
 | |
|                 throw error;
 | |
|             }
 | |
|         });
 | |
| 
 | |
|         test('should handle Safari resource loading efficiently', async ({ page, browserName }) => {
 | |
|             test.skip(browserName !== 'webkit', 'Safari-specific test');
 | |
|             
 | |
|             await page.goto(`${BASE_URL}/trainer/dashboard/`);
 | |
|             
 | |
|             // Monitor for resource loading cascade issues
 | |
|             const resourceTimings = await page.evaluate(() => {
 | |
|                 const entries = performance.getEntriesByType('resource');
 | |
|                 return entries.map(entry => ({
 | |
|                     name: entry.name,
 | |
|                     duration: entry.duration,
 | |
|                     transferSize: entry.transferSize || 0
 | |
|                 }));
 | |
|             });
 | |
|             
 | |
|             // Check for excessively long resource loads
 | |
|             const slowResources = resourceTimings.filter(r => r.duration > 5000);
 | |
|             console.log('Slow resources in Safari:', slowResources);
 | |
|             
 | |
|             expect(slowResources.length).toBeLessThan(3); // Allow some tolerance
 | |
|         });
 | |
|     });
 | |
| 
 | |
|     test.describe('Memory Usage Tests', () => {
 | |
|         test('should maintain reasonable memory usage', async ({ page, browserName }) => {
 | |
|             test.skip(browserName !== 'chromium', 'Memory API only available in Chromium');
 | |
|             
 | |
|             // Initial memory
 | |
|             const initialMemory = await monitorMemoryUsage(page);
 | |
|             
 | |
|             // Navigate through multiple pages to stress test
 | |
|             const pages = [
 | |
|                 '/trainer/dashboard/',
 | |
|                 '/trainer/profile/',
 | |
|                 '/trainer/certificate-reports/',
 | |
|                 '/trainer/events/',
 | |
|                 '/trainer/profile/edit/'
 | |
|             ];
 | |
|             
 | |
|             for (const pagePath of pages) {
 | |
|                 await page.goto(`${BASE_URL}${pagePath}`);
 | |
|                 await page.waitForLoadState('networkidle');
 | |
|             }
 | |
|             
 | |
|             // Final memory check
 | |
|             const finalMemory = await monitorMemoryUsage(page);
 | |
|             
 | |
|             if (initialMemory && finalMemory) {
 | |
|                 const memoryIncrease = finalMemory.usedJSHeapSize - initialMemory.usedJSHeapSize;
 | |
|                 console.log(`Memory usage increase: ${memoryIncrease.toFixed(2)}MB`);
 | |
|                 
 | |
|                 expect(finalMemory.usedJSHeapSize).toBeLessThan(PERFORMANCE_TARGETS.maxMemoryUsage);
 | |
|                 expect(memoryIncrease).toBeLessThan(50); // Max 50MB increase during navigation
 | |
|             }
 | |
|         });
 | |
| 
 | |
|         test('should clean up memory after heavy operations', async ({ page, browserName }) => {
 | |
|             test.skip(browserName !== 'chromium', 'Memory API only available in Chromium');
 | |
|             
 | |
|             await page.goto(`${BASE_URL}/trainer/events/create/`);
 | |
|             
 | |
|             const beforeMemory = await monitorMemoryUsage(page);
 | |
|             
 | |
|             // Simulate heavy form interaction
 | |
|             for (let i = 0; i < 10; i++) {
 | |
|                 await page.reload();
 | |
|                 await page.waitForLoadState('networkidle');
 | |
|             }
 | |
|             
 | |
|             // Force garbage collection if available
 | |
|             await page.evaluate(() => {
 | |
|                 if (window.gc) {
 | |
|                     window.gc();
 | |
|                 }
 | |
|             });
 | |
|             
 | |
|             const afterMemory = await monitorMemoryUsage(page);
 | |
|             
 | |
|             if (beforeMemory && afterMemory) {
 | |
|                 console.log(`Memory before: ${beforeMemory.usedJSHeapSize.toFixed(2)}MB, after: ${afterMemory.usedJSHeapSize.toFixed(2)}MB`);
 | |
|                 
 | |
|                 // Memory should not have grown excessively
 | |
|                 const memoryGrowth = afterMemory.usedJSHeapSize - beforeMemory.usedJSHeapSize;
 | |
|                 expect(memoryGrowth).toBeLessThan(30); // Max 30MB growth for heavy operations
 | |
|             }
 | |
|         });
 | |
|     });
 | |
| 
 | |
|     test.describe('DOM Complexity Tests', () => {
 | |
|         test('should maintain reasonable DOM complexity', async ({ page }) => {
 | |
|             const pages = [
 | |
|                 '/trainer/dashboard/',
 | |
|                 '/trainer/profile/',
 | |
|                 '/trainer/events/'
 | |
|             ];
 | |
|             
 | |
|             for (const pagePath of pages) {
 | |
|                 const metrics = await measurePagePerformance(page, `${BASE_URL}${pagePath}`);
 | |
|                 
 | |
|                 console.log(`DOM nodes on ${pagePath}: ${metrics.nodeCount}`);
 | |
|                 expect(metrics.nodeCount).toBeLessThan(PERFORMANCE_TARGETS.maxDomNodes);
 | |
|                 expect(metrics.depth).toBeLessThan(20); // Reasonable DOM depth
 | |
|             }
 | |
|         });
 | |
| 
 | |
|         test('should optimize form rendering performance', async ({ page }) => {
 | |
|             await page.goto(`${BASE_URL}/trainer/events/create/`);
 | |
|             
 | |
|             const formMetrics = await page.evaluate(() => {
 | |
|                 const forms = document.querySelectorAll('form');
 | |
|                 const inputs = document.querySelectorAll('input, select, textarea');
 | |
|                 
 | |
|                 return {
 | |
|                     formCount: forms.length,
 | |
|                     inputCount: inputs.length,
 | |
|                     renderTime: performance.now()
 | |
|                 };
 | |
|             });
 | |
|             
 | |
|             console.log('Form complexity:', formMetrics);
 | |
|             
 | |
|             // Forms should render efficiently
 | |
|             expect(formMetrics.inputCount).toBeLessThan(100); // Reasonable form complexity
 | |
|             expect(formMetrics.renderTime).toBeLessThan(1000); // Quick render time
 | |
|         });
 | |
|     });
 | |
| });
 | |
| 
 | |
| // Export performance test configuration
 | |
| module.exports = {
 | |
|     testDir: __dirname,
 | |
|     timeout: TEST_TIMEOUT,
 | |
|     retries: 2, // Performance tests may need retries
 | |
|     workers: 1, // Sequential for accurate performance measurement
 | |
|     use: {
 | |
|         baseURL: BASE_URL,
 | |
|         screenshot: 'only-on-failure',
 | |
|         video: 'retain-on-failure',
 | |
|         trace: 'retain-on-failure'
 | |
|     }
 | |
| }; |