Migrated from design-system-swarm with fresh git history.
Old project history preserved in /home/overbits/apps/design-system-swarm
Core components:
- MCP Server (Python FastAPI with mcp 1.23.1)
- Claude Plugin (agents, commands, skills, strategies, hooks, core)
- DSS Backend (dss-mvp1 - token translation, Figma sync)
- Admin UI (Node.js/React)
- Server (Node.js/Express)
- Storybook integration (dss-mvp1/.storybook)
Self-contained configuration:
- All paths relative or use DSS_BASE_PATH=/home/overbits/dss
- PYTHONPATH configured for dss-mvp1 and dss-claude-plugin
- .env file with all configuration
- Claude plugin uses ${CLAUDE_PLUGIN_ROOT} for portability
Migration completed: $(date)
🤖 Clean migration with full functionality preserved
407 lines
14 KiB
Python
407 lines
14 KiB
Python
#!/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 ║
|
|
║ ║
|
|
╚═══════════════════════════════════════════════════════════════════════════╝
|
|
""")
|