#!/usr/bin/env node /** * Standalone test for Context Overflow Dashboard * Run with: node test_context_overflow_standalone.js */ const puppeteer = require('puppeteer'); const AuthHelper = require('./auth_helper'); const BASE_URL = process.env.BASE_URL || 'http://127.0.0.1:5000'; const HEADLESS = process.env.HEADLESS !== 'false'; const SLOW_MO = parseInt(process.env.SLOW_MO || '100', 10); async function testContextOverflow() { console.log('🚀 Starting Context Overflow Dashboard Test'); console.log(` Base URL: ${BASE_URL}`); console.log(` Headless: ${HEADLESS}`); console.log(''); let browser; let page; try { // Launch browser browser = await puppeteer.launch({ headless: HEADLESS, slowMo: SLOW_MO, args: ['--no-sandbox', '--disable-setuid-sandbox'] }); page = await browser.newPage(); await page.setViewport({ width: 1280, height: 800 }); // Log browser console errors page.on('console', msg => { if (msg.type() === 'error') { console.log(` Browser error: ${msg.text()}`); } }); // Enable request/response logging page.on('response', response => { const url = response.url(); if (url.includes('context-overflow')) { console.log(` 📡 Response: ${url} - Status: ${response.status()}`); } }); const authHelper = new AuthHelper(page, BASE_URL); // Set longer timeout page.setDefaultTimeout(30000); // Login console.log('📝 Logging in...'); await authHelper.ensureAuthenticated(); console.log('✅ Logged in successfully'); console.log(''); // Navigate to context overflow page console.log('📊 Navigating to context overflow page...'); await page.goto(`${BASE_URL}/metrics/context-overflow`, { waitUntil: 'domcontentloaded', timeout: 30000 }); console.log('✅ Page loaded'); // Wait for main container await page.waitForSelector('#context-overflow', { timeout: 10000 }); console.log('✅ Found context overflow container'); // Check loading state console.log('âŗ Waiting for data to load...'); const loadingResult = await page.waitForFunction( () => { const loading = document.getElementById('loading'); const content = document.getElementById('content'); // Log current state console.log('loadContextData check - loading display:', loading ? loading.style.display : 'no loading element'); console.log('loadContextData check - content display:', content ? content.style.display : 'no content element'); return loading && loading.style.display === 'none' && content && content.style.display !== 'none'; }, { timeout: 15000 } ).catch(_err => { console.log('❌ Timeout waiting for data to load'); return null; }); if (loadingResult) { console.log('✅ Data loaded successfully'); } // Get page data console.log(''); console.log('📊 Analyzing page content...'); const pageData = await page.evaluate(() => { const results = { sections: {}, metrics: {}, errors: [] }; // Check for error messages const alerts = document.querySelectorAll('.alert-danger'); alerts.forEach(alert => { results.errors.push(alert.textContent.trim()); }); // Check sections results.sections.hasOverviewCards = !!document.querySelector('.overflow-grid'); results.sections.hasContextChart = !!document.getElementById('context-chart'); results.sections.hasModelStats = !!document.getElementById('model-stats'); results.sections.hasTruncatedList = !!document.getElementById('truncated-list'); // Get metrics const truncationRate = document.getElementById('truncation-rate'); results.metrics.truncationRate = truncationRate ? truncationRate.textContent.trim() : null; const avgTokensLost = document.getElementById('avg-tokens-lost'); results.metrics.avgTokensLost = avgTokensLost ? avgTokensLost.textContent.trim() : null; const modelsTracked = document.getElementById('models-tracked'); results.metrics.modelsTracked = modelsTracked ? modelsTracked.textContent.trim() : null; const dataCoverage = document.getElementById('data-coverage'); results.metrics.dataCoverage = dataCoverage ? dataCoverage.textContent.trim() : null; // Check for no data message const noDataMessages = document.querySelectorAll('.no-data-message'); results.hasNoDataMessage = noDataMessages.length > 0; if (noDataMessages.length > 0) { results.noDataText = Array.from(noDataMessages).map(el => el.textContent.trim()); } return results; }); // Display results console.log(''); console.log('📋 Page Sections:'); console.log(` Overview cards: ${pageData.sections.hasOverviewCards ? '✅' : '❌'}`); console.log(` Context chart: ${pageData.sections.hasContextChart ? '✅' : '❌'}`); console.log(` Model stats: ${pageData.sections.hasModelStats ? '✅' : '❌'}`); console.log(` Truncated list: ${pageData.sections.hasTruncatedList ? '✅' : '❌'}`); console.log(''); console.log('📈 Metrics:'); console.log(` Truncation rate: ${pageData.metrics.truncationRate || 'N/A'}`); console.log(` Avg tokens lost: ${pageData.metrics.avgTokensLost || 'N/A'}`); console.log(` Models tracked: ${pageData.metrics.modelsTracked || 'N/A'}`); console.log(` Data coverage: ${pageData.metrics.dataCoverage || 'N/A'}`); if (pageData.errors.length > 0) { console.log(''); console.log('❌ Errors found:'); pageData.errors.forEach(err => console.log(` - ${err}`)); } if (pageData.hasNoDataMessage) { console.log(''); console.log('â„šī¸ No data messages:'); pageData.noDataText.forEach(msg => console.log(` - ${msg}`)); } // Test API directly console.log(''); console.log('🔌 Testing API endpoint...'); const apiResponse = await page.evaluate(async (baseUrl) => { try { const res = await fetch(`${baseUrl}/metrics/api/context-overflow?period=30d`, { method: 'GET', credentials: 'include', headers: { 'Accept': 'application/json' } }); const text = await res.text(); let data; try { data = JSON.parse(text); } catch { data = { parseError: true, rawText: text.substring(0, 200) }; } return { status: res.status, ok: res.ok, headers: Object.fromEntries(res.headers.entries()), data }; } catch (error) { return { error: error.message }; } }, BASE_URL); console.log(` Status: ${apiResponse.status}`); console.log(` OK: ${apiResponse.ok}`); if (apiResponse.data) { if (apiResponse.data.parseError) { console.log(' ❌ Failed to parse JSON response'); console.log(` Raw response: ${apiResponse.data.rawText}`); } else { console.log(` Response status: ${apiResponse.data.status}`); if (apiResponse.data.overview) { console.log(''); console.log(' 📊 API Data Overview:'); console.log(` Total requests: ${apiResponse.data.overview.total_requests}`); console.log(` Requests with context: ${apiResponse.data.overview.requests_with_context_data}`); console.log(` Truncated requests: ${apiResponse.data.overview.truncated_requests}`); console.log(` Truncation rate: ${apiResponse.data.overview.truncation_rate}%`); console.log(` Avg tokens truncated: ${apiResponse.data.overview.avg_tokens_truncated}`); } if (apiResponse.data.model_stats) { console.log(` Model stats count: ${apiResponse.data.model_stats.length}`); } if (apiResponse.data.context_limits) { console.log(` Context limits count: ${apiResponse.data.context_limits.length}`); } if (apiResponse.data.chart_data) { console.log(` Chart data points: ${apiResponse.data.chart_data.length}`); } } } else if (apiResponse.error) { console.log(` ❌ Error: ${apiResponse.error}`); } // Test time range selector console.log(''); console.log('🕐 Testing time range selector...'); const timeRangeButtons = await page.$$('.time-range-btn'); console.log(` Found ${timeRangeButtons.length} time range buttons`); if (timeRangeButtons.length > 0) { // Click 7d button await page.click('[data-period="7d"]'); await new Promise(resolve => setTimeout(resolve, 1000)); const is7dActive = await page.evaluate(() => { const btn = document.querySelector('[data-period="7d"]'); return btn && btn.classList.contains('active'); }); console.log(` 7d button active: ${is7dActive ? '✅' : '❌'}`); } console.log(''); console.log('✅ Test completed successfully!'); } catch (error) { console.error(''); console.error('❌ Test failed with error:', error.message); console.error(error.stack); } finally { if (browser) { await browser.close(); } } } // Run the test testContextOverflow().catch(console.error);