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