/** * 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' } };