const puppeteer = require('puppeteer'); const AuthHelper = require('./auth_helper'); const { getPuppeteerLaunchOptions } = require('./puppeteer_config'); // NAVIGATION NOTE: Using 'domcontentloaded' instead of 'networkidle2' for page.goto() // because networkidle2 waits for no network activity for 500ms, but WebSocket // connections and background polling keep the network active, causing infinite hangs. // See: test_login_validation.js and auth_helper.js for detailed explanation. // Test configuration const BASE_URL = 'http://127.0.0.1:5000'; // Colors for console output const colors = { reset: '\x1b[0m', bright: '\x1b[1m', green: '\x1b[32m', red: '\x1b[31m', yellow: '\x1b[33m', blue: '\x1b[34m', cyan: '\x1b[36m' }; function log(message, type = 'info') { const timestamp = new Date().toISOString().split('T')[1].split('.')[0]; const typeColors = { 'info': colors.cyan, 'success': colors.green, 'error': colors.red, 'warning': colors.yellow, 'section': colors.blue }; const color = typeColors[type] || colors.reset; console.log(`${color}[${timestamp}] ${message}${colors.reset}`); } async function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function createResearch(page, query, waitForCompletion = false) { log(`šŸ”¬ Creating research: "${query}"`, 'info'); // Navigate to home/research page await page.goto(BASE_URL, { waitUntil: 'domcontentloaded' }); // Wait for the query input await page.waitForSelector('#query', { timeout: 10000 }); // Clear and type query await page.click('#query', { clickCount: 3 }); await page.type('#query', query); // Submit the research const submitButton = await page.$('button[type="submit"]'); if (submitButton) { await submitButton.click(); } else { // Try pressing Enter await page.keyboard.press('Enter'); } // Wait for navigation or status update await delay(3000); if (waitForCompletion) { log('ā³ Waiting for research to complete...', 'info'); // Wait for completion (max 30 seconds for test) const startTime = Date.now(); const maxWaitTime = 30000; while (Date.now() - startTime < maxWaitTime) { try { // Check if we're on results page const url = page.url(); if (url.includes('/results/') || url.includes('/research/')) { // Check for completion indicators const completed = await page.evaluate(() => { const statusEl = document.querySelector('.status-badge, .research-status'); if (statusEl && statusEl.textContent.toLowerCase().includes('completed')) { return true; } const progressEl = document.querySelector('.progress-bar, .progress'); if (progressEl && progressEl.textContent.includes('100%')) { return true; } return false; }); if (completed) { log('āœ… Research completed', 'success'); break; } } } catch { // Continue waiting } await delay(1000); } } } async function testHistoryPage() { const browser = await puppeteer.launch(getPuppeteerLaunchOptions()); let page = await browser.newPage(); // Set console log handler page.on('console', msg => { if (msg.type() === 'error') { log(`Browser console error: ${msg.text()}`, 'error'); } }); try { // Create auth helper and register user const authHelper = new AuthHelper(page, BASE_URL); log('šŸ” Authenticating...', 'info'); await authHelper.ensureAuthenticatedWithTimeout(); page = authHelper.page; // Verify we're logged in const isLoggedIn = await authHelper.isLoggedIn(); if (!isLoggedIn) { throw new Error('Failed to login after registration'); } log('āœ… User registered and logged in', 'success'); // Create some research items for history log('\n=== CREATING TEST RESEARCH ITEMS ===', 'section'); const queries = [ 'What is machine learning?', 'History of artificial intelligence' ]; for (const query of queries) { await createResearch(page, query, false); // Don't wait for full completion await delay(2000); // Wait between researches } // Navigate to history page log('\n=== TESTING HISTORY PAGE ===', 'section'); await page.goto(`${BASE_URL}/history`, { waitUntil: 'domcontentloaded' }); // Wait for history content to load await page.waitForSelector('body', { timeout: 10000 }); // Check if we have history items or appropriate message const pageContent = await page.evaluate(() => { const historyItems = document.querySelectorAll('.history-item, .research-item, .list-group-item, [data-research-id]'); const emptyMessage = document.querySelector('.empty-state, .no-history, .alert-info'); return { title: document.title, hasHistoryContainer: !!document.querySelector('.ldr-history-container, .ldr-history-list, .research-history'), itemCount: historyItems.length, hasEmptyMessage: !!emptyMessage, emptyMessageText: emptyMessage?.textContent || '', items: Array.from(historyItems).slice(0, 5).map(item => ({ text: item.textContent?.trim().substring(0, 100) || '', hasLink: !!item.querySelector('a'), hasDeleteButton: !!item.querySelector('.delete-btn, .btn-danger, [onclick*="delete"]') })) }; }); log(`šŸ“Š History page content:`, 'info'); log(` - Title: ${pageContent.title}`, 'info'); log(` - Has history container: ${pageContent.hasHistoryContainer}`, 'info'); log(` - Total items found: ${pageContent.itemCount}`, 'info'); log(` - Has empty message: ${pageContent.hasEmptyMessage}`, 'info'); if (pageContent.itemCount > 0) { log('āœ… History items found on page', 'success'); // Log first few items pageContent.items.forEach((item, index) => { log(` - Item ${index + 1}: ${item.text.substring(0, 50)}...`, 'info'); }); } else if (pageContent.hasEmptyMessage) { log(`ā„¹ļø Empty history message: ${pageContent.emptyMessageText.substring(0, 100)}`, 'info'); log('āš ļø No history items found (research might not have saved)', 'warning'); } else { log('āš ļø No history items or empty message found', 'warning'); } // Test search/filter if available const searchInput = await page.$('#search-input, input[type="search"], .search-input'); if (searchInput && pageContent.itemCount > 0) { log('\n=== TESTING SEARCH/FILTER ===', 'section'); await searchInput.type('machine learning'); await delay(500); // Wait for filter to apply const filteredCount = await page.evaluate(() => { const visibleItems = document.querySelectorAll('.history-item:not([style*="display: none"]), .research-item:not(.d-none)'); return visibleItems.length; }); log(`šŸ“Š Filtered results: ${filteredCount} items`, 'info'); if (filteredCount >= 0) { log('āœ… Search field is present', 'success'); } } // Test navigation elements const navigationElements = await page.evaluate(() => { return { hasPagination: !!document.querySelector('.pagination, .page-link'), hasBackButton: !!document.querySelector('a[href="/"], .back-btn'), hasNewResearchButton: !!document.querySelector('a[href="/research"], .new-research-btn') }; }); log('\n=== NAVIGATION ELEMENTS ===', 'section'); log(` - Has pagination: ${navigationElements.hasPagination}`, 'info'); log(` - Has back/home button: ${navigationElements.hasBackButton}`, 'info'); log(` - Has new research button: ${navigationElements.hasNewResearchButton}`, 'info'); // Test re-run button on history items log('\n=== TESTING RE-RUN BUTTON ===', 'section'); const rerunButtonInfo = await page.evaluate(() => { const historyItems = document.querySelectorAll('.ldr-history-item, [data-research-id]'); const rerunButtons = document.querySelectorAll('.ldr-rerun-btn'); // Helper to check true visibility function isVisible(el) { if (!el) return false; const style = getComputedStyle(el); const rect = el.getBoundingClientRect(); return style.display !== 'none' && style.visibility !== 'hidden' && parseFloat(style.opacity) > 0 && rect.width > 0 && rect.height > 0; } // Check each item for re-run button const itemDetails = []; historyItems.forEach(item => { const status = item.querySelector('.ldr-status-badge, .status-badge'); const rerunBtn = item.querySelector('.ldr-rerun-btn'); const statusText = status?.textContent?.trim().toLowerCase() || 'unknown'; itemDetails.push({ status: statusText, hasRerunBtn: !!rerunBtn, isVisible: isVisible(rerunBtn), // Re-run should only show on completed items isCorrect: statusText === 'completed' ? (!!rerunBtn && isVisible(rerunBtn)) : !rerunBtn }); }); // Check button styling if any exist let buttonStyle = null; if (rerunButtons.length > 0) { const btn = rerunButtons[0]; const style = getComputedStyle(btn); buttonStyle = { hasIcon: !!btn.querySelector('i, .fa-redo, [class*="fa-"]'), cursor: style.cursor, hasTitle: !!btn.title }; } return { totalItems: historyItems.length, totalRerunButtons: rerunButtons.length, items: itemDetails, allCorrect: itemDetails.every(i => i.isCorrect), buttonStyle }; }); log(` - Total history items: ${rerunButtonInfo.totalItems}`, 'info'); log(` - Re-run buttons found: ${rerunButtonInfo.totalRerunButtons}`, 'info'); if (rerunButtonInfo.totalItems > 0) { rerunButtonInfo.items.forEach((item, idx) => { const mark = item.isCorrect ? 'āœ“' : 'āœ—'; log(` - Item ${idx + 1}: status=${item.status}, hasBtn=${item.hasRerunBtn}, visible=${item.isVisible} ${mark}`, 'info'); }); if (rerunButtonInfo.allCorrect) { log('āœ… Re-run buttons correctly shown only on completed items', 'success'); } else { log('āš ļø Re-run button visibility issue detected', 'warning'); } if (rerunButtonInfo.buttonStyle) { log(` - Button has icon: ${rerunButtonInfo.buttonStyle.hasIcon}`, 'info'); log(` - Button cursor: ${rerunButtonInfo.buttonStyle.cursor}`, 'info'); if (rerunButtonInfo.buttonStyle.cursor === 'pointer') { log('āœ… Re-run button has pointer cursor', 'success'); } } } else { log('ā„¹ļø No history items to test re-run button on', 'info'); } // Test re-run button click if available if (rerunButtonInfo.totalRerunButtons > 0) { log('\n=== TESTING RE-RUN BUTTON CLICK ===', 'section'); // Get query before clicking const queryBefore = await page.evaluate(() => { const item = document.querySelector('.ldr-history-item, [data-research-id]'); const queryEl = item?.querySelector('.ldr-history-item-query, .query-text, h5, h6'); return queryEl?.textContent?.trim() || ''; }); log(` - Query to re-run: "${queryBefore.substring(0, 50)}..."`, 'info'); // Click the re-run button await page.click('.ldr-rerun-btn'); await delay(2000); // Check if we navigated and form is populated const afterClick = await page.evaluate(() => { const queryEl = document.getElementById('query'); return { url: window.location.pathname, queryValue: queryEl?.value || '', hasNotification: !!document.querySelector('.alert-info') }; }); log(` - Navigated to: ${afterClick.url}`, 'info'); log(` - Form query: "${afterClick.queryValue.substring(0, 50)}..."`, 'info'); log(` - Re-run notification: ${afterClick.hasNotification}`, 'info'); if (afterClick.url === '/' && afterClick.queryValue.length > 0) { log('āœ… Re-run button works correctly', 'success'); } else { log('āš ļø Re-run navigation or form population issue', 'warning'); } } log('\nāœ… History page test completed successfully!', 'success'); } catch (error) { log(`\nāŒ Test failed: ${error.message}`, 'error'); throw error; } finally { await browser.close(); } } // Run the test testHistoryPage().catch(error => { console.error('Test execution failed:', error); process.exit(1); });