项目文件夹

文件
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

357 行
14 KiB
JavaScript

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
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);
});