import { test, expect } from '@playwright/test'; /** * Performance testing suite for Leptos components * Tests component rendering, interaction, and stress performance * * NOTE: This test suite uses development-mode performance thresholds * which are more relaxed than production expectations to account for: * - Debug builds and development tooling * - Development server overhead * - Unoptimized WASM compilation * - Development environment variations * * Production thresholds should be significantly stricter. */ test.describe('Leptos Performance Testing Suite', () => { test.beforeEach(async ({ page }) => { // Navigate to Leptos example app await page.goto('/'); await page.waitForLoadState('networkidle'); }); test.describe('Page Load Performance', () => { test('initial page load time is under 3 seconds', async ({ page }) => { const startTime = Date.now(); await page.goto('/'); await page.waitForLoadState('networkidle'); const loadTime = Date.now() - startTime; // Initial load should be fast expect(loadTime).toBeLessThan(3000); console.log(`📊 Initial page load time: ${loadTime}ms`); }); test('time to interactive is reasonable', async ({ page }) => { // Measure time to interactive (when buttons become clickable) const startTime = Date.now(); await page.goto('/'); // Wait for interactive elements to be ready await page.waitForSelector('button, input, select', { state: 'visible' }); const tti = Date.now() - startTime; // Time to interactive should be reasonable expect(tti).toBeLessThan(2000); console.log(`⚡ Time to interactive: ${tti}ms`); }); test('memory usage is stable', async ({ page }) => { // Get initial memory info const initialMemory = await page.evaluate(() => { if ('memory' in performance) { return (performance as any).memory.usedJSHeapSize; } return null; }); if (initialMemory) { // Navigate and interact to trigger memory usage await page.goto('/'); await page.waitForLoadState('networkidle'); // Perform some interactions const buttons = page.locator('button'); if (await buttons.count() > 0) { for (let i = 0; i < Math.min(await buttons.count(), 3); i++) { await buttons.nth(i).click(); await page.waitForTimeout(100); } } // Check memory after interactions const finalMemory = await page.evaluate(() => { if ('memory' in performance) { return (performance as any).memory.usedJSHeapSize; } return null; }); if (finalMemory) { const memoryIncrease = finalMemory - initialMemory; const memoryIncreaseMB = memoryIncrease / (1024 * 1024); // Memory increase should be reasonable (less than 50MB) expect(memoryIncreaseMB).toBeLessThan(50); console.log(`🧠 Memory increase: ${memoryIncreaseMB.toFixed(2)}MB`); } } }); }); test.describe('Component Rendering Performance', () => { test('component render time is fast', async ({ page }) => { // Measure component rendering performance const renderStart = performance.now(); // Trigger a re-render by interacting with components const buttons = page.locator('button'); if (await buttons.count() > 0) { await buttons.first().click(); // Wait for any animations or state changes await page.waitForTimeout(100); const renderEnd = performance.now(); const renderTime = renderEnd - renderStart; // Component render should be fast (development mode - relaxed threshold) expect(renderTime).toBeLessThan(1000); console.log(`🎨 Component render time: ${renderTime.toFixed(2)}ms`); } }); test('large component lists render efficiently', async ({ page }) => { // Look for components that might render lists const listContainers = page.locator('[class*="list"], [class*="table"], [class*="grid"]'); if (await listContainers.count() > 0) { const container = listContainers.first(); // Measure render time for list components const startTime = performance.now(); // Wait for any dynamic content to load await page.waitForTimeout(200); const endTime = performance.now(); const renderTime = endTime - startTime; // List rendering should be efficient (development mode - relaxed threshold) expect(renderTime).toBeLessThan(500); console.log(`📋 List component render time: ${renderTime.toFixed(2)}ms`); } }); test('form validation is responsive', async ({ page }) => { const inputs = page.locator('input[type="text"], input[type="email"]'); if (await inputs.count() > 0) { const input = inputs.first(); // Measure validation response time const startTime = performance.now(); await input.fill('test@example.com'); await input.blur(); // Wait for validation to complete await page.waitForTimeout(100); const endTime = performance.now(); const validationTime = endTime - startTime; // Validation should be responsive (development mode - relaxed threshold) expect(validationTime).toBeLessThan(1000); console.log(`✅ Form validation time: ${validationTime.toFixed(2)}ms`); } }); }); test.describe('Interaction Performance', () => { test('button clicks are responsive', async ({ page }) => { const buttons = page.locator('button'); if (await buttons.count() > 0) { const button = buttons.first(); // Measure click response time const startTime = performance.now(); await button.click(); // Wait for any state changes await page.waitForTimeout(50); const endTime = performance.now(); const clickTime = endTime - startTime; // Button clicks should be responsive (development mode - relaxed threshold) expect(clickTime).toBeLessThan(1000); console.log(`🖱️ Button click response time: ${clickTime.toFixed(2)}ms`); } }); test('dropdown interactions are smooth', async ({ page }) => { const dropdownTriggers = page.locator('button[aria-haspopup="true"], [data-state="closed"]'); if (await dropdownTriggers.count() > 0) { const trigger = dropdownTriggers.first(); // Measure dropdown open time const startTime = performance.now(); await trigger.click(); // Wait for dropdown to appear const dropdown = page.locator('[role="menu"], [data-state="open"]'); if (await dropdown.count() > 0) { await expect(dropdown.first()).toBeVisible(); const endTime = performance.now(); const dropdownTime = endTime - startTime; // Dropdown should open quickly expect(dropdownTime).toBeLessThan(150); console.log(`📋 Dropdown open time: ${dropdownTime.toFixed(2)}ms`); } } }); test('modal interactions are performant', async ({ page }) => { const modalTriggers = page.locator('button[aria-haspopup="dialog"]'); if (await modalTriggers.count() > 0) { const trigger = modalTriggers.first(); // Measure modal open time const startTime = performance.now(); await trigger.click(); // Wait for modal to appear const modal = page.locator('[role="dialog"]'); if (await modal.count() > 0) { await expect(modal.first()).toBeVisible(); const endTime = performance.now(); const modalTime = endTime - startTime; // Modal should open quickly expect(modalTime).toBeLessThan(200); console.log(`🪟 Modal open time: ${modalTime.toFixed(2)}ms`); } } }); }); test.describe('Network and Resource Performance', () => { test('asset loading is optimized', async ({ page }) => { // Listen for network requests const requests: string[] = []; page.on('request', request => { requests.push(request.url()); }); await page.goto('/'); await page.waitForLoadState('networkidle'); // Check for unnecessary requests const unnecessaryRequests = requests.filter(url => url.includes('.woff') || url.includes('.ttf') || url.includes('analytics') || url.includes('tracking') ); // Should not have unnecessary requests expect(unnecessaryRequests.length).toBeLessThan(5); console.log(`🌐 Total network requests: ${requests.length}`); console.log(`❌ Unnecessary requests: ${unnecessaryRequests.length}`); }); test('CSS and JS bundle sizes are reasonable', async ({ page }) => { // Get resource timing information const resourceTimings = await page.evaluate(() => { return performance.getEntriesByType('resource').map(entry => ({ name: entry.name, duration: entry.duration, transferSize: (entry as any).transferSize || 0 })); }); // Check for large resources const largeResources = resourceTimings.filter(resource => resource.transferSize > 100 * 1024 // 100KB ); // Should not have too many large resources expect(largeResources.length).toBeLessThan(10); console.log(`📦 Large resources (>100KB): ${largeResources.length}`); // Log resource details largeResources.forEach(resource => { const sizeKB = resource.transferSize / 1024; console.log(` - ${resource.name}: ${sizeKB.toFixed(2)}KB`); }); }); }); test.describe('Responsive Performance', () => { test('mobile viewport performance is maintained', async ({ page }) => { // Set mobile viewport await page.setViewportSize({ width: 375, height: 667 }); const startTime = Date.now(); await page.goto('/'); await page.waitForLoadState('networkidle'); const mobileLoadTime = Date.now() - startTime; // Mobile should load within reasonable time expect(mobileLoadTime).toBeLessThan(4000); console.log(`📱 Mobile load time: ${mobileLoadTime}ms`); }); test('tablet viewport performance is maintained', async ({ page }) => { // Set tablet viewport await page.setViewportSize({ width: 768, height: 1024 }); const startTime = Date.now(); await page.goto('/'); await page.waitForLoadState('networkidle'); const tabletLoadTime = Date.now() - startTime; // Tablet should load within reasonable time expect(tabletLoadTime).toBeLessThan(3500); console.log(`📱 Tablet load time: ${tabletLoadTime}ms`); }); test('desktop viewport performance is optimal', async ({ page }) => { // Set desktop viewport await page.setViewportSize({ width: 1280, height: 720 }); const startTime = Date.now(); await page.goto('/'); await page.waitForLoadState('networkidle'); const desktopLoadTime = Date.now() - startTime; // Desktop should load quickly expect(desktopLoadTime).toBeLessThan(3000); console.log(`🖥️ Desktop load time: ${desktopLoadTime}ms`); }); }); test.describe('Stress Testing', () => { test('rapid interactions remain responsive', async ({ page }) => { const buttons = page.locator('button'); if (await buttons.count() > 0) { const button = buttons.first(); // Perform rapid clicks const startTime = performance.now(); for (let i = 0; i < 10; i++) { await button.click(); await page.waitForTimeout(10); } const endTime = performance.now(); const totalTime = endTime - startTime; // Should handle rapid interactions efficiently (development mode - relaxed threshold) expect(totalTime).toBeLessThan(2000); console.log(`⚡ Rapid interaction time: ${totalTime.toFixed(2)}ms`); } }); test('large data sets render efficiently', async ({ page }) => { // Look for components that might handle large datasets const dataComponents = page.locator('[class*="table"], [class*="list"], [class*="grid"]'); if (await dataComponents.count() > 0) { const component = dataComponents.first(); // Measure render performance const startTime = performance.now(); // Wait for any dynamic content await page.waitForTimeout(300); const endTime = performance.now(); const renderTime = endTime - startTime; // Should render large datasets efficiently (development mode - relaxed threshold) expect(renderTime).toBeLessThan(500); console.log(`📊 Large dataset render time: ${renderTime.toFixed(2)}ms`); } }); }); });