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
266 lines
9.8 KiB
JavaScript
266 lines
9.8 KiB
JavaScript
/**
|
|
* Error Recovery UI Test
|
|
* Tests how the UI handles various error conditions gracefully
|
|
* CI-compatible: Works in both local and CI environments
|
|
*/
|
|
|
|
const puppeteer = require('puppeteer');
|
|
const AuthHelper = require('./auth_helper');
|
|
const { getPuppeteerLaunchOptions } = require('./puppeteer_config');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const BASE_URL = 'http://127.0.0.1:5000';
|
|
|
|
async function testErrorRecovery() {
|
|
const isCI = !!process.env.CI;
|
|
console.log(`🧪 Running error recovery test (CI mode: ${isCI})`);
|
|
|
|
// Create screenshots directory
|
|
const screenshotsDir = path.join(__dirname, 'screenshots');
|
|
if (!fs.existsSync(screenshotsDir)) {
|
|
fs.mkdirSync(screenshotsDir, { recursive: true });
|
|
}
|
|
|
|
const browser = await puppeteer.launch(getPuppeteerLaunchOptions());
|
|
const page = await browser.newPage();
|
|
const auth = new AuthHelper(page, BASE_URL);
|
|
|
|
// Set longer timeout for CI
|
|
const timeout = isCI ? 60000 : 30000;
|
|
page.setDefaultTimeout(timeout);
|
|
page.setDefaultNavigationTimeout(timeout);
|
|
|
|
let testsPassed = 0;
|
|
let testsFailed = 0;
|
|
|
|
// Track console errors
|
|
const consoleErrors = [];
|
|
page.on('console', msg => {
|
|
if (msg.type() === 'error') {
|
|
consoleErrors.push(msg.text());
|
|
}
|
|
});
|
|
|
|
try {
|
|
// Setup: Authenticate
|
|
console.log('🔐 Authenticating...');
|
|
await auth.ensureAuthenticatedWithTimeout();
|
|
console.log('✅ Authentication successful\n');
|
|
|
|
// Test 1: Invalid research query (empty)
|
|
console.log('📝 Test 1: Empty query validation');
|
|
try {
|
|
await page.goto(`${BASE_URL}/`, { waitUntil: 'domcontentloaded' });
|
|
|
|
// Try to submit empty query
|
|
const submitButton = await page.$('button[type="submit"]');
|
|
if (submitButton) {
|
|
await submitButton.click();
|
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
|
|
// Check that we're still on the same page (form validation prevented submission)
|
|
const currentUrl = page.url();
|
|
if (currentUrl === `${BASE_URL}/` || currentUrl.includes('/auth/')) {
|
|
console.log('✅ Empty query was properly rejected');
|
|
testsPassed++;
|
|
} else {
|
|
console.log('⚠️ Form submitted with empty query (may be expected)');
|
|
testsPassed++; // Some forms may allow this
|
|
}
|
|
} else {
|
|
console.log('⚠️ Submit button not found');
|
|
testsPassed++;
|
|
}
|
|
} catch (error) {
|
|
console.log(`❌ Test 1 failed: ${error.message}`);
|
|
testsFailed++;
|
|
}
|
|
|
|
// Test 2: Navigate to non-existent research ID
|
|
console.log('\n📝 Test 2: Non-existent research page');
|
|
try {
|
|
await page.goto(`${BASE_URL}/research/results/nonexistent-id-99999`, {
|
|
waitUntil: 'domcontentloaded',
|
|
timeout: 30000
|
|
});
|
|
|
|
// Should show error message or redirect
|
|
const pageContent = await page.content();
|
|
const hasErrorMessage = pageContent.includes('not found') ||
|
|
pageContent.includes('error') ||
|
|
pageContent.includes('Error') ||
|
|
pageContent.includes('404') ||
|
|
page.url().includes('/auth/') ||
|
|
page.url() === `${BASE_URL}/`;
|
|
|
|
if (hasErrorMessage) {
|
|
console.log('✅ Invalid research ID handled gracefully');
|
|
testsPassed++;
|
|
} else {
|
|
console.log('⚠️ Page loaded without error indication');
|
|
testsPassed++; // May still be valid behavior
|
|
}
|
|
} catch (error) {
|
|
console.log(`✅ Invalid research ID caused expected error: ${error.message}`);
|
|
testsPassed++; // Error is expected behavior
|
|
}
|
|
|
|
// Test 3: Settings page error handling
|
|
console.log('\n📝 Test 3: Settings page loads without JS errors');
|
|
try {
|
|
const preErrorCount = consoleErrors.length;
|
|
await page.goto(`${BASE_URL}/settings/`, { waitUntil: 'domcontentloaded' });
|
|
|
|
// Wait for page to stabilize
|
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
|
|
// Check for critical JS errors (not all errors are bad)
|
|
const newErrors = consoleErrors.slice(preErrorCount);
|
|
const criticalErrors = newErrors.filter(e =>
|
|
e.includes('Uncaught') ||
|
|
e.includes('TypeError') ||
|
|
e.includes('ReferenceError')
|
|
);
|
|
|
|
if (criticalErrors.length === 0) {
|
|
console.log('✅ Settings page loads without critical JS errors');
|
|
testsPassed++;
|
|
} else {
|
|
console.log(`⚠️ Settings page has ${criticalErrors.length} critical errors`);
|
|
criticalErrors.forEach(e => console.log(` - ${e}`));
|
|
testsFailed++;
|
|
}
|
|
} catch (error) {
|
|
console.log(`❌ Test 3 failed: ${error.message}`);
|
|
testsFailed++;
|
|
}
|
|
|
|
// Test 4: Metrics page graceful degradation
|
|
console.log('\n📝 Test 4: Metrics page handles no data gracefully');
|
|
try {
|
|
await page.goto(`${BASE_URL}/metrics/`, { waitUntil: 'domcontentloaded' });
|
|
|
|
// Page should load even with no metrics data
|
|
const hasMetricsContent = await page.evaluate(() => {
|
|
const body = document.body;
|
|
return body && body.innerText.length > 100;
|
|
});
|
|
|
|
if (hasMetricsContent) {
|
|
console.log('✅ Metrics page loads gracefully');
|
|
testsPassed++;
|
|
} else {
|
|
console.log('⚠️ Metrics page appears empty but loaded');
|
|
testsPassed++; // Still valid
|
|
}
|
|
} catch (error) {
|
|
console.log(`❌ Test 4 failed: ${error.message}`);
|
|
testsFailed++;
|
|
}
|
|
|
|
// Test 5: History page with no history
|
|
console.log('\n📝 Test 5: History page handles empty history');
|
|
try {
|
|
await page.goto(`${BASE_URL}/history/`, { waitUntil: 'domcontentloaded' });
|
|
|
|
// Should show empty state or message
|
|
const pageContent = await page.content();
|
|
const hasContent = pageContent.includes('history') ||
|
|
pageContent.includes('History') ||
|
|
pageContent.includes('research') ||
|
|
pageContent.includes('No ') ||
|
|
pageContent.includes('empty');
|
|
|
|
if (hasContent) {
|
|
console.log('✅ History page handles empty state gracefully');
|
|
testsPassed++;
|
|
} else {
|
|
console.log('⚠️ History page content unclear');
|
|
testsPassed++;
|
|
}
|
|
} catch (error) {
|
|
console.log(`❌ Test 5 failed: ${error.message}`);
|
|
testsFailed++;
|
|
}
|
|
|
|
// Test 6: API error simulation
|
|
console.log('\n📝 Test 6: UI handles API errors');
|
|
try {
|
|
await page.goto(`${BASE_URL}/`, { waitUntil: 'domcontentloaded' });
|
|
|
|
// Monitor for failed requests
|
|
let failedRequests = 0;
|
|
page.on('requestfailed', () => failedRequests++);
|
|
|
|
// Try to trigger an API call (visit settings)
|
|
await page.goto(`${BASE_URL}/settings/`, { waitUntil: 'domcontentloaded' });
|
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
|
|
// Check page is still usable
|
|
const isPageUsable = await page.evaluate(() => {
|
|
return document.body !== null && document.body.innerHTML.length > 0;
|
|
});
|
|
|
|
if (isPageUsable) {
|
|
console.log(`✅ UI remains usable (${failedRequests} failed requests handled)`);
|
|
testsPassed++;
|
|
} else {
|
|
console.log('❌ UI became unusable after errors');
|
|
testsFailed++;
|
|
}
|
|
} catch (error) {
|
|
console.log(`❌ Test 6 failed: ${error.message}`);
|
|
testsFailed++;
|
|
}
|
|
|
|
// Summary
|
|
console.log('\n' + '='.repeat(50));
|
|
console.log('📊 ERROR RECOVERY TEST SUMMARY');
|
|
console.log('='.repeat(50));
|
|
console.log(`✅ Passed: ${testsPassed}`);
|
|
console.log(`❌ Failed: ${testsFailed}`);
|
|
console.log(`📊 Success Rate: ${Math.round((testsPassed / (testsPassed + testsFailed)) * 100)}%`);
|
|
|
|
if (testsFailed > 0) {
|
|
console.log('\n⚠️ Some error recovery tests failed');
|
|
if (!isCI) {
|
|
await page.screenshot({
|
|
path: path.join(screenshotsDir, 'error_recovery_final.png'),
|
|
fullPage: true
|
|
});
|
|
}
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log('\n🎉 All error recovery tests passed!');
|
|
process.exit(0);
|
|
|
|
} catch (error) {
|
|
console.error('\n❌ Test suite failed:', error.message);
|
|
|
|
// Skip diagnostic screenshot in CI — error context is in the logs
|
|
if (!isCI) {
|
|
try {
|
|
await page.screenshot({
|
|
path: path.join(screenshotsDir, `error_recovery_error_${Date.now()}.png`),
|
|
fullPage: true
|
|
});
|
|
console.log('📸 Error screenshot saved');
|
|
} catch (screenshotError) {
|
|
console.log('Could not save screenshot:', screenshotError.message);
|
|
}
|
|
}
|
|
|
|
process.exit(1);
|
|
} finally {
|
|
await browser.close();
|
|
}
|
|
}
|
|
|
|
// Run the test
|
|
testErrorRecovery().catch(error => {
|
|
console.error('Fatal error:', error);
|
|
process.exit(1);
|
|
});
|