ci: Use Cypress' skip to skip cached links instead of trying to modify the subjects array (which won't work after the tests are registered and running)

pull/6263/head
Jason Stirnaman 2025-07-29 08:30:33 -05:00
parent b3cfd0d52e
commit ad92a57ef7
1 changed files with 181 additions and 257 deletions

View File

@ -7,6 +7,7 @@ describe('Article', () => {
.filter((s) => s.trim() !== '') .filter((s) => s.trim() !== '')
: []; : [];
let validationStrategy = null; let validationStrategy = null;
let shouldSkipAllTests = false; // Flag to skip tests when all files are cached
// Always use HEAD for downloads to avoid timeouts // Always use HEAD for downloads to avoid timeouts
const useHeadForDownloads = true; const useHeadForDownloads = true;
@ -15,97 +16,6 @@ describe('Article', () => {
before(() => { before(() => {
// Initialize the broken links report // Initialize the broken links report
cy.task('initializeBrokenLinksReport'); cy.task('initializeBrokenLinksReport');
// Get source file paths for incremental validation
const testSubjectsData = Cypress.env('test_subjects_data');
let sourceFilePaths = subjects; // fallback to subjects if no data available
if (testSubjectsData) {
try {
const urlToSourceData = JSON.parse(testSubjectsData);
// Extract source file paths from the structured data
sourceFilePaths = urlToSourceData.map((item) => item.source);
} catch (e) {
console.warn(
'Could not parse test_subjects_data, using subjects as fallback'
);
}
}
// Only run incremental validation if we have source file paths
if (sourceFilePaths.length > 0) {
cy.log('🔄 Running incremental validation analysis...');
// Run incremental validation with proper error handling
cy.task('runIncrementalValidation', sourceFilePaths)
.then((results) => {
validationStrategy = results.validationStrategy;
// Save cache statistics and validation strategy for reporting
cy.task('saveCacheStatistics', results.cacheStats);
cy.task('saveValidationStrategy', validationStrategy);
// Update subjects to only test files that need validation
if (results.filesToValidate.length > 0) {
// Convert file paths to URLs using shared utility via Cypress task
const urlPromises = results.filesToValidate.map((file) =>
cy.task('filePathToUrl', file.filePath)
);
cy.wrap(Promise.all(urlPromises)).then((urls) => {
subjects = urls;
cy.log(
`📊 Cache Analysis: ${results.cacheStats.hitRate}% hit rate`
);
cy.log(
`🔄 Testing ${subjects.length} pages (${results.cacheStats.cacheHits} cached)`
);
});
} else {
// All files are cached, no validation needed
subjects = [];
cy.log('✨ All files cached - skipping validation');
cy.log(
`📊 Cache hit rate: ${results.cacheStats.hitRate}% (${results.cacheStats.cacheHits}/${results.cacheStats.totalFiles} files cached)`
);
}
})
.catch((error) => {
cy.log('❌ Incremental validation failed: ' + error.message);
cy.log(
'🔄 Falling back to test all provided subjects without cache optimization'
);
// Set fallback mode but don't fail the test
validationStrategy = {
fallback: true,
error: error.message,
unchanged: [],
changed: sourceFilePaths.map((filePath) => ({
filePath,
error: 'fallback',
})),
total: sourceFilePaths.length,
};
cy.log(`📋 Testing ${subjects.length} pages in fallback mode`);
});
} else {
cy.log('⚠️ No source file paths available, using all provided subjects');
// Set a simple validation strategy when no source data is available
validationStrategy = {
noSourceData: true,
unchanged: [],
changed: [],
total: subjects.length,
};
cy.log(
`📋 Testing ${subjects.length} pages without incremental validation`
);
}
}); });
// Helper function to identify download links // Helper function to identify download links
@ -216,64 +126,103 @@ describe('Article', () => {
// Test implementation for subjects // Test implementation for subjects
// Add debugging information about test subjects // Add debugging information about test subjects
it('Test Setup Validation', function () { it('Test Setup Validation', function () {
cy.log(`📋 Test Configuration:`); cy.log(`📋 Initial Test Configuration:`);
cy.log(` • Test subjects count: ${subjects.length}`); cy.log(` • Initial test subjects count: ${subjects.length}`);
cy.log(` • Validation strategy: ${validationStrategy || 'Not set'}`);
// Check if we're in fallback mode due to cache system issues // Get source file paths for incremental validation
if (validationStrategy && validationStrategy.fallback) { const testSubjectsData = Cypress.env('test_subjects_data');
cy.log('⚠️ Running in fallback mode due to cache system error'); let sourceFilePaths = subjects; // fallback to subjects if no data available
cy.log(` • Error: ${validationStrategy.error}`);
cy.log(' • All files will be tested without cache optimization');
// In fallback mode, if we have no subjects, that might be expected if (testSubjectsData) {
if (subjects.length === 0) { try {
cy.log(' No subjects to test in fallback mode'); const urlToSourceData = JSON.parse(testSubjectsData);
// Extract source file paths from the structured data
sourceFilePaths = urlToSourceData.map((item) => item.source);
cy.log(` • Source files to analyze: ${sourceFilePaths.length}`);
} catch (e) {
cy.log( cy.log(
' This indicates no test subjects were provided to the runner' '⚠️ Could not parse test_subjects_data, using subjects as fallback'
); );
} else { sourceFilePaths = subjects;
cy.log(`✅ Testing ${subjects.length} subjects in fallback mode`);
} }
} else if (subjects.length === 0) { }
cy.log(' No test subjects to validate - analyzing reason:');
// Check if this is due to cache optimization // Only run incremental validation if we have source file paths
const testSubjectsData = Cypress.env('test_subjects_data'); if (sourceFilePaths.length > 0) {
if ( cy.log('🔄 Running incremental validation analysis...');
testSubjectsData && cy.log(
testSubjectsData !== '[]' && ` • Analyzing ${sourceFilePaths.length} files: ${sourceFilePaths.join(', ')}`
testSubjectsData !== '' );
) {
cy.log('✅ Cache optimization is active - all files were cached'); // Run incremental validation with proper error handling
try { cy.task('runIncrementalValidation', sourceFilePaths).then((results) => {
const urlToSourceData = JSON.parse(testSubjectsData); if (!results) {
cy.log(`📊 Files processed: ${urlToSourceData.length}`); cy.log('⚠️ No results returned from incremental validation');
cy.log( cy.log(
'💡 This means all links have been validated recently and are cached' '🔄 Falling back to test all provided subjects without cache optimization'
);
return;
}
// Check if results have expected structure
if (!results.validationStrategy || !results.cacheStats) {
cy.log('⚠️ Incremental validation results missing expected fields');
cy.log(` • Results: ${JSON.stringify(results)}`);
cy.log(
'🔄 Falling back to test all provided subjects without cache optimization'
);
return;
}
validationStrategy = results.validationStrategy;
// Save cache statistics and validation strategy for reporting
cy.task('saveCacheStatistics', results.cacheStats);
cy.task('saveValidationStrategy', validationStrategy);
// Update subjects to only test files that need validation
if (results.filesToValidate && results.filesToValidate.length > 0) {
// Convert file paths to URLs using shared utility via Cypress task
const urlPromises = results.filesToValidate.map((file) =>
cy.task('filePathToUrl', file.filePath)
);
cy.wrap(Promise.all(urlPromises)).then((urls) => {
subjects = urls;
cy.log(
`📊 Cache Analysis: ${results.cacheStats.hitRate}% hit rate`
);
cy.log(
`🔄 Testing ${subjects.length} pages (${results.cacheStats.cacheHits} cached)`
);
cy.log('✅ Incremental validation completed - ready to test');
});
} else {
// All files are cached, no validation needed
shouldSkipAllTests = true; // Set flag to skip all tests
cy.log('✨ All files cached - will skip all validation tests');
cy.log(
`📊 Cache hit rate: ${results.cacheStats.hitRate}% (${results.cacheStats.cacheHits}/${results.cacheStats.totalFiles} files cached)`
); );
cy.log('🎯 No new validation needed - this is the expected outcome'); cy.log('🎯 No new validation needed - this is the expected outcome');
} catch (e) { cy.log('⏭️ All link validation tests will be skipped');
cy.log(
'✅ Cache optimization active (could not parse detailed data)'
);
} }
} else { });
cy.log('⚠️ No test subjects data available');
cy.log(' Possible reasons:');
cy.log(' • No files were provided to test');
cy.log(' • File mapping failed during setup');
cy.log(' • No files matched the test criteria');
cy.log(
' This is not necessarily an error - may be expected for some runs'
);
}
} else { } else {
cy.log(`✅ Ready to test ${subjects.length} pages`); cy.log('⚠️ No source file paths available, using all provided subjects');
subjects.slice(0, 5).forEach((subject) => cy.log(`${subject}`));
if (subjects.length > 5) { // Set a simple validation strategy when no source data is available
cy.log(` ... and ${subjects.length - 5} more pages`); validationStrategy = {
} noSourceData: true,
unchanged: [],
changed: [],
total: subjects.length,
};
cy.log(
`📋 Testing ${subjects.length} pages without incremental validation`
);
} }
// Check for truly problematic scenarios // Check for truly problematic scenarios
@ -303,67 +252,55 @@ describe('Article', () => {
subjects.forEach((subject) => { subjects.forEach((subject) => {
it(`${subject} has valid internal links`, function () { it(`${subject} has valid internal links`, function () {
// Skip test if all files are cached
if (shouldSkipAllTests) {
cy.log('✅ All files cached - skipping internal links test');
this.skip();
return;
}
// Add error handling for page visit failures // Add error handling for page visit failures
cy.visit(`${subject}`, { timeout: 20000 }) cy.visit(`${subject}`, { timeout: 20000 }).then(() => {
.then(() => { cy.log(`✅ Successfully loaded page: ${subject}`);
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 // Test internal links
cy.get('article, .api-content') cy.get('article, .api-content').then(($article) => {
.then(($article) => { // Find links without failing the test if none are found
// Find links without failing the test if none are found const $links = $article.find('a[href^="/"]');
const $links = $article.find('a[href^="/"]'); if ($links.length === 0) {
if ($links.length === 0) { cy.log('No internal links found on this page');
cy.log('No internal links found on this page'); return;
return; }
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
} }
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 () { it(`${subject} has valid anchor links`, function () {
cy.visit(`${subject}`) // Skip test if all files are cached
.then(() => { if (shouldSkipAllTests) {
cy.log(`✅ Successfully loaded page for anchor testing: ${subject}`); cy.log('✅ All files cached - skipping anchor links test');
}) this.skip();
.catch((error) => { return;
cy.log(`❌ Failed to load page for anchor testing: ${subject}`); }
cy.log(` • Error: ${error.message}`);
throw error; cy.visit(`${subject}`).then(() => {
}); cy.log(`✅ Successfully loaded page for anchor testing: ${subject}`);
});
// Define selectors for anchor links to ignore, such as behavior triggers // Define selectors for anchor links to ignore, such as behavior triggers
const ignoreLinks = ['.tabs a[href^="#"]', '.code-tabs a[href^="#"]']; const ignoreLinks = ['.tabs a[href^="#"]', '.code-tabs a[href^="#"]'];
@ -414,6 +351,13 @@ describe('Article', () => {
}); });
it(`${subject} has valid external links`, function () { it(`${subject} has valid external links`, function () {
// Skip test if all files are cached
if (shouldSkipAllTests) {
cy.log('✅ All files cached - skipping external links test');
this.skip();
return;
}
// Check if we should skip external links entirely // Check if we should skip external links entirely
if (Cypress.env('skipExternalLinks') === true) { if (Cypress.env('skipExternalLinks') === true) {
cy.log( cy.log(
@ -422,82 +366,62 @@ describe('Article', () => {
return; return;
} }
cy.visit(`${subject}`) cy.visit(`${subject}`).then(() => {
.then(() => { cy.log(
cy.log( `✅ Successfully loaded page for external link testing: ${subject}`
`✅ 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 // Define allowed external domains to test
const allowedExternalDomains = ['github.com', 'kapa.ai']; const allowedExternalDomains = ['github.com', 'kapa.ai'];
// Test external links // Test external links
cy.get('article, .api-content') cy.get('article, .api-content').then(($article) => {
.then(($article) => { // Find links without failing the test if none are found
// Find links without failing the test if none are found const $links = $article.find('a[href^="http"]');
const $links = $article.find('a[href^="http"]'); if ($links.length === 0) {
if ($links.length === 0) { cy.log('No external links found on this page');
cy.log('No external links found on this page'); return;
return; }
}
cy.log( cy.log(`🔍 Found ${$links.length} total external links on ${subject}`);
`🔍 Found ${$links.length} total external links on ${subject}`
);
// Filter links to only include allowed domains // Filter links to only include allowed domains
const $allowedLinks = $links.filter((_, el) => { const $allowedLinks = $links.filter((_, el) => {
const href = el.getAttribute('href'); const href = el.getAttribute('href');
try { try {
const url = new URL(href); const url = new URL(href);
return allowedExternalDomains.some( return allowedExternalDomains.some(
(domain) => (domain) =>
url.hostname === domain || url.hostname.endsWith(`.${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; } catch (urlError) {
cy.log(`⚠️ Invalid URL found: ${href}`);
return false;
} }
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;
}); });
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;
}
});
});
}); });
}); });
}); });