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() !== '')
: [];
let validationStrategy = null;
let shouldSkipAllTests = false; // Flag to skip tests when all files are cached
// Always use HEAD for downloads to avoid timeouts
const useHeadForDownloads = true;
@ -15,97 +16,6 @@ describe('Article', () => {
before(() => {
// Initialize the broken links report
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
@ -216,64 +126,103 @@ 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'}`);
cy.log(`📋 Initial Test Configuration:`);
cy.log(` • Initial test subjects count: ${subjects.length}`);
// Check if we're in fallback mode due to cache system issues
if (validationStrategy && validationStrategy.fallback) {
cy.log('⚠️ Running in fallback mode due to cache system error');
cy.log(` • Error: ${validationStrategy.error}`);
cy.log(' • All files will be tested without cache optimization');
// Get source file paths for incremental validation
const testSubjectsData = Cypress.env('test_subjects_data');
let sourceFilePaths = subjects; // fallback to subjects if no data available
// In fallback mode, if we have no subjects, that might be expected
if (subjects.length === 0) {
cy.log(' No subjects to test in fallback mode');
if (testSubjectsData) {
try {
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(
' This indicates no test subjects were provided to the runner'
'⚠️ Could not parse test_subjects_data, using subjects as fallback'
);
} else {
cy.log(`✅ Testing ${subjects.length} subjects in fallback mode`);
sourceFilePaths = subjects;
}
} else if (subjects.length === 0) {
cy.log(' No test subjects to validate - analyzing reason:');
}
// Check if this is due to cache optimization
const testSubjectsData = Cypress.env('test_subjects_data');
if (
testSubjectsData &&
testSubjectsData !== '[]' &&
testSubjectsData !== ''
) {
cy.log('✅ Cache optimization is active - all files were cached');
try {
const urlToSourceData = JSON.parse(testSubjectsData);
cy.log(`📊 Files processed: ${urlToSourceData.length}`);
// Only run incremental validation if we have source file paths
if (sourceFilePaths.length > 0) {
cy.log('🔄 Running incremental validation analysis...');
cy.log(
` • Analyzing ${sourceFilePaths.length} files: ${sourceFilePaths.join(', ')}`
);
// Run incremental validation with proper error handling
cy.task('runIncrementalValidation', sourceFilePaths).then((results) => {
if (!results) {
cy.log('⚠️ No results returned from incremental validation');
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');
} catch (e) {
cy.log(
'✅ Cache optimization active (could not parse detailed data)'
);
cy.log('⏭️ All link validation tests will be skipped');
}
} 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 {
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`);
}
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`
);
}
// Check for truly problematic scenarios
@ -303,67 +252,55 @@ describe('Article', () => {
subjects.forEach((subject) => {
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
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
});
cy.visit(`${subject}`, { timeout: 20000 }).then(() => {
cy.log(`✅ Successfully loaded page: ${subject}`);
});
// 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;
}
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 () {
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;
});
// Skip test if all files are cached
if (shouldSkipAllTests) {
cy.log('✅ All files cached - skipping anchor links test');
this.skip();
return;
}
cy.visit(`${subject}`).then(() => {
cy.log(`✅ Successfully loaded page for anchor testing: ${subject}`);
});
// Define selectors for anchor links to ignore, such as behavior triggers
const ignoreLinks = ['.tabs a[href^="#"]', '.code-tabs a[href^="#"]'];
@ -414,6 +351,13 @@ describe('Article', () => {
});
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
if (Cypress.env('skipExternalLinks') === true) {
cy.log(
@ -422,82 +366,62 @@ describe('Article', () => {
return;
}
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;
});
cy.visit(`${subject}`).then(() => {
cy.log(
`✅ Successfully loaded page for external link testing: ${subject}`
);
});
// 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;
}
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;
}
cy.log(
`🔍 Found ${$links.length} total external links on ${subject}`
);
cy.log(`🔍 Found ${$links.length} total external links on ${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(', ')}`
// 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}`)
);
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;
}
});
});
});
});
});