/** * Research Form Validation Test * Tests that the research form correctly validates input * 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 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 function testResearchFormValidation() { const isCI = !!process.env.CI; console.log(`๐Ÿงช Running research form validation 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(); const baseUrl = 'http://127.0.0.1:5000'; // Increase default timeout in CI if (isCI) { page.setDefaultTimeout(60000); page.setDefaultNavigationTimeout(60000); } let testsPassed = 0; let testsFailed = 0; console.log('๐Ÿงช Starting research form validation tests...\n'); try { // Authenticate (CI uses pre-created user for speed) console.log('๐Ÿ“‹ Setup: Authenticating...'); const authHelper = new AuthHelper(page, baseUrl); try { // Use ensureAuthenticated which tries CI user first, then falls back to registration await authHelper.ensureAuthenticatedWithTimeout(); console.log('โœ… Authenticated\n'); } catch (authError) { console.log(`โš ๏ธ Could not authenticate: ${authError.message}`); console.log('โŒ Cannot authenticate - skipping research form tests'); await browser.close(); process.exit(1); } // Navigate to home page (research form) console.log('๐Ÿ“„ Navigating to research page...'); await page.goto(`${baseUrl}/`, { waitUntil: 'domcontentloaded', timeout: 30000 }); const currentUrl = page.url(); if (currentUrl.includes('/auth/login')) { console.log(`โŒ Redirected to login - not authenticated`); throw new Error('Not authenticated'); } console.log('โœ… Research page loaded\n'); // Test 1: Research form exists console.log('๐Ÿ“‹ Test 1: Research form exists'); const researchForm = await page.$('#research-form, form[action*="research"], form'); if (researchForm) { console.log('โœ… Research form found'); testsPassed++; } else { console.log('โŒ Research form not found'); testsFailed++; } // Test 2: Query input exists and is required console.log('\n๐Ÿ“‹ Test 2: Query input validation'); // Look for query textarea or input const queryInput = await page.$('textarea[name="query"], input[name="query"], #query, textarea#research-query'); if (queryInput) { const tagName = await page.evaluate(el => el.tagName.toLowerCase(), queryInput); const isRequired = await page.evaluate(el => el.required || el.hasAttribute('required'), queryInput); const placeholder = await page.evaluate(el => el.placeholder, queryInput); console.log(` Query input type: ${tagName}, required: ${isRequired}`); console.log(` Placeholder: "${placeholder?.substring(0, 40)}..."`); console.log('โœ… Query input found'); testsPassed++; // Test 3: Empty query validation console.log('\n๐Ÿ“‹ Test 3: Empty query handling'); // Clear the query input await page.evaluate(el => { el.value = ''; }, queryInput); // Check if submit button is disabled or if form prevents submission const submitBtn = await page.$('button[type="submit"], #start-research, .ldr-btn-research'); if (submitBtn) { const isDisabled = await page.evaluate(el => el.disabled, submitBtn); console.log(` Submit button disabled: ${isDisabled}`); if (isDisabled) { console.log('โœ… Submit button disabled for empty query'); testsPassed++; } else { // Check if there's client-side validation console.log('โš ๏ธ Submit button enabled - empty query handled server-side'); // This is still acceptable } } } else { console.log('โš ๏ธ Query input not found with expected selectors'); } // Test 4: Check for number inputs (iterations, questions) console.log('\n๐Ÿ“‹ Test 4: Numeric inputs have constraints'); const numberInputs = await page.$$('input[type="number"]'); console.log(` Found ${numberInputs.length} number inputs`); for (const input of numberInputs.slice(0, 3)) { const attrs = await page.evaluate(el => ({ name: el.name || el.id, min: el.min, max: el.max, value: el.value }), input); if (attrs.name) { console.log(` โœ“ "${attrs.name}": min=${attrs.min || 'none'}, max=${attrs.max || 'none'}, value=${attrs.value}`); } } if (numberInputs.length > 0) { // Check that at least one has min attribute let hasMinConstraint = false; for (const input of numberInputs) { const min = await page.evaluate(el => el.min, input); if (min) { hasMinConstraint = true; break; } } if (hasMinConstraint) { console.log('โœ… Number inputs have min constraints'); testsPassed++; } else { console.log('โš ๏ธ Number inputs may lack min constraints'); } } // Test 5: No JavaScript errors on page console.log('\n๐Ÿ“‹ Test 5: No critical JavaScript errors'); const jsErrors = []; page.on('pageerror', err => jsErrors.push(err.message)); // Interact with the form to trigger any lazy-loaded JS if (queryInput) { await queryInput.click(); await page.keyboard.type('test query', { delay: 30 }); await new Promise(resolve => setTimeout(resolve, 500)); } if (jsErrors.length === 0) { console.log('โœ… No JavaScript page errors'); testsPassed++; } else { console.log(`โŒ ${jsErrors.length} JavaScript error(s):`); jsErrors.slice(0, 2).forEach(err => console.log(` - ${err.substring(0, 80)}`)); testsFailed++; } // Test 6: Form structure check console.log('\n๐Ÿ“‹ Test 6: Form has expected structure'); const formElements = await page.$$('select, input[type="radio"], .ldr-dropdown, .custom-dropdown'); console.log(` Found ${formElements.length} selection elements (dropdowns, radios)`); if (formElements.length > 0) { console.log('โœ… Form has selection elements for configuration'); testsPassed++; } else { console.log('โš ๏ธ Form may be missing configuration options'); } // Take a screenshot (skip in CI) if (!isCI) { await page.screenshot({ path: path.join(screenshotsDir, 'research_form_validation_test.png'), fullPage: true }); console.log('\n๐Ÿ“ธ Screenshot saved to screenshots/research_form_validation_test.png'); } // Summary console.log('\n' + '='.repeat(50)); console.log(`๐Ÿ“Š Test Summary: ${testsPassed} passed, ${testsFailed} failed`); console.log('='.repeat(50)); if (testsFailed > 0) { throw new Error(`${testsFailed} test(s) failed`); } console.log('\n๐ŸŽ‰ All research form validation tests passed!'); } catch (error) { console.error('\nโŒ Test failed:', error.message); // Take error screenshot (skip in CI) if (!isCI) { try { await page.screenshot({ path: path.join(screenshotsDir, 'research_form_validation_error.png'), fullPage: true }); console.log('๐Ÿ“ธ Error screenshot saved'); } catch (screenshotError) { console.log('โš ๏ธ Could not take error screenshot:', screenshotError.message); } } await browser.close(); process.exit(1); } await browser.close(); console.log('\nโœ… Test completed successfully'); process.exit(0); } // Run the test testResearchFormValidation().catch(error => { console.error('Fatal error:', error); process.exit(1); });