/** * Settings Form Validation Test * Tests that settings form inputs validate correctly * CI-compatible: Works in both local and CI environments */ const puppeteer = require('puppeteer'); const AuthHelper = require('./auth_helper'); const { Timer, 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 testSettingsValidation() { const isCI = !!process.env.CI; const testTimer = new Timer('test_settings_validation'); console.log(`๐Ÿงช Running settings 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 settings 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 settings tests'); await browser.close(); testTimer.summary(); process.exit(1); } // Navigate to settings page console.log('๐Ÿ“„ Navigating to settings page...'); await page.goto(`${baseUrl}/settings/`, { waitUntil: 'domcontentloaded', timeout: 30000 }); const currentUrl = page.url(); if (!currentUrl.includes('/settings')) { console.log(`โŒ Could not access settings page, redirected to: ${currentUrl}`); throw new Error('Cannot access settings page'); } console.log('โœ… Settings page loaded\n'); // Test 1: Page loads without JavaScript errors console.log('๐Ÿ“‹ Test 1: Settings page loads without JS errors'); // Check for console errors const consoleErrors = []; page.on('console', msg => { if (msg.type() === 'error') { consoleErrors.push(msg.text()); } }); // Wait a moment for any delayed JS to run await new Promise(resolve => setTimeout(resolve, 2000)); if (consoleErrors.length === 0) { console.log('โœ… No JavaScript console errors detected'); testsPassed++; } else { console.log(`โš ๏ธ ${consoleErrors.length} console error(s) detected:`); consoleErrors.slice(0, 3).forEach(err => console.log(` - ${err.substring(0, 80)}`)); // Don't fail - some console errors might be acceptable } // Test 2: Check for number inputs with validation console.log('\n๐Ÿ“‹ Test 2: Number inputs have min/max attributes'); const numberInputs = await page.$$('input[type="number"]'); console.log(` Found ${numberInputs.length} number inputs`); let inputsWithValidation = 0; for (const input of numberInputs.slice(0, 5)) { // Check first 5 const attrs = await page.evaluate(el => ({ name: el.name || el.id, min: el.min, max: el.max, step: el.step }), input); if (attrs.min || attrs.max) { inputsWithValidation++; console.log(` โœ“ "${attrs.name}" has min=${attrs.min} max=${attrs.max}`); } } if (inputsWithValidation > 0 || numberInputs.length === 0) { console.log('โœ… Number inputs have validation attributes'); testsPassed++; } else { console.log('โš ๏ธ Number inputs may lack validation attributes'); } // Test 3: Settings form exists and has inputs console.log('\n๐Ÿ“‹ Test 3: Settings form structure'); const formInputs = await page.$$('input, select, textarea'); console.log(` Found ${formInputs.length} form inputs`); if (formInputs.length > 0) { console.log('โœ… Settings page has form inputs'); testsPassed++; } else { console.log('โŒ Settings page has no form inputs'); testsFailed++; } // Test 4: Check for checkboxes and toggles console.log('\n๐Ÿ“‹ Test 4: Checkbox/toggle inputs'); const checkboxInputs = await page.$$('input[type="checkbox"]'); console.log(` Found ${checkboxInputs.length} checkbox inputs`); if (checkboxInputs.length > 0) { // Try to toggle a checkbox try { const firstCheckbox = checkboxInputs[0]; const initialState = await page.evaluate(el => el.checked, firstCheckbox); await firstCheckbox.click(); await new Promise(resolve => setTimeout(resolve, 500)); const newState = await page.evaluate(el => el.checked, firstCheckbox); if (initialState !== newState) { console.log('โœ… Checkboxes are interactive'); testsPassed++; } else { console.log('โš ๏ธ Checkbox click did not change state'); } // Toggle back await firstCheckbox.click(); } catch (e) { console.log(`โš ๏ธ Could not test checkbox interaction: ${e.message}`); } } else { console.log('โš ๏ธ No checkboxes found'); } // Test 5: No error alerts on page load console.log('\n๐Ÿ“‹ Test 5: No error alerts on page load'); const errorAlerts = await page.$$('.alert-danger, .error-message, .alert-error'); if (errorAlerts.length === 0) { console.log('โœ… No error alerts on settings page'); testsPassed++; } else { console.log(`โš ๏ธ Found ${errorAlerts.length} error alert(s) on page`); for (const alert of errorAlerts.slice(0, 2)) { const text = await page.evaluate(el => el.textContent, alert); console.log(` - ${text.trim().substring(0, 60)}`); } } // Take a screenshot of the settings page (skip in CI) if (!isCI) { await page.screenshot({ path: path.join(screenshotsDir, 'settings_validation_test.png'), fullPage: true }); console.log('\n๐Ÿ“ธ Screenshot saved to screenshots/settings_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 settings 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, 'settings_validation_error.png'), fullPage: true }); console.log('๐Ÿ“ธ Error screenshot saved'); } catch (screenshotError) { console.log('โš ๏ธ Could not take error screenshot:', screenshotError.message); } } await browser.close(); testTimer.summary(); process.exit(1); } await browser.close(); testTimer.summary(); console.log('\nโœ… Test completed successfully'); } // Run the test testSettingsValidation().catch(console.error);