#!/usr/bin/env node /** * Research Form UI Tests * * Tests for the research form including advanced options, mode selection, * dropdowns, and form submission. * * Run: node test_research_form_ci.js */ const { setupTest, teardownTest, TestResults, log, delay, navigateTo, withTimeout } = require('./test_lib'); // ============================================================================ // Research Form Basic Tests // ============================================================================ const ResearchFormBasicTests = { async formElementsPresent(page, baseUrl) { await navigateTo(page, `${baseUrl}/`); const result = await page.evaluate(() => { const queryInput = document.querySelector('textarea[name="query"], input[name="query"], #query, #research-query'); const submitBtn = document.querySelector('button[type="submit"], .start-research, #start-research'); const form = document.querySelector('form'); return { hasQueryInput: !!queryInput, hasSubmitBtn: !!submitBtn, hasForm: !!form, queryType: queryInput?.tagName?.toLowerCase(), submitText: submitBtn?.textContent?.trim() }; }); const passed = result.hasQueryInput && result.hasSubmitBtn; return { passed, message: `Form elements: query=${result.hasQueryInput} (${result.queryType}), submit="${result.submitText}"` }; }, async queryInputAcceptsText(page, baseUrl) { await navigateTo(page, `${baseUrl}/`); const testQuery = 'Test research query about artificial intelligence'; const result = await page.evaluate((query) => { const queryInput = document.querySelector('textarea[name="query"], input[name="query"], #query, #research-query'); if (!queryInput) return { hasInput: false }; queryInput.value = query; queryInput.dispatchEvent(new Event('input', { bubbles: true })); return { hasInput: true, value: queryInput.value, matches: queryInput.value === query }; }, testQuery); return { passed: result.matches, message: result.matches ? 'Query input accepts and displays text correctly' : `Query input issue (has input: ${result.hasInput}, value length: ${result.value?.length || 0})` }; }, async queryInputPlaceholder(page, baseUrl) { await navigateTo(page, `${baseUrl}/`); const result = await page.evaluate(() => { const queryInput = document.querySelector('textarea[name="query"], input[name="query"], #query, #research-query'); return { hasInput: !!queryInput, placeholder: queryInput?.placeholder, hasPlaceholder: !!queryInput?.placeholder && queryInput.placeholder.length > 0 }; }); return { passed: result.hasPlaceholder, message: result.hasPlaceholder ? `Placeholder text: "${result.placeholder?.substring(0, 50)}..."` : 'No placeholder text set' }; }, async submitButtonState(page, baseUrl) { await navigateTo(page, `${baseUrl}/`); const result = await page.evaluate(() => { const submitBtn = document.querySelector('button[type="submit"], .start-research, #start-research'); const queryInput = document.querySelector('textarea[name="query"], input[name="query"], #query'); // Check initial state const initialDisabled = submitBtn?.disabled; // Type something if (queryInput) { queryInput.value = 'Test query'; queryInput.dispatchEvent(new Event('input', { bubbles: true })); } // Check after typing const afterTypingDisabled = submitBtn?.disabled; return { hasButton: !!submitBtn, initialDisabled, afterTypingDisabled, buttonText: submitBtn?.textContent?.trim() }; }); // Button should either always be enabled or enable after typing const passed = result.hasButton && (result.afterTypingDisabled === false || result.initialDisabled === false); return { passed, message: `Submit button: "${result.buttonText}" (initial disabled=${result.initialDisabled}, after typing=${result.afterTypingDisabled})` }; } }; // ============================================================================ // Research Mode Tests // ============================================================================ const ResearchModeTests = { async modeSelectionExists(page, baseUrl) { await navigateTo(page, `${baseUrl}/`); const result = await page.evaluate(() => { // Look for mode selection (radio buttons, toggle, or dropdown) const modeRadios = document.querySelectorAll('input[type="radio"][name*="mode"]'); const modeSelect = document.querySelector('select[name*="mode"], #research-mode'); const modeToggle = document.querySelector('.mode-toggle, [class*="mode-selector"], .research-mode'); // Also check for Quick/Detailed buttons/tabs const modeButtons = document.querySelectorAll('[data-mode], .mode-btn, .mode-option'); return { hasRadios: modeRadios.length > 0, radioCount: modeRadios.length, hasSelect: !!modeSelect, hasToggle: !!modeToggle, hasButtons: modeButtons.length > 0, buttonCount: modeButtons.length }; }); const hasMode = result.hasRadios || result.hasSelect || result.hasToggle || result.hasButtons; if (!hasMode) { return { passed: null, skipped: true, message: 'No mode selection UI found' }; } return { passed: true, message: `Mode selection: radios=${result.radioCount}, select=${result.hasSelect}, toggle=${result.hasToggle}, buttons=${result.buttonCount}` }; }, async modeToggleFunctionality(page, baseUrl) { await navigateTo(page, `${baseUrl}/`); const result = await page.evaluate(() => { // Find mode options const modeRadios = document.querySelectorAll('input[type="radio"][name*="mode"]'); const modeButtons = document.querySelectorAll('[data-mode], .mode-btn, .mode-option'); if (modeRadios.length >= 2) { const firstMode = modeRadios[0]; const secondMode = modeRadios[1]; firstMode.click(); const firstChecked = firstMode.checked; secondMode.click(); const secondChecked = secondMode.checked; const firstUnchecked = !firstMode.checked; return { type: 'radio', canToggle: firstChecked && secondChecked && firstUnchecked, modes: Array.from(modeRadios).map(r => r.value) }; } if (modeButtons.length >= 2) { const firstBtn = modeButtons[0]; const secondBtn = modeButtons[1]; firstBtn.click(); const firstActive = firstBtn.classList.contains('active') || firstBtn.getAttribute('aria-selected') === 'true'; secondBtn.click(); const secondActive = secondBtn.classList.contains('active') || secondBtn.getAttribute('aria-selected') === 'true'; return { type: 'buttons', canToggle: true, firstActive, secondActive }; } return { type: 'none', canToggle: false }; }); if (result.type === 'none') { return { passed: null, skipped: true, message: 'No mode toggle elements found' }; } return { passed: result.canToggle, message: `Mode toggle (${result.type}): works=${result.canToggle}, modes=${result.modes?.join(', ') || 'button-based'}` }; } }; // ============================================================================ // Advanced Options Tests // ============================================================================ const AdvancedOptionsTests = { async advancedOptionsToggle(page, baseUrl) { await navigateTo(page, `${baseUrl}/`); const result = await page.evaluate(() => { // Look for advanced options toggle const toggleBtn = document.querySelector( '[data-toggle="advanced"], ' + '.advanced-toggle, ' + '#advanced-options-toggle, ' + 'button[aria-controls*="advanced"], ' + '.collapsible-header, ' + '[class*="advanced"] button, ' + 'details summary' ); const advancedSection = document.querySelector( '.advanced-options, ' + '#advanced-options, ' + '[class*="advanced-settings"], ' + 'details' ); return { hasToggle: !!toggleBtn, hasSection: !!advancedSection, toggleText: toggleBtn?.textContent?.trim()?.substring(0, 50), sectionVisible: advancedSection ? window.getComputedStyle(advancedSection).display !== 'none' : null }; }); if (!result.hasToggle && !result.hasSection) { return { passed: null, skipped: true, message: 'No advanced options section found' }; } return { passed: true, message: `Advanced options: toggle="${result.toggleText}", section visible=${result.sectionVisible}` }; }, async advancedOptionsExpand(page, baseUrl) { await navigateTo(page, `${baseUrl}/`); // Click advanced options toggle const clicked = await page.evaluate(() => { const toggleBtn = document.querySelector( '[data-toggle="advanced"], ' + '.advanced-toggle, ' + '#advanced-options-toggle, ' + 'button[aria-controls*="advanced"], ' + '.collapsible-header, ' + 'details summary' ); if (toggleBtn) { toggleBtn.click(); return true; } return false; }); if (!clicked) { return { passed: null, skipped: true, message: 'No advanced options toggle to click' }; } await delay(300); const result = await page.evaluate(() => { const advancedSection = document.querySelector( '.advanced-options, ' + '#advanced-options, ' + '[class*="advanced-settings"], ' + 'details[open]' ); // Look for advanced option inputs const modelSelect = document.querySelector('select[name*="model"], #model-select'); const iterationsInput = document.querySelector('input[name*="iteration"], #iterations'); const strategySelect = document.querySelector('select[name*="strategy"], #strategy'); return { sectionExpanded: !!advancedSection && window.getComputedStyle(advancedSection).display !== 'none', hasModelSelect: !!modelSelect, hasIterations: !!iterationsInput, hasStrategy: !!strategySelect }; }); const hasContent = result.hasModelSelect || result.hasIterations || result.hasStrategy; return { passed: result.sectionExpanded || hasContent, message: `Advanced options expanded: model=${result.hasModelSelect}, iterations=${result.hasIterations}, strategy=${result.hasStrategy}` }; }, async modelProviderDropdown(page, baseUrl) { await navigateTo(page, `${baseUrl}/`); // Expand advanced options first await page.evaluate(() => { const toggle = document.querySelector('[data-toggle="advanced"], .advanced-toggle, details summary'); if (toggle) toggle.click(); }); await delay(300); const result = await page.evaluate(() => { const providerSelect = document.querySelector( 'select[name*="provider"], ' + '#provider, ' + '#model-provider, ' + 'select[name*="llm"]' ); if (!providerSelect) return { hasSelect: false }; const options = Array.from(providerSelect.options).map(o => ({ value: o.value, text: o.text })); return { hasSelect: true, optionCount: options.length, options: options.slice(0, 10) }; }); if (!result.hasSelect) { return { passed: null, skipped: true, message: 'No model provider dropdown found' }; } return { passed: result.optionCount > 0, message: `Provider dropdown: ${result.optionCount} options (${result.options.map(o => o.text).join(', ')})` }; }, async searchEngineDropdown(page, baseUrl) { await navigateTo(page, `${baseUrl}/`); // Expand advanced options await page.evaluate(() => { const toggle = document.querySelector('[data-toggle="advanced"], .advanced-toggle, details summary'); if (toggle) toggle.click(); }); await delay(300); const result = await page.evaluate(() => { // Check for standard