Files
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 lines
14 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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);
});