#!/usr/bin/env python3 """ DSS Admin UI - Phase 1: Smoke Test (Component Loading) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Framework: Pytest-Playwright (Python-based) Purpose: Validate all 51 components load successfully with zero console errors Coverage: Component loading, DOM rendering, error detection Test Strategy: 1. Navigate to component 2. Wait for load (max 3s) 3. Check console logs (zero errors expected) 4. Validate DOM rendering (not blank) 5. Record result Generated: 2025-12-08 Author: Gemini 3 Pro Expert Analysis Status: Ready for Implementation """ import pytest import json import time from pathlib import Path from playwright.sync_api import sync_playwright, expect import subprocess import re # Component registry from component-registry.js COMPONENTS = { # Tools components 'ds-metrics-panel': 'Tools', 'ds-console-viewer': 'Tools', 'ds-token-inspector': 'Tools', 'ds-figma-status': 'Tools', 'ds-activity-log': 'Tools', 'ds-visual-diff': 'Tools', 'ds-accessibility-report': 'Tools', 'ds-screenshot-gallery': 'Tools', 'ds-network-monitor': 'Tools', 'ds-test-results': 'Tools', 'ds-system-log': 'Tools', 'ds-figma-extract-quick': 'Tools', 'ds-quick-wins-script': 'Tools', 'ds-regression-testing': 'Tools', 'ds-storybook-figma-compare': 'Tools', 'ds-storybook-live-compare': 'Tools', 'ds-figma-extraction': 'Tools', 'ds-project-analysis': 'Tools', 'ds-figma-plugin': 'Tools', 'ds-token-list': 'Tools', 'ds-asset-list': 'Tools', 'ds-component-list': 'Tools', 'ds-navigation-demos': 'Tools', 'ds-figma-live-compare': 'Tools', 'ds-esre-editor': 'Tools', 'ds-chat-panel': 'Tools', 'ds-quick-wins': 'Tools', # Layout components 'ds-shell': 'Layout', 'ds-panel': 'Layout', 'ds-activity-bar': 'Layout', 'ds-ai-chat-sidebar': 'Layout', 'ds-project-selector': 'Layout', # Metrics components 'ds-frontpage': 'Metrics', 'ds-metric-card': 'Metrics', 'ds-metrics-dashboard': 'Metrics', # Admin components 'ds-admin-settings': 'Admin', 'ds-project-list': 'Admin', 'ds-user-settings': 'Admin', # UI Elements 'ds-action-bar': 'UI', 'ds-badge': 'UI', 'ds-base-tool': 'Base', 'ds-button': 'UI', 'ds-card': 'UI', 'ds-component-base': 'Base', 'ds-input': 'UI', 'ds-notification-center': 'UI', 'ds-toast': 'UI', 'ds-toast-provider': 'UI', 'ds-workflow': 'UI', # Listing components 'ds-icon-list': 'Listings', 'ds-jira-issues': 'Listings', } BASE_URL = "http://localhost:5173" # Vite dev server TIMEOUT = 3000 # 3 seconds for component load class ConsoleLogCapture: """Capture and analyze console logs during test""" def __init__(self): self.logs = [] self.errors = [] self.warnings = [] def handle_console_message(self, msg): """Handle console events from browser""" entry = { 'type': msg.type, 'text': msg.text, 'timestamp': time.time() } self.logs.append(entry) if msg.type == 'error': self.errors.append(msg.text) elif msg.type == 'warning': self.warnings.append(msg.text) def has_critical_errors(self): """Check for critical errors (not warnings)""" critical_patterns = [ r"Cannot read properties of undefined", r"Unknown component:", r"Failed to hydrate", r"Uncaught.*Error", r"fetch failed", r"CORS", ] for error in self.errors: for pattern in critical_patterns: if re.search(pattern, error, re.IGNORECASE): return True, error return False, None def get_summary(self): """Get log summary statistics""" return { 'total_logs': len(self.logs), 'errors': len(self.errors), 'warnings': len(self.warnings), 'critical_errors': self.has_critical_errors()[0] } @pytest.fixture(scope="session") def browser_context(): """Start Playwright browser session""" with sync_playwright() as p: browser = p.chromium.launch() context = browser.new_context() yield context context.close() browser.close() @pytest.fixture def page(browser_context): """Create new page for each test""" page = browser_context.new_page() yield page page.close() def test_admin_page_loads(page): """Test 1: Verify admin page loads without crashing""" page.goto(f"{BASE_URL}/") page.wait_for_load_state("networkidle") # Check for shell component presence shell = page.query_selector("ds-shell") assert shell is not None, "Main shell component not found on page" @pytest.mark.parametrize("component_tag,category", [ (tag, cat) for tag, cat in COMPONENTS.items() ]) def test_component_loads(page, component_tag, category): """ Test 2: Verify each component loads successfully Runs for all 51 components: - Navigate to page - Trigger component load - Check for errors - Verify DOM rendering """ console_capture = ConsoleLogCapture() page.on("console", console_capture.handle_console_message) # Navigate to admin page page.goto(f"{BASE_URL}/") page.wait_for_load_state("networkidle") # Attempt to load component via registry try: # Check if component is in registry registry_check = page.evaluate(f""" () => {{ const registry = window.__COMPONENT_REGISTRY__; return registry && '{component_tag}' in registry; }} """) if not registry_check: pytest.skip(f"Component {component_tag} not in registry") # Hydrate component result = page.evaluate(f""" async () => {{ try {{ const {{ hydrateComponent }} = await import('../js/config/component-registry.js'); const container = document.createElement('div'); document.body.appendChild(container); const element = await hydrateComponent('{component_tag}', container); return {{ success: true, tagName: element?.tagName?.toLowerCase(), html: element?.outerHTML?.substring(0, 100) }}; }} catch (err) {{ return {{ success: false, error: err.message }}; }} }} """) # Wait for component to render page.wait_for_timeout(500) # Check results has_errors, error_msg = console_capture.has_critical_errors() assert result['success'], f"Failed to load {component_tag}: {result.get('error')}" assert not has_errors, f"Critical error in {component_tag}: {error_msg}" # Verify DOM element exists element = page.query_selector(component_tag) assert element is not None, f"Component {component_tag} not found in DOM after load" except Exception as e: pytest.fail(f"Test failed for {component_tag}: {str(e)}") finally: # Log summary summary = console_capture.get_summary() print(f"\n[{category}] {component_tag}") print(f" Logs: {summary['total_logs']}, Errors: {summary['errors']}, Warnings: {summary['warnings']}") def test_component_dom_rendering(page, component_tag='ds-shell', category='Layout'): """ Test 3: Verify component DOM rendering Checks that component properly renders with visible content """ console_capture = ConsoleLogCapture() page.on("console", console_capture.handle_console_message) page.goto(f"{BASE_URL}/") page.wait_for_load_state("networkidle") # Check that component is in DOM element = page.query_selector(component_tag) assert element is not None, f"{component_tag} not found in initial DOM" # Check that element has content (not completely blank) content = page.evaluate(f""" () => {{ const el = document.querySelector('{component_tag}'); return {{ innerHTML: el?.innerHTML?.length > 0, textContent: el?.textContent?.trim().length > 0, childNodes: el?.childNodes?.length || 0, hasAttributes: el?.attributes?.length > 0 }}; }} """) assert content['innerHTML'] or content['childNodes'] > 0, f"{component_tag} has no rendered content" def test_no_uncaught_errors(page): """ Test 4: Verify no uncaught errors during page lifecycle Records all console errors and validates clean execution """ captured_errors = [] def handle_error(msg): if msg.type == 'error': captured_errors.append(msg.text) page.on("console", handle_error) page.on("pageerror", lambda err: captured_errors.append(f"Uncaught: {str(err)}")) page.goto(f"{BASE_URL}/") page.wait_for_load_state("networkidle") # Allow brief time for deferred errors page.wait_for_timeout(1000) # Filter out non-critical messages critical_errors = [ e for e in captured_errors if not any(x in e for x in ['Deprecated', 'Warning', 'chrome-extension']) ] assert len(critical_errors) == 0, f"Found {len(critical_errors)} critical errors: {critical_errors[:5]}" def test_api_endpoints_available(page): """ Test 5: Verify API endpoints are available Tests connectivity to backend API """ page.goto(f"{BASE_URL}/") page.wait_for_load_state("networkidle") # Test key API endpoints endpoints = [ "/api/projects", "/api/auth/me", "/api/mcp/tools", ] for endpoint in endpoints: try: response = page.evaluate(f""" async () => {{ try {{ const resp = await fetch('{endpoint}'); return {{ status: resp.status, ok: resp.ok }}; }} catch (err) {{ return {{ error: err.message }}; }} }} """) # We expect either success or a specific error (not network failure) assert 'error' not in response or 'fetch' not in response.get('error', ''), \ f"Network error accessing {endpoint}: {response.get('error')}" except Exception as e: pytest.skip(f"Could not test {endpoint}: {str(e)}") # ──────────────────────────────────────────────────────────────────────────── # Test Execution & Reporting # ──────────────────────────────────────────────────────────────────────────── @pytest.fixture(scope="session", autouse=True) def print_test_header(): """Print header with test information""" print("\n" + "="*80) print("DSS Admin UI - Phase 1: Smoke Test (Component Loading)") print("="*80) print(f"Framework: Pytest-Playwright") print(f"Components: {len(COMPONENTS)} registered components") print(f"Base URL: {BASE_URL}") print(f"Timeout: {TIMEOUT}ms per component") print("="*80) def pytest_runtest_makereport(item, call): """Hook to capture test results""" if call.when == "call": if "component_tag" in item.callspec.params: component = item.callspec.params['component_tag'] category = item.callspec.params.get('category', 'Unknown') status = "✅ PASS" if item.outcomes == [] else "❌ FAIL" print(f" {status} [{category}] {component}") if __name__ == "__main__": print(f""" ╔═══════════════════════════════════════════════════════════════════════════╗ ║ ║ ║ DSS Admin UI - Phase 1 Smoke Test ║ ║ pytest-playwright Component Loading Validation ║ ║ ║ ║ To run tests: ║ ║ $ pytest .dss/test_smoke_phase1.py -v ║ ║ ║ ║ To run specific component: ║ ║ $ pytest .dss/test_smoke_phase1.py -k ds-shell -v ║ ║ ║ ║ To run with coverage: ║ ║ $ pytest .dss/test_smoke_phase1.py --cov=admin-ui -v ║ ║ ║ ║ To run with headless browser (CI/CD): ║ ║ $ pytest .dss/test_smoke_phase1.py --headless -v ║ ║ ║ ╚═══════════════════════════════════════════════════════════════════════════╝ """)