/** * E2E Tests for Chat Message Flow * Tests sending messages and research integration * * Prerequisites: Web server running on http://127.0.0.1:5000 */ const puppeteer = require('puppeteer'); const AuthHelper = require('../auth_helper'); const { getPuppeteerLaunchOptions } = require('../puppeteer_config'); const fs = require('fs'); const path = require('path'); const BASE_URL = process.env.BASE_URL || 'http://127.0.0.1:5000'; const isCI = !!process.env.CI; // Timeouts adjusted for CI environment const TIMEOUTS = { navigation: isCI ? 60000 : 30000, selector: isCI ? 30000 : 10000, element: isCI ? 10000 : 5000, }; async function testChatMessageFlow() { console.log(`πŸ§ͺ Running chat message flow test (CI mode: ${isCI})`); // Create screenshots directory if it doesn't exist const screenshotsDir = path.join(__dirname, '..', 'screenshots'); if (!fs.existsSync(screenshotsDir)) { fs.mkdirSync(screenshotsDir, { recursive: true }); } const browser = await puppeteer.launch(getPuppeteerLaunchOptions()); const page = await browser.newPage(); // Set viewport await page.setViewport({ width: 1280, height: 800 }); // Set timeouts if (isCI) { page.setDefaultTimeout(60000); page.setDefaultNavigationTimeout(60000); } // Enable console logging for debugging page.on('console', msg => { if (msg.type() === 'error') { console.log(`BROWSER ERROR: ${msg.text()}`); } }); page.on('pageerror', error => { console.log('PAGE ERROR:', error.message); }); const authHelper = new AuthHelper(page, BASE_URL); let testsPassed = 0; let testsFailed = 0; try { // Ensure we're logged in console.log('πŸ” Ensuring authentication...'); await authHelper.ensureAuthenticated(); console.log('βœ… Authentication verified\n'); // Navigate to chat page to get CSRF token await page.goto(`${BASE_URL}/chat/`, { waitUntil: 'domcontentloaded', timeout: TIMEOUTS.navigation }); await page.waitForSelector('.ldr-chat-container', { timeout: TIMEOUTS.selector }); // Get CSRF token from the page const csrfToken = await page.evaluate(() => { const meta = document.querySelector('meta[name="csrf-token"]'); if (meta) return meta.content; const input = document.querySelector('input[name="csrf_token"]'); if (input) return input.value; return null; }); if (!csrfToken) { console.log('⚠️ Warning: CSRF token not found, tests may fail'); } else { console.log('βœ… CSRF token obtained\n'); } // Test 1: Send message via API console.log('πŸ“‹ Test 1: Send message via API'); try { // Create a session first const sessionResponse = await page.evaluate(async (token) => { const response = await fetch('/api/chat/sessions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': token || '' }, credentials: 'same-origin', body: JSON.stringify({ initial_query: 'Message flow test' }) }); return response.json(); }, csrfToken); if (!sessionResponse.success) { throw new Error('Failed to create session'); } const sessionId = sessionResponse.session_id; // Send a message without triggering research (faster test) const messageResponse = await page.evaluate(async (sid, token) => { const response = await fetch(`/api/chat/sessions/${sid}/messages`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': token || '' }, credentials: 'same-origin', body: JSON.stringify({ content: 'Hello, this is a test message', trigger_research: false }) }); return response.json(); }, sessionId, csrfToken); if (!messageResponse.success || !messageResponse.message_id) { throw new Error(`Failed to send message: ${JSON.stringify(messageResponse)}`); } console.log(` Message sent: ${messageResponse.message_id.substring(0, 8)}...`); console.log('βœ… Test 1 PASSED: Message sent via API\n'); testsPassed++; } catch (error) { console.log(`❌ Test 1 FAILED: ${error.message}\n`); await page.screenshot({ path: path.join(screenshotsDir, 'chat_message_send_failed.png') }); testsFailed++; } // Test 2: Get messages for session console.log('πŸ“‹ Test 2: Get messages for session'); try { // Create session and send message const sessionResponse = await page.evaluate(async (token) => { const response = await fetch('/api/chat/sessions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': token || '' }, credentials: 'same-origin', body: JSON.stringify({ initial_query: 'Get messages test' }) }); return response.json(); }, csrfToken); const sessionId = sessionResponse.session_id; // Send a message await page.evaluate(async (sid, token) => { await fetch(`/api/chat/sessions/${sid}/messages`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': token || '' }, credentials: 'same-origin', body: JSON.stringify({ content: 'Test message for retrieval', trigger_research: false }) }); }, sessionId, csrfToken); // Get messages const messagesResponse = await page.evaluate(async (sid) => { const response = await fetch(`/api/chat/sessions/${sid}/messages`); return response.json(); }, sessionId); if (!messagesResponse.success) { throw new Error('Failed to get messages'); } if (messagesResponse.messages.length === 0) { throw new Error('No messages returned'); } console.log(` Retrieved ${messagesResponse.messages.length} message(s)`); console.log('βœ… Test 2 PASSED: Messages retrieved\n'); testsPassed++; } catch (error) { console.log(`❌ Test 2 FAILED: ${error.message}\n`); await page.screenshot({ path: path.join(screenshotsDir, 'chat_get_messages_failed.png') }); testsFailed++; } // Test 3: Message triggers research (with trigger_research=true) console.log('πŸ“‹ Test 3: Message triggers research'); try { // Create session const sessionResponse = await page.evaluate(async (token) => { const response = await fetch('/api/chat/sessions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': token || '' }, credentials: 'same-origin', body: JSON.stringify({ initial_query: 'Research trigger test' }) }); return response.json(); }, csrfToken); const sessionId = sessionResponse.session_id; // Send a message with research trigger const messageResponse = await page.evaluate(async (sid, token) => { const response = await fetch(`/api/chat/sessions/${sid}/messages`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': token || '' }, credentials: 'same-origin', body: JSON.stringify({ content: 'What is quantum computing?', trigger_research: true }) }); return response.json(); }, sessionId, csrfToken); if (!messageResponse.success) { throw new Error(`Failed to send message: ${JSON.stringify(messageResponse)}`); } // When trigger_research is true, research_id should be returned if (!messageResponse.research_id) { throw new Error('No research_id returned'); } console.log(` Research triggered: ${messageResponse.research_id.substring(0, 8)}...`); console.log(` Research mode: ${messageResponse.research_mode}`); console.log('βœ… Test 3 PASSED: Research triggered\n'); testsPassed++; } catch (error) { console.log(`❌ Test 3 FAILED: ${error.message}\n`); await page.screenshot({ path: path.join(screenshotsDir, 'chat_research_trigger_failed.png') }); testsFailed++; } // Test 4: Multiple messages maintain sequence order console.log('πŸ“‹ Test 4: Multiple messages maintain sequence order'); try { // Create session const sessionResponse = await page.evaluate(async (token) => { const response = await fetch('/api/chat/sessions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': token || '' }, credentials: 'same-origin', body: JSON.stringify({ initial_query: 'Sequence test' }) }); return response.json(); }, csrfToken); const sessionId = sessionResponse.session_id; // Send multiple messages for (let i = 1; i <= 5; i++) { await page.evaluate(async (sid, content, token) => { await fetch(`/api/chat/sessions/${sid}/messages`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': token || '' }, credentials: 'same-origin', body: JSON.stringify({ content, trigger_research: false }) }); }, sessionId, `Message number ${i}`, csrfToken); } // Get messages and verify sequence const messagesResponse = await page.evaluate(async (sid) => { const response = await fetch(`/api/chat/sessions/${sid}/messages`); return response.json(); }, sessionId); if (messagesResponse.messages.length !== 5) { throw new Error(`Expected 5 messages, got ${messagesResponse.messages.length}`); } // Verify sequence numbers are in order for (let i = 0; i < messagesResponse.messages.length; i++) { const expectedSeq = i + 1; const actualSeq = messagesResponse.messages[i].sequence_number; if (actualSeq !== expectedSeq) { throw new Error(`Sequence mismatch at index ${i}: expected ${expectedSeq}, got ${actualSeq}`); } } console.log(' 5 messages with correct sequence numbers'); console.log('βœ… Test 4 PASSED: Message sequence maintained\n'); testsPassed++; } catch (error) { console.log(`❌ Test 4 FAILED: ${error.message}\n`); await page.screenshot({ path: path.join(screenshotsDir, 'chat_sequence_failed.png') }); testsFailed++; } // Test 5: Message with special characters console.log('πŸ“‹ Test 5: Message with special characters'); try { // Create session const sessionResponse = await page.evaluate(async (token) => { const response = await fetch('/api/chat/sessions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': token || '' }, credentials: 'same-origin', body: JSON.stringify({ initial_query: 'Special chars test' }) }); return response.json(); }, csrfToken); const sessionId = sessionResponse.session_id; const specialContent = 'Message with and "quotes" & Γ©mojis πŸŽ‰'; // Send message with special characters const messageResponse = await page.evaluate(async (sid, content, token) => { const response = await fetch(`/api/chat/sessions/${sid}/messages`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': token || '' }, credentials: 'same-origin', body: JSON.stringify({ content, trigger_research: false }) }); return response.json(); }, sessionId, specialContent, csrfToken); if (!messageResponse.success) { throw new Error('Failed to send message with special characters'); } // Retrieve and verify content is preserved const messagesResponse = await page.evaluate(async (sid) => { const response = await fetch(`/api/chat/sessions/${sid}/messages`); return response.json(); }, sessionId); const lastMessage = messagesResponse.messages[messagesResponse.messages.length - 1]; if (lastMessage.content !== specialContent) { throw new Error('Message content not preserved correctly'); } console.log(' Special characters preserved in message'); console.log('βœ… Test 5 PASSED: Special characters handled\n'); testsPassed++; } catch (error) { console.log(`❌ Test 5 FAILED: ${error.message}\n`); await page.screenshot({ path: path.join(screenshotsDir, 'chat_special_chars_failed.png') }); testsFailed++; } // Test 6: Empty message rejected console.log('πŸ“‹ Test 6: Empty message rejected'); try { // Create session const sessionResponse = await page.evaluate(async (token) => { const response = await fetch('/api/chat/sessions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': token || '' }, credentials: 'same-origin', body: JSON.stringify({ initial_query: 'Empty message test' }) }); return response.json(); }, csrfToken); const sessionId = sessionResponse.session_id; // Try to send empty message const messageResponse = await page.evaluate(async (sid, token) => { const response = await fetch(`/api/chat/sessions/${sid}/messages`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': token || '' }, credentials: 'same-origin', body: JSON.stringify({ content: '', trigger_research: false }) }); return { status: response.status, data: await response.json() }; }, sessionId, csrfToken); if (messageResponse.status !== 400) { throw new Error(`Expected 400 for empty message, got ${messageResponse.status}`); } console.log(' Empty message correctly rejected with 400'); console.log('βœ… Test 6 PASSED: Empty message validation works\n'); testsPassed++; } catch (error) { console.log(`❌ Test 6 FAILED: ${error.message}\n`); await page.screenshot({ path: path.join(screenshotsDir, 'chat_empty_message_failed.png') }); testsFailed++; } // Test 7: Message to nonexistent session returns 404 console.log('πŸ“‹ Test 7: Message to nonexistent session'); try { const fakeSessionId = 'nonexistent-session-id-12345'; const messageResponse = await page.evaluate(async (sid, token) => { const response = await fetch(`/api/chat/sessions/${sid}/messages`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': token || '' }, credentials: 'same-origin', body: JSON.stringify({ content: 'Test message', trigger_research: false }) }); return { status: response.status, data: await response.json() }; }, fakeSessionId, csrfToken); if (messageResponse.status !== 404) { throw new Error(`Expected 404 for nonexistent session, got ${messageResponse.status}`); } console.log(' Nonexistent session correctly returns 404'); console.log('βœ… Test 7 PASSED: 404 for nonexistent session\n'); testsPassed++; } catch (error) { console.log(`❌ Test 7 FAILED: ${error.message}\n`); await page.screenshot({ path: path.join(screenshotsDir, 'chat_404_failed.png') }); testsFailed++; } } catch (error) { console.log(`πŸ’₯ Test suite error: ${error.message}`); testsFailed++; } finally { await browser.close(); } // Print summary console.log('─'.repeat(50)); console.log(`πŸ“Š Chat Message Flow Tests Summary`); console.log(` βœ… Passed: ${testsPassed}`); console.log(` ❌ Failed: ${testsFailed}`); console.log('─'.repeat(50)); if (testsFailed > 0) { process.exit(1); } } testChatMessageFlow().catch(error => { console.error('πŸ’₯ Test runner error:', error); process.exit(1); });