Files
metabuilder/frontends/workflowui/test-server/integration.spec.ts
2026-03-09 22:30:41 +00:00

281 lines
9.5 KiB
TypeScript

/**
* WorkflowUI Integration Tests
* End-to-end tests for workflow creation and execution
* Tests both TypeScript and Python workflows with mock DBAL backend
*/
import { test, expect, Page } from '@playwright/test';
import * as fs from 'fs';
import * as path from 'path';
const WORKFLOWUI_URL = 'http://localhost:3000';
const MOCK_DBAL_URL = 'http://localhost:8080';
const TENANT_ID = 'test-tenant';
// Load test workflow definitions
const loadWorkflowDefinition = (filename: string) => {
const filePath = path.join(__dirname, '../test-workflows', filename);
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
};
// Helper: Wait for element with retry
async function waitForElement(
page: Page,
selector: string,
timeout = 10000
): Promise<boolean> {
try {
await page.waitForSelector(selector, { timeout, state: 'visible' });
return true;
} catch (e) {
return false;
}
}
// Helper: Create workflow via API
async function createWorkflowViaAPI(workflowDef: any): Promise<string> {
const response = await fetch(`${MOCK_DBAL_URL}/api/v1/${TENANT_ID}/workflows`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(workflowDef),
});
if (!response.ok) {
throw new Error(`Failed to create workflow: ${response.statusText}`);
}
const workflow = await response.json();
return workflow.id;
}
// Helper: Execute workflow via API
async function executeWorkflowViaAPI(workflowId: string): Promise<any> {
const response = await fetch(
`${MOCK_DBAL_URL}/api/v1/${TENANT_ID}/workflows/${workflowId}/execute`,
{ method: 'POST' }
);
if (!response.ok) {
throw new Error(`Failed to execute workflow: ${response.statusText}`);
}
return response.json();
}
// Helper: Get execution status via API
async function getExecutionStatus(executionId: string): Promise<any> {
const response = await fetch(
`${MOCK_DBAL_URL}/api/v1/${TENANT_ID}/executions/${executionId}`
);
if (!response.ok) {
throw new Error(`Failed to get execution: ${response.statusText}`);
}
return response.json();
}
// Helper: Wait for execution to complete
async function waitForExecutionComplete(executionId: string, maxWait = 10000): Promise<any> {
const startTime = Date.now();
while (Date.now() - startTime < maxWait) {
const execution = await getExecutionStatus(executionId);
if (execution.status === 'success' || execution.status === 'error') {
return execution;
}
await new Promise((resolve) => setTimeout(resolve, 500));
}
throw new Error('Execution timeout');
}
test.describe('WorkflowUI Integration Tests', () => {
test.beforeEach(async ({ page }) => {
// Wait for mock DBAL to be ready
await page.goto(MOCK_DBAL_URL + '/health');
await expect(page.locator('pre')).toContainText('ok');
});
test('Mock DBAL server is running', async ({ page }) => {
await page.goto(MOCK_DBAL_URL + '/health');
const healthData = await page.locator('pre').textContent();
expect(healthData).toContain('ok');
console.log('✓ Mock DBAL server health check passed');
});
test('WorkflowUI loads successfully', async ({ page }) => {
await page.goto(WORKFLOWUI_URL);
await expect(page).toHaveTitle(/WorkflowUI|MetaBuilder/);
console.log('✓ WorkflowUI loaded successfully');
});
test('TypeScript Math Workflow - Full E2E', async ({ page }) => {
console.log('\n=== TypeScript Math Workflow Test ===');
// Load workflow definition
const workflowDef = loadWorkflowDefinition('typescript-math.json');
console.log(`Loaded workflow: ${workflowDef.name}`);
// Create workflow via API
const workflowId = await createWorkflowViaAPI(workflowDef);
console.log(`Created workflow ID: ${workflowId}`);
// Navigate to workflow editor
await page.goto(`${WORKFLOWUI_URL}/editor/${workflowId}`);
console.log('Navigated to workflow editor');
// Wait for editor to load
const editorLoaded = await waitForElement(page, '[class*="editor"]', 15000);
expect(editorLoaded).toBe(true);
console.log('✓ Editor loaded');
// Take screenshot of workflow
await page.screenshot({
path: path.join(__dirname, '../test-results/typescript-workflow-editor.png'),
fullPage: true,
});
console.log('✓ Screenshot captured: typescript-workflow-editor.png');
// Execute workflow via API
const execution = await executeWorkflowViaAPI(workflowId);
console.log(`Started execution: ${execution.id}`);
// Wait for execution to complete
const completedExecution = await waitForExecutionComplete(execution.id);
console.log('✓ Execution completed');
// Verify execution results
expect(completedExecution.status).toBe('success');
expect(completedExecution.outputs.result).toBe(workflowDef.expectedOutput);
console.log(`✓ Execution result: ${completedExecution.outputs.result}`);
console.log('✓ Expected result: ' + workflowDef.expectedOutput);
// Verify node results
for (const [nodeId, expectedOutput] of Object.entries(
workflowDef.expectedNodeResults
)) {
const nodeResult = completedExecution.nodeResults[nodeId];
expect(nodeResult).toBeDefined();
expect(nodeResult.status).toBe('success');
console.log(`✓ Node ${nodeId}: ${JSON.stringify(nodeResult.output)}`);
}
console.log('✅ TypeScript Math Workflow - PASSED\n');
});
test('Python Data Processing Workflow - Full E2E', async ({ page }) => {
console.log('\n=== Python Data Processing Workflow Test ===');
// Load workflow definition
const workflowDef = loadWorkflowDefinition('python-data.json');
console.log(`Loaded workflow: ${workflowDef.name}`);
// Create workflow via API
const workflowId = await createWorkflowViaAPI(workflowDef);
console.log(`Created workflow ID: ${workflowId}`);
// Navigate to workflow editor
await page.goto(`${WORKFLOWUI_URL}/editor/${workflowId}`);
console.log('Navigated to workflow editor');
// Wait for editor to load
const editorLoaded = await waitForElement(page, '[class*="editor"]', 15000);
expect(editorLoaded).toBe(true);
console.log('✓ Editor loaded');
// Take screenshot
await page.screenshot({
path: path.join(__dirname, '../test-results/python-workflow-editor.png'),
fullPage: true,
});
console.log('✓ Screenshot captured: python-workflow-editor.png');
// Execute workflow via API
const execution = await executeWorkflowViaAPI(workflowId);
console.log(`Started execution: ${execution.id}`);
// Wait for execution to complete
const completedExecution = await waitForExecutionComplete(execution.id);
console.log('✓ Execution completed');
// Verify execution results
expect(completedExecution.status).toBe('success');
expect(completedExecution.outputs.result).toBe(workflowDef.expectedOutput);
console.log(`✓ Execution result: ${completedExecution.outputs.result}`);
console.log('✓ Expected result: ' + workflowDef.expectedOutput);
// Verify node results
const nodeIds = Object.keys(workflowDef.expectedNodeResults);
for (const nodeId of nodeIds) {
const nodeResult = completedExecution.nodeResults[nodeId];
expect(nodeResult).toBeDefined();
expect(nodeResult.status).toBe('success');
console.log(`✓ Node ${nodeId}: Success`);
}
console.log('✅ Python Data Processing Workflow - PASSED\n');
});
test('Workflow Execution History', async ({ page }) => {
console.log('\n=== Workflow Execution History Test ===');
// Create a simple workflow
const workflowDef = loadWorkflowDefinition('typescript-math.json');
const workflowId = await createWorkflowViaAPI(workflowDef);
// Execute multiple times
const executionIds: string[] = [];
for (let i = 0; i < 3; i++) {
const execution = await executeWorkflowViaAPI(workflowId);
executionIds.push(execution.id);
await waitForExecutionComplete(execution.id);
}
console.log(`✓ Executed workflow 3 times: ${executionIds.join(', ')}`);
// Verify execution history via API
const response = await fetch(
`${MOCK_DBAL_URL}/api/v1/${TENANT_ID}/workflows/${workflowId}/executions`
);
const { executions, total } = await response.json();
expect(total).toBe(3);
expect(executions.length).toBe(3);
console.log(`✓ Execution history contains ${total} executions`);
// All executions should be successful
for (const exec of executions) {
expect(exec.status).toBe('success');
}
console.log('✓ All executions successful');
console.log('✅ Workflow Execution History - PASSED\n');
});
test('Plugin API Endpoint', async ({ page }) => {
console.log('\n=== Plugin API Endpoint Test ===');
await page.goto(`${WORKFLOWUI_URL}/api/plugins`);
const content = await page.locator('pre').textContent();
const pluginData = JSON.parse(content || '{}');
// Verify structure
expect(pluginData).toHaveProperty('categories');
expect(pluginData).toHaveProperty('nodes');
expect(pluginData).toHaveProperty('languages');
expect(pluginData).toHaveProperty('totalNodes');
console.log(`✓ Total nodes: ${pluginData.totalNodes}`);
console.log(`✓ Languages: ${pluginData.languages.join(', ')}`);
console.log(`✓ Categories: ${pluginData.categories.length}`);
// Verify language health
expect(pluginData.languageHealth).toHaveProperty('ts');
expect(pluginData.languageHealth.ts).toBe(true);
console.log('✓ TypeScript language health: OK');
console.log('✅ Plugin API Endpoint - PASSED\n');
});
});