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
270 lines
10 KiB
JavaScript
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);
|
|
});
|