项目文件夹

文件
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

371 行
14 KiB
JavaScript

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
const puppeteer = require('puppeteer');
const { getPuppeteerLaunchOptions } = require('./puppeteer_config');
const AuthHelper = require('./auth_helper');
// 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 startResearch(page, query) {
log(`🔬 Starting research: "${query}"`, 'info');
await page.goto(`${BASE_URL}/`, { waitUntil: 'domcontentloaded' });
await page.waitForSelector('#query', { timeout: 10000 });
// Use longer iterations for cancellation test
await page.evaluate(() => {
// Try to set iterations to a higher value if the field exists
const iterationsInput = document.querySelector('input[name="iterations"], #iterations');
if (iterationsInput) {
iterationsInput.value = '3';
}
});
// Set query
await page.evaluate((q) => {
document.getElementById('query').value = q;
}, query);
// Submit form and wait for potential navigation or AJAX response
const submitButton = await page.$('button[type="submit"], #submit-research, #start-research-btn');
if (!submitButton) {
log('❌ Submit button not found', 'error');
return false;
}
// Click submit and wait for navigation to research page
try {
await Promise.all([
page.waitForNavigation({ waitUntil: 'domcontentloaded', timeout: 30000 }),
submitButton.click()
]);
} catch (e) {
// Navigation might timeout but research could still have started
log(`Navigation wait: ${e.message}`, 'info');
}
// The cancellation flow needs the research/progress page; wait for either the
// URL to land or any progress UI to appear instead of a fixed sleep.
await page.waitForFunction(
() => {
const p = window.location.pathname;
return p.includes('/research/') ||
p.includes('/progress') ||
!!document.querySelector('.cancel-btn, .stop-btn, .progress-info, .progress-text');
},
{ timeout: 5000 }
).catch(() => {});
// Check if we're on a research page (URL-based check is safest during navigation)
const url = page.url();
if (url.includes('/research/') || url.includes('/progress')) {
log('✅ Research started (page navigated)', 'success');
return true;
}
return false;
}
async function testCancellation() {
const browser = await puppeteer.launch(getPuppeteerLaunchOptions());
const page = await browser.newPage();
// Set console log handler
page.on('console', msg => {
if (msg.type() === 'error' && !msg.text().includes('favicon')) {
log(`Browser console error: ${msg.text()}`, 'error');
}
});
try {
// Register user using AuthHelper for consistent behavior
const auth = new AuthHelper(page, BASE_URL);
await auth.ensureAuthenticated();
// Navigate to home page after authentication
await page.goto(BASE_URL, { waitUntil: 'domcontentloaded', timeout: 10000 });
// Test 1: Cancel during early stage
log('\n=== TEST 1: EARLY CANCELLATION ===', 'section');
await startResearch(page, 'Complex analysis of quantum computing applications');
// The cancel button only renders once the research starts; the waitForSelector
// below is the real signal we need, so no settle delay is required here.
// Find and click cancel button - use waitForSelector to ensure page is stable
let cancelButton = null;
try {
cancelButton = await page.waitForSelector('.cancel-btn, .stop-btn, button[data-action="cancel"]', { timeout: 5000 });
} catch {
log('⚠️ No cancel button found (may not be visible yet)', 'warning');
}
if (cancelButton) {
log('🛑 Clicking cancel button...', 'info');
// Handle potential confirmation dialog
page.once('dialog', async dialog => {
log(`📋 Confirmation dialog: ${dialog.message()}`, 'info');
await dialog.accept();
});
await cancelButton.click();
// Poll for the server-acknowledged cancel state instead of a fixed sleep;
// .catch(() => {}) keeps behavior identical when the page navigates away.
await page.waitForFunction(
() => {
const status = document.querySelector('.status, .research-status')?.textContent || '';
return /cancel|stop|suspend/i.test(status);
},
{ timeout: 5000 }
).catch(() => {});
// Check if research was cancelled - wrap in try-catch for navigation safety
try {
const cancelStatus = await page.evaluate(() => {
const statusElement = document.querySelector('.status, .research-status');
const progressText = document.querySelector('.progress-info, .progress-text');
return {
status: statusElement?.textContent || '',
progress: progressText?.textContent || '',
hasCancelButton: !!document.querySelector('.cancel-btn, .stop-btn')
};
});
log(`📊 Status after cancellation:`, 'info');
log(` - Status: ${cancelStatus.status}`, 'info');
log(` - Progress: ${cancelStatus.progress}`, 'info');
if (cancelStatus.status.toLowerCase().includes('cancel') ||
cancelStatus.status.toLowerCase().includes('stopped') ||
cancelStatus.status.toLowerCase().includes('suspended')) {
log('✅ Research cancelled successfully', 'success');
}
} catch {
// Page may have navigated after cancellation - that's OK
log('✅ Cancellation triggered (page state changed)', 'success');
}
} else {
log('⚠️ No cancel button found', 'warning');
}
// Test 2: Cancel during mid-stage
log('\n=== TEST 2: MID-STAGE CANCELLATION ===', 'section');
await startResearch(page, 'Comprehensive study of artificial intelligence ethics');
// Wait for research to progress
log('⏳ Waiting for research to progress...', 'info');
await delay(10000); // Wait 10 seconds
// Get progress before cancellation - wrap in try-catch
let progressBefore = { percentage: '0%', text: '' };
try {
progressBefore = await page.evaluate(() => {
const progressBar = document.querySelector('.progress-bar, [role="progressbar"]');
const progressText = document.querySelector('.progress-info, .progress-text');
return {
percentage: progressBar?.getAttribute('aria-valuenow') ||
progressBar?.style.width ||
progressText?.textContent || '0%',
text: progressText?.textContent || ''
};
});
} catch {
log('⚠️ Could not get progress (page navigating)', 'warning');
}
log(`📊 Progress before cancellation: ${progressBefore.percentage}`, 'info');
// Cancel research
let cancelButton2 = null;
try {
cancelButton2 = await page.waitForSelector('.cancel-btn, .stop-btn, button[data-action="cancel"]', { timeout: 5000 });
} catch {
log('⚠️ No cancel button found for mid-stage test', 'warning');
}
if (cancelButton2) {
page.once('dialog', async dialog => {
await dialog.accept();
});
await cancelButton2.click();
// Same pattern as the early-cancel branch: wait for the cancel state to
// surface in the UI rather than sleeping for a fixed interval.
await page.waitForFunction(
() => {
const status = document.querySelector('.status-badge, .research-status')?.textContent || '';
return /cancel|stop|suspend/i.test(status);
},
{ timeout: 5000 }
).catch(() => {});
try {
const afterCancel = await page.evaluate(() => {
const progressText = document.querySelector('.progress-info, .progress-text');
const statusBadge = document.querySelector('.status-badge, .research-status');
return {
progress: progressText?.textContent || '',
status: statusBadge?.textContent || ''
};
});
log(`📊 After mid-stage cancellation:`, 'info');
log(` - Progress: ${afterCancel.progress}`, 'info');
log(` - Status: ${afterCancel.status}`, 'info');
} catch {
log('✅ Cancellation triggered (page state changed)', 'success');
}
log('✅ Mid-stage cancellation tested', 'success');
}
// Test 3: Check cancelled research in history
log('\n=== TEST 3: CANCELLED RESEARCH IN HISTORY ===', 'section');
await page.goto(`${BASE_URL}/history`, { waitUntil: 'domcontentloaded' });
try {
await page.waitForSelector('.ldr-history-container, .ldr-history-list, .history-item', { timeout: 10000 });
const cancelledItems = await page.evaluate(() => {
const items = [];
const historyItems = document.querySelectorAll('.history-item');
historyItems.forEach(item => {
const status = item.querySelector('.status-badge, .status')?.textContent || '';
const query = item.querySelector('.query-text, .research-query')?.textContent || '';
if (status.toLowerCase().includes('cancel') ||
status.toLowerCase().includes('suspended') ||
status.toLowerCase().includes('stopped')) {
items.push({
query: query.trim(),
status: status.trim()
});
}
});
return items;
});
if (cancelledItems.length > 0) {
log(`✅ Found ${cancelledItems.length} cancelled research items in history:`, 'success');
cancelledItems.forEach(item => {
log(` - "${item.query}" - Status: ${item.status}`, 'info');
});
} else {
log('⚠️ No cancelled items found in history (research may have completed too fast)', 'warning');
}
} catch {
log('⚠️ Could not check history (page issue)', 'warning');
}
// Test 4: Attempt to resume cancelled research (if feature exists)
log('\n=== TEST 4: RESUME FUNCTIONALITY ===', 'section');
try {
const resumeButtons = await page.$$('.resume-btn, button[data-action="resume"]');
if (resumeButtons.length > 0) {
log('📊 Resume functionality available', 'info');
// Click first resume button
await resumeButtons[0].click();
// Wait for the resume to actually route to a research/progress page
// instead of using a fixed sleep.
await page.waitForFunction(
() => {
const p = window.location.pathname;
return p.includes('/research') || p.includes('/progress');
},
{ timeout: 5000 }
).catch(() => {});
// Check if research resumed
const url = page.url();
if (url.includes('/research') || url.includes('/progress')) {
log('✅ Resume functionality works', 'success');
}
} else {
log('️ No resume functionality found (might not be implemented)', 'info');
}
} catch {
log('️ Resume test skipped (page issue)', 'info');
}
// Test 5: Multiple cancellations
log('\n=== TEST 5: RAPID CANCELLATION ===', 'section');
await startResearch(page, 'Quick test for immediate cancellation');
// The waitForSelector below already waits for the cancel button to render;
// no extra settle delay is needed.
try {
const quickCancel = await page.waitForSelector('.cancel-btn, .stop-btn', { timeout: 3000 });
if (quickCancel) {
page.once('dialog', async dialog => {
await dialog.accept();
});
await quickCancel.click();
log('✅ Immediate cancellation tested', 'success');
}
} catch {
log('⚠️ Quick cancel button not found (research may have completed)', 'warning');
}
log('\n✅ Research cancellation test completed successfully!', 'success');
} catch (error) {
log(`\n❌ Test failed: ${error.message}`, 'error');
throw error;
} finally {
await browser.close();
}
}
// Run the test
testCancellation().catch(error => {
console.error('Test execution failed:', error);
process.exit(1);
});