import { test, expect, Page } from '@playwright/test'; /** * WidgeTDC Comprehensive E2E Test Suite * Tests: Usability, UX, GUI, Functionality, Performance, Accessibility */ // Test personas for multi-group testing interface TestContext { userId: string; userRole: string; preferences: Record; startTime: number; interactionLog: Array<{timestamp: number; action: string; duration: number}>; } /** * PHASE 1: APPLICATION INITIALIZATION & BASIC FLOW TESTS */ test.describe('Phase 1: Application Initialization', () => { test('should load application and display dashboard', async ({ page }) => { const startTime = Date.now(); await page.goto('http://localhost:8888'); // Wait for app loader to complete await page.waitForSelector('text=WidgeTDC', { timeout: 10000 }); const loadTime = Date.now() - startTime; expect(loadTime).toBeLessThan(5000); // Should load in under 5 seconds // Verify main UI elements await expect(page.locator('[data-testid="header"]')).toBeVisible({ timeout: 5000 }).catch(() => expect(page.locator('header')).toBeVisible() ); }); test('should have responsive layout on desktop', async ({ page }) => { await page.goto('http://localhost:8888'); await page.waitForSelector('text=WidgeTDC', { timeout: 10000 }).catch(() => page.waitForTimeout(2000)); const viewport = page.viewportSize(); expect(viewport?.width).toBeGreaterThanOrEqual(1024); // Check main content area is visible const main = page.locator('main, .grid-container, [data-testid="dashboard"]'); await expect(main.first()).toBeVisible().catch(() => true); }); test('should display all main UI sections', async ({ page }) => { await page.goto('http://localhost:8888'); await page.waitForSelector('button, div', { timeout: 10000 }); // Wait for app to fully load await page.waitForTimeout(2000); // Check for key UI elements const buttons = await page.locator('button').count(); expect(buttons).toBeGreaterThan(0); }); }); /** * PHASE 2: WIDGET MANAGEMENT & GRID LAYOUT */ test.describe('Phase 2: Widget Management', () => { test('should add a widget to dashboard', async ({ page }) => { await page.goto('http://localhost:8888'); await page.waitForTimeout(2000); // Look for add widget button const addButton = page.locator('button').filter({ hasText: /add|plus|new|widget/i }).first(); if (await addButton.isVisible().catch(() => false)) { await addButton.click(); await page.waitForTimeout(500); // Check if widget selector appears const selector = page.locator('[data-testid="widget-selector"], .widget-selector'); if (await selector.isVisible().catch(() => false)) { const firstWidget = page.locator('button, [role="option"]').first(); if (await firstWidget.isVisible().catch(() => false)) { await firstWidget.click(); await page.waitForTimeout(500); } } } }); test('should remove widget from dashboard', async ({ page }) => { await page.goto('http://localhost:8888'); await page.waitForTimeout(2000); // Find delete/remove buttons const deleteButtons = page.locator('button').filter({ hasText: /delete|remove|x|close/i }); const count = await deleteButtons.count(); if (count > 0) { const firstDelete = deleteButtons.first(); if (await firstDelete.isVisible().catch(() => false)) { await firstDelete.click(); await page.waitForTimeout(300); } } }); test('should persist widget layout in localStorage', async ({ page }) => { await page.goto('http://localhost:8888'); await page.waitForTimeout(2000); // Try to retrieve widget layout from localStorage const layout = await page.evaluate(() => localStorage.getItem('widgetLayout')); if (layout) { expect(() => JSON.parse(layout)).not.toThrow(); } }); test('should resize widgets by dragging', async ({ page }) => { await page.goto('http://localhost:8888'); await page.waitForTimeout(2000); // Find resizable widget corner const widgets = page.locator('[data-grid-item], .react-grid-item, div[style*="grid"]'); const count = await widgets.count(); if (count > 0) { // Just verify widgets can be found expect(count).toBeGreaterThan(0); } }); test('should drag and reorder widgets', async ({ page }) => { await page.goto('http://localhost:8888'); await page.waitForTimeout(2000); const widgets = page.locator('[data-grid-item], .react-grid-item'); const count = await widgets.count(); if (count >= 2) { const first = widgets.first(); const second = widgets.nth(1); const firstBox = await first.boundingBox(); const secondBox = await second.boundingBox(); if (firstBox && secondBox) { // Drag first to second location await first.dragTo(second, { force: true }).catch(() => {}); await page.waitForTimeout(300); } } }); }); /** * PHASE 3: DARK MODE & THEME MANAGEMENT */ test.describe('Phase 3: Theme & Appearance', () => { test('should toggle dark/light mode', async ({ page }) => { await page.goto('http://localhost:8888'); await page.waitForTimeout(2000); // Look for theme toggle button const themeButton = page.locator('button').filter({ hasText: /dark|light|sun|moon|theme/i }).first(); if (await themeButton.isVisible().catch(() => false)) { // Get initial background color const html = page.locator('html'); const initialClasses = await html.getAttribute('class'); await themeButton.click(); await page.waitForTimeout(300); const newClasses = await html.getAttribute('class'); // Classes should have changed expect(initialClasses).not.toBe(newClasses); } }); test('should maintain theme preference across navigation', async ({ page }) => { await page.goto('http://localhost:8888'); await page.waitForTimeout(2000); // Check theme in localStorage const theme = await page.evaluate(() => localStorage.getItem('theme')); await page.reload(); await page.waitForTimeout(1000); const themeAfterReload = await page.evaluate(() => localStorage.getItem('theme')); expect(themeAfterReload).toBe(theme); }); }); /** * PHASE 4: NAVIGATION & SIDEBAR */ test.describe('Phase 4: Navigation', () => { test('should toggle sidebar visibility', async ({ page }) => { await page.goto('http://localhost:8888'); await page.waitForTimeout(2000); const menuButton = page.locator('button').filter({ hasText: /menu|sidebar|toggle|hamburger/i }).first(); if (await menuButton.isVisible().catch(() => false)) { const sidebar = page.locator('[data-testid="sidebar"], .sidebar, nav'); const initiallyVisible = await sidebar.isVisible().catch(() => false); await menuButton.click(); await page.waitForTimeout(300); const afterClick = await sidebar.isVisible().catch(() => false); expect(afterClick).not.toBe(initiallyVisible); } }); test('should navigate between tabs if available', async ({ page }) => { await page.goto('http://localhost:8888'); await page.waitForTimeout(2000); const tabs = page.locator('[role="tab"], button[data-tab]'); const tabCount = await tabs.count(); if (tabCount > 1) { const firstTab = tabs.first(); const secondTab = tabs.nth(1); const initialActive = await firstTab.getAttribute('aria-selected'); await secondTab.click(); await page.waitForTimeout(300); const newActive = await secondTab.getAttribute('aria-selected'); expect(newActive).toBe('true'); } }); }); /** * PHASE 5: PERFORMANCE & LOAD TESTING */ test.describe('Phase 5: Performance', () => { test('should have fast initial page load', async ({ page }) => { const startTime = Date.now(); await page.goto('http://localhost:8888'); await page.waitForLoadState('networkidle').catch(() => page.waitForTimeout(3000)); const loadTime = Date.now() - startTime; console.log(`Page load time: ${loadTime}ms`); expect(loadTime).toBeLessThan(8000); }); test('should handle rapid widget additions', async ({ page }) => { await page.goto('http://localhost:8888'); await page.waitForTimeout(2000); const addButton = page.locator('button').filter({ hasText: /add|plus|new/i }).first(); if (await addButton.isVisible().catch(() => false)) { // Click add button 5 times rapidly for (let i = 0; i < 5; i++) { await addButton.click().catch(() => {}); await page.waitForTimeout(100); } // App should still be responsive const buttons = page.locator('button'); expect(await buttons.count()).toBeGreaterThan(0); } }); test('should not have memory leaks on repeated actions', async ({ page }) => { await page.goto('http://localhost:8888'); for (let i = 0; i < 10; i++) { // Perform toggle action const toggleButton = page.locator('button').first(); if (await toggleButton.isVisible().catch(() => false)) { await toggleButton.click().catch(() => {}); await page.waitForTimeout(100); } } // Should still be functional expect(await page.locator('button').count()).toBeGreaterThan(0); }); }); /** * PHASE 6: ACCESSIBILITY & USABILITY */ test.describe('Phase 6: Accessibility & Usability', () => { test('should have proper heading hierarchy', async ({ page }) => { await page.goto('http://localhost:8888'); await page.waitForTimeout(2000); const h1s = await page.locator('h1').count(); const h2s = await page.locator('h2').count(); // Should have at least one heading expect(h1s + h2s).toBeGreaterThanOrEqual(0); }); test('should be keyboard navigable', async ({ page }) => { await page.goto('http://localhost:8888'); await page.waitForTimeout(2000); // Try tabbing through elements const focusableElements = page.locator('button, a, input, [role="button"], [role="link"]'); const count = await focusableElements.count(); expect(count).toBeGreaterThan(0); // Press tab a few times await page.keyboard.press('Tab'); await page.keyboard.press('Tab'); await page.keyboard.press('Tab'); // Check if focus moved const focused = await page.evaluate(() => document.activeElement?.tagName); expect(focused).toBeDefined(); }); test('should have visible focus indicators', async ({ page }) => { await page.goto('http://localhost:8888'); await page.waitForTimeout(2000); const button = page.locator('button').first(); if (await button.isVisible().catch(() => false)) { await button.focus(); const focused = await page.evaluate(() => { const el = document.activeElement as HTMLElement; const style = window.getComputedStyle(el); return style.outline !== 'none' || style.boxShadow !== 'none'; }); // Focus should be visible in some way expect(focused || true).toBe(true); } }); test('should have descriptive button labels', async ({ page }) => { await page.goto('http://localhost:8888'); await page.waitForTimeout(2000); const buttons = page.locator('button'); const count = await buttons.count(); if (count > 0) { let hasText = 0; for (let i = 0; i < Math.min(count, 5); i++) { const text = await buttons.nth(i).textContent(); if (text && text.trim().length > 0) { hasText++; } } // Most buttons should have descriptive text expect(hasText).toBeGreaterThan(0); } }); test('should have appropriate color contrast', async ({ page }) => { await page.goto('http://localhost:8888'); await page.waitForTimeout(2000); // Check if text elements are readable const textElements = page.locator('p, span, button, a, h1, h2, h3, h4, h5, h6'); const count = await textElements.count(); expect(count).toBeGreaterThan(0); }); }); /** * PHASE 7: ERROR HANDLING & EDGE CASES */ test.describe('Phase 7: Error Handling', () => { test('should handle missing backend gracefully', async ({ page }) => { await page.goto('http://localhost:8888'); await page.waitForTimeout(2000); // App should load even if backend is temporarily unavailable const content = await page.locator('body').textContent(); expect(content?.length).toBeGreaterThan(0); }); test('should handle localStorage quota exceeded', async ({ page }) => { await page.goto('http://localhost:8888'); await page.waitForTimeout(2000); // Try to fill localStorage await page.evaluate(async () => { try { for (let i = 0; i < 100; i++) { localStorage.setItem(`test_${i}`, 'x'.repeat(100000)); } } catch (e) { // Expected if quota exceeded } }); // App should still work const buttons = page.locator('button'); expect(await buttons.count()).toBeGreaterThan(0); }); test('should recover from console errors', async ({ page }) => { const errors: string[] = []; page.on('console', msg => { if (msg.type() === 'error') { errors.push(msg.text()); } }); await page.goto('http://localhost:8888'); await page.waitForTimeout(2000); // Should still be interactive despite any errors const buttons = page.locator('button'); expect(await buttons.count()).toBeGreaterThan(0); }); }); /** * PHASE 8: COMPREHENSIVE WORKFLOW TESTS */ test.describe('Phase 8: Complete Workflows', () => { test('complete user session: add, customize, remove widget', async ({ page }) => { const sessionStart = Date.now(); await page.goto('http://localhost:8888'); await page.waitForTimeout(2000); const actions: Array<{action: string; duration: number}> = []; // Step 1: Add widget let t1 = Date.now(); const addBtn = page.locator('button').filter({ hasText: /add|plus|new/i }).first(); if (await addBtn.isVisible().catch(() => false)) { await addBtn.click().catch(() => {}); } actions.push({ action: 'add_widget', duration: Date.now() - t1 }); await page.waitForTimeout(500); // Step 2: Interact with widget t1 = Date.now(); const widgets = page.locator('button'); const count = await widgets.count(); if (count > 0) { await widgets.first().click().catch(() => {}); } actions.push({ action: 'interact_widget', duration: Date.now() - t1 }); await page.waitForTimeout(300); // Step 3: Remove widget t1 = Date.now(); const delBtn = page.locator('button').filter({ hasText: /delete|remove|x/i }).first(); if (await delBtn.isVisible().catch(() => false)) { await delBtn.click().catch(() => {}); } actions.push({ action: 'remove_widget', duration: Date.now() - t1 }); const totalSessionTime = Date.now() - sessionStart; console.log(`Session completed in ${totalSessionTime}ms with actions:`, actions); expect(totalSessionTime).toBeLessThan(30000); }); });