/** * API Key Inputs Test * Tests the API key input fields on the research form for cloud providers * CI-compatible: Works in both local and CI environments */ const puppeteer = require('puppeteer'); const AuthHelper = require('./auth_helper'); const { getPuppeteerLaunchOptions } = require('./puppeteer_config'); const fs = require('fs'); const path = require('path'); // API key provider configurations const API_KEY_PROVIDERS = [ { provider: 'OPENAI', containerId: 'openai_api_key_container', inputId: 'openai_api_key', settingKey: 'llm.openai.api_key' }, { provider: 'ANTHROPIC', containerId: 'anthropic_api_key_container', inputId: 'anthropic_api_key', settingKey: 'llm.anthropic.api_key' }, { provider: 'GOOGLE', containerId: 'google_api_key_container', inputId: 'google_api_key', settingKey: 'llm.google.api_key' }, { provider: 'OPENROUTER', containerId: 'openrouter_api_key_container', inputId: 'openrouter_api_key', settingKey: 'llm.openrouter.api_key' }, { provider: 'XAI', containerId: 'xai_api_key_container', inputId: 'xai_api_key', settingKey: 'llm.xai.api_key' }, { provider: 'IONOS', containerId: 'ionos_api_key_container', inputId: 'ionos_api_key', settingKey: 'llm.ionos.api_key' }, { provider: 'OPENAI_ENDPOINT', containerId: 'openai_endpoint_api_key_container', inputId: 'openai_endpoint_api_key', settingKey: 'llm.openai_endpoint.api_key' }, { provider: 'OLLAMA', containerId: 'ollama_api_key_container', inputId: 'ollama_api_key', settingKey: 'llm.ollama.api_key' }, ]; // Local providers that should NOT show API key fields (except Ollama which has optional key) const LOCAL_PROVIDERS_NO_KEY = ['LMSTUDIO', 'LLAMACPP']; async function testApiKeyInputs() { const isCI = !!process.env.CI; console.log(`\n๐Ÿงช Running API Key Inputs test (CI mode: ${isCI})\n`); // 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; function logTest(name, passed, message = '') { if (passed) { console.log(` โœ… ${name}${message ? ': ' + message : ''}`); testsPassed++; } else { console.log(` โŒ ${name}${message ? ': ' + message : ''}`); testsFailed++; } } try { // Authenticate console.log('๐Ÿ“‹ Setup: Authenticating...'); const authHelper = new AuthHelper(page, baseUrl); const testUser = { username: `apikey_test_${Date.now()}`, password: 'TestPass123!' }; await authHelper.ensureAuthenticatedWithTimeout(); console.log('โœ… Authenticated\n'); // Navigate to research page console.log('๐Ÿ“„ Navigating to research page...'); await page.goto(`${baseUrl}/`, { waitUntil: 'networkidle2', timeout: 30000 }); const currentUrl = page.url(); if (currentUrl.includes('/auth/login')) { throw new Error('Redirected to login - not authenticated'); } console.log('โœ… Research page loaded\n'); // ===== TEST GROUP 1: API Key HTML Elements Exist ===== console.log('๐Ÿ“‹ Test Group 1: API Key HTML Elements Exist'); for (const config of API_KEY_PROVIDERS) { const containerExists = await page.$(`#${config.containerId}`) !== null; const inputExists = await page.$(`#${config.inputId}`) !== null; logTest( `${config.provider} API key elements`, containerExists && inputExists, containerExists && inputExists ? 'container and input found' : 'missing elements' ); } // ===== TEST GROUP 2: API Key Inputs Are Password Type ===== console.log('\n๐Ÿ“‹ Test Group 2: API Key Inputs Are Password Type'); for (const config of API_KEY_PROVIDERS) { const inputType = await page.$eval(`#${config.inputId}`, el => el.type).catch(() => null); logTest( `${config.provider} input is password type`, inputType === 'password', inputType === 'password' ? 'correctly masked' : `got type: ${inputType}` ); } // ===== TEST GROUP 3: All API Key Containers Initially Hidden ===== console.log('\n๐Ÿ“‹ Test Group 3: API Key Containers Initially Hidden'); for (const config of API_KEY_PROVIDERS) { const displayStyle = await page.$eval(`#${config.containerId}`, el => { return window.getComputedStyle(el).display; }).catch(() => 'error'); // Initially all should be hidden (display: none) logTest( `${config.provider} container initially hidden`, displayStyle === 'none', displayStyle === 'none' ? 'correctly hidden' : `display: ${displayStyle}` ); } // ===== TEST GROUP 4: Ensure Advanced Options Expanded ===== console.log('\n๐Ÿ“‹ Test Group 4: Ensure Advanced Options Expanded'); const advancedToggle = await page.$('.ldr-advanced-options-toggle'); if (advancedToggle) { // Check if panel is already expanded (default open) const panel = await page.$('.ldr-advanced-options-panel'); const isAlreadyExpanded = panel ? await page.evaluate(el => { const style = window.getComputedStyle(el); return style.visibility !== 'hidden' && style.opacity !== '0'; }, panel) : false; if (!isAlreadyExpanded) { await advancedToggle.click(); await page.waitForTimeout(500); // Wait for animation } logTest('Advanced options expanded', true, isAlreadyExpanded ? 'already open by default' : 'expanded via click'); } else { logTest('Advanced options toggle', false, 'toggle not found'); } // ===== TEST GROUP 5: Provider Selection Shows Correct API Key Field ===== console.log('\n๐Ÿ“‹ Test Group 5: Provider Selection Shows Correct API Key Field'); const providerSelect = await page.$('#model_provider'); if (!providerSelect) { logTest('Provider select', false, 'provider dropdown not found'); } else { for (const config of API_KEY_PROVIDERS) { // Select the provider await page.select('#model_provider', config.provider); await page.waitForTimeout(300); // Wait for visibility toggle // Check that this provider's API key container is visible const isVisible = await page.$eval(`#${config.containerId}`, el => { return window.getComputedStyle(el).display !== 'none'; }).catch(() => false); logTest( `${config.provider} shows API key field when selected`, isVisible, isVisible ? 'field visible' : 'field still hidden' ); // Check that other API key containers are hidden for (const otherConfig of API_KEY_PROVIDERS) { if (otherConfig.provider !== config.provider) { const otherHidden = await page.$eval(`#${otherConfig.containerId}`, el => { return window.getComputedStyle(el).display === 'none'; }).catch(() => true); if (!otherHidden) { logTest( `${otherConfig.provider} hidden when ${config.provider} selected`, false, 'should be hidden but is visible' ); } } } } } // ===== TEST GROUP 6: Local Providers Don't Show API Key (except Ollama) ===== console.log('\n๐Ÿ“‹ Test Group 6: Local Providers Without API Key Fields'); for (const localProvider of LOCAL_PROVIDERS_NO_KEY) { // Try to select the provider (may not exist in dropdown) try { await page.select('#model_provider', localProvider); await page.waitForTimeout(300); // Check that no cloud API key containers are visible let anyVisible = false; for (const config of API_KEY_PROVIDERS) { // Skip Ollama since it has optional API key if (config.provider === 'OLLAMA') continue; const isVisible = await page.$eval(`#${config.containerId}`, el => { return window.getComputedStyle(el).display !== 'none'; }).catch(() => false); if (isVisible) { anyVisible = true; break; } } logTest( `${localProvider} hides cloud API key fields`, !anyVisible, anyVisible ? 'some API key fields visible' : 'all hidden correctly' ); } catch { // Provider may not be in the dropdown, that's OK console.log(` โญ๏ธ ${localProvider} provider not in dropdown, skipping`); } } // ===== TEST GROUP 7: API Key Input Accepts Text ===== console.log('\n๐Ÿ“‹ Test Group 7: API Key Input Accepts Text'); // Select OpenAI provider await page.select('#model_provider', 'OPENAI'); await page.waitForTimeout(300); const testApiKey = 'sk-test-key-12345'; await page.type('#openai_api_key', testApiKey); const inputValue = await page.$eval('#openai_api_key', el => el.value); logTest( 'API key input accepts text', inputValue === testApiKey, inputValue === testApiKey ? 'value stored correctly' : `got: ${inputValue}` ); // ===== TEST GROUP 8: API Key Persists After Provider Switch and Back ===== console.log('\n๐Ÿ“‹ Test Group 8: API Key Value Persistence'); // Switch to another provider await page.select('#model_provider', 'ANTHROPIC'); await page.waitForTimeout(300); // Switch back to OpenAI await page.select('#model_provider', 'OPENAI'); await page.waitForTimeout(300); const persistedValue = await page.$eval('#openai_api_key', el => el.value); logTest( 'API key persists after provider switch', persistedValue === testApiKey, persistedValue === testApiKey ? 'value preserved' : `got: ${persistedValue}` ); // ===== TEST GROUP 9: API Key Save Handler Fires ===== console.log('\n๐Ÿ“‹ Test Group 9: API Key Save Handler'); // Listen for network requests to verify save let saveRequestFired = false; page.on('request', request => { if (request.url().includes('/settings/api/') && request.method() === 'PUT') { saveRequestFired = true; } }); // Clear and type a new value, then blur to trigger save await page.$eval('#openai_api_key', el => { el.value = ''; }); await page.type('#openai_api_key', 'sk-new-test-key'); await page.click('#model_provider'); // Click elsewhere to trigger blur/change await page.waitForTimeout(500); logTest( 'API key save request fires on change', saveRequestFired, saveRequestFired ? 'save request detected' : 'no save request detected' ); // Take a screenshot of the final state await page.screenshot({ path: path.join(screenshotsDir, 'api_key_inputs_final.png'), fullPage: true }); } catch (error) { console.error(`\nโŒ Test error: ${error.message}`); // Take error screenshot try { await page.screenshot({ path: path.join(screenshotsDir, 'api_key_inputs_error.png'), fullPage: true }); } catch { // Ignore screenshot errors } testsFailed++; } finally { await browser.close(); } // Summary console.log('\n' + '='.repeat(50)); console.log(`๐Ÿ“Š Test Summary: ${testsPassed} passed, ${testsFailed} failed`); console.log('='.repeat(50)); if (testsFailed > 0) { console.log('\nโŒ Some tests failed'); process.exit(1); } else { console.log('\nโœ… All tests passed!'); process.exit(0); } } // Run the tests testApiKeyInputs().catch(error => { console.error('Fatal error:', error); process.exit(1); });