ci: Enhance logging for troubleshooting test failures not due to broken links

pull/6256/head
Jason Stirnaman 2025-07-28 16:49:46 -05:00
parent 4ae1dec4be
commit 8a26400577
2 changed files with 320 additions and 52 deletions

View File

@ -47,7 +47,9 @@ describe('Article', () => {
cy.wrap(Promise.all(urlPromises)).then((urls) => {
subjects = urls;
cy.log(`📊 Cache Analysis: ${results.cacheStats.hitRate}% hit rate`);
cy.log(
`📊 Cache Analysis: ${results.cacheStats.hitRate}% hit rate`
);
cy.log(
`🔄 Testing ${subjects.length} pages (${results.cacheStats.cacheHits} cached)`
);
@ -60,7 +62,23 @@ describe('Article', () => {
})
.catch((error) => {
cy.log('❌ Error during incremental validation task: ' + error.message);
Cypress.fail('Incremental validation task failed. See logs for details.');
// Provide more debugging information for validation failures
cy.log('🔍 Validation Error Details:');
cy.log(` • Error Type: ${error.name || 'Unknown'}`);
cy.log(` • Error Message: ${error.message}`);
if (error.stack) {
const stackLines = error.stack.split('\n').slice(0, 3);
cy.log(` • Stack Trace: ${stackLines.join(' -> ')}`);
}
cy.log(
'💡 This error occurred during cache analysis or file validation setup'
);
cy.log(' Check that all files exist and are readable');
Cypress.fail(
`Incremental validation task failed: ${error.message}. Check logs for details.`
);
});
});
@ -170,30 +188,99 @@ describe('Article', () => {
}
// Test implementation for subjects
// Add debugging information about test subjects
it('Test Setup Validation', function () {
cy.log(`📋 Test Configuration:`);
cy.log(` • Test subjects count: ${subjects.length}`);
cy.log(` • Validation strategy: ${validationStrategy || 'Not set'}`);
if (subjects.length === 0) {
cy.log('⚠️ No test subjects found - this may indicate:');
cy.log(' • All files were cached and skipped');
cy.log(' • No files matched the test criteria');
cy.log(' • File mapping failed during setup');
// Don't fail if this is expected (cache hit scenario)
const testSubjectsData = Cypress.env('test_subjects_data');
if (testSubjectsData) {
cy.log(
' Test subjects data is available, cache optimization likely active'
);
} else {
cy.log('❌ No test subjects data available - potential setup issue');
}
} else {
cy.log(`✅ Ready to test ${subjects.length} pages`);
subjects.slice(0, 5).forEach((subject) => cy.log(`${subject}`));
if (subjects.length > 5) {
cy.log(` ... and ${subjects.length - 5} more pages`);
}
}
});
subjects.forEach((subject) => {
it(`${subject} has valid internal links`, function () {
cy.visit(`${subject}`, { timeout: 20000 });
// Add error handling for page visit failures
cy.visit(`${subject}`, { timeout: 20000 })
.then(() => {
cy.log(`✅ Successfully loaded page: ${subject}`);
})
.catch((error) => {
cy.log(`❌ Failed to load page: ${subject}`);
cy.log(` • Error: ${error.message}`);
cy.log('💡 This could indicate:');
cy.log(' • Hugo server not running or crashed');
cy.log(' • Invalid URL or routing issue');
cy.log(' • Network connectivity problems');
throw error; // Re-throw to fail the test properly
});
// Test internal links
cy.get('article, .api-content').then(($article) => {
// Find links without failing the test if none are found
const $links = $article.find('a[href^="/"]');
if ($links.length === 0) {
cy.log('No internal links found on this page');
return;
}
cy.get('article, .api-content')
.then(($article) => {
// Find links without failing the test if none are found
const $links = $article.find('a[href^="/"]');
if ($links.length === 0) {
cy.log('No internal links found on this page');
return;
}
// Now test each link
cy.wrap($links).each(($a) => {
const href = $a.attr('href');
const linkText = $a.text().trim();
testLink(href, linkText, subject);
cy.log(`🔍 Testing ${$links.length} internal links on ${subject}`);
// Now test each link
cy.wrap($links).each(($a) => {
const href = $a.attr('href');
const linkText = $a.text().trim();
try {
testLink(href, linkText, subject);
} catch (error) {
cy.log(`❌ Error testing link ${href}: ${error.message}`);
throw error; // Re-throw to fail the test
}
});
})
.catch((error) => {
cy.log(`❌ Error finding article content on ${subject}`);
cy.log(` • Error: ${error.message}`);
cy.log('💡 This could indicate:');
cy.log(' • Page structure changed (missing article/.api-content)');
cy.log(' • Page failed to render properly');
cy.log(' • JavaScript errors preventing DOM updates');
throw error;
});
});
});
it(`${subject} has valid anchor links`, function () {
cy.visit(`${subject}`);
cy.visit(`${subject}`)
.then(() => {
cy.log(`✅ Successfully loaded page for anchor testing: ${subject}`);
})
.catch((error) => {
cy.log(`❌ Failed to load page for anchor testing: ${subject}`);
cy.log(` • Error: ${error.message}`);
throw error;
});
// Define selectors for anchor links to ignore, such as behavior triggers
const ignoreLinks = ['.tabs a[href^="#"]', '.code-tabs a[href^="#"]'];
@ -208,6 +295,8 @@ describe('Article', () => {
return;
}
cy.log(`🔗 Testing ${$anchorLinks.length} anchor links on ${subject}`);
cy.wrap($anchorLinks).each(($a) => {
const href = $a.prop('href');
const linkText = $a.text().trim();
@ -250,48 +339,82 @@ describe('Article', () => {
return;
}
cy.visit(`${subject}`);
cy.visit(`${subject}`)
.then(() => {
cy.log(
`✅ Successfully loaded page for external link testing: ${subject}`
);
})
.catch((error) => {
cy.log(
`❌ Failed to load page for external link testing: ${subject}`
);
cy.log(` • Error: ${error.message}`);
throw error;
});
// Define allowed external domains to test
const allowedExternalDomains = ['github.com', 'kapa.ai'];
// Test external links
cy.get('article, .api-content').then(($article) => {
// Find links without failing the test if none are found
const $links = $article.find('a[href^="http"]');
if ($links.length === 0) {
cy.log('No external links found on this page');
return;
}
// Filter links to only include allowed domains
const $allowedLinks = $links.filter((_, el) => {
const href = el.getAttribute('href');
try {
const url = new URL(href);
return allowedExternalDomains.some(
(domain) =>
url.hostname === domain || url.hostname.endsWith(`.${domain}`)
);
} catch (e) {
return false;
cy.get('article, .api-content')
.then(($article) => {
// Find links without failing the test if none are found
const $links = $article.find('a[href^="http"]');
if ($links.length === 0) {
cy.log('No external links found on this page');
return;
}
});
if ($allowedLinks.length === 0) {
cy.log('No links to allowed external domains found on this page');
return;
}
cy.log(
`🔍 Found ${$links.length} total external links on ${subject}`
);
cy.log(
`Found ${$allowedLinks.length} links to allowed external domains to test`
);
cy.wrap($allowedLinks).each(($a) => {
const href = $a.attr('href');
const linkText = $a.text().trim();
testLink(href, linkText, subject);
// Filter links to only include allowed domains
const $allowedLinks = $links.filter((_, el) => {
const href = el.getAttribute('href');
try {
const url = new URL(href);
return allowedExternalDomains.some(
(domain) =>
url.hostname === domain || url.hostname.endsWith(`.${domain}`)
);
} catch (urlError) {
cy.log(`⚠️ Invalid URL found: ${href}`);
return false;
}
});
if ($allowedLinks.length === 0) {
cy.log('No links to allowed external domains found on this page');
cy.log(
` • Allowed domains: ${allowedExternalDomains.join(', ')}`
);
return;
}
cy.log(
`🌐 Testing ${$allowedLinks.length} links to allowed external domains`
);
cy.wrap($allowedLinks).each(($a) => {
const href = $a.attr('href');
const linkText = $a.text().trim();
try {
testLink(href, linkText, subject);
} catch (error) {
cy.log(
`❌ Error testing external link ${href}: ${error.message}`
);
throw error;
}
});
})
.catch((error) => {
cy.log(`❌ Error processing external links on ${subject}`);
cy.log(` • Error: ${error.message}`);
throw error;
});
});
});
});
});

View File

@ -393,6 +393,93 @@ async function main() {
' This usually indicates test errors unrelated to link validation.'
);
// Provide detailed failure analysis
if (results) {
console.warn('📊 Detailed Test Results:');
console.warn(` • Total Tests: ${results.totalTests || 0}`);
console.warn(` • Tests Passed: ${results.totalPassed || 0}`);
console.warn(` • Tests Failed: ${results.totalFailed || 0}`);
console.warn(` • Tests Pending: ${results.totalPending || 0}`);
console.warn(` • Tests Skipped: ${results.totalSkipped || 0}`);
console.warn(` • Duration: ${results.totalDuration || '0'}ms`);
// Show run-level information
if (results.runs && results.runs.length > 0) {
console.warn(` • Spec Files: ${results.runs.length}`);
// Show failures by spec file
const failedRuns = results.runs.filter(
(run) => run.stats?.failures > 0
);
if (failedRuns.length > 0) {
console.warn('📋 Failed Spec Files:');
failedRuns.forEach((run) => {
console.warn(
`${run.spec?.relative || run.spec?.name || 'Unknown spec'}`
);
if (run.stats) {
console.warn(` - Failures: ${run.stats.failures}`);
console.warn(` - Duration: ${run.stats.duration || 0}ms`);
}
// Show test-level failures if available
if (run.tests) {
const failedTests = run.tests.filter(
(test) => test.state === 'failed'
);
if (failedTests.length > 0) {
console.warn(` - Failed Tests:`);
failedTests.forEach((test, idx) => {
if (idx < 3) {
// Limit to first 3 failed tests per spec
console.warn(` * ${test.title || 'Unnamed test'}`);
if (test.err?.message) {
// Truncate very long error messages
const errorMsg =
test.err.message.length > 200
? test.err.message.substring(0, 200) + '...'
: test.err.message;
console.warn(` Error: ${errorMsg}`);
}
}
});
if (failedTests.length > 3) {
console.warn(
` ... and ${failedTests.length - 3} more failed tests`
);
}
}
}
});
}
}
// Check for browser/system level issues
if (results.browserName) {
console.warn(
` • Browser: ${results.browserName} ${results.browserVersion || ''}`
);
}
// Suggest common solutions
console.warn('💡 Common Causes & Solutions:');
console.warn(
' • Page load timeouts: Check if Hugo server is responding properly'
);
console.warn(
' • Network timeouts: Verify external link connectivity'
);
console.warn(
' • Browser crashes: Check for memory or resource issues'
);
console.warn(
' • Test logic errors: Review test assertions and selectors'
);
console.warn(
` • Hugo server logs: Check ${HUGO_LOG_FILE} for errors`
);
}
// We should not consider special case domains (those with expected errors) as failures
// but we'll still report other test failures
cypressFailed = true;
@ -408,9 +495,52 @@ async function main() {
}
} catch (err) {
console.error(`❌ Cypress execution error: ${err.message}`);
// Provide more detailed error information
if (err.stack) {
console.error('📋 Error Stack Trace:');
// Only show the first few lines of the stack trace to avoid overwhelming output
const stackLines = err.stack.split('\n').slice(0, 5);
stackLines.forEach((line) => console.error(` ${line}`));
if (err.stack.split('\n').length > 5) {
console.error(' ... (truncated)');
}
}
// Check if error is related to common issues
const errorMsg = err.message.toLowerCase();
if (errorMsg.includes('timeout') || errorMsg.includes('timed out')) {
console.error('🕐 Timeout detected - possible causes:');
console.error(
' • Hugo server not responding (check if it started properly)'
);
console.error(' • Network connectivity issues');
console.error(' • External links taking too long to respond');
console.error(' • Page load timeouts (heavy pages or slow rendering)');
} else if (
errorMsg.includes('connection') ||
errorMsg.includes('econnrefused')
) {
console.error('🔌 Connection issues detected:');
console.error(' • Hugo server may not be running or accessible');
console.error(` • Check if port ${HUGO_PORT} is available`);
console.error(' • Firewall or network restrictions');
} else if (errorMsg.includes('browser') || errorMsg.includes('chrome')) {
console.error('🌐 Browser issues detected:');
console.error(
' • Chrome/browser may not be available in CI environment'
);
console.error(' • Browser crashed or failed to start');
console.error(' • Insufficient memory or resources');
}
console.error(
`Check Hugo server logs at ${HUGO_LOG_FILE} for any server issues`
`📝 Hugo server logs: Check ${HUGO_LOG_FILE} for server issues`
);
console.error('💡 Additional debugging steps:');
console.error(' • Verify Hugo server started successfully');
console.error(' • Check if test URLs are accessible manually');
console.error(' • Review Cypress screenshots/videos if available');
// Still try to display broken links report if available
displayBrokenLinksReport();
@ -454,6 +584,21 @@ async function main() {
}
main().catch((err) => {
console.error(`Fatal error: ${err}`);
console.error(`💥 Fatal error during test execution: ${err.message || err}`);
if (err.stack) {
console.error('📋 Fatal Error Stack Trace:');
console.error(err.stack);
}
console.error(
'🔍 This error occurred in the main test runner flow, not within Cypress tests.'
);
console.error('💡 Common causes:');
console.error(' • File system permissions issues');
console.error(' • Missing dependencies or modules');
console.error(' • Hugo server startup failures');
console.error(' • Process management errors');
process.exit(1);
});