Files
desktop/apps/hanzo-desktop/e2e-tests/chat-flow.spec.ts
zeekay 5e90c26e6e feat: comprehensive E2E testing with Zen Nano model
- Add E2E workflow matching zoo-desktop structure
- Copy all 6 E2E test suites (40 tests total):
  * engine-integration.spec.ts - Hanzo node + engine tests
  * model-management.spec.ts - Model/engine switching tests
  * chat-flow.spec.ts - Complete chat flow tests
  * chat-ui.spec.ts - UI detection tests
  * chat-navigation.spec.ts - Navigation tests
  * onboarding-and-lm-studio.spec.ts - Onboarding automation
- Configure for hanzo-node backend (ports 3690-3692)
- Use zenlm/zen-nano Q4_K_M model (373MB) from HuggingFace
- Test across Linux, macOS, Windows
- Build hanzo-node from source per platform
- Run full E2E suite with Playwright

Tests verify:
- API connectivity
- Model loading
- Engine switching
- Chat messaging
- Performance
- Error handling
2025-11-12 10:18:48 -08:00

418 lines
13 KiB
TypeScript

import { test, expect } from '@playwright/test';
/**
* Complete Chat Flow Tests
*
* Tests the end-to-end chat functionality with zoo-node and Hanzo engine.
* These tests verify:
* - Complete onboarding process with running zoo-node
* - Sending chat messages to AI
* - Receiving and displaying AI responses
* - Message history persistence
* - Streaming vs non-streaming responses
*/
const ZOO_NODE_URL = process.env.ZOO_NODE_URL || 'http://localhost:9550';
const TEST_TIMEOUT = 60000; // 60 seconds for chat responses
test.describe('Chat Interface Access', () => {
test('should attempt to access chat interface', async ({ page }) => {
await page.goto('http://localhost:1420');
await page.waitForLoadState('networkidle');
const currentUrl = page.url();
console.log('Starting URL:', currentUrl);
// Try various routes that might have chat
const chatRoutes = ['/', '/chat', '/inbox', '/conversation'];
for (const route of chatRoutes) {
await page.goto(`http://localhost:1420${route}`);
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
const url = page.url();
console.log(`Route ${route} -> ${url}`);
// Check if we found a chat interface
const hasChatInput = await page.locator('textarea, input[type="text"]').count() > 0;
if (hasChatInput && !url.includes('terms-conditions')) {
console.log(`✅ Found chat input on ${route}`);
await page.screenshot({ path: `/tmp/chat-interface-${route.replace('/', 'root')}.png` });
break;
}
}
await page.screenshot({ path: '/tmp/chat-access-final.png' });
});
test('should verify chat UI components exist', async ({ page }) => {
await page.goto('http://localhost:1420');
await page.waitForLoadState('networkidle');
// Look for chat-related elements
const chatElements = {
textareas: await page.locator('textarea').count(),
messageContainers: await page.locator('[role="log"], [role="feed"], .messages, .chat-messages').count(),
sendButtons: await page.locator('button:has-text("send"), button:has([aria-label*="send" i])').count(),
};
console.log('Chat UI elements found:', chatElements);
const totalChatElements = Object.values(chatElements).reduce((a, b) => a + b, 0);
if (totalChatElements > 0) {
console.log('✅ Chat UI components detected');
} else {
console.log('⚠️ No chat UI found (may require onboarding)');
}
});
});
test.describe('Message Sending via API', () => {
test('should send a test message via zoo-node API', async ({ request }) => {
const message = {
prompt: 'Hello, this is a test message from E2E tests.',
max_tokens: 50,
temperature: 0.7,
};
console.log('Sending test message via API...');
try {
const response = await request.post(`${ZOO_NODE_URL}/v2/text_inference`, {
data: message,
timeout: TEST_TIMEOUT,
});
console.log('Response status:', response.status());
if (response.ok()) {
const result = await response.json();
console.log('AI Response:', JSON.stringify(result, null, 2));
expect(result).toBeDefined();
console.log('✅ Message sent and response received');
} else {
const error = await response.text();
console.log('⚠️ Message failed:', error);
}
} catch (error) {
console.log('⚠️ API test error:', error);
}
});
test('should test chat completion endpoint', async ({ request }) => {
const chatPayload = {
messages: [
{ role: 'user', content: 'What is 2+2?' }
],
model: 'FREE_TEXT_INFERENCE',
max_tokens: 20,
};
console.log('Testing chat completion endpoint...');
try {
const response = await request.post(`${ZOO_NODE_URL}/v2/chat/completions`, {
data: chatPayload,
timeout: TEST_TIMEOUT,
});
console.log('Chat completion status:', response.status());
if (response.ok()) {
const result = await response.json();
console.log('Chat result:', JSON.stringify(result, null, 2));
expect(result).toBeDefined();
console.log('✅ Chat completion working');
} else {
console.log(`⚠️ Chat completion returned ${response.status()}`);
}
} catch (error) {
console.log('⚠️ Chat completion test error:', error);
}
});
});
test.describe('Chat UI Interaction', () => {
test('should try to send message through UI', async ({ page }) => {
await page.goto('http://localhost:1420');
await page.waitForLoadState('networkidle');
// Try to find chat input
const chatInput = page.locator('textarea, input[placeholder*="message" i], input[placeholder*="chat" i]').first();
const inputExists = await chatInput.count() > 0;
if (inputExists) {
console.log('✅ Found chat input field');
try {
// Try to type a message
await chatInput.fill('Hello from E2E test');
console.log('✅ Typed message into input');
await page.screenshot({ path: '/tmp/chat-message-typed.png' });
// Look for send button
const sendButton = page.locator('button:has-text("send"), button:has([aria-label*="send" i]), button[type="submit"]').first();
const buttonExists = await sendButton.count() > 0;
if (buttonExists) {
const isDisabled = await sendButton.isDisabled();
console.log('Send button disabled:', isDisabled);
if (!isDisabled) {
// Try to click send
await sendButton.click();
console.log('✅ Clicked send button');
// Wait for response
await page.waitForTimeout(5000);
await page.screenshot({ path: '/tmp/chat-after-send.png' });
}
}
} catch (error) {
console.log('⚠️ Error during UI interaction:', error);
}
} else {
console.log('⚠️ No chat input found (may require onboarding)');
}
});
test('should check for message history display', async ({ page }) => {
await page.goto('http://localhost:1420');
await page.waitForLoadState('networkidle');
// Look for message containers
const messageContainers = await page.locator('.message, [data-role="message"], [class*="message"]').count();
console.log(`Found ${messageContainers} message elements`);
if (messageContainers > 0) {
console.log('✅ Message history UI detected');
await page.screenshot({ path: '/tmp/chat-history.png', fullPage: true });
} else {
console.log('⚠️ No message history visible');
}
});
});
test.describe('Response Streaming', () => {
test('should test WebSocket connection for streaming', async ({ page }) => {
await page.goto('http://localhost:1420');
await page.waitForLoadState('networkidle');
// Check if WebSocket connection is established
const wsConnected = await page.evaluate(() => {
// Check if any WebSocket connections exist
return (window as any).__websocket_connected || false;
});
console.log('WebSocket status from page:', wsConnected ? 'Connected' : 'Not detected');
// Take screenshot of network state
await page.screenshot({ path: '/tmp/websocket-check.png' });
});
test('should verify streaming endpoint availability', async ({ request }) => {
// Test the streaming endpoint exists
try {
const response = await request.post(`${ZOO_NODE_URL}/v2/text_inference_stream`, {
data: {
prompt: 'Count to 5',
max_tokens: 30,
},
timeout: 15000,
});
console.log('Streaming endpoint status:', response.status());
if (response.status() < 500) {
console.log('✅ Streaming endpoint is available');
} else {
console.log('⚠️ Streaming endpoint returned error');
}
} catch (error) {
console.log('⚠️ Streaming test error:', error);
}
});
});
test.describe('Message Persistence', () => {
test('should verify chat history persists across page reloads', async ({ page }) => {
await page.goto('http://localhost:1420');
await page.waitForLoadState('networkidle');
// Get initial state
const initialMessages = await page.locator('.message, [data-role="message"]').count();
console.log('Initial messages:', initialMessages);
// Reload page
await page.reload();
await page.waitForLoadState('networkidle');
// Check if messages persist
const afterReloadMessages = await page.locator('.message, [data-role="message"]').count();
console.log('Messages after reload:', afterReloadMessages);
if (afterReloadMessages === initialMessages) {
console.log('✅ Message count consistent after reload');
} else {
console.log('⚠️ Message count changed after reload');
}
});
test('should check conversation storage via API', async ({ request }) => {
// Get list of conversations
const response = await request.get(`${ZOO_NODE_URL}/v2/all_inboxes_for_profile`);
if (response.ok()) {
const inboxes = await response.json();
console.log('Available inboxes:', JSON.stringify(inboxes, null, 2));
if (Array.isArray(inboxes) && inboxes.length > 0) {
console.log(`✅ Found ${inboxes.length} conversation inbox(es)`);
} else {
console.log('⚠️ No conversations found');
}
} else {
console.log(`⚠️ Inbox query failed with status ${response.status()}`);
}
});
});
test.describe('Multi-Turn Conversations', () => {
test('should test multiple message exchanges via API', async ({ request }) => {
const messages = [
'Hello, can you hear me?',
'What is your name?',
'Thank you for responding.',
];
for (let i = 0; i < messages.length; i++) {
console.log(`\nSending message ${i + 1}/${messages.length}: "${messages[i]}"`);
try {
const response = await request.post(`${ZOO_NODE_URL}/v2/text_inference`, {
data: {
prompt: messages[i],
max_tokens: 50,
temperature: 0.7,
},
timeout: TEST_TIMEOUT,
});
if (response.ok()) {
const result = await response.json();
console.log(`Response ${i + 1}:`, JSON.stringify(result, null, 2));
console.log('✅ Message exchange successful');
} else {
console.log(`⚠️ Message ${i + 1} failed with status ${response.status()}`);
}
// Wait between messages
await new Promise(resolve => setTimeout(resolve, 2000));
} catch (error) {
console.log(`⚠️ Error on message ${i + 1}:`, error);
}
}
});
});
test.describe('Chat Performance', () => {
test('should measure response time', async ({ request }) => {
const startTime = Date.now();
const response = await request.post(`${ZOO_NODE_URL}/v2/text_inference`, {
data: {
prompt: 'Say hello',
max_tokens: 10,
temperature: 0.1,
},
timeout: TEST_TIMEOUT,
});
const endTime = Date.now();
const responseTime = endTime - startTime;
console.log(`Response time: ${responseTime}ms`);
if (response.ok()) {
console.log('✅ Response received');
if (responseTime < 10000) {
console.log('✅ Response time acceptable (<10s)');
} else {
console.log('⚠️ Response time slow (>10s)');
}
}
});
test('should verify concurrent request handling', async ({ request }) => {
const requests = Array(3).fill(null).map((_, i) =>
request.post(`${ZOO_NODE_URL}/v2/text_inference`, {
data: {
prompt: `Test message ${i + 1}`,
max_tokens: 10,
},
timeout: TEST_TIMEOUT,
})
);
console.log('Sending 3 concurrent requests...');
try {
const responses = await Promise.all(requests);
const successCount = responses.filter(r => r.ok()).length;
console.log(`✅ ${successCount}/3 concurrent requests succeeded`);
} catch (error) {
console.log('⚠️ Concurrent request test error:', error);
}
});
});
test.describe('Error Handling in Chat', () => {
test('should handle empty messages gracefully', async ({ request }) => {
const response = await request.post(`${ZOO_NODE_URL}/v2/text_inference`, {
data: {
prompt: '',
max_tokens: 10,
},
});
console.log('Empty message response status:', response.status());
// Should return an error, not crash
expect(response.status()).toBeGreaterThanOrEqual(400);
console.log('✅ Empty message handled gracefully');
});
test('should handle very long messages', async ({ request }) => {
const longMessage = 'test '.repeat(1000); // 5000 characters
try {
const response = await request.post(`${ZOO_NODE_URL}/v2/text_inference`, {
data: {
prompt: longMessage,
max_tokens: 10,
},
timeout: TEST_TIMEOUT,
});
console.log('Long message response status:', response.status());
if (response.ok()) {
console.log('✅ Long message handled successfully');
} else {
console.log('⚠️ Long message rejected (expected behavior)');
}
} catch (error) {
console.log('⚠️ Long message test error:', error);
}
});
});