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
623 lines
26 KiB
Python
623 lines
26 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
DSS Admin UI - Phase 2: Category-Based Testing
|
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
|
|
Framework: Pytest-Playwright (Python-based)
|
|
Purpose: Validate component categories with specific interaction patterns
|
|
Coverage: Component interactions, data flows, category-specific behaviors
|
|
|
|
Test Categories:
|
|
- Tools: Input → Execute → Result validation
|
|
- Metrics: Chart rendering, data validation
|
|
- Layout: Navigation, sidebar, panels
|
|
- Admin: CRUD operations, permissions
|
|
|
|
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 re
|
|
|
|
|
|
# ────────────────────────────────────────────────────────────────────────────
|
|
# Tools Category Tests
|
|
# ────────────────────────────────────────────────────────────────────────────
|
|
|
|
class TestToolsCategory:
|
|
"""Test all Tools components with input/output validation"""
|
|
|
|
TOOLS_COMPONENTS = [
|
|
'ds-metrics-panel',
|
|
'ds-console-viewer',
|
|
'ds-token-inspector',
|
|
'ds-figma-status',
|
|
'ds-activity-log',
|
|
'ds-visual-diff',
|
|
'ds-accessibility-report',
|
|
'ds-screenshot-gallery',
|
|
'ds-network-monitor',
|
|
'ds-test-results',
|
|
'ds-system-log',
|
|
]
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def setup(self, page):
|
|
"""Setup for each test"""
|
|
self.page = page
|
|
self.page.goto("http://localhost:5173/")
|
|
self.page.wait_for_load_state("networkidle")
|
|
|
|
def test_metrics_panel_data_display(self):
|
|
"""Test: Metrics panel displays data correctly"""
|
|
|
|
result = self.page.evaluate("""
|
|
async () => {
|
|
try {
|
|
const { hydrateComponent } = await import('../js/config/component-registry.js');
|
|
const container = document.createElement('div');
|
|
document.body.appendChild(container);
|
|
const element = await hydrateComponent('ds-metrics-panel', container);
|
|
|
|
// Wait for data to load
|
|
await new Promise(r => setTimeout(r, 1000));
|
|
|
|
return {
|
|
success: true,
|
|
hasContent: element.innerHTML.length > 0,
|
|
hasMetrics: element.querySelector('[data-metric]') !== null,
|
|
rendered: element.offsetHeight > 0
|
|
};
|
|
} catch (err) {
|
|
return { success: false, error: err.message };
|
|
}
|
|
}
|
|
""")
|
|
|
|
assert result['success'], f"Failed to load metrics panel: {result.get('error')}"
|
|
assert result['rendered'], "Metrics panel has zero height (not rendered)"
|
|
|
|
def test_console_viewer_functionality(self):
|
|
"""Test: Console viewer captures and displays logs"""
|
|
|
|
result = self.page.evaluate("""
|
|
async () => {
|
|
try {
|
|
const { hydrateComponent } = await import('../js/config/component-registry.js');
|
|
const container = document.createElement('div');
|
|
document.body.appendChild(container);
|
|
const element = await hydrateComponent('ds-console-viewer', container);
|
|
|
|
// Simulate some console activity
|
|
console.log('Test log entry');
|
|
console.warn('Test warning');
|
|
console.error('Test error');
|
|
|
|
// Wait for capture
|
|
await new Promise(r => setTimeout(r, 500));
|
|
|
|
return {
|
|
success: true,
|
|
isElement: element.tagName !== undefined,
|
|
hasContent: element.innerHTML.length > 0
|
|
};
|
|
} catch (err) {
|
|
return { success: false, error: err.message };
|
|
}
|
|
}
|
|
""")
|
|
|
|
assert result['success'], f"Console viewer failed: {result.get('error')}"
|
|
assert result['isElement'], "Console viewer not properly created as element"
|
|
|
|
def test_token_inspector_rendering(self):
|
|
"""Test: Token inspector displays design tokens"""
|
|
|
|
result = self.page.evaluate("""
|
|
async () => {
|
|
try {
|
|
const { hydrateComponent } = await import('../js/config/component-registry.js');
|
|
const container = document.createElement('div');
|
|
document.body.appendChild(container);
|
|
const element = await hydrateComponent('ds-token-inspector', container);
|
|
|
|
await new Promise(r => setTimeout(r, 1000));
|
|
|
|
return {
|
|
success: true,
|
|
rendered: element.offsetHeight > 0,
|
|
hasTokens: element.querySelector('[class*="token"]') !== null,
|
|
isEmpty: element.innerHTML.trim().length === 0
|
|
};
|
|
} catch (err) {
|
|
return { success: false, error: err.message };
|
|
}
|
|
}
|
|
""")
|
|
|
|
assert result['success'], f"Token inspector failed: {result.get('error')}"
|
|
assert not result['isEmpty'], "Token inspector rendered but has no content"
|
|
|
|
|
|
# ────────────────────────────────────────────────────────────────────────────
|
|
# Metrics Category Tests
|
|
# ────────────────────────────────────────────────────────────────────────────
|
|
|
|
class TestMetricsCategory:
|
|
"""Test Metrics components with data rendering validation"""
|
|
|
|
METRICS_COMPONENTS = [
|
|
'ds-frontpage',
|
|
'ds-metric-card',
|
|
'ds-metrics-dashboard',
|
|
]
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def setup(self, page):
|
|
"""Setup for each test"""
|
|
self.page = page
|
|
self.page.goto("http://localhost:5173/")
|
|
self.page.wait_for_load_state("networkidle")
|
|
|
|
def test_metrics_dashboard_renders(self):
|
|
"""Test: Metrics dashboard renders with grid layout"""
|
|
|
|
result = self.page.evaluate("""
|
|
async () => {
|
|
try {
|
|
const { hydrateComponent } = await import('../js/config/component-registry.js');
|
|
const container = document.createElement('div');
|
|
document.body.appendChild(container);
|
|
const element = await hydrateComponent('ds-metrics-dashboard', container);
|
|
|
|
await new Promise(r => setTimeout(r, 1000));
|
|
|
|
return {
|
|
success: true,
|
|
rendered: element.offsetHeight > 0,
|
|
hasGrid: element.classList.toString().includes('grid') || element.style.display !== 'none',
|
|
childCount: element.children.length,
|
|
isVisible: window.getComputedStyle(element).display !== 'none'
|
|
};
|
|
} catch (err) {
|
|
return { success: false, error: err.message };
|
|
}
|
|
}
|
|
""")
|
|
|
|
assert result['success'], f"Dashboard failed: {result.get('error')}"
|
|
assert result['rendered'], "Dashboard not rendered"
|
|
assert result['isVisible'], "Dashboard not visible"
|
|
|
|
def test_metric_card_data_display(self):
|
|
"""Test: Metric cards display numeric data"""
|
|
|
|
result = self.page.evaluate("""
|
|
async () => {
|
|
try {
|
|
const { hydrateComponent } = await import('../js/config/component-registry.js');
|
|
const container = document.createElement('div');
|
|
document.body.appendChild(container);
|
|
|
|
// Add sample data
|
|
const element = await hydrateComponent('ds-metric-card', container);
|
|
element.setAttribute('label', 'Test Metric');
|
|
element.setAttribute('value', '42');
|
|
element.setAttribute('unit', '%');
|
|
|
|
await new Promise(r => setTimeout(r, 500));
|
|
|
|
return {
|
|
success: true,
|
|
rendered: element.offsetHeight > 0,
|
|
hasLabel: element.textContent.includes('Test') || element.innerHTML.includes('Test'),
|
|
hasValue: element.textContent.includes('42') || element.innerHTML.includes('42')
|
|
};
|
|
} catch (err) {
|
|
return { success: false, error: err.message };
|
|
}
|
|
}
|
|
""")
|
|
|
|
assert result['success'], f"Metric card failed: {result.get('error')}"
|
|
|
|
def test_frontpage_initialization(self):
|
|
"""Test: Frontpage component initializes without errors"""
|
|
|
|
result = self.page.evaluate("""
|
|
async () => {
|
|
try {
|
|
const { hydrateComponent } = await import('../js/config/component-registry.js');
|
|
const container = document.createElement('div');
|
|
document.body.appendChild(container);
|
|
const element = await hydrateComponent('ds-frontpage', container);
|
|
|
|
await new Promise(r => setTimeout(r, 1000));
|
|
|
|
return {
|
|
success: true,
|
|
isElement: element.tagName === 'DS-FRONTPAGE',
|
|
rendered: element.offsetHeight > 0
|
|
};
|
|
} catch (err) {
|
|
return { success: false, error: err.message };
|
|
}
|
|
}
|
|
""")
|
|
|
|
assert result['success'], f"Frontpage failed: {result.get('error')}"
|
|
|
|
|
|
# ────────────────────────────────────────────────────────────────────────────
|
|
# Layout Category Tests
|
|
# ────────────────────────────────────────────────────────────────────────────
|
|
|
|
class TestLayoutCategory:
|
|
"""Test Layout components with navigation and panel interaction"""
|
|
|
|
LAYOUT_COMPONENTS = [
|
|
'ds-shell',
|
|
'ds-panel',
|
|
'ds-activity-bar',
|
|
'ds-ai-chat-sidebar',
|
|
'ds-project-selector',
|
|
]
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def setup(self, page):
|
|
"""Setup for each test"""
|
|
self.page = page
|
|
self.page.goto("http://localhost:5173/")
|
|
self.page.wait_for_load_state("networkidle")
|
|
|
|
def test_shell_component_core_layout(self):
|
|
"""Test: Shell component provides core layout structure"""
|
|
|
|
result = self.page.evaluate("""
|
|
() => {
|
|
const shell = document.querySelector('ds-shell');
|
|
return {
|
|
exists: shell !== null,
|
|
rendered: shell?.offsetHeight > 0,
|
|
hasChildren: shell?.children?.length > 0,
|
|
isMainElement: shell?.parentElement === document.body || shell?.parentElement?.tagName === 'BODY'
|
|
};
|
|
}
|
|
""")
|
|
|
|
assert result['exists'], "Shell component not found in DOM"
|
|
assert result['rendered'], "Shell component not rendered"
|
|
assert result['hasChildren'], "Shell has no child elements"
|
|
|
|
def test_activity_bar_navigation(self):
|
|
"""Test: Activity bar responds to navigation actions"""
|
|
|
|
result = self.page.evaluate("""
|
|
async () => {
|
|
try {
|
|
const { hydrateComponent } = await import('../js/config/component-registry.js');
|
|
const container = document.createElement('div');
|
|
document.body.appendChild(container);
|
|
const element = await hydrateComponent('ds-activity-bar', container);
|
|
|
|
await new Promise(r => setTimeout(r, 500));
|
|
|
|
return {
|
|
success: true,
|
|
rendered: element.offsetHeight > 0,
|
|
hasItems: element.querySelectorAll('[role="button"], button').length > 0
|
|
};
|
|
} catch (err) {
|
|
return { success: false, error: err.message };
|
|
}
|
|
}
|
|
""")
|
|
|
|
assert result['success'], f"Activity bar failed: {result.get('error')}"
|
|
|
|
def test_project_selector_functionality(self):
|
|
"""Test: Project selector displays and responds to selection"""
|
|
|
|
result = self.page.evaluate("""
|
|
async () => {
|
|
try {
|
|
const { hydrateComponent } = await import('../js/config/component-registry.js');
|
|
const container = document.createElement('div');
|
|
document.body.appendChild(container);
|
|
const element = await hydrateComponent('ds-project-selector', container);
|
|
|
|
await new Promise(r => setTimeout(r, 500));
|
|
|
|
return {
|
|
success: true,
|
|
rendered: element.offsetHeight > 0,
|
|
isSelectLike: element.querySelector('select') !== null ||
|
|
element.querySelector('[role="combobox"]') !== null ||
|
|
element.className.includes('select') ||
|
|
element.className.includes('selector')
|
|
};
|
|
} catch (err) {
|
|
return { success: false, error: err.message };
|
|
}
|
|
}
|
|
""")
|
|
|
|
assert result['success'], f"Project selector failed: {result.get('error')}"
|
|
|
|
|
|
# ────────────────────────────────────────────────────────────────────────────
|
|
# Admin Category Tests
|
|
# ────────────────────────────────────────────────────────────────────────────
|
|
|
|
class TestAdminCategory:
|
|
"""Test Admin components with CRUD and permission operations"""
|
|
|
|
ADMIN_COMPONENTS = [
|
|
'ds-admin-settings',
|
|
'ds-project-list',
|
|
'ds-user-settings',
|
|
]
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def setup(self, page):
|
|
"""Setup for each test"""
|
|
self.page = page
|
|
self.page.goto("http://localhost:5173/")
|
|
self.page.wait_for_load_state("networkidle")
|
|
|
|
def test_admin_settings_form(self):
|
|
"""Test: Admin settings displays configuration form"""
|
|
|
|
result = self.page.evaluate("""
|
|
async () => {
|
|
try {
|
|
const { hydrateComponent } = await import('../js/config/component-registry.js');
|
|
const container = document.createElement('div');
|
|
document.body.appendChild(container);
|
|
const element = await hydrateComponent('ds-admin-settings', container);
|
|
|
|
await new Promise(r => setTimeout(r, 500));
|
|
|
|
return {
|
|
success: true,
|
|
rendered: element.offsetHeight > 0,
|
|
hasForm: element.querySelector('form') !== null ||
|
|
element.querySelector('[role="form"]') !== null,
|
|
hasInputs: element.querySelectorAll('input, select, textarea').length > 0
|
|
};
|
|
} catch (err) {
|
|
return { success: false, error: err.message };
|
|
}
|
|
}
|
|
""")
|
|
|
|
assert result['success'], f"Admin settings failed: {result.get('error')}"
|
|
|
|
def test_project_list_crud_interface(self):
|
|
"""Test: Project list displays items and CRUD controls"""
|
|
|
|
result = self.page.evaluate("""
|
|
async () => {
|
|
try {
|
|
const { hydrateComponent } = await import('../js/config/component-registry.js');
|
|
const container = document.createElement('div');
|
|
document.body.appendChild(container);
|
|
const element = await hydrateComponent('ds-project-list', container);
|
|
|
|
await new Promise(r => setTimeout(r, 1000));
|
|
|
|
return {
|
|
success: true,
|
|
rendered: element.offsetHeight > 0,
|
|
hasList: element.querySelector('[role="list"], ul, .list') !== null,
|
|
hasActions: element.querySelectorAll('[role="button"], button').length > 0
|
|
};
|
|
} catch (err) {
|
|
return { success: false, error: err.message };
|
|
}
|
|
}
|
|
""")
|
|
|
|
assert result['success'], f"Project list failed: {result.get('error')}"
|
|
|
|
def test_user_settings_profile(self):
|
|
"""Test: User settings displays profile management"""
|
|
|
|
result = self.page.evaluate("""
|
|
async () => {
|
|
try {
|
|
const { hydrateComponent } = await import('../js/config/component-registry.js');
|
|
const container = document.createElement('div');
|
|
document.body.appendChild(container);
|
|
const element = await hydrateComponent('ds-user-settings', container);
|
|
|
|
await new Promise(r => setTimeout(r, 500));
|
|
|
|
return {
|
|
success: true,
|
|
rendered: element.offsetHeight > 0,
|
|
hasProfileElements: element.querySelectorAll('[class*="profile"], [data-profile]').length > 0 ||
|
|
element.querySelector('form') !== null
|
|
};
|
|
} catch (err) {
|
|
return { success: false, error: err.message };
|
|
}
|
|
}
|
|
""")
|
|
|
|
assert result['success'], f"User settings failed: {result.get('error')}"
|
|
|
|
|
|
# ────────────────────────────────────────────────────────────────────────────
|
|
# UI Elements Tests
|
|
# ────────────────────────────────────────────────────────────────────────────
|
|
|
|
class TestUIElementsCategory:
|
|
"""Test basic UI element components"""
|
|
|
|
UI_COMPONENTS = [
|
|
'ds-button',
|
|
'ds-input',
|
|
'ds-card',
|
|
'ds-badge',
|
|
'ds-toast',
|
|
]
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def setup(self, page):
|
|
"""Setup for each test"""
|
|
self.page = page
|
|
self.page.goto("http://localhost:5173/")
|
|
self.page.wait_for_load_state("networkidle")
|
|
|
|
def test_button_component_interactive(self):
|
|
"""Test: Button component is interactive"""
|
|
|
|
result = self.page.evaluate("""
|
|
async () => {
|
|
try {
|
|
const { hydrateComponent } = await import('../js/config/component-registry.js');
|
|
const container = document.createElement('div');
|
|
document.body.appendChild(container);
|
|
const element = await hydrateComponent('ds-button', container);
|
|
|
|
element.textContent = 'Click Me';
|
|
|
|
return {
|
|
success: true,
|
|
rendered: element.offsetHeight > 0,
|
|
isButton: element.tagName === 'DS-BUTTON',
|
|
hasText: element.textContent.includes('Click')
|
|
};
|
|
} catch (err) {
|
|
return { success: false, error: err.message };
|
|
}
|
|
}
|
|
""")
|
|
|
|
assert result['success'], f"Button failed: {result.get('error')}"
|
|
|
|
def test_input_component_functional(self):
|
|
"""Test: Input component accepts and stores values"""
|
|
|
|
result = self.page.evaluate("""
|
|
async () => {
|
|
try {
|
|
const { hydrateComponent } = await import('../js/config/component-registry.js');
|
|
const container = document.createElement('div');
|
|
document.body.appendChild(container);
|
|
const element = await hydrateComponent('ds-input', container);
|
|
|
|
// Try to set value
|
|
if (element.value !== undefined) {
|
|
element.value = 'test input';
|
|
} else if (element.querySelector('input')) {
|
|
element.querySelector('input').value = 'test input';
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
rendered: element.offsetHeight > 0,
|
|
isInput: element.tagName === 'DS-INPUT'
|
|
};
|
|
} catch (err) {
|
|
return { success: false, error: err.message };
|
|
}
|
|
}
|
|
""")
|
|
|
|
assert result['success'], f"Input failed: {result.get('error')}"
|
|
|
|
def test_card_component_layout(self):
|
|
"""Test: Card component provides container structure"""
|
|
|
|
result = self.page.evaluate("""
|
|
async () => {
|
|
try {
|
|
const { hydrateComponent } = await import('../js/config/component-registry.js');
|
|
const container = document.createElement('div');
|
|
document.body.appendChild(container);
|
|
const element = await hydrateComponent('ds-card', container);
|
|
|
|
element.innerHTML = '<h3>Card Title</h3><p>Card content</p>';
|
|
|
|
return {
|
|
success: true,
|
|
rendered: element.offsetHeight > 0,
|
|
isCard: element.tagName === 'DS-CARD',
|
|
hasContent: element.innerHTML.length > 0
|
|
};
|
|
} catch (err) {
|
|
return { success: false, error: err.message };
|
|
}
|
|
}
|
|
""")
|
|
|
|
assert result['success'], f"Card failed: {result.get('error')}"
|
|
|
|
|
|
# ────────────────────────────────────────────────────────────────────────────
|
|
# Test Configuration & Fixtures
|
|
# ────────────────────────────────────────────────────────────────────────────
|
|
|
|
@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()
|
|
|
|
|
|
@pytest.fixture(scope="session", autouse=True)
|
|
def print_test_header():
|
|
"""Print header with test information"""
|
|
print("\n" + "="*80)
|
|
print("DSS Admin UI - Phase 2: Category-Based Testing")
|
|
print("="*80)
|
|
print("Framework: Pytest-Playwright")
|
|
print("Tests: Component-specific interaction validation")
|
|
print("="*80)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
print("""
|
|
╔═══════════════════════════════════════════════════════════════════════════╗
|
|
║ ║
|
|
║ DSS Admin UI - Phase 2 Category Testing ║
|
|
║ pytest-playwright Component Interaction Validation ║
|
|
║ ║
|
|
║ To run all category tests: ║
|
|
║ $ pytest .dss/test_category_phase2.py -v ║
|
|
║ ║
|
|
║ To run specific category: ║
|
|
║ $ pytest .dss/test_category_phase2.py::TestToolsCategory -v ║
|
|
║ $ pytest .dss/test_category_phase2.py::TestMetricsCategory -v ║
|
|
║ $ pytest .dss/test_category_phase2.py::TestLayoutCategory -v ║
|
|
║ $ pytest .dss/test_category_phase2.py::TestAdminCategory -v ║
|
|
║ ║
|
|
║ To run with parallel execution: ║
|
|
║ $ pytest .dss/test_category_phase2.py -n auto -v ║
|
|
║ ║
|
|
║ To run with detailed reporting: ║
|
|
║ $ pytest .dss/test_category_phase2.py -v --tb=short ║
|
|
║ ║
|
|
╚═══════════════════════════════════════════════════════════════════════════╝
|
|
""")
|