mirror of
https://github.com/hanzoai/desktop.git
synced 2026-10-30 14:58:42 +00:00
418 lines
13 KiB
TypeScript
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}/v1/node/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}/v1/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}/v1/node/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}/v1/node/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}/v1/node/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}/v1/node/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}/v1/node/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}/v1/node/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}/v1/node/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);
|
|
}
|
|
});
|
|
});
|