/// describe('Article', () => { let subjects = Cypress.env('test_subjects').split(','); let validationStrategy = null; // Always use HEAD for downloads to avoid timeouts const useHeadForDownloads = true; // Set up initialization for tests 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' ); } } // Run incremental validation analysis with source file paths 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'); } }) .catch((error) => { cy.log('❌ Error during incremental validation task: ' + error.message); // 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'); // Instead of failing completely, fall back to testing all provided subjects cy.log( '🔄 Falling back to test all provided subjects without cache optimization' ); // Reset validation strategy to indicate fallback mode validationStrategy = { fallback: true, error: error.message, unchanged: [], changed: sourceFilePaths.map((filePath) => ({ filePath, error: 'fallback', })), total: sourceFilePaths.length, }; // Keep original subjects for testing (should come from test_subjects env var) cy.log(`📋 Testing ${subjects.length} pages in fallback mode`); }); }); // Helper function to identify download links function isDownloadLink(href) { // Check for common download file extensions const downloadExtensions = [ '.pdf', '.zip', '.tar.gz', '.tgz', '.rar', '.exe', '.dmg', '.pkg', '.deb', '.rpm', '.xlsx', '.csv', '.doc', '.docx', '.ppt', '.pptx', ]; // Check for download domains or paths const downloadDomains = ['dl.influxdata.com', 'downloads.influxdata.com']; // Check if URL contains a download extension const hasDownloadExtension = downloadExtensions.some((ext) => href.toLowerCase().endsWith(ext) ); // Check if URL is from a download domain const isFromDownloadDomain = downloadDomains.some((domain) => href.toLowerCase().includes(domain) ); // Return true if either condition is met return hasDownloadExtension || isFromDownloadDomain; } // Helper function to make appropriate request based on link type function testLink(href, linkText = '', pageUrl) { // Common request options for both methods const requestOptions = { failOnStatusCode: true, timeout: 15000, // Increased timeout for reliability followRedirect: true, // Explicitly follow redirects retryOnNetworkFailure: true, // Retry on network issues retryOnStatusCodeFailure: true, // Retry on 5xx errors }; function handleFailedLink(url, status, type, redirectChain = '') { // Report the broken link cy.task('reportBrokenLink', { url: url + redirectChain, status, type, linkText, page: pageUrl, }); // Throw error for broken links throw new Error( `BROKEN ${type.toUpperCase()} LINK: ${url} (status: ${status})${redirectChain} on ${pageUrl}` ); } if (useHeadForDownloads && isDownloadLink(href)) { cy.log(`** Testing download link with HEAD: ${href} **`); cy.request({ method: 'HEAD', url: href, ...requestOptions, }).then((response) => { // Check final status after following any redirects if (response.status >= 400) { // Build redirect info string if available const redirectInfo = response.redirects && response.redirects.length > 0 ? ` (redirected to: ${response.redirects.join(' -> ')})` : ''; handleFailedLink(href, response.status, 'download', redirectInfo); } }); } else { cy.log(`** Testing link: ${href} **`); cy.log(JSON.stringify(requestOptions)); cy.request({ url: href, ...requestOptions, }).then((response) => { // Check final status after following any redirects if (response.status >= 400) { // Build redirect info string if available const redirectInfo = response.redirects && response.redirects.length > 0 ? ` (redirected to: ${response.redirects.join(' -> ')})` : ''; handleFailedLink(href, response.status, 'regular', redirectInfo); } }); } } // 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'}`); // 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'); // Ensure we have subjects to test in fallback mode expect(subjects.length).to.be.greaterThan( 0, 'Should have test subjects in fallback mode' ); } else if (subjects.length === 0) { cy.log('âš ī¸ No test subjects found - analyzing cause:'); 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('✅ Cache optimization active - this is expected'); cy.log('â„šī¸ Test subjects data is available, all files cached'); } else { cy.log('❌ No test subjects data available - potential setup issue'); // Only fail if we have no data and no subjects - indicates a real problem expect(testSubjectsData).to.not.be.empty( 'Should have test subjects data when no subjects to test' ); } } 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`); } } // Always pass if we get to this point - the setup is valid cy.log('✅ Test setup validation completed successfully'); }); subjects.forEach((subject) => { it(`${subject} has valid internal links`, function () { // 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.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; }); // Define selectors for anchor links to ignore, such as behavior triggers const ignoreLinks = ['.tabs a[href^="#"]', '.code-tabs a[href^="#"]']; const anchorSelector = 'a[href^="#"]:not(' + ignoreLinks.join('):not(') + ')'; cy.get('article, .api-content').then(($article) => { const $anchorLinks = $article.find(anchorSelector); if ($anchorLinks.length === 0) { cy.log('No anchor links found on this page'); 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(); if (href && href.length > 1) { // Get just the fragment part const url = new URL(href); const anchorId = url.hash.substring(1); // Remove the # character if (!anchorId) { cy.log(`Skipping empty anchor in ${href}`); return; } // Use DOM to check if the element exists cy.window().then((win) => { const element = win.document.getElementById(anchorId); if (!element) { cy.task('reportBrokenLink', { url: `#${anchorId}`, status: 404, type: 'anchor', linkText, page: subject, }); cy.log(`âš ī¸ Missing anchor target: #${anchorId}`); } }); } }); }); }); it(`${subject} has valid external links`, function () { // Check if we should skip external links entirely if (Cypress.env('skipExternalLinks') === true) { cy.log( 'Skipping all external links as configured by skipExternalLinks' ); 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; }); // 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.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(', ')}` ); 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; }); }); }); });