learningcircuit--local-deep-research
7a0da7932b
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
357 行
14 KiB
JavaScript
357 行
14 KiB
JavaScript
const puppeteer = require('puppeteer');
|
||
const AuthHelper = require('./auth_helper');
|
||
const { getPuppeteerLaunchOptions } = require('./puppeteer_config');
|
||
|
||
// 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.
|
||
// Test configuration
|
||
const BASE_URL = 'http://127.0.0.1:5000';
|
||
|
||
// Colors for console output
|
||
const colors = {
|
||
reset: '\x1b[0m',
|
||
bright: '\x1b[1m',
|
||
green: '\x1b[32m',
|
||
red: '\x1b[31m',
|
||
yellow: '\x1b[33m',
|
||
blue: '\x1b[34m',
|
||
cyan: '\x1b[36m'
|
||
};
|
||
|
||
function log(message, type = 'info') {
|
||
const timestamp = new Date().toISOString().split('T')[1].split('.')[0];
|
||
const typeColors = {
|
||
'info': colors.cyan,
|
||
'success': colors.green,
|
||
'error': colors.red,
|
||
'warning': colors.yellow,
|
||
'section': colors.blue
|
||
};
|
||
const color = typeColors[type] || colors.reset;
|
||
console.log(`${color}[${timestamp}] ${message}${colors.reset}`);
|
||
}
|
||
|
||
async function delay(ms) {
|
||
return new Promise(resolve => setTimeout(resolve, ms));
|
||
}
|
||
|
||
async function createResearch(page, query, waitForCompletion = false) {
|
||
log(`🔬 Creating research: "${query}"`, 'info');
|
||
|
||
// Navigate to home/research page
|
||
await page.goto(BASE_URL, { waitUntil: 'domcontentloaded' });
|
||
|
||
// Wait for the query input
|
||
await page.waitForSelector('#query', { timeout: 10000 });
|
||
|
||
// Clear and type query
|
||
await page.click('#query', { clickCount: 3 });
|
||
await page.type('#query', query);
|
||
|
||
// Submit the research
|
||
const submitButton = await page.$('button[type="submit"]');
|
||
if (submitButton) {
|
||
await submitButton.click();
|
||
} else {
|
||
// Try pressing Enter
|
||
await page.keyboard.press('Enter');
|
||
}
|
||
|
||
// Wait for navigation or status update
|
||
await delay(3000);
|
||
|
||
if (waitForCompletion) {
|
||
log('⏳ Waiting for research to complete...', 'info');
|
||
|
||
// Wait for completion (max 30 seconds for test)
|
||
const startTime = Date.now();
|
||
const maxWaitTime = 30000;
|
||
|
||
while (Date.now() - startTime < maxWaitTime) {
|
||
try {
|
||
// Check if we're on results page
|
||
const url = page.url();
|
||
if (url.includes('/results/') || url.includes('/research/')) {
|
||
// Check for completion indicators
|
||
const completed = await page.evaluate(() => {
|
||
const statusEl = document.querySelector('.status-badge, .research-status');
|
||
if (statusEl && statusEl.textContent.toLowerCase().includes('completed')) {
|
||
return true;
|
||
}
|
||
const progressEl = document.querySelector('.progress-bar, .progress');
|
||
if (progressEl && progressEl.textContent.includes('100%')) {
|
||
return true;
|
||
}
|
||
return false;
|
||
});
|
||
|
||
if (completed) {
|
||
log('✅ Research completed', 'success');
|
||
break;
|
||
}
|
||
}
|
||
} catch {
|
||
// Continue waiting
|
||
}
|
||
|
||
await delay(1000);
|
||
}
|
||
}
|
||
}
|
||
|
||
async function testHistoryPage() {
|
||
const browser = await puppeteer.launch(getPuppeteerLaunchOptions());
|
||
|
||
let page = await browser.newPage();
|
||
|
||
// Set console log handler
|
||
page.on('console', msg => {
|
||
if (msg.type() === 'error') {
|
||
log(`Browser console error: ${msg.text()}`, 'error');
|
||
}
|
||
});
|
||
|
||
try {
|
||
// Create auth helper and register user
|
||
const authHelper = new AuthHelper(page, BASE_URL);
|
||
|
||
log('🔐 Authenticating...', 'info');
|
||
await authHelper.ensureAuthenticatedWithTimeout();
|
||
page = authHelper.page;
|
||
|
||
// Verify we're logged in
|
||
const isLoggedIn = await authHelper.isLoggedIn();
|
||
if (!isLoggedIn) {
|
||
throw new Error('Failed to login after registration');
|
||
}
|
||
log('✅ User registered and logged in', 'success');
|
||
|
||
// Create some research items for history
|
||
log('\n=== CREATING TEST RESEARCH ITEMS ===', 'section');
|
||
const queries = [
|
||
'What is machine learning?',
|
||
'History of artificial intelligence'
|
||
];
|
||
|
||
for (const query of queries) {
|
||
await createResearch(page, query, false); // Don't wait for full completion
|
||
await delay(2000); // Wait between researches
|
||
}
|
||
|
||
// Navigate to history page
|
||
log('\n=== TESTING HISTORY PAGE ===', 'section');
|
||
await page.goto(`${BASE_URL}/history`, { waitUntil: 'domcontentloaded' });
|
||
|
||
// Wait for history content to load
|
||
await page.waitForSelector('body', { timeout: 10000 });
|
||
|
||
// Check if we have history items or appropriate message
|
||
const pageContent = await page.evaluate(() => {
|
||
const historyItems = document.querySelectorAll('.history-item, .research-item, .list-group-item, [data-research-id]');
|
||
const emptyMessage = document.querySelector('.empty-state, .no-history, .alert-info');
|
||
|
||
return {
|
||
title: document.title,
|
||
hasHistoryContainer: !!document.querySelector('.ldr-history-container, .ldr-history-list, .research-history'),
|
||
itemCount: historyItems.length,
|
||
hasEmptyMessage: !!emptyMessage,
|
||
emptyMessageText: emptyMessage?.textContent || '',
|
||
items: Array.from(historyItems).slice(0, 5).map(item => ({
|
||
text: item.textContent?.trim().substring(0, 100) || '',
|
||
hasLink: !!item.querySelector('a'),
|
||
hasDeleteButton: !!item.querySelector('.delete-btn, .btn-danger, [onclick*="delete"]')
|
||
}))
|
||
};
|
||
});
|
||
|
||
log(`📊 History page content:`, 'info');
|
||
log(` - Title: ${pageContent.title}`, 'info');
|
||
log(` - Has history container: ${pageContent.hasHistoryContainer}`, 'info');
|
||
log(` - Total items found: ${pageContent.itemCount}`, 'info');
|
||
log(` - Has empty message: ${pageContent.hasEmptyMessage}`, 'info');
|
||
|
||
if (pageContent.itemCount > 0) {
|
||
log('✅ History items found on page', 'success');
|
||
|
||
// Log first few items
|
||
pageContent.items.forEach((item, index) => {
|
||
log(` - Item ${index + 1}: ${item.text.substring(0, 50)}...`, 'info');
|
||
});
|
||
} else if (pageContent.hasEmptyMessage) {
|
||
log(`ℹ️ Empty history message: ${pageContent.emptyMessageText.substring(0, 100)}`, 'info');
|
||
log('⚠️ No history items found (research might not have saved)', 'warning');
|
||
} else {
|
||
log('⚠️ No history items or empty message found', 'warning');
|
||
}
|
||
|
||
// Test search/filter if available
|
||
const searchInput = await page.$('#search-input, input[type="search"], .search-input');
|
||
if (searchInput && pageContent.itemCount > 0) {
|
||
log('\n=== TESTING SEARCH/FILTER ===', 'section');
|
||
await searchInput.type('machine learning');
|
||
await delay(500); // Wait for filter to apply
|
||
|
||
const filteredCount = await page.evaluate(() => {
|
||
const visibleItems = document.querySelectorAll('.history-item:not([style*="display: none"]), .research-item:not(.d-none)');
|
||
return visibleItems.length;
|
||
});
|
||
|
||
log(`📊 Filtered results: ${filteredCount} items`, 'info');
|
||
|
||
if (filteredCount >= 0) {
|
||
log('✅ Search field is present', 'success');
|
||
}
|
||
}
|
||
|
||
// Test navigation elements
|
||
const navigationElements = await page.evaluate(() => {
|
||
return {
|
||
hasPagination: !!document.querySelector('.pagination, .page-link'),
|
||
hasBackButton: !!document.querySelector('a[href="/"], .back-btn'),
|
||
hasNewResearchButton: !!document.querySelector('a[href="/research"], .new-research-btn')
|
||
};
|
||
});
|
||
|
||
log('\n=== NAVIGATION ELEMENTS ===', 'section');
|
||
log(` - Has pagination: ${navigationElements.hasPagination}`, 'info');
|
||
log(` - Has back/home button: ${navigationElements.hasBackButton}`, 'info');
|
||
log(` - Has new research button: ${navigationElements.hasNewResearchButton}`, 'info');
|
||
|
||
// Test re-run button on history items
|
||
log('\n=== TESTING RE-RUN BUTTON ===', 'section');
|
||
const rerunButtonInfo = await page.evaluate(() => {
|
||
const historyItems = document.querySelectorAll('.ldr-history-item, [data-research-id]');
|
||
const rerunButtons = document.querySelectorAll('.ldr-rerun-btn');
|
||
|
||
// Helper to check true visibility
|
||
function isVisible(el) {
|
||
if (!el) return false;
|
||
const style = getComputedStyle(el);
|
||
const rect = el.getBoundingClientRect();
|
||
return style.display !== 'none' &&
|
||
style.visibility !== 'hidden' &&
|
||
parseFloat(style.opacity) > 0 &&
|
||
rect.width > 0 && rect.height > 0;
|
||
}
|
||
|
||
// Check each item for re-run button
|
||
const itemDetails = [];
|
||
historyItems.forEach(item => {
|
||
const status = item.querySelector('.ldr-status-badge, .status-badge');
|
||
const rerunBtn = item.querySelector('.ldr-rerun-btn');
|
||
const statusText = status?.textContent?.trim().toLowerCase() || 'unknown';
|
||
|
||
itemDetails.push({
|
||
status: statusText,
|
||
hasRerunBtn: !!rerunBtn,
|
||
isVisible: isVisible(rerunBtn),
|
||
// Re-run should only show on completed items
|
||
isCorrect: statusText === 'completed' ? (!!rerunBtn && isVisible(rerunBtn)) : !rerunBtn
|
||
});
|
||
});
|
||
|
||
// Check button styling if any exist
|
||
let buttonStyle = null;
|
||
if (rerunButtons.length > 0) {
|
||
const btn = rerunButtons[0];
|
||
const style = getComputedStyle(btn);
|
||
buttonStyle = {
|
||
hasIcon: !!btn.querySelector('i, .fa-redo, [class*="fa-"]'),
|
||
cursor: style.cursor,
|
||
hasTitle: !!btn.title
|
||
};
|
||
}
|
||
|
||
return {
|
||
totalItems: historyItems.length,
|
||
totalRerunButtons: rerunButtons.length,
|
||
items: itemDetails,
|
||
allCorrect: itemDetails.every(i => i.isCorrect),
|
||
buttonStyle
|
||
};
|
||
});
|
||
|
||
log(` - Total history items: ${rerunButtonInfo.totalItems}`, 'info');
|
||
log(` - Re-run buttons found: ${rerunButtonInfo.totalRerunButtons}`, 'info');
|
||
|
||
if (rerunButtonInfo.totalItems > 0) {
|
||
rerunButtonInfo.items.forEach((item, idx) => {
|
||
const mark = item.isCorrect ? '✓' : '✗';
|
||
log(` - Item ${idx + 1}: status=${item.status}, hasBtn=${item.hasRerunBtn}, visible=${item.isVisible} ${mark}`, 'info');
|
||
});
|
||
|
||
if (rerunButtonInfo.allCorrect) {
|
||
log('✅ Re-run buttons correctly shown only on completed items', 'success');
|
||
} else {
|
||
log('⚠️ Re-run button visibility issue detected', 'warning');
|
||
}
|
||
|
||
if (rerunButtonInfo.buttonStyle) {
|
||
log(` - Button has icon: ${rerunButtonInfo.buttonStyle.hasIcon}`, 'info');
|
||
log(` - Button cursor: ${rerunButtonInfo.buttonStyle.cursor}`, 'info');
|
||
if (rerunButtonInfo.buttonStyle.cursor === 'pointer') {
|
||
log('✅ Re-run button has pointer cursor', 'success');
|
||
}
|
||
}
|
||
} else {
|
||
log('ℹ️ No history items to test re-run button on', 'info');
|
||
}
|
||
|
||
// Test re-run button click if available
|
||
if (rerunButtonInfo.totalRerunButtons > 0) {
|
||
log('\n=== TESTING RE-RUN BUTTON CLICK ===', 'section');
|
||
|
||
// Get query before clicking
|
||
const queryBefore = await page.evaluate(() => {
|
||
const item = document.querySelector('.ldr-history-item, [data-research-id]');
|
||
const queryEl = item?.querySelector('.ldr-history-item-query, .query-text, h5, h6');
|
||
return queryEl?.textContent?.trim() || '';
|
||
});
|
||
|
||
log(` - Query to re-run: "${queryBefore.substring(0, 50)}..."`, 'info');
|
||
|
||
// Click the re-run button
|
||
await page.click('.ldr-rerun-btn');
|
||
await delay(2000);
|
||
|
||
// Check if we navigated and form is populated
|
||
const afterClick = await page.evaluate(() => {
|
||
const queryEl = document.getElementById('query');
|
||
return {
|
||
url: window.location.pathname,
|
||
queryValue: queryEl?.value || '',
|
||
hasNotification: !!document.querySelector('.alert-info')
|
||
};
|
||
});
|
||
|
||
log(` - Navigated to: ${afterClick.url}`, 'info');
|
||
log(` - Form query: "${afterClick.queryValue.substring(0, 50)}..."`, 'info');
|
||
log(` - Re-run notification: ${afterClick.hasNotification}`, 'info');
|
||
|
||
if (afterClick.url === '/' && afterClick.queryValue.length > 0) {
|
||
log('✅ Re-run button works correctly', 'success');
|
||
} else {
|
||
log('⚠️ Re-run navigation or form population issue', 'warning');
|
||
}
|
||
}
|
||
|
||
log('\n✅ History page test completed successfully!', 'success');
|
||
|
||
} catch (error) {
|
||
log(`\n❌ Test failed: ${error.message}`, 'error');
|
||
|
||
|
||
throw error;
|
||
} finally {
|
||
await browser.close();
|
||
}
|
||
}
|
||
|
||
// Run the test
|
||
testHistoryPage().catch(error => {
|
||
console.error('Test execution failed:', error);
|
||
process.exit(1);
|
||
});
|