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>
		
			
				
	
	
		
			733 lines
		
	
	
		
			No EOL
		
	
	
		
			28 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			733 lines
		
	
	
		
			No EOL
		
	
	
		
			28 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /**
 | |
|  * Stability & Regression Test Suite
 | |
|  * 
 | |
|  * Tests for preventing regressions after major architectural refactoring:
 | |
|  * - PHP segfault prevention (monitoring disabled areas)
 | |
|  * - Long-running operation stability
 | |
|  * - Memory leak detection  
 | |
|  * - Browser crash prevention (especially Safari)
 | |
|  * - Background job stability
 | |
|  * - Resource exhaustion protection
 | |
|  * - Error recovery mechanisms
 | |
|  * 
 | |
|  * @package HVAC_Community_Events
 | |
|  * @version 3.0.0
 | |
|  * @created 2025-08-20
 | |
|  */
 | |
| 
 | |
| const { test, expect, authHelpers } = 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 = 180000; // Extended for stability tests
 | |
| 
 | |
| // Stability test scenarios
 | |
| const STRESS_TEST_SCENARIOS = {
 | |
|     rapidNavigation: {
 | |
|         pages: ['/trainer/dashboard/', '/trainer/events/', '/trainer/profile/', '/trainer/certificate-reports/'],
 | |
|         iterations: 10,
 | |
|         delay: 500
 | |
|     },
 | |
|     formSubmission: {
 | |
|         forms: ['/trainer/profile/edit/', '/trainer/events/create/'],
 | |
|         iterations: 5,
 | |
|         delay: 1000
 | |
|     },
 | |
|     resourceLoading: {
 | |
|         pages: ['/find-trainer/', '/trainer/dashboard/'],
 | |
|         iterations: 8,
 | |
|         delay: 2000
 | |
|     }
 | |
| };
 | |
| 
 | |
| // Memory thresholds
 | |
| const MEMORY_LIMITS = {
 | |
|     maxHeapSize: 150, // MB
 | |
|     maxHeapGrowth: 50, // MB per operation
 | |
|     maxLeakRate: 10   // MB per minute
 | |
| };
 | |
| 
 | |
| // Helper functions
 | |
| async function loginAsTrainer(page) {
 | |
|     await authHelpers.loginAs(page, 'trainer');
 | |
| }
 | |
| 
 | |
| async function monitorMemoryUsage(page) {
 | |
|     return await page.evaluate(() => {
 | |
|         if (performance.memory) {
 | |
|             return {
 | |
|                 used: performance.memory.usedJSHeapSize / 1024 / 1024, // MB
 | |
|                 total: performance.memory.totalJSHeapSize / 1024 / 1024, // MB
 | |
|                 limit: performance.memory.jsHeapSizeLimit / 1024 / 1024, // MB
 | |
|                 timestamp: Date.now()
 | |
|             };
 | |
|         }
 | |
|         return null;
 | |
|     });
 | |
| }
 | |
| 
 | |
| async function forceGarbageCollection(page) {
 | |
|     await page.evaluate(() => {
 | |
|         if (window.gc) {
 | |
|             window.gc();
 | |
|         }
 | |
|         
 | |
|         // Force some cleanup
 | |
|         if (window.performance && window.performance.clearResourceTimings) {
 | |
|             window.performance.clearResourceTimings();
 | |
|         }
 | |
|     });
 | |
| }
 | |
| 
 | |
| async function detectPageErrors(page) {
 | |
|     return await page.evaluate(() => {
 | |
|         const errors = [];
 | |
|         
 | |
|         // Check for JavaScript errors in console
 | |
|         if (window.console && window.console.logs) {
 | |
|             errors.push(...window.console.logs.filter(log => 
 | |
|                 log.includes('error') || log.includes('Error')
 | |
|             ));
 | |
|         }
 | |
|         
 | |
|         // Check for PHP errors in page content
 | |
|         const content = document.body.textContent;
 | |
|         const phpErrors = [
 | |
|             'Fatal error:',
 | |
|             'Parse error:',
 | |
|             'Warning:',
 | |
|             'Notice:',
 | |
|             'Deprecated:',
 | |
|             'Catchable fatal error:',
 | |
|             'segmentation fault'
 | |
|         ];
 | |
|         
 | |
|         phpErrors.forEach(errorType => {
 | |
|             if (content.includes(errorType)) {
 | |
|                 errors.push(`PHP Error: ${errorType}`);
 | |
|             }
 | |
|         });
 | |
|         
 | |
|         // Check for broken images/resources
 | |
|         const brokenImages = Array.from(document.querySelectorAll('img')).filter(img => 
 | |
|             !img.complete || img.naturalHeight === 0
 | |
|         );
 | |
|         
 | |
|         if (brokenImages.length > 0) {
 | |
|             errors.push(`${brokenImages.length} broken images`);
 | |
|         }
 | |
|         
 | |
|         return errors;
 | |
|     });
 | |
| }
 | |
| 
 | |
| async function stressTestNavigation(page, scenario) {
 | |
|     const results = {
 | |
|         iterations: 0,
 | |
|         errors: [],
 | |
|         loadTimes: [],
 | |
|         memoryUsage: []
 | |
|     };
 | |
|     
 | |
|     for (let i = 0; i < scenario.iterations; i++) {
 | |
|         for (const pagePath of scenario.pages) {
 | |
|             const startTime = Date.now();
 | |
|             
 | |
|             try {
 | |
|                 await page.goto(`${BASE_URL}${pagePath}`, { 
 | |
|                     waitUntil: 'domcontentloaded',
 | |
|                     timeout: 15000 
 | |
|                 });
 | |
|                 
 | |
|                 const loadTime = Date.now() - startTime;
 | |
|                 results.loadTimes.push(loadTime);
 | |
|                 
 | |
|                 // Check for errors
 | |
|                 const pageErrors = await detectPageErrors(page);
 | |
|                 results.errors.push(...pageErrors);
 | |
|                 
 | |
|                 // Monitor memory
 | |
|                 const memory = await monitorMemoryUsage(page);
 | |
|                 if (memory) {
 | |
|                     results.memoryUsage.push(memory);
 | |
|                 }
 | |
|                 
 | |
|                 // Brief pause
 | |
|                 await page.waitForTimeout(scenario.delay);
 | |
|                 
 | |
|             } catch (error) {
 | |
|                 results.errors.push(`Navigation error: ${error.message}`);
 | |
|             }
 | |
|         }
 | |
|         
 | |
|         results.iterations = i + 1;
 | |
|         
 | |
|         // Force cleanup every few iterations
 | |
|         if (i % 3 === 0) {
 | |
|             await forceGarbageCollection(page);
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     return results;
 | |
| }
 | |
| 
 | |
| async function stressTestForms(page, scenario) {
 | |
|     const results = {
 | |
|         iterations: 0,
 | |
|         errors: [],
 | |
|         submissionTimes: [],
 | |
|         memoryUsage: []
 | |
|     };
 | |
|     
 | |
|     for (let i = 0; i < scenario.iterations; i++) {
 | |
|         for (const formPath of scenario.forms) {
 | |
|             try {
 | |
|                 await page.goto(`${BASE_URL}${formPath}`);
 | |
|                 await page.waitForSelector('form', { timeout: 10000 });
 | |
|                 
 | |
|                 // Fill form with test data
 | |
|                 const startTime = Date.now();
 | |
|                 
 | |
|                 const textInputs = await page.$$('input[type="text"], input[type="email"], textarea');
 | |
|                 for (const input of textInputs.slice(0, 3)) { // Limit to first 3 inputs
 | |
|                     await input.fill(`Test data ${Date.now()}`);
 | |
|                 }
 | |
|                 
 | |
|                 // Submit if possible
 | |
|                 const submitButton = await page.$('button[type="submit"], input[type="submit"]');
 | |
|                 if (submitButton) {
 | |
|                     await submitButton.click();
 | |
|                     await page.waitForTimeout(2000);
 | |
|                 }
 | |
|                 
 | |
|                 const submissionTime = Date.now() - startTime;
 | |
|                 results.submissionTimes.push(submissionTime);
 | |
|                 
 | |
|                 // Check for errors
 | |
|                 const pageErrors = await detectPageErrors(page);
 | |
|                 results.errors.push(...pageErrors);
 | |
|                 
 | |
|                 // Monitor memory
 | |
|                 const memory = await monitorMemoryUsage(page);
 | |
|                 if (memory) {
 | |
|                     results.memoryUsage.push(memory);
 | |
|                 }
 | |
|                 
 | |
|                 await page.waitForTimeout(scenario.delay);
 | |
|                 
 | |
|             } catch (error) {
 | |
|                 results.errors.push(`Form error: ${error.message}`);
 | |
|             }
 | |
|         }
 | |
|         
 | |
|         results.iterations = i + 1;
 | |
|         
 | |
|         if (i % 2 === 0) {
 | |
|             await forceGarbageCollection(page);
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     return results;
 | |
| }
 | |
| 
 | |
| async function takeStabilityScreenshot(page, name, metrics) {
 | |
|     const screenshotDir = path.join(__dirname, '../../screenshots/stability');
 | |
|     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(`Stability metrics for ${name}:`, metrics);
 | |
| }
 | |
| 
 | |
| test.describe('Stability & Regression Tests', () => {
 | |
|     test.setTimeout(TEST_TIMEOUT);
 | |
|     
 | |
|     test.beforeEach(async ({ page }) => {
 | |
|         await page.setViewportSize({ width: 1280, height: 720 });
 | |
|         
 | |
|         // Set up error monitoring
 | |
|         page.on('pageerror', error => {
 | |
|             console.log('Page error detected:', error.message);
 | |
|         });
 | |
|         
 | |
|         page.on('requestfailed', request => {
 | |
|             console.log('Request failed:', request.url(), request.failure()?.errorText);
 | |
|         });
 | |
|         
 | |
|         // Login before each test to ensure authenticated access
 | |
|         await authHelpers.loginAs(page, 'trainer');
 | |
|     });
 | |
| 
 | |
|     test.describe('PHP Segfault Prevention Tests', () => {
 | |
|         test('should not trigger segfaults with monitoring systems disabled', async ({ page }) => {
 | |
|             
 | |
|             // Test operations that previously caused segfaults
 | |
|             const riskOperations = [
 | |
|                 async () => {
 | |
|                     await page.goto(`${BASE_URL}/trainer/dashboard/`);
 | |
|                     await page.waitForLoadState('domcontentloaded');
 | |
|                 },
 | |
|                 async () => {
 | |
|                     await page.goto(`${BASE_URL}/trainer/events/create/`);
 | |
|                     await page.waitForSelector('form, iframe', { timeout: 10000 });
 | |
|                 },
 | |
|                 async () => {
 | |
|                     await page.goto(`${BASE_URL}/find-trainer/`);
 | |
|                     await page.waitForLoadState('networkidle', { timeout: 30000 });
 | |
|                 },
 | |
|                 async () => {
 | |
|                     // Heavy form interaction
 | |
|                     await page.goto(`${BASE_URL}/trainer/profile/edit/`);
 | |
|                     const inputs = await page.$$('input, select, textarea');
 | |
|                     for (const input of inputs.slice(0, 5)) {
 | |
|                         await input.fill('Test data');
 | |
|                     }
 | |
|                 }
 | |
|             ];
 | |
|             
 | |
|             for (let i = 0; i < riskOperations.length; i++) {
 | |
|                 const operation = riskOperations[i];
 | |
|                 
 | |
|                 try {
 | |
|                     await operation();
 | |
|                     
 | |
|                     // Check for segfault indicators
 | |
|                     const errors = await detectPageErrors(page);
 | |
|                     const hasSegfault = errors.some(error => 
 | |
|                         error.includes('segmentation fault') ||
 | |
|                         error.includes('Fatal error') ||
 | |
|                         error.includes('Process terminated')
 | |
|                     );
 | |
|                     
 | |
|                     expect(hasSegfault).toBeFalsy();
 | |
|                     console.log(`Risk operation ${i + 1} completed safely`);
 | |
|                     
 | |
|                 } catch (error) {
 | |
|                     // Should not crash the test
 | |
|                     console.log(`Risk operation ${i + 1} error:`, error.message);
 | |
|                     expect(error.message).not.toContain('segmentation fault');
 | |
|                 }
 | |
|                 
 | |
|                 await page.waitForTimeout(2000);
 | |
|             }
 | |
|         });
 | |
| 
 | |
|         test('should handle resource-intensive operations safely', async ({ page }) => {
 | |
|             
 | |
|             // Simulate resource-intensive operations
 | |
|             const heavyOperations = [
 | |
|                 {
 | |
|                     name: 'Multiple simultaneous AJAX requests',
 | |
|                     operation: async () => {
 | |
|                         await page.goto(`${BASE_URL}/trainer/dashboard/`);
 | |
|                         
 | |
|                         // Trigger multiple requests
 | |
|                         await page.evaluate(() => {
 | |
|                             for (let i = 0; i < 5; i++) {
 | |
|                                 fetch(window.location.href + '?test=' + i);
 | |
|                             }
 | |
|                         });
 | |
|                     }
 | |
|                 },
 | |
|                 {
 | |
|                     name: 'Large form data processing',
 | |
|                     operation: async () => {
 | |
|                         await page.goto(`${BASE_URL}/trainer/profile/edit/`);
 | |
|                         
 | |
|                         // Fill form with large amounts of data
 | |
|                         const largeText = 'Lorem ipsum dolor sit amet, '.repeat(100);
 | |
|                         const textareas = await page.$$('textarea');
 | |
|                         for (const textarea of textareas.slice(0, 2)) {
 | |
|                             await textarea.fill(largeText);
 | |
|                         }
 | |
|                     }
 | |
|                 },
 | |
|                 {
 | |
|                     name: 'Rapid page transitions',
 | |
|                     operation: async () => {
 | |
|                         const pages = ['/trainer/dashboard/', '/trainer/events/', '/trainer/profile/'];
 | |
|                         for (const pagePath of pages) {
 | |
|                             await page.goto(`${BASE_URL}${pagePath}`, { 
 | |
|                                 waitUntil: 'domcontentloaded',
 | |
|                                 timeout: 10000 
 | |
|                             });
 | |
|                             await page.waitForTimeout(100); // Minimal delay
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|             ];
 | |
|             
 | |
|             for (const operation of heavyOperations) {
 | |
|                 try {
 | |
|                     await operation.operation();
 | |
|                     
 | |
|                     // Check system stability
 | |
|                     const isStable = await page.evaluate(() => {
 | |
|                         return document.readyState === 'complete' && 
 | |
|                                !document.body.textContent.includes('Fatal error');
 | |
|                     });
 | |
|                     
 | |
|                     expect(isStable).toBeTruthy();
 | |
|                     console.log(`Heavy operation "${operation.name}" completed safely`);
 | |
|                     
 | |
|                 } catch (error) {
 | |
|                     console.log(`Heavy operation "${operation.name}" error:`, error.message);
 | |
|                     expect(error.message).not.toContain('timeout');
 | |
|                 }
 | |
|                 
 | |
|                 await page.waitForTimeout(3000);
 | |
|             }
 | |
|         });
 | |
|     });
 | |
| 
 | |
|     test.describe('Memory Leak Detection Tests', () => {
 | |
|         test('should not leak memory during normal operations', async ({ page, browserName }) => {
 | |
|             test.skip(browserName !== 'chromium', 'Memory monitoring requires Chromium');
 | |
|             
 | |
|             
 | |
|             const memorySnapshots = [];
 | |
|             const operations = [
 | |
|                 () => page.goto(`${BASE_URL}/trainer/dashboard/`),
 | |
|                 () => page.goto(`${BASE_URL}/trainer/events/`),
 | |
|                 () => page.goto(`${BASE_URL}/trainer/profile/`),
 | |
|                 () => page.goto(`${BASE_URL}/trainer/certificate-reports/`)
 | |
|             ];
 | |
|             
 | |
|             // Baseline memory
 | |
|             await forceGarbageCollection(page);
 | |
|             const baselineMemory = await monitorMemoryUsage(page);
 | |
|             memorySnapshots.push({ operation: 'baseline', ...baselineMemory });
 | |
|             
 | |
|             // Perform operations and measure memory
 | |
|             for (let i = 0; i < operations.length; i++) {
 | |
|                 await operations[i]();
 | |
|                 await page.waitForLoadState('domcontentloaded');
 | |
|                 
 | |
|                 // Force cleanup and measure
 | |
|                 await forceGarbageCollection(page);
 | |
|                 await page.waitForTimeout(1000);
 | |
|                 
 | |
|                 const memory = await monitorMemoryUsage(page);
 | |
|                 if (memory) {
 | |
|                     memorySnapshots.push({ 
 | |
|                         operation: `operation_${i}`, 
 | |
|                         ...memory 
 | |
|                     });
 | |
|                 }
 | |
|             }
 | |
|             
 | |
|             // Analyze memory growth
 | |
|             if (memorySnapshots.length > 1) {
 | |
|                 const initialMemory = memorySnapshots[0].used;
 | |
|                 const finalMemory = memorySnapshots[memorySnapshots.length - 1].used;
 | |
|                 const memoryGrowth = finalMemory - initialMemory;
 | |
|                 
 | |
|                 console.log('Memory usage progression:', memorySnapshots.map(s => ({
 | |
|                     operation: s.operation,
 | |
|                     used: `${s.used.toFixed(2)}MB`
 | |
|                 })));
 | |
|                 
 | |
|                 // Memory growth should be reasonable
 | |
|                 expect(memoryGrowth).toBeLessThan(MEMORY_LIMITS.maxHeapGrowth);
 | |
|                 expect(finalMemory).toBeLessThan(MEMORY_LIMITS.maxHeapSize);
 | |
|             }
 | |
|         });
 | |
| 
 | |
|         test('should handle memory pressure gracefully', async ({ page, browserName }) => {
 | |
|             test.skip(browserName !== 'chromium', 'Memory testing requires Chromium');
 | |
|             
 | |
|             
 | |
|             // Create memory pressure
 | |
|             await page.evaluate(() => {
 | |
|                 // Create some memory pressure (but not enough to crash)
 | |
|                 const arrays = [];
 | |
|                 for (let i = 0; i < 100; i++) {
 | |
|                     arrays.push(new Array(10000).fill(Math.random()));
 | |
|                 }
 | |
|                 window.memoryPressureArrays = arrays;
 | |
|             });
 | |
|             
 | |
|             const pressuredMemory = await monitorMemoryUsage(page);
 | |
|             
 | |
|             // Perform normal operations under memory pressure
 | |
|             await page.goto(`${BASE_URL}/trainer/dashboard/`);
 | |
|             await page.goto(`${BASE_URL}/trainer/events/create/`);
 | |
|             
 | |
|             // Check if operations still work
 | |
|             const stillFunctional = await page.evaluate(() => {
 | |
|                 return document.readyState === 'complete' &&
 | |
|                        document.body.textContent.length > 0;
 | |
|             });
 | |
|             
 | |
|             expect(stillFunctional).toBeTruthy();
 | |
|             
 | |
|             // Cleanup
 | |
|             await page.evaluate(() => {
 | |
|                 delete window.memoryPressureArrays;
 | |
|             });
 | |
|             
 | |
|             await forceGarbageCollection(page);
 | |
|             
 | |
|             const cleanedMemory = await monitorMemoryUsage(page);
 | |
|             console.log('Memory under pressure:', {
 | |
|                 baseline: pressuredMemory?.used.toFixed(2) + 'MB',
 | |
|                 cleaned: cleanedMemory?.used.toFixed(2) + 'MB'
 | |
|             });
 | |
|         });
 | |
|     });
 | |
| 
 | |
|     test.describe('Browser Crash Prevention Tests', () => {
 | |
|         test('should prevent Safari crashes with resource loading', async ({ page, browserName }) => {
 | |
|             test.skip(browserName !== 'webkit', 'Safari-specific test');
 | |
|             
 | |
|             // Extended timeout for Safari
 | |
|             test.setTimeout(240000);
 | |
|             
 | |
|             try {
 | |
|                 // Test the previously problematic find-trainer page
 | |
|                 await page.goto(`${BASE_URL}/find-trainer/`, { 
 | |
|                     waitUntil: 'networkidle',
 | |
|                     timeout: 60000 
 | |
|                 });
 | |
|                 
 | |
|                 // Verify page loaded completely
 | |
|                 const pageState = await page.evaluate(() => ({
 | |
|                     readyState: document.readyState,
 | |
|                     hasContent: document.body.textContent.length > 100,
 | |
|                     hasMap: !!document.querySelector('[class*="map"], #map, .mapgeo'),
 | |
|                     hasTrainerCards: document.querySelectorAll('.trainer-card, .hvac-trainer').length
 | |
|                 }));
 | |
|                 
 | |
|                 expect(pageState.readyState).toBe('complete');
 | |
|                 expect(pageState.hasContent).toBeTruthy();
 | |
|                 
 | |
|                 console.log('Safari page load successful:', pageState);
 | |
|                 
 | |
|                 // Test interaction without crashing
 | |
|                 const interactiveElements = await page.$$('button, a, input');
 | |
|                 if (interactiveElements.length > 0) {
 | |
|                     await interactiveElements[0].click();
 | |
|                     await page.waitForTimeout(2000);
 | |
|                 }
 | |
|                 
 | |
|                 await takeStabilityScreenshot(page, 'safari-stability', pageState);
 | |
|                 
 | |
|             } catch (error) {
 | |
|                 console.error('Safari test failed:', error);
 | |
|                 
 | |
|                 // Take screenshot for debugging
 | |
|                 await page.screenshot({ 
 | |
|                     path: path.join(__dirname, '../../screenshots/safari-crash-debug.png'),
 | |
|                     fullPage: true 
 | |
|                 });
 | |
|                 
 | |
|                 // Re-throw if it's a crash, but allow timeouts
 | |
|                 if (!error.message.includes('timeout')) {
 | |
|                     throw error;
 | |
|                 }
 | |
|             }
 | |
|         });
 | |
| 
 | |
|         test('should handle resource loading cascade issues', async ({ page }) => {
 | |
|             
 | |
|             // Monitor resource loading
 | |
|             const resourcePromises = [];
 | |
|             page.on('response', response => {
 | |
|                 if (response.url().includes('.css') || response.url().includes('.js')) {
 | |
|                     resourcePromises.push({
 | |
|                         url: response.url(),
 | |
|                         status: response.status(),
 | |
|                         timing: Date.now()
 | |
|                     });
 | |
|                 }
 | |
|             });
 | |
|             
 | |
|             await page.goto(`${BASE_URL}/trainer/dashboard/`);
 | |
|             await page.waitForLoadState('networkidle');
 | |
|             
 | |
|             // Analyze resource loading pattern
 | |
|             const cssResources = resourcePromises.filter(r => r.url.includes('.css'));
 | |
|             const failedResources = resourcePromises.filter(r => r.status >= 400);
 | |
|             
 | |
|             console.log('Resource loading analysis:', {
 | |
|                 totalResources: resourcePromises.length,
 | |
|                 cssFiles: cssResources.length,
 | |
|                 failedResources: failedResources.length
 | |
|             });
 | |
|             
 | |
|             // Should not have excessive CSS files (consolidated)
 | |
|             expect(cssResources.length).toBeLessThan(10);
 | |
|             
 | |
|             // Should not have many failed resources
 | |
|             expect(failedResources.length).toBeLessThan(3);
 | |
|             
 | |
|             if (failedResources.length > 0) {
 | |
|                 console.log('Failed resources:', failedResources.map(r => r.url));
 | |
|             }
 | |
|         });
 | |
|     });
 | |
| 
 | |
|     test.describe('Long-Running Operation Tests', () => {
 | |
|         test('should handle extended user sessions', async ({ page }) => {
 | |
|             
 | |
|             const sessionResults = await stressTestNavigation(page, STRESS_TEST_SCENARIOS.rapidNavigation);
 | |
|             
 | |
|             // Analyze session stability
 | |
|             console.log('Extended session results:', {
 | |
|                 iterations: sessionResults.iterations,
 | |
|                 errors: sessionResults.errors.length,
 | |
|                 avgLoadTime: sessionResults.loadTimes.reduce((a, b) => a + b, 0) / sessionResults.loadTimes.length,
 | |
|                 memoryTrend: sessionResults.memoryUsage.length > 0 ? 
 | |
|                     sessionResults.memoryUsage[sessionResults.memoryUsage.length - 1].used - sessionResults.memoryUsage[0].used : 0
 | |
|             });
 | |
|             
 | |
|             // Should complete most iterations without major errors
 | |
|             expect(sessionResults.iterations).toBe(STRESS_TEST_SCENARIOS.rapidNavigation.iterations);
 | |
|             
 | |
|             // Should not have critical errors
 | |
|             const criticalErrors = sessionResults.errors.filter(error => 
 | |
|                 error.includes('Fatal') || error.includes('segmentation')
 | |
|             );
 | |
|             expect(criticalErrors.length).toBe(0);
 | |
|             
 | |
|             // Load times should remain reasonable
 | |
|             const averageLoadTime = sessionResults.loadTimes.reduce((a, b) => a + b, 0) / sessionResults.loadTimes.length;
 | |
|             expect(averageLoadTime).toBeLessThan(5000); // 5 seconds max average
 | |
|             
 | |
|             await takeStabilityScreenshot(page, 'extended-session', sessionResults);
 | |
|         });
 | |
| 
 | |
|         test('should handle concurrent user simulation', async ({ browser }) => {
 | |
|             const concurrentUsers = 3;
 | |
|             const contexts = await Promise.all(
 | |
|                 Array(concurrentUsers).fill().map(() => browser.newContext())
 | |
|             );
 | |
|             
 | |
|             const userPromises = contexts.map(async (context, index) => {
 | |
|                 const page = await context.newPage();
 | |
|                 
 | |
|                 try {
 | |
|                             
 | |
|                     // Simulate different user behaviors
 | |
|                     const behavior = index % 2 === 0 ? 'navigator' : 'form_user';
 | |
|                     
 | |
|                     if (behavior === 'navigator') {
 | |
|                         return await stressTestNavigation(page, {
 | |
|                             pages: ['/trainer/dashboard/', '/trainer/events/', '/trainer/profile/'],
 | |
|                             iterations: 3,
 | |
|                             delay: 1000
 | |
|                         });
 | |
|                     } else {
 | |
|                         return await stressTestForms(page, {
 | |
|                             forms: ['/trainer/profile/edit/'],
 | |
|                             iterations: 2,
 | |
|                             delay: 2000
 | |
|                         });
 | |
|                     }
 | |
|                 } catch (error) {
 | |
|                     return { error: error.message };
 | |
|                 }
 | |
|             });
 | |
|             
 | |
|             const results = await Promise.all(userPromises);
 | |
|             
 | |
|             // Analyze concurrent usage results
 | |
|             const successfulUsers = results.filter(r => !r.error);
 | |
|             const errorCounts = results.map(r => r.errors?.length || 0);
 | |
|             
 | |
|             console.log('Concurrent users results:', {
 | |
|                 successfulUsers: successfulUsers.length,
 | |
|                 totalErrors: errorCounts.reduce((a, b) => a + b, 0)
 | |
|             });
 | |
|             
 | |
|             // Most users should succeed
 | |
|             expect(successfulUsers.length).toBeGreaterThanOrEqual(concurrentUsers - 1);
 | |
|             
 | |
|             // Total errors should be manageable
 | |
|             const totalErrors = errorCounts.reduce((a, b) => a + b, 0);
 | |
|             expect(totalErrors).toBeLessThan(10);
 | |
|             
 | |
|             // Cleanup
 | |
|             await Promise.all(contexts.map(ctx => ctx.close()));
 | |
|         });
 | |
| 
 | |
|         test('should recover from temporary failures', async ({ page }) => {
 | |
|             
 | |
|             // Simulate temporary failures and recovery
 | |
|             const recoveryTests = [
 | |
|                 {
 | |
|                     name: 'Network interruption simulation',
 | |
|                     test: async () => {
 | |
|                         // Simulate network issues
 | |
|                         await page.route('**/*', route => {
 | |
|                             if (Math.random() < 0.1) { // 10% failure rate
 | |
|                                 route.abort();
 | |
|                             } else {
 | |
|                                 route.continue();
 | |
|                             }
 | |
|                         });
 | |
|                         
 | |
|                         await page.goto(`${BASE_URL}/trainer/dashboard/`);
 | |
|                         await page.waitForTimeout(3000);
 | |
|                         
 | |
|                         // Remove route handler (recovery)
 | |
|                         await page.unroute('**/*');
 | |
|                         
 | |
|                         // Try normal operation
 | |
|                         await page.goto(`${BASE_URL}/trainer/profile/`);
 | |
|                         
 | |
|                         return await page.locator('body').isVisible();
 | |
|                     }
 | |
|                 },
 | |
|                 {
 | |
|                     name: 'Resource timeout recovery',
 | |
|                     test: async () => {
 | |
|                         // Add timeout to slow resources
 | |
|                         await page.route('**/*.css', route => {
 | |
|                             setTimeout(() => route.continue(), 2000);
 | |
|                         });
 | |
|                         
 | |
|                         await page.goto(`${BASE_URL}/trainer/events/`, { timeout: 30000 });
 | |
|                         
 | |
|                         // Remove handler
 | |
|                         await page.unroute('**/*.css');
 | |
|                         
 | |
|                         return await page.locator('body').isVisible();
 | |
|                     }
 | |
|                 }
 | |
|             ];
 | |
|             
 | |
|             for (const recoveryTest of recoveryTests) {
 | |
|                 try {
 | |
|                     const recovered = await recoveryTest.test();
 | |
|                     expect(recovered).toBeTruthy();
 | |
|                     console.log(`Recovery test "${recoveryTest.name}": PASSED`);
 | |
|                 } catch (error) {
 | |
|                     console.log(`Recovery test "${recoveryTest.name}": FAILED -`, error.message);
 | |
|                     // Allow some failures in recovery tests
 | |
|                 }
 | |
|                 
 | |
|                 await page.waitForTimeout(2000);
 | |
|             }
 | |
|         });
 | |
|     });
 | |
| });
 | |
| 
 | |
| // Export stability test configuration
 | |
| module.exports = {
 | |
|     testDir: __dirname,
 | |
|     timeout: TEST_TIMEOUT,
 | |
|     retries: 2, // Allow retries for stability tests
 | |
|     workers: 1, // Sequential to avoid interference
 | |
|     use: {
 | |
|         baseURL: BASE_URL,
 | |
|         screenshot: 'only-on-failure',
 | |
|         video: 'retain-on-failure',
 | |
|         trace: 'on-first-retry'
 | |
|     }
 | |
| }; |