/** * Research Submit Test * Tests the research form submission workflow * CI-compatible: Works in both local and CI environments */ const puppeteer = require('puppeteer'); const AuthHelper = require('./auth_helper'); const { CI_TEST_USER } = require('./auth_helper'); const { getPuppeteerLaunchOptions } = require('./puppeteer_config'); const { setupDefaultModel } = require('./model_helper'); const fs = require('fs'); const path = require('path'); // 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. (async () => { let browser; const isCI = !!process.env.CI; // Skip in CI: setupDefaultModel() triggers provider-change JS that clears the // query field, causing the form to submit empty and redirect to /. This is a // test-infrastructure issue, not a product bug. if (isCI) { console.log('โญ๏ธ Skipping research submit test in CI (known model-setup race condition)'); process.exit(0); } console.log(`๐Ÿงช Running research submit test (CI mode: ${isCI})`); // Create screenshots directory const screenshotsDir = path.join(__dirname, 'screenshots'); if (!fs.existsSync(screenshotsDir)) { fs.mkdirSync(screenshotsDir, { recursive: true }); } try { browser = await puppeteer.launch(getPuppeteerLaunchOptions()); let page = await browser.newPage(); // Set longer timeout for CI const timeout = isCI ? 60000 : 30000; page.setDefaultTimeout(timeout); page.setDefaultNavigationTimeout(timeout); // Log console messages only if verbose if (process.env.VERBOSE) { page.on('console', msg => { console.log(`[${msg.type()}] ${msg.text()}`); }); } // Authenticate (tries CI user first, falls back to registration) const auth = new AuthHelper(page); console.log('๐Ÿ” Authenticating...'); await auth.ensureAuthenticated(); page = auth.page; console.log('\n๐Ÿ  Navigating to home page...'); await page.goto('http://127.0.0.1:5000/', { waitUntil: 'domcontentloaded' }); // Set up model configuration console.log('๐Ÿ”ง Configuring model...'); const modelConfigured = await setupDefaultModel(page); if (!modelConfigured) { throw new Error('Failed to configure model'); } // Wait for and fill the query field await page.waitForSelector('#query', { timeout: 10000 }); await page.type('#query', 'What is Node.js?'); console.log('โœ… Query entered'); // Check if model and search engine are pre-selected const formValues = await page.evaluate(() => { return { query: document.querySelector('#query')?.value, model: document.querySelector('#model')?.value || document.querySelector('input[name="model"]')?.value, searchEngine: document.querySelector('#search_engine')?.value || document.querySelector('input[name="search_engine"]')?.value }; }); console.log('๐Ÿ“‹ Form values:', formValues); console.log('\n๐Ÿš€ Submitting research...'); // Submit the form with robust retry logic const submitButton = await page.$('button[type="submit"]'); const navigationTimeout = isCI ? 45000 : 15000; let navigationSucceeded = false; if (submitButton) { try { // Try Promise.all approach first await Promise.all([ page.waitForNavigation({ waitUntil: 'domcontentloaded', timeout: navigationTimeout }), submitButton.click() ]); navigationSucceeded = true; } catch (navError) { console.log(` Navigation wait failed: ${navError.message}`); // Check if we're already on a different page const currentUrl = page.url(); if (currentUrl.includes('/research/') || currentUrl.includes('/progress/')) { console.log(' Already navigated to research page'); navigationSucceeded = true; } else { // Wait a bit and check URL again await new Promise(resolve => setTimeout(resolve, 3000)); const urlAfterWait = page.url(); if (urlAfterWait.includes('/research/') || urlAfterWait.includes('/progress/')) { console.log(' Navigation completed after wait'); navigationSucceeded = true; } } } } if (!navigationSucceeded) { // Try alternative submit method with Enter key console.log(' Trying Enter key submission...'); await page.keyboard.press('Enter'); await new Promise(resolve => setTimeout(resolve, 5000)); } // Check where we ended up const url = page.url(); console.log('๐Ÿ“ Current URL:', url); if (url.includes('/research/') || url.includes('/progress/')) { console.log('โœ… Research submitted successfully!'); // URL contains /research/ - that's sufficient proof research started // NOTE: Don't use page.evaluate() here - it hangs when the page has // active WebSocket/SSE connections for research progress updates. // The URL check is enough to verify successful submission. console.log('โœ… Research is processing'); console.log('\n๐ŸŽ‰ Simple research test passed!'); process.exit(0); } else { // Check for error messages const errorMessage = await page.evaluate(() => { const alert = document.querySelector('.alert-danger, .error-message'); return alert ? alert.textContent : null; }); if (errorMessage) { throw new Error(`Research submission failed: ${errorMessage}`); } else { throw new Error(`Unexpected redirect to: ${url}`); } } } catch (error) { console.error('โŒ Test failed:', error.message); console.error('Stack trace:', error.stack); // Take screenshot on error if (browser) { try { const pages = await browser.pages(); const page = pages[0]; if (page) { // Log current state for debugging try { const currentUrl = page.url(); console.log('Current URL at failure:', currentUrl); } catch { console.log('Could not get current URL'); } // Skip diagnostic screenshot in CI โ€” error context is in the logs if (!isCI) { try { await page.screenshot({ path: path.join(screenshotsDir, `research_submit_error_${Date.now()}.png`), fullPage: true }); console.log('๐Ÿ“ธ Error screenshot saved'); } catch (screenshotError) { console.error('Failed to save screenshot:', screenshotError.message); } } } } catch (browserError) { console.error('Error accessing browser pages:', browserError.message); } } process.exit(1); } finally { if (browser) { try { await browser.close(); } catch (closeError) { console.error('Error closing browser:', closeError.message); } } } })();