Files
wehub-resource-sync 7a0da7932b
Backwards Compatibility / Verify Encryption Constants (push) Waiting to run
Backwards Compatibility / PyPI Version Compatibility (push) Waiting to run
Backwards Compatibility / Database Migration Tests (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
CodeQL Advanced / Analyze (python) (push) Waiting to run
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Blocked by required conditions
Docker Tests (Consolidated) / detect-changes (push) Waiting to run
Docker Tests (Consolidated) / Build Test Image (push) Waiting to run
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Blocked by required conditions
Docker Tests (Consolidated) / Accessibility Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / LLM Unit Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / LLM Example Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / Production Image Smoke Test (push) Blocked by required conditions
Docker Tests (Consolidated) / Infrastructure Tests (push) Blocked by required conditions
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Waiting to run
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
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

270 lines
10 KiB
JavaScript

/**
* Export Dropdown UI Tests
*
* Tests for the export dropdown functionality on the results page.
* Verifies that:
* 1. PDF button is separate from dropdown
* 2. Export dropdown opens and shows all formats
* 3. All expected export options are present (Markdown, LaTeX, Quarto, ODT, RIS)
*/
const puppeteer = require('puppeteer');
const AuthHelper = require('./auth_helper');
const { getPuppeteerLaunchOptions } = require('./puppeteer_config');
const BASE_URL = 'http://127.0.0.1:5000';
const colors = {
reset: '\x1b[0m',
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 testExportDropdown() {
const browser = await puppeteer.launch(getPuppeteerLaunchOptions());
const page = await browser.newPage();
// Track test results
const results = {
passed: 0,
failed: 0,
tests: []
};
function recordTest(name, passed, details = '') {
results.tests.push({ name, passed, details });
if (passed) {
results.passed++;
log(`✅ ${name}`, 'success');
} else {
results.failed++;
log(`❌ ${name}: ${details}`, 'error');
}
}
try {
// Authenticate
const authHelper = new AuthHelper(page, BASE_URL);
log('🔐 Authenticating...', 'info');
await authHelper.ensureAuthenticated();
log('✅ Authenticated', 'success');
// Navigate to history to find a completed research
log('\n=== FINDING COMPLETED RESEARCH ===', 'section');
await page.goto(`${BASE_URL}/history`, { waitUntil: 'domcontentloaded' });
await delay(2000);
// Look for a completed research item
const completedResearch = await page.evaluate(() => {
const items = document.querySelectorAll('.ldr-history-item, [data-research-id]');
for (const item of items) {
const status = item.querySelector('.ldr-status-badge, .status-badge');
if (status && status.textContent.toLowerCase().includes('completed')) {
const link = item.querySelector('a[href*="/results/"]');
return link ? link.href : null;
}
}
// If no completed research, return the first research link
const firstLink = document.querySelector('a[href*="/results/"]');
return firstLink ? firstLink.href : null;
});
if (!completedResearch) {
log('⚠️ No completed research found, creating mock results page test', 'warning');
// We can still test the HTML structure by navigating to a fake results URL
// and checking if the export elements exist in the template
await page.goto(`${BASE_URL}/results/test-123`, { waitUntil: 'domcontentloaded' });
} else {
log(`📍 Navigating to: ${completedResearch}`, 'info');
await page.goto(completedResearch, { waitUntil: 'domcontentloaded' });
}
await delay(2000);
log('\n=== TESTING EXPORT UI STRUCTURE ===', 'section');
// Test 1: PDF button exists and is separate from dropdown
const pdfButtonExists = await page.evaluate(() => {
const pdfBtn = document.getElementById('download-pdf-btn');
if (!pdfBtn) return { exists: false };
// Check it's not inside a dropdown
const isInDropdown = pdfBtn.closest('.dropdown-menu') !== null;
return {
exists: true,
isInDropdown,
text: pdfBtn.textContent.trim(),
hasIcon: !!pdfBtn.querySelector('i.fa-file-pdf')
};
});
recordTest(
'PDF button exists and is separate from dropdown',
pdfButtonExists.exists && !pdfButtonExists.isInDropdown,
pdfButtonExists.exists ?
(pdfButtonExists.isInDropdown ? 'PDF button is inside dropdown' : '') :
'PDF button not found'
);
// Test 2: Export dropdown button exists
const exportDropdownExists = await page.evaluate(() => {
const dropdownBtn = document.getElementById('export-dropdown-btn');
if (!dropdownBtn) return { exists: false };
return {
exists: true,
text: dropdownBtn.textContent.trim(),
hasToggle: dropdownBtn.classList.contains('dropdown-toggle'),
hasIcon: !!dropdownBtn.querySelector('i.fa-download')
};
});
recordTest(
'Export dropdown button exists',
exportDropdownExists.exists && exportDropdownExists.hasToggle,
exportDropdownExists.exists ? '' : 'Export dropdown button not found'
);
// Test 3: Click dropdown and verify it opens
if (exportDropdownExists.exists) {
await page.click('#export-dropdown-btn');
await delay(500);
const dropdownOpened = await page.evaluate(() => {
const menu = document.querySelector('#export-dropdown-btn + .dropdown-menu, .dropdown-menu[aria-labelledby="export-dropdown-btn"]');
if (!menu) return { opened: false, reason: 'Menu not found' };
const isVisible = menu.classList.contains('show') ||
getComputedStyle(menu).display !== 'none';
return { opened: isVisible, reason: isVisible ? '' : 'Menu not visible' };
});
recordTest(
'Export dropdown opens on click',
dropdownOpened.opened,
dropdownOpened.reason
);
}
// Test 4: Check all expected export options exist
const expectedFormats = [
{ id: 'export-markdown-btn', name: 'Markdown', icon: 'fa-file-alt' },
{ id: 'export-latex-btn', name: 'LaTeX', icon: 'fa-file-code' },
{ id: 'export-quarto-btn', name: 'Quarto', icon: 'fa-file-code' },
{ id: 'export-odt-btn', name: 'ODT (LibreOffice)', icon: 'fa-file-word' },
{ id: 'export-ris-btn', name: 'RIS (Zotero)', icon: 'fa-file-export' }
];
for (const format of expectedFormats) {
const formatExists = await page.evaluate((formatId) => {
const btn = document.getElementById(formatId);
if (!btn) return { exists: false };
return {
exists: true,
text: btn.textContent.trim(),
isInDropdown: btn.closest('.dropdown-menu') !== null,
isClickable: btn.tagName === 'A' || btn.tagName === 'BUTTON'
};
}, format.id);
recordTest(
`${format.name} export option exists in dropdown`,
formatExists.exists && formatExists.isInDropdown,
formatExists.exists ?
(formatExists.isInDropdown ? '' : 'Not in dropdown menu') :
`${format.id} not found`
);
}
// Test 5: Verify dropdown has proper structure (dividers)
const dropdownStructure = await page.evaluate(() => {
const menu = document.querySelector('.dropdown-menu[aria-labelledby="export-dropdown-btn"]');
if (!menu) return { valid: false, reason: 'Menu not found' };
const dividers = menu.querySelectorAll('.dropdown-divider, hr.dropdown-divider');
const items = menu.querySelectorAll('.dropdown-item');
return {
valid: true,
itemCount: items.length,
dividerCount: dividers.length,
hasProperStructure: items.length >= 5 && dividers.length >= 2
};
});
recordTest(
'Dropdown has proper structure with dividers',
dropdownStructure.valid && dropdownStructure.hasProperStructure,
dropdownStructure.valid ?
`${dropdownStructure.itemCount} items, ${dropdownStructure.dividerCount} dividers` :
dropdownStructure.reason
);
// Test 6: Close dropdown by clicking elsewhere
await page.click('body');
await delay(300);
const dropdownClosed = await page.evaluate(() => {
const menu = document.querySelector('.dropdown-menu[aria-labelledby="export-dropdown-btn"]');
if (!menu) return { closed: true };
const isHidden = !menu.classList.contains('show') &&
getComputedStyle(menu).display === 'none';
return { closed: isHidden };
});
recordTest(
'Dropdown closes when clicking elsewhere',
dropdownClosed.closed,
dropdownClosed.closed ? '' : 'Dropdown still visible'
);
// Print summary
log('\n=== TEST SUMMARY ===', 'section');
log(`Total: ${results.passed + results.failed} tests`, 'info');
log(`Passed: ${results.passed}`, 'success');
if (results.failed > 0) {
log(`Failed: ${results.failed}`, 'error');
}
if (results.failed > 0) {
throw new Error(`${results.failed} test(s) failed`);
}
log('\n✅ All export dropdown tests passed!', 'success');
} catch (error) {
log(`\n❌ Test failed: ${error.message}`, 'error');
throw error;
} finally {
await browser.close();
}
}
// Run tests
testExportDropdown().catch(error => {
console.error('Test execution failed:', error);
process.exit(1);
});