stabilizaiton work - projects rework for maintenanceability and ui livening

This commit is contained in:
master
2026-02-03 23:40:04 +02:00
parent 074ce117ba
commit 557feefdc3
3305 changed files with 186813 additions and 107843 deletions

View File

@@ -0,0 +1,404 @@
// -----------------------------------------------------------------------------
// workflow-visualizer.visual.spec.ts
// Sprint: SPRINT_20260117_032_ReleaseOrchestrator_workflow_visualization
// Task: TASK-032-12 - Visual Regression Tests for DAG Visualization
// Description: Playwright visual regression tests for workflow visualization
// -----------------------------------------------------------------------------
import { test, expect } from '@playwright/test';
test.describe('Workflow DAG Visualization', () => {
test.beforeEach(async ({ page }) => {
// Navigate to test workflow page
await page.goto('/workflows/test-run-001');
await page.waitForSelector('.workflow-visualizer');
});
test.describe('Node Rendering', () => {
test('renders nodes at various complexities', async ({ page }) => {
// Test with 10 nodes
await page.goto('/workflows/test-10-nodes');
await page.waitForSelector('.node', { timeout: 5000 });
await expect(page.locator('.node')).toHaveCount(10);
await expect(page.locator('.dag-canvas')).toHaveScreenshot('dag-10-nodes.png');
// Test with 50 nodes
await page.goto('/workflows/test-50-nodes');
await page.waitForSelector('.node', { timeout: 10000 });
await expect(page.locator('.node')).toHaveCount(50);
await expect(page.locator('.dag-canvas')).toHaveScreenshot('dag-50-nodes.png');
});
test('renders large workflow (100+ nodes) with acceptable performance', async ({ page }) => {
const startTime = Date.now();
await page.goto('/workflows/test-100-nodes');
await page.waitForSelector('.node', { timeout: 15000 });
const loadTime = Date.now() - startTime;
expect(loadTime).toBeLessThan(10000); // Should load within 10 seconds
await expect(page.locator('.node')).toHaveCount(100);
await expect(page.locator('.dag-canvas')).toHaveScreenshot('dag-100-nodes.png', {
maxDiffPixelRatio: 0.05 // Allow 5% variance for large graphs
});
});
});
test.describe('Node Status States', () => {
test('displays pending node correctly', async ({ page }) => {
await page.goto('/workflows/test-pending');
const pendingNode = page.locator('.node-pending').first();
await expect(pendingNode).toBeVisible();
await expect(pendingNode).toHaveScreenshot('node-pending.png');
});
test('displays running node with animation', async ({ page }) => {
await page.goto('/workflows/test-running');
const runningNode = page.locator('.node-running').first();
await expect(runningNode).toBeVisible();
// Check for pulse animation
const statusIndicator = runningNode.locator('.pulse');
await expect(statusIndicator).toBeVisible();
// Capture animation frame
await expect(runningNode).toHaveScreenshot('node-running.png');
});
test('displays succeeded node correctly', async ({ page }) => {
await page.goto('/workflows/test-succeeded');
const succeededNode = page.locator('.node-succeeded').first();
await expect(succeededNode).toBeVisible();
await expect(succeededNode).toHaveScreenshot('node-succeeded.png');
});
test('displays failed node correctly', async ({ page }) => {
await page.goto('/workflows/test-failed');
const failedNode = page.locator('.node-failed').first();
await expect(failedNode).toBeVisible();
await expect(failedNode).toHaveScreenshot('node-failed.png');
});
test('displays skipped node correctly', async ({ page }) => {
await page.goto('/workflows/test-skipped');
const skippedNode = page.locator('.node-skipped').first();
await expect(skippedNode).toBeVisible();
await expect(skippedNode).toHaveScreenshot('node-skipped.png');
});
test('displays cancelled node correctly', async ({ page }) => {
await page.goto('/workflows/test-cancelled');
const cancelledNode = page.locator('.node-cancelled').first();
await expect(cancelledNode).toBeVisible();
await expect(cancelledNode).toHaveScreenshot('node-cancelled.png');
});
test('node state transition animation', async ({ page }) => {
await page.goto('/workflows/test-transition');
// Initial state - pending
const node = page.locator('[data-step-id="step-1"]');
await expect(node).toHaveClass(/node-pending/);
// Trigger transition
await page.click('[data-action="start-workflow"]');
// Wait for running state
await expect(node).toHaveClass(/node-running/, { timeout: 5000 });
await expect(node).toHaveScreenshot('node-transition-running.png');
// Wait for completed state
await expect(node).toHaveClass(/node-succeeded/, { timeout: 10000 });
await expect(node).toHaveScreenshot('node-transition-succeeded.png');
});
});
test.describe('Edge Rendering', () => {
test('renders static edges correctly', async ({ page }) => {
await page.goto('/workflows/test-static');
const edges = page.locator('.edge-path');
await expect(edges.first()).toBeVisible();
await expect(page.locator('.edges-layer')).toHaveScreenshot('edges-static.png');
});
test('renders animated edges for in-progress steps', async ({ page }) => {
await page.goto('/workflows/test-running');
const animatedEdge = page.locator('.edge.animated');
await expect(animatedEdge).toBeVisible();
// Verify animation is present (dash animation)
const edgePath = animatedEdge.locator('.edge-path');
const strokeDasharray = await edgePath.evaluate(el =>
window.getComputedStyle(el).getPropertyValue('stroke-dasharray')
);
expect(strokeDasharray).not.toBe('none');
});
test('highlights critical path edges', async ({ page }) => {
await page.goto('/workflows/test-completed');
// Enable critical path
await page.click('button:has-text("Critical Path")');
const criticalEdge = page.locator('.edge.critical');
await expect(criticalEdge.first()).toBeVisible();
await expect(page.locator('.edges-layer')).toHaveScreenshot('edges-critical-path.png');
});
});
test.describe('Layout Algorithms', () => {
test('dagre layout renders correctly', async ({ page }) => {
await page.goto('/workflows/test-layout');
await page.selectOption('.layout-selector select', 'dagre');
await page.waitForTimeout(500); // Wait for layout animation
await expect(page.locator('.dag-canvas')).toHaveScreenshot('layout-dagre.png');
});
test('elk layout renders correctly', async ({ page }) => {
await page.goto('/workflows/test-layout');
await page.selectOption('.layout-selector select', 'elk');
await page.waitForTimeout(500);
await expect(page.locator('.dag-canvas')).toHaveScreenshot('layout-elk.png');
});
test('force-directed layout renders correctly', async ({ page }) => {
await page.goto('/workflows/test-layout');
await page.selectOption('.layout-selector select', 'force');
await page.waitForTimeout(1000); // Force layout needs more time
await expect(page.locator('.dag-canvas')).toHaveScreenshot('layout-force.png', {
maxDiffPixelRatio: 0.1 // Force layout may have slight variations
});
});
});
test.describe('Zoom and Pan', () => {
test('zoom controls work correctly', async ({ page }) => {
await page.goto('/workflows/test-10-nodes');
// Zoom in
await page.click('button[title="Zoom In"]');
await page.click('button[title="Zoom In"]');
await expect(page.locator('.zoom-label')).toContainText('150%');
await expect(page.locator('.dag-canvas')).toHaveScreenshot('zoom-150.png');
// Zoom out
await page.click('button[title="Zoom Out"]');
await page.click('button[title="Zoom Out"]');
await page.click('button[title="Zoom Out"]');
await expect(page.locator('.zoom-label')).toContainText('75%');
await expect(page.locator('.dag-canvas')).toHaveScreenshot('zoom-75.png');
});
test('fit to view resets viewport', async ({ page }) => {
await page.goto('/workflows/test-10-nodes');
// Pan and zoom
await page.click('button[title="Zoom In"]');
await page.click('button[title="Zoom In"]');
// Fit to view
await page.click('button[title="Fit to View"]');
await expect(page.locator('.zoom-label')).toContainText('100%');
await expect(page.locator('.dag-canvas')).toHaveScreenshot('zoom-fit.png');
});
test('mouse wheel zooms', async ({ page }) => {
await page.goto('/workflows/test-10-nodes');
const canvas = page.locator('.canvas-container');
// Scroll to zoom
await canvas.hover();
await page.mouse.wheel(0, -100); // Zoom in
await page.waitForTimeout(200);
const zoomLabel = await page.locator('.zoom-label').textContent();
expect(parseInt(zoomLabel || '100')).toBeGreaterThan(100);
});
test('drag to pan', async ({ page }) => {
await page.goto('/workflows/test-50-nodes');
const canvas = page.locator('.canvas-container');
// Get initial viewbox
const initialViewBox = await page.locator('.dag-canvas').getAttribute('viewBox');
// Drag to pan
const box = await canvas.boundingBox();
if (box) {
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
await page.mouse.down();
await page.mouse.move(box.x + box.width / 2 + 100, box.y + box.height / 2 + 50);
await page.mouse.up();
}
// Viewbox should have changed
const newViewBox = await page.locator('.dag-canvas').getAttribute('viewBox');
expect(newViewBox).not.toBe(initialViewBox);
});
});
test.describe('Node Selection', () => {
test('clicking node selects it', async ({ page }) => {
await page.goto('/workflows/test-10-nodes');
const node = page.locator('.node').first();
await node.click();
await expect(node).toHaveClass(/selected/);
await expect(node).toHaveScreenshot('node-selected.png');
});
test('double-clicking node opens details', async ({ page }) => {
await page.goto('/workflows/test-10-nodes');
const node = page.locator('.node').first();
await node.dblclick();
await expect(page.locator('.step-detail-panel')).toBeVisible();
});
});
test.describe('Minimap', () => {
test('minimap renders for large workflows', async ({ page }) => {
await page.goto('/workflows/test-50-nodes');
await expect(page.locator('.minimap')).toBeVisible();
await expect(page.locator('.minimap')).toHaveScreenshot('minimap.png');
});
test('minimap shows viewport indicator', async ({ page }) => {
await page.goto('/workflows/test-50-nodes');
await expect(page.locator('.viewport-indicator')).toBeVisible();
});
test('minimap hidden for small workflows', async ({ page }) => {
await page.goto('/workflows/test-5-nodes');
await expect(page.locator('.minimap')).not.toBeVisible();
});
});
test.describe('Responsive Layout', () => {
test('mobile viewport adjusts layout', async ({ page }) => {
await page.setViewportSize({ width: 375, height: 667 });
await page.goto('/workflows/test-10-nodes');
// Toolbar should wrap
await expect(page.locator('.visualizer-toolbar')).toHaveScreenshot('toolbar-mobile.png');
// Minimap should be hidden
await expect(page.locator('.minimap')).not.toBeVisible();
});
test('tablet viewport renders correctly', async ({ page }) => {
await page.setViewportSize({ width: 768, height: 1024 });
await page.goto('/workflows/test-10-nodes');
await expect(page.locator('.workflow-visualizer')).toHaveScreenshot('visualizer-tablet.png');
});
test('desktop viewport renders correctly', async ({ page }) => {
await page.setViewportSize({ width: 1920, height: 1080 });
await page.goto('/workflows/test-10-nodes');
await expect(page.locator('.workflow-visualizer')).toHaveScreenshot('visualizer-desktop.png');
});
});
test.describe('Dark Mode', () => {
test('dark mode renders correctly', async ({ page }) => {
await page.goto('/workflows/test-10-nodes?theme=dark');
await expect(page.locator('.workflow-visualizer')).toHaveClass(/dark-mode/);
await expect(page.locator('.dag-canvas')).toHaveScreenshot('dag-dark-mode.png');
});
test('node states in dark mode', async ({ page }) => {
await page.goto('/workflows/test-all-states?theme=dark');
await expect(page.locator('.dag-canvas')).toHaveScreenshot('nodes-dark-mode.png');
});
});
test.describe('Legend', () => {
test('legend displays all states', async ({ page }) => {
await page.goto('/workflows/test-10-nodes');
const legend = page.locator('.legend');
await expect(legend).toBeVisible();
await expect(legend.locator('.legend-item')).toHaveCount(5);
await expect(legend).toHaveScreenshot('legend.png');
});
});
test.describe('Loading and Error States', () => {
test('loading overlay displays correctly', async ({ page }) => {
// Intercept API to delay response
await page.route('**/api/v1/workflows/*/graph', async route => {
await new Promise(resolve => setTimeout(resolve, 2000));
await route.continue();
});
await page.goto('/workflows/test-10-nodes');
await expect(page.locator('.loading-overlay')).toBeVisible();
await expect(page.locator('.loading-overlay')).toHaveScreenshot('loading-state.png');
});
test('error overlay displays correctly', async ({ page }) => {
// Mock API error
await page.route('**/api/v1/workflows/*/graph', async route => {
await route.fulfill({
status: 500,
body: JSON.stringify({ error: 'Internal Server Error' })
});
});
await page.goto('/workflows/test-10-nodes');
await expect(page.locator('.error-overlay')).toBeVisible();
await expect(page.locator('.error-overlay')).toHaveScreenshot('error-state.png');
});
});
});
test.describe('Time-Travel Controls', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/workflows/test-completed/debug');
await page.waitForSelector('.time-travel-controls');
});
test('controls render correctly', async ({ page }) => {
await expect(page.locator('.time-travel-controls')).toHaveScreenshot('time-travel-controls.png');
});
test('timeline with markers', async ({ page }) => {
await expect(page.locator('.timeline-container')).toHaveScreenshot('timeline-markers.png');
});
test('playhead position updates', async ({ page }) => {
// Step forward
await page.click('button[title*="Step Forward"]');
await page.click('button[title*="Step Forward"]');
await expect(page.locator('.timeline')).toHaveScreenshot('timeline-stepped.png');
});
});
test.describe('Step Detail Panel', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/workflows/test-completed');
await page.locator('.node').first().click();
await page.waitForSelector('.step-detail-panel');
});
test('panel renders correctly', async ({ page }) => {
await expect(page.locator('.step-detail-panel')).toHaveScreenshot('step-panel.png');
});
test('logs tab renders correctly', async ({ page }) => {
await page.click('.tab:has-text("Logs")');
await expect(page.locator('.logs-tab')).toHaveScreenshot('logs-tab.png');
});
test('timing tab renders correctly', async ({ page }) => {
await page.click('.tab:has-text("Timing")');
await expect(page.locator('.timing-tab')).toHaveScreenshot('timing-tab.png');
});
test('error state panel', async ({ page }) => {
await page.goto('/workflows/test-failed');
await page.locator('.node-failed').first().click();
await expect(page.locator('.step-detail-panel')).toHaveScreenshot('step-panel-error.png');
});
});