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