From f40e51267f9add2486a1428fb8e2066a706dcc84 Mon Sep 17 00:00:00 2001 From: Paul Keen <125715+pftg@users.noreply.github.com> Date: Fri, 17 Oct 2025 12:06:22 +0200 Subject: [PATCH 1/4] fix: correct contact URL from /contact to /contact-us in 19 blog posts --- ...ality-evaluation-non-technical-founders.md | 2 +- ...i-calculator-startup-decision-framework.md | 2 +- ...opers-contractors-budget-guide-founders.md | 2 +- ...astructure-spending-evaluation-founders.md | 2 +- .../2025/kamal-2-deployment-complete-guide.md | 2 +- ...m-accountability-non-technical-founders.md | 2 +- ...by-on-rails-development-cost-guide-2025.md | 2 +- .../index.md | 2 +- ...rails-apis-architecture-design-patterns.md | 2 +- .../index.md | 2 +- ...duct-teams-cost-center-to-profit-driver.md | 2 +- .../index.md | 2 +- .../index.md | 2 +- .../index.md | 4 +- ...-7-upgrade-guide-step-by-step-migration.md | 4 +- ...mance-optimization-15-proven-techniques.md | 2 +- ...ement-best-practices-large-applications.md | 2 +- ...testing-strategy-unit-tests-integration.md | 2 +- .../index.md | 2 +- test/system/blog_link_validation_test.rb | 293 ++++++++++++++++++ test/system/blog_recommendation_links_test.rb | 273 ++++++++++++++++ 21 files changed, 587 insertions(+), 21 deletions(-) create mode 100644 test/system/blog_link_validation_test.rb create mode 100644 test/system/blog_recommendation_links_test.rb diff --git a/content/blog/2025/code-quality-evaluation-non-technical-founders.md b/content/blog/2025/code-quality-evaluation-non-technical-founders.md index 83f7da881..d5104ac14 100644 --- a/content/blog/2025/code-quality-evaluation-non-technical-founders.md +++ b/content/blog/2025/code-quality-evaluation-non-technical-founders.md @@ -1122,7 +1122,7 @@ JetThoughts has performed code quality assessments for 50+ startups, helping non - Quality roadmap aligned with your business stage - CTO coaching on sustainable development practices -[**Schedule a code quality assessment**](/contact) to understand your technical risk before it becomes a $260K problem. +[**Schedule a code quality assessment**](/contact-us) to understand your technical risk before it becomes a $260K problem. --- diff --git a/content/blog/2025/fractional-cto-roi-calculator-startup-decision-framework.md b/content/blog/2025/fractional-cto-roi-calculator-startup-decision-framework.md index a8c32eba7..87b00e0c2 100644 --- a/content/blog/2025/fractional-cto-roi-calculator-startup-decision-framework.md +++ b/content/blog/2025/fractional-cto-roi-calculator-startup-decision-framework.md @@ -1632,7 +1632,7 @@ Choose engagement model: JetThoughts has provided fractional CTO services to 50+ startups from pre-seed to Series B, delivering average ROI of 225% within 12 months. Our fractional CTOs have led technical teams at companies you know, and we specialize in pragmatic technical leadership that accelerates your product development without burning runway. -[**Book a free 30-minute ROI consultation**](/contact) to: +[**Book a free 30-minute ROI consultation**](/contact-us) to: - Calculate specific ROI for your startup - Review your technical risk profile - Discuss engagement options and pricing diff --git a/content/blog/2025/hiring-developers-contractors-budget-guide-founders.md b/content/blog/2025/hiring-developers-contractors-budget-guide-founders.md index aaafe1c2f..a93e4e3f8 100644 --- a/content/blog/2025/hiring-developers-contractors-budget-guide-founders.md +++ b/content/blog/2025/hiring-developers-contractors-budget-guide-founders.md @@ -2007,7 +2007,7 @@ Your $100K buys: JetThoughts has helped 50+ non-technical founders navigate the "full-time vs contractor" decision and avoid expensive hiring mistakes. -**[Book a 60-minute Hiring Strategy Consultation](/contact)** to discuss: +**[Book a 60-minute Hiring Strategy Consultation](/contact-us)** to discuss: - Your specific budget and timeline constraints - Which hiring model fits your situation best - How to evaluate candidates when you're not technical diff --git a/content/blog/2025/infrastructure-spending-evaluation-founders.md b/content/blog/2025/infrastructure-spending-evaluation-founders.md index 2418aa14a..f4c9f8b31 100644 --- a/content/blog/2025/infrastructure-spending-evaluation-founders.md +++ b/content/blog/2025/infrastructure-spending-evaluation-founders.md @@ -1298,7 +1298,7 @@ If you've identified wasteful infrastructure spending and want expert guidance o - "Identified $96,000/year in unnecessary spending we were about to approve" - E-commerce startup, pre-revenue - "Finally understand what we're paying for and why" - Mobile app, $50K MRR -[**Book your infrastructure audit**](/contact) or [**explore our Fractional CTO services**](/services/fractional-cto) for ongoing guidance. +[**Book your infrastructure audit**](/contact-us) or [**explore our Fractional CTO services**](/services/fractional-cto) for ongoing guidance. --- diff --git a/content/blog/2025/kamal-2-deployment-complete-guide.md b/content/blog/2025/kamal-2-deployment-complete-guide.md index 00b85988f..ce7cb6593 100644 --- a/content/blog/2025/kamal-2-deployment-complete-guide.md +++ b/content/blog/2025/kamal-2-deployment-complete-guide.md @@ -912,4 +912,4 @@ The future of **Rails deployment** is here with Kamal 2.0. By following this gui *Ready to revolutionize your Rails deployment strategy? Start with Kamal 2.0 today and join thousands of developers who have already made the switch from expensive platforms to cost-effective, powerful deployment solutions.* -**Need help with your Kamal 2.0 deployment?** [Contact JetThoughts](https://jetthoughts.com/contact) - we specialize in Rails deployment optimization and have helped dozens of companies successfully migrate to Kamal 2.0 with significant cost savings and improved performance. \ No newline at end of file +**Need help with your Kamal 2.0 deployment?** [Contact JetThoughts](https://jetthoughts.com/contact-us) - we specialize in Rails deployment optimization and have helped dozens of companies successfully migrate to Kamal 2.0 with significant cost savings and improved performance. \ No newline at end of file diff --git a/content/blog/2025/remote-team-accountability-non-technical-founders.md b/content/blog/2025/remote-team-accountability-non-technical-founders.md index 400841c2e..ef10fa0f1 100644 --- a/content/blog/2025/remote-team-accountability-non-technical-founders.md +++ b/content/blog/2025/remote-team-accountability-non-technical-founders.md @@ -1315,4 +1315,4 @@ JetThoughts has helped 50+ founders implement healthy remote accountability with - Accountability framework setup (daily updates, demos, metrics) - Training for your team (how to work async-first) -[Contact us](https://www.jetthoughts.com/contact) if you want help implementing these patterns with your remote team. +[Contact us](https://www.jetthoughts.com/contact-us) if you want help implementing these patterns with your remote team. diff --git a/content/blog/2025/ruby-on-rails-development-cost-guide-2025.md b/content/blog/2025/ruby-on-rails-development-cost-guide-2025.md index 6f1d90698..a3c18ea2d 100644 --- a/content/blog/2025/ruby-on-rails-development-cost-guide-2025.md +++ b/content/blog/2025/ruby-on-rails-development-cost-guide-2025.md @@ -348,7 +348,7 @@ The Rails ecosystem offers incredible value when approached strategically. Wheth **Ready to start your Rails project?** At JetThoughts, we've helped hundreds of companies navigate Rails development costs and build successful applications. Our experienced team provides transparent pricing and delivers quality code that scales with your business. -[Get your free Rails project estimate today →](/contact) +[Get your free Rails project estimate today →](/contact-us) *Want more insights on Rails development? Subscribe to our newsletter for weekly tips on building better Rails applications faster and more cost-effectively.* diff --git a/content/blog/building-rag-applications-rails-pgvector/index.md b/content/blog/building-rag-applications-rails-pgvector/index.md index 78e0ad984..cc511337c 100644 --- a/content/blog/building-rag-applications-rails-pgvector/index.md +++ b/content/blog/building-rag-applications-rails-pgvector/index.md @@ -724,7 +724,7 @@ Key takeaways: - [Background Jobs Best Practices with Sidekiq](https://www.jetthoughts.com/blog/sidekiq-best-practices/) - [Building Real-Time Features with ActionCable](https://www.jetthoughts.com/blog/actioncable-real-time-features/) -Have questions about implementing RAG in your Rails app? [Contact JetThoughts](https://www.jetthoughts.com/contact/) for consulting and development services. +Have questions about implementing RAG in your Rails app? [Contact JetThoughts](https://www.jetthoughts.com/contact-us/) for consulting and development services. --- diff --git a/content/blog/building-scalable-rails-apis-architecture-design-patterns.md b/content/blog/building-scalable-rails-apis-architecture-design-patterns.md index d33682700..5a3fa1e96 100644 --- a/content/blog/building-scalable-rails-apis-architecture-design-patterns.md +++ b/content/blog/building-scalable-rails-apis-architecture-design-patterns.md @@ -913,7 +913,7 @@ Our API development services include: - Testing, monitoring, and deployment - Ongoing maintenance and feature development -Ready to build an API that scales? [Contact us for an API development consultation](https://jetthoughts.com/contact/) and let's discuss your project requirements. +Ready to build an API that scales? [Contact us for an API development consultation](https://jetthoughts.com/contact-us/) and let's discuss your project requirements. ## Related Resources diff --git a/content/blog/cost-optimization-llm-applications-token-management/index.md b/content/blog/cost-optimization-llm-applications-token-management/index.md index 7cdd84cd9..1e1649b21 100644 --- a/content/blog/cost-optimization-llm-applications-token-management/index.md +++ b/content/blog/cost-optimization-llm-applications-token-management/index.md @@ -1427,4 +1427,4 @@ For more on building production-ready LLM applications, check out our guides on --- -*Have questions about implementing these optimization strategies? [Contact our team](https://jetthoughts.com/contact) for a free cost optimization consultation.* +*Have questions about implementing these optimization strategies? [Contact our team](https://jetthoughts.com/contact-us) for a free cost optimization consultation.* diff --git a/content/blog/internal-product-teams-cost-center-to-profit-driver.md b/content/blog/internal-product-teams-cost-center-to-profit-driver.md index 6d55ca21b..7061c2b45 100644 --- a/content/blog/internal-product-teams-cost-center-to-profit-driver.md +++ b/content/blog/internal-product-teams-cost-center-to-profit-driver.md @@ -412,7 +412,7 @@ Download our **Internal Product ROI Calculator** to start quantifying your team' --- -*Need help implementing value measurement for your internal team? Our engineering management consultants have helped dozens of internal product leaders prove ROI and secure budget increases. [Schedule a consultation](/contact) to discuss your specific situation.* +*Need help implementing value measurement for your internal team? Our engineering management consultants have helped dozens of internal product leaders prove ROI and secure budget increases. [Schedule a consultation](/contact-us) to discuss your specific situation.* --- diff --git a/content/blog/langchain-memory-systems-conversational-ai/index.md b/content/blog/langchain-memory-systems-conversational-ai/index.md index 9c3d6051e..26b63faac 100644 --- a/content/blog/langchain-memory-systems-conversational-ai/index.md +++ b/content/blog/langchain-memory-systems-conversational-ai/index.md @@ -852,7 +852,7 @@ Our conversational AI services include: - Production deployment and monitoring - Performance optimization and scaling -Ready to build AI that remembers? [Contact us for a conversational AI consultation](https://jetthoughts.com/contact/) and let's discuss your project requirements. +Ready to build AI that remembers? [Contact us for a conversational AI consultation](https://jetthoughts.com/contact-us/) and let's discuss your project requirements. --- diff --git a/content/blog/langgraph-workflows-state-machines-ai-agents/index.md b/content/blog/langgraph-workflows-state-machines-ai-agents/index.md index ca8dcbac8..9558db782 100644 --- a/content/blog/langgraph-workflows-state-machines-ai-agents/index.md +++ b/content/blog/langgraph-workflows-state-machines-ai-agents/index.md @@ -1121,4 +1121,4 @@ LangGraph 1.0 represents the **maturation of agent orchestration**—from experi Start building your production-ready agent workflows today. The code examples in this guide provide everything you need to move from concept to deployment. -*Have questions about implementing LangGraph workflows? [Contact our team](/contact) for expert guidance on production AI systems.* +*Have questions about implementing LangGraph workflows? [Contact our team](/contact-us) for expert guidance on production AI systems.* diff --git a/content/blog/production-scaling-langchain-crewai-enterprise/index.md b/content/blog/production-scaling-langchain-crewai-enterprise/index.md index 941024f3a..a630a9fe5 100644 --- a/content/blog/production-scaling-langchain-crewai-enterprise/index.md +++ b/content/blog/production-scaling-langchain-crewai-enterprise/index.md @@ -2904,7 +2904,7 @@ Your AI agent application can deliver similar results—if you build the product - Root cause analysis templates - Post-mortem documentation examples -**[Download Enterprise AI Architecture Blueprint →](https://jetthoughts.com/contact)** +**[Download Enterprise AI Architecture Blueprint →](https://jetthoughts.com/contact-us)** --- @@ -2927,7 +2927,7 @@ JetThoughts specializes in taking AI prototypes to enterprise-grade production d - 24/7 monitoring and incident response - Training and knowledge transfer -**[Schedule a Free Architecture Consultation →](https://jetthoughts.com/contact)** +**[Schedule a Free Architecture Consultation →](https://jetthoughts.com/contact-us)** --- diff --git a/content/blog/rails-7-upgrade-guide-step-by-step-migration.md b/content/blog/rails-7-upgrade-guide-step-by-step-migration.md index 05a41d474..c43487bb8 100644 --- a/content/blog/rails-7-upgrade-guide-step-by-step-migration.md +++ b/content/blog/rails-7-upgrade-guide-step-by-step-migration.md @@ -433,7 +433,7 @@ The key is taking it step by step, testing thoroughly, and not rushing the proce **Need help with your Rails upgrade?** -At JetThoughts, we've helped dozens of companies upgrade their Rails applications safely and efficiently. If you'd rather have experts handle the upgrade while you focus on your business, [let's talk about your Rails upgrade project](https://jetthoughts.com/contact/). +At JetThoughts, we've helped dozens of companies upgrade their Rails applications safely and efficiently. If you'd rather have experts handle the upgrade while you focus on your business, [let's talk about your Rails upgrade project](https://jetthoughts.com/contact-us/). We offer comprehensive Rails upgrade services including: - Pre-upgrade assessment and planning @@ -441,4 +441,4 @@ We offer comprehensive Rails upgrade services including: - Post-upgrade optimization and training - Ongoing Rails maintenance and support -Ready to get started? [Contact us today](https://jetthoughts.com/contact/) for a free Rails upgrade consultation. \ No newline at end of file +Ready to get started? [Contact us today](https://jetthoughts.com/contact-us/) for a free Rails upgrade consultation. \ No newline at end of file diff --git a/content/blog/rails-performance-optimization-15-proven-techniques.md b/content/blog/rails-performance-optimization-15-proven-techniques.md index efb0ffde5..80317b0e0 100644 --- a/content/blog/rails-performance-optimization-15-proven-techniques.md +++ b/content/blog/rails-performance-optimization-15-proven-techniques.md @@ -538,7 +538,7 @@ Our performance optimization services include: - Background job architecture and optimization - Ongoing performance monitoring and maintenance -Ready to make your Rails app blazing fast? [Contact us for a performance audit](https://jetthoughts.com/contact/) and let's discuss how we can speed up your application. +Ready to make your Rails app blazing fast? [Contact us for a performance audit](https://jetthoughts.com/contact-us/) and let's discuss how we can speed up your application. ## Next Steps diff --git a/content/blog/ruby-memory-management-best-practices-large-applications.md b/content/blog/ruby-memory-management-best-practices-large-applications.md index cead64e2e..0e229bdba 100644 --- a/content/blog/ruby-memory-management-best-practices-large-applications.md +++ b/content/blog/ruby-memory-management-best-practices-large-applications.md @@ -1314,5 +1314,5 @@ Our memory optimization services include: - Production monitoring and alerting setup - Team training on Ruby memory best practices -Ready to build memory-efficient Ruby applications? [Contact us for a memory optimization consultation](https://jetthoughts.com/contact/) and let's discuss how we can help your application run leaner and faster. +Ready to build memory-efficient Ruby applications? [Contact us for a memory optimization consultation](https://jetthoughts.com/contact-us/) and let's discuss how we can help your application run leaner and faster. diff --git a/content/blog/ruby-on-rails-testing-strategy-unit-tests-integration.md b/content/blog/ruby-on-rails-testing-strategy-unit-tests-integration.md index a54f451c3..8257e9dd5 100644 --- a/content/blog/ruby-on-rails-testing-strategy-unit-tests-integration.md +++ b/content/blog/ruby-on-rails-testing-strategy-unit-tests-integration.md @@ -1453,5 +1453,5 @@ Our testing and quality assurance services include: - Team training on TDD and testing best practices - Code review and quality assessment -Ready to build confidence in your Rails application? [Contact us for a testing strategy consultation](https://jetthoughts.com/contact/) and let's discuss how we can help you ship better software faster. +Ready to build confidence in your Rails application? [Contact us for a testing strategy consultation](https://jetthoughts.com/contact-us/) and let's discuss how we can help you ship better software faster. diff --git a/content/blog/testing-monitoring-llm-applications-production/index.md b/content/blog/testing-monitoring-llm-applications-production/index.md index 132d3a92d..d4b4a0375 100644 --- a/content/blog/testing-monitoring-llm-applications-production/index.md +++ b/content/blog/testing-monitoring-llm-applications-production/index.md @@ -1094,7 +1094,7 @@ Our LLM development services include: - Prompt engineering and regression testing - Cost optimization and performance tuning -Ready to ship LLM features with confidence? [Contact us for an LLM development consultation](https://jetthoughts.com/contact/) and let's discuss your AI application requirements. +Ready to ship LLM features with confidence? [Contact us for an LLM development consultation](https://jetthoughts.com/contact-us/) and let's discuss your AI application requirements. ## Related Resources diff --git a/test/system/blog_link_validation_test.rb b/test/system/blog_link_validation_test.rb new file mode 100644 index 000000000..e3dd34e5b --- /dev/null +++ b/test/system/blog_link_validation_test.rb @@ -0,0 +1,293 @@ +# frozen_string_literal: true + +require "application_system_test_case" + +# Blog link validation tests - validates blog post URLs and tag page navigation +# Focus: User behavior - can users navigate to blog posts? Do links work correctly? +# NOT testing: Implementation details, CSS classes, HTML structure +class BlogLinkValidationTest < ApplicationSystemTestCase + def setup + Capybara.current_driver = :desktop_chrome + screenshot_section "desktop" + super + end + + # Test 1: Blog index accessibility - Can users access the blog listing? + def test_blog_index_accessibility + visit "/blog/" + + # Validate user can see blog heading + assert_selector "h1", text: "Blog", wait: 3 + + # Validate blog posts are rendered for user + assert_selector ".blog-post", minimum: 1, wait: 5 + end + + # Test 2-11: Recent 10 blog post URL accessibility + # Testing behavior: Can users access recent blog posts via direct URLs? + def test_recent_blog_post_1_building_rag_applications + visit "/blog/building-rag-applications-rails-pgvector/" + + # Validate user sees the blog post content + assert_selector "h1", text: "Building RAG Applications", wait: 5 + assert_selector "article", wait: 3 + end + + def test_recent_blog_post_2_langchain_memory_systems + visit "/blog/langchain-memory-systems-conversational-ai/" + + assert_selector "h1", text: "Building Stateful Conversational AI", wait: 5 + assert_selector "article", wait: 3 + end + + def test_recent_blog_post_3_cost_optimization + visit "/blog/cost-optimization-llm-applications-token-management/" + + assert_selector "h1", text: "Cost Optimization for LLM Applications", wait: 5 + assert_selector "article", wait: 3 + end + + def test_recent_blog_post_4_crewai_multi_agent + visit "/blog/crewai-multi-agent-systems-orchestration/" + + assert_selector "h1", text: "CrewAI Multi-Agent Systems", wait: 5 + assert_selector "article", wait: 3 + end + + def test_recent_blog_post_5_production_scaling + visit "/blog/production-scaling-langchain-crewai-enterprise/" + + assert_selector "h1", text: "From Prototype to Production", wait: 5 + assert_selector "article", wait: 3 + end + + def test_recent_blog_post_6_langchain_architecture + visit "/blog/langchain-architecture-production-ready-agents/" + + assert_selector "h1", text: "LangChain Architecture Deep Dive", wait: 5 + assert_selector "article", wait: 3 + end + + def test_recent_blog_post_7_langgraph_workflows + visit "/blog/langgraph-workflows-state-machines-ai-agents/" + + assert_selector "h1", text: "Mastering LangGraph", wait: 5 + assert_selector "article", wait: 3 + end + + def test_recent_blog_post_8_testing_monitoring + visit "/blog/testing-monitoring-llm-applications-production/" + + assert_selector "h1", text: "Testing and Monitoring LLM Applications", wait: 5 + assert_selector "article", wait: 3 + end + + def test_recent_blog_post_9_fractional_cto_roi + visit "/blog/fractional-cto-roi-calculator-startup-decision-framework/" + + assert_selector "h1", text: "Fractional CTO ROI Calculator", wait: 5 + assert_selector "article", wait: 3 + end + + def test_recent_blog_post_10_infrastructure_spending + visit "/blog/infrastructure-spending-evaluation-founders/" + + assert_selector "h1", text: "How to Know If Your Developers Are Wasting Money", wait: 5 + assert_selector "article", wait: 3 + end + + # Test 12: Blog post navigation from index + # Testing behavior: Can users click blog post links from the blog index? + def test_navigate_to_blog_post_from_index + visit "/blog/" + + # User clicks first blog post link + within(".blog") do + first_post = find(".blog-post", match: :first, visible: true, wait: 10) + + # Validate post has title (user can see what they're clicking) + post_title = first_post.find(".post-title", wait: 3) + refute_empty post_title.text, "Blog post should have a title" + + # Click the link wrapping the post + first_post.find(:xpath, "..").click # parent element + end + + # Validate user lands on blog post (after clicking, outside of within block) + assert_selector "article", wait: 5 + end + + # Test 13: Blog pagination navigation + # Testing behavior: Can users navigate through blog pages? + def test_blog_pagination_navigation + visit "/blog/" + + # Check if pagination exists (might not if only one page of posts) + if has_selector?("#pagination", wait: 2) + scroll_to find("#pagination") + + # Test pagination link functionality (if next page exists) + if has_link?("Next", wait: 1) + click_link "Next" + + # Validate user navigated to page 2 + assert_selector "h1", text: "Blog", wait: 3 + assert_selector ".blog-post", minimum: 1, wait: 5 + end + end + end + + # Test 14: Tag page navigation + # Testing behavior: Can users access tag pages and filter blog posts? + def test_tag_page_navigation + visit "/blog/" + + # Find a tag link if tags are displayed on blog index + if has_selector?(".blog-tags a", wait: 2) + within(".blog-tags") do + first_tag = find("a", match: :first, visible: true, wait: 5) + first_tag.click + end + + # Validate user lands on tag page (outside within block) + assert_selector "h1", wait: 5 + + # User should see blog posts filtered by tag + assert_selector ".blog-post", minimum: 1, wait: 5 + else + # If no tags on index, try a known tag page directly + visit "/tags/" + + if has_selector?("h1", wait: 2) + assert_selector "h1", wait: 3 + else + skip "No tag pages available for testing" + end + end + end + + # Test 15: Blog post internal navigation + # Testing behavior: Can users use table of contents or internal links? + def test_blog_post_internal_navigation + visit "/blog/langchain-architecture-production-ready-agents/" + + # Check if table of contents exists + if has_selector?(".table-of-contents", wait: 2) + within(".table-of-contents") do + first_toc_link = find("a", match: :first, visible: true, wait: 5) + first_toc_link.click + + # Validate page scrolled to section (URL should have hash) + assert_match(/#/, current_url) + end + end + end + + # Test 16: Blog search or filter functionality + # Testing behavior: Can users search or filter blog posts? + def test_blog_search_functionality + visit "/blog/" + + # Check if search exists + if has_selector?("input[type='search']", wait: 2) || has_selector?("input[type='text'][placeholder*='Search']", wait: 2) + search_field = find("input[type='search'], input[type='text'][placeholder*='Search']", visible: true, wait: 5) + search_field.fill_in with: "Rails" + + # Some search implementations require submit button + if has_button?("Search", wait: 1) + click_button "Search" + end + + # Validate search results appear + assert_selector ".blog-post", minimum: 1, wait: 5 + else + skip "No search functionality available for testing" + end + end + + # Test 17: Related posts navigation + # Testing behavior: Can users navigate to related posts from a blog post? + def test_related_posts_navigation + visit "/blog/building-rag-applications-rails-pgvector/" + + # Check if related posts section exists + if has_selector?(".related-posts", wait: 2) || has_selector?("[class*='related']", wait: 2) + related_section = find(".related-posts, [class*='related']", match: :first, visible: true, wait: 5) + + within(related_section) do + if has_link?(wait: 2) + first_related_link = find("a", match: :first, visible: true, wait: 5) + first_related_link.click + + # Validate user lands on another blog post + assert_selector "article", wait: 5 + end + end + else + skip "No related posts section available for testing" + end + end + + # Test 18: Blog post sharing links + # Testing behavior: Are social sharing links accessible? + def test_blog_post_sharing_links + visit "/blog/cost-optimization-llm-applications-token-management/" + + # Check if social sharing links exist + if has_selector?(".share", wait: 2) || has_selector?("[class*='share']", wait: 2) + share_section = find(".share, [class*='share']", match: :first, visible: true, wait: 5) + + within(share_section) do + # Validate share links are present (not clicking to avoid external navigation) + assert_selector "a", minimum: 1, wait: 3 + end + else + skip "No social sharing functionality available for testing" + end + end + + # Test 19: Blog RSS/feed accessibility + # Testing behavior: Can users access blog feed? + def test_blog_feed_accessibility + visit "/blog/" + + # Check if RSS link exists + if has_link?("RSS", wait: 2) || has_link?("Feed", wait: 2) + # Don't click (external), just validate link exists + assert_selector "a[href*='rss'], a[href*='feed'], a[href*='xml']", wait: 3 + else + # Try direct feed URLs + visit "/blog/index.xml" + + # XML feeds typically have specific content type + # If accessible, page should load without 404 + refute_text "404", wait: 2 + end + rescue Capybara::ElementNotFound + skip "No blog feed available for testing" + end + + # Test 20: Blog archive/category navigation + # Testing behavior: Can users browse blog by date or category? + def test_blog_archive_navigation + visit "/blog/" + + # Check if archive or category navigation exists + if has_selector?(".archive", wait: 2) || has_selector?(".categories", wait: 2) + archive_section = find(".archive, .categories", match: :first, visible: true, wait: 5) + + within(archive_section) do + if has_link?(wait: 2) + first_archive_link = find("a", match: :first, visible: true, wait: 5) + first_archive_link.click + + # Validate user lands on filtered view + assert_selector "h1", wait: 5 + assert_selector ".blog-post", minimum: 1, wait: 5 + end + end + else + skip "No archive/category navigation available for testing" + end + end +end diff --git a/test/system/blog_recommendation_links_test.rb b/test/system/blog_recommendation_links_test.rb new file mode 100644 index 000000000..246c63093 --- /dev/null +++ b/test/system/blog_recommendation_links_test.rb @@ -0,0 +1,273 @@ +# frozen_string_literal: true + +require "application_system_test_case" + +# Blog recommendation and resource links validation +# Focus: User behavior - Can users access recommended posts, free resources, and contact links? +# NOT testing: Implementation details, CSS classes, HTML structure +class BlogRecommendationLinksTest < ApplicationSystemTestCase + def setup + Capybara.current_driver = :desktop_chrome + screenshot_section "desktop" + super + end + + # Test 1: Building RAG Applications - Recommendation block links + # Testing behavior: Can users access recommended posts from RAG article? + def test_rag_applications_recommendation_links_work + visit "/blog/building-rag-applications-rails-pgvector/" + + # Wait for page to fully load + assert_selector "h1", text: "Building RAG Applications", wait: 5 + + # Scroll to recommendation section (typically at bottom) + scroll_to_bottom + + # Find recommendation/related posts section + if has_selector?(".related-posts, [class*='related'], [class*='recommend']", wait: 3) + within(".related-posts, [class*='related'], [class*='recommend']", match: :first) do + # Get all recommendation links + links = all("a[href*='/blog/']", wait: 5) + + assert links.count > 0, "Should have at least one recommendation link" + + # Test each recommendation link is accessible + links.each_with_index do |link, index| + href = link[:href] + + # Visit the recommended post + visit href + + # Validate user lands on a valid blog post (not 404) + refute_text "404", wait: 3 + refute_text "Page Not Found", wait: 2 + assert_selector "article, .blog-post", wait: 5 + + # Return to original post for next link check + go_back if index < links.count - 1 + end + end + else + skip "No recommendation section found on RAG applications post" + end + end + + # Test 2: Hiring Developers Contractors - Recommendation and contact links + # Testing behavior: Can users access recommended resources and contact pages? + def test_hiring_developers_recommendation_and_contact_links_work + visit "/blog/hiring-developers-contractors-budget-guide-founders/" + + assert_selector "h1", text: "hiring", wait: 5, case_sensitive: false + + scroll_to_bottom + + # Test recommendation links + if has_selector?(".related-posts, [class*='related'], [class*='recommend']", wait: 3) + within(".related-posts, [class*='related'], [class*='recommend']", match: :first) do + links = all("a[href]", wait: 5) + + links.each do |link| + href = link[:href] + next unless href.include?('/blog/') || href.include?('/contact') + + visit href + + # Validate link works (not broken) + refute_text "404", wait: 3 + refute_text "Page Not Found", wait: 2 + + go_back + end + end + end + end + + # Test 3: Code Quality Evaluation - Free Resources links + # Testing behavior: Can users access free resources mentioned in post? + def test_code_quality_free_resources_links_work + visit "/blog/code-quality-evaluation-non-technical-founders/" + + assert_selector "h1", wait: 5 + + # Look for "Free Resources" section + if has_text?("Free Resources", wait: 3) || has_text?("Resources", wait: 3) + # Find all links in resources section + links = all("a[href*='/contact'], a[href*='/blog/'], a[href*='resource']", wait: 5) + + links.each do |link| + href = link[:href] + + # Skip external links (focus on internal navigation) + next if href.start_with?('http') && !href.include?('jetthoughts') + + visit href + + # Validate resource link works + refute_text "404", wait: 3 + assert_selector "body", wait: 3 + + go_back + end + else + skip "No Free Resources section found" + end + end + + # Test 4: Infrastructure Spending - Contact section links + # Testing behavior: Can users contact the company from blog post? + def test_infrastructure_spending_contact_links_work + visit "/blog/infrastructure-spending-evaluation-founders/" + + assert_selector "h1", wait: 5 + + scroll_to_bottom + + # Find contact links in various sections + contact_links = all("a[href*='/contact'], a[href*='mailto:']", wait: 5) + + assert contact_links.count > 0, "Should have at least one contact link" + + contact_links.each do |link| + href = link[:href] + + # Skip mailto links (can't validate email behavior in browser) + next if href.start_with?('mailto:') + + visit href + + # Validate contact page loads + refute_text "404", wait: 3 + refute_text "Page Not Found", wait: 2 + assert_selector "body", wait: 3 + + go_back + end + end + + # Test 5: Remote Team Accountability - All link types validation + # Testing behavior: Can users navigate from remote team post to all linked resources? + def test_remote_team_all_links_work + visit "/blog/remote-team-accountability-non-technical-founders/" + + assert_selector "h1", wait: 5 + + scroll_to_bottom + + # Collect all internal links (blog posts, contact, resources) + internal_links = all("a[href^='/']", wait: 5).map { |link| link[:href] }.uniq + + # Filter to relevant link types + relevant_links = internal_links.select do |href| + href.include?('/blog/') || href.include?('/contact') || href.include?('/resource') + end + + assert relevant_links.count > 0, "Should have at least one internal link" + + relevant_links.each do |href| + visit href + + # Validate link destination is accessible + refute_text "404", wait: 3 + refute_text "Page Not Found", wait: 2 + assert_selector "body", wait: 3 + + go_back + end + end + + # Test 6: Comprehensive broken link detection across all specified posts + # Testing behavior: Systematically validate NO broken links exist on these posts + def test_no_broken_links_across_founder_posts + posts = [ + "/blog/building-rag-applications-rails-pgvector/", + "/blog/hiring-developers-contractors-budget-guide-founders/", + "/blog/code-quality-evaluation-non-technical-founders/", + "/blog/infrastructure-spending-evaluation-founders/", + "/blog/remote-team-accountability-non-technical-founders/" + ] + + broken_links_report = [] + + posts.each do |post_url| + visit post_url + + # Wait for page load + assert_selector "h1", wait: 5 + scroll_to_bottom + + # Get all internal links on page + internal_links = all("a[href^='/']", wait: 5) + + internal_links.each do |link| + href = link[:href] + link_text = link.text.strip + + # Skip anchor-only links + next if href == '/' || href.start_with?('/#') + + visit href + + # Check if link is broken + if has_text?("404", wait: 2) || has_text?("Page Not Found", wait: 2) + broken_links_report << { + source_post: post_url, + broken_url: href, + link_text: link_text + } + end + + go_back + end + end + + # Assert NO broken links found + assert_empty broken_links_report, + "Found broken links:\n#{broken_links_report.map { |r| " - Post: #{r[:source_post]}\n Link: #{r[:broken_url]}\n Text: '#{r[:link_text]}'" }.join("\n")}" + end + + # Test 7: Validate recommendation block exists and has accessible links + # Testing behavior: Do recommendation blocks consistently provide working links? + def test_recommendation_blocks_consistently_provide_working_links + posts_with_recommendations = [ + "/blog/building-rag-applications-rails-pgvector/", + "/blog/hiring-developers-contractors-budget-guide-founders/", + "/blog/code-quality-evaluation-non-technical-founders/" + ] + + posts_with_recommendations.each do |post_url| + visit post_url + + assert_selector "h1", wait: 5 + scroll_to_bottom + + # Look for recommendation/related section + if has_selector?(".related-posts, [class*='related'], [class*='recommend']", wait: 3) + within(".related-posts, [class*='related'], [class*='recommend']", match: :first) do + links = all("a[href]", wait: 5) + + # Validate at least one recommendation exists + assert links.count > 0, "Post #{post_url} should have recommendation links" + + # Test first recommendation link works + first_link = links.first + href = first_link[:href] + + visit href + + # Validate recommendation link works + refute_text "404", wait: 3 + refute_text "Page Not Found", wait: 2 + + go_back + end + end + end + end + + private + + def scroll_to_bottom + execute_script "window.scrollTo(0, document.body.scrollHeight)" + sleep 1 # Allow time for lazy-loaded content + end +end From 2a5b76e643c0f859369c0e1fe7360804d52228d6 Mon Sep 17 00:00:00 2001 From: Paul Keen <125715+pftg@users.noreply.github.com> Date: Fri, 17 Oct 2025 12:06:39 +0200 Subject: [PATCH 2/4] fix: correct CTA shortcode parameter from button-link to button-url --- .../blog/internal-product-teams-cost-center-to-profit-driver.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/blog/internal-product-teams-cost-center-to-profit-driver.md b/content/blog/internal-product-teams-cost-center-to-profit-driver.md index 7061c2b45..c8cb14a50 100644 --- a/content/blog/internal-product-teams-cost-center-to-profit-driver.md +++ b/content/blog/internal-product-teams-cost-center-to-profit-driver.md @@ -406,7 +406,7 @@ Download our **Internal Product ROI Calculator** to start quantifying your team' {{< cta title="Get the ROI Calculator" description="Transform your internal team from cost center to profit driver with our proven framework and templates." button-text="Download Free Calculator" - button-link="/resources/internal-product-roi-calculator" >}} + button-url="/resources/internal-product-roi-calculator" >}} *No email required. Instant download.* From dc707c6fa5a384fdda1ff681f630569240465ab9 Mon Sep 17 00:00:00 2001 From: Paul Keen <125715+pftg@users.noreply.github.com> Date: Fri, 17 Oct 2025 12:07:00 +0200 Subject: [PATCH 3/4] fix: remove broken Related Posts section (Hugo template handles this automatically) --- .../blog/building-rag-applications-rails-pgvector/index.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/content/blog/building-rag-applications-rails-pgvector/index.md b/content/blog/building-rag-applications-rails-pgvector/index.md index cc511337c..d2c2bfa0a 100644 --- a/content/blog/building-rag-applications-rails-pgvector/index.md +++ b/content/blog/building-rag-applications-rails-pgvector/index.md @@ -718,12 +718,6 @@ Key takeaways: - Implement query analytics with [Ahoy](https://www.jetthoughts.com/blog/analytics-tracking-rails-ahoy/) - Add [Sidekiq Pro](https://www.jetthoughts.com/blog/sidekiq-optimization/) for better job management -### Related Posts - -- [Optimizing PostgreSQL for Rails Applications](https://www.jetthoughts.com/blog/postgresql-optimization/) -- [Background Jobs Best Practices with Sidekiq](https://www.jetthoughts.com/blog/sidekiq-best-practices/) -- [Building Real-Time Features with ActionCable](https://www.jetthoughts.com/blog/actioncable-real-time-features/) - Have questions about implementing RAG in your Rails app? [Contact JetThoughts](https://www.jetthoughts.com/contact-us/) for consulting and development services. --- From f91d43d0b67dc22d763036ad25ab24edfa482897 Mon Sep 17 00:00:00 2001 From: Paul Keen <125715+pftg@users.noreply.github.com> Date: Fri, 17 Oct 2025 12:19:14 +0200 Subject: [PATCH 4/4] cleanup --- test/system/blog_link_validation_test.rb | 293 ------------------ test/system/blog_recommendation_links_test.rb | 273 ---------------- 2 files changed, 566 deletions(-) delete mode 100644 test/system/blog_link_validation_test.rb delete mode 100644 test/system/blog_recommendation_links_test.rb diff --git a/test/system/blog_link_validation_test.rb b/test/system/blog_link_validation_test.rb deleted file mode 100644 index e3dd34e5b..000000000 --- a/test/system/blog_link_validation_test.rb +++ /dev/null @@ -1,293 +0,0 @@ -# frozen_string_literal: true - -require "application_system_test_case" - -# Blog link validation tests - validates blog post URLs and tag page navigation -# Focus: User behavior - can users navigate to blog posts? Do links work correctly? -# NOT testing: Implementation details, CSS classes, HTML structure -class BlogLinkValidationTest < ApplicationSystemTestCase - def setup - Capybara.current_driver = :desktop_chrome - screenshot_section "desktop" - super - end - - # Test 1: Blog index accessibility - Can users access the blog listing? - def test_blog_index_accessibility - visit "/blog/" - - # Validate user can see blog heading - assert_selector "h1", text: "Blog", wait: 3 - - # Validate blog posts are rendered for user - assert_selector ".blog-post", minimum: 1, wait: 5 - end - - # Test 2-11: Recent 10 blog post URL accessibility - # Testing behavior: Can users access recent blog posts via direct URLs? - def test_recent_blog_post_1_building_rag_applications - visit "/blog/building-rag-applications-rails-pgvector/" - - # Validate user sees the blog post content - assert_selector "h1", text: "Building RAG Applications", wait: 5 - assert_selector "article", wait: 3 - end - - def test_recent_blog_post_2_langchain_memory_systems - visit "/blog/langchain-memory-systems-conversational-ai/" - - assert_selector "h1", text: "Building Stateful Conversational AI", wait: 5 - assert_selector "article", wait: 3 - end - - def test_recent_blog_post_3_cost_optimization - visit "/blog/cost-optimization-llm-applications-token-management/" - - assert_selector "h1", text: "Cost Optimization for LLM Applications", wait: 5 - assert_selector "article", wait: 3 - end - - def test_recent_blog_post_4_crewai_multi_agent - visit "/blog/crewai-multi-agent-systems-orchestration/" - - assert_selector "h1", text: "CrewAI Multi-Agent Systems", wait: 5 - assert_selector "article", wait: 3 - end - - def test_recent_blog_post_5_production_scaling - visit "/blog/production-scaling-langchain-crewai-enterprise/" - - assert_selector "h1", text: "From Prototype to Production", wait: 5 - assert_selector "article", wait: 3 - end - - def test_recent_blog_post_6_langchain_architecture - visit "/blog/langchain-architecture-production-ready-agents/" - - assert_selector "h1", text: "LangChain Architecture Deep Dive", wait: 5 - assert_selector "article", wait: 3 - end - - def test_recent_blog_post_7_langgraph_workflows - visit "/blog/langgraph-workflows-state-machines-ai-agents/" - - assert_selector "h1", text: "Mastering LangGraph", wait: 5 - assert_selector "article", wait: 3 - end - - def test_recent_blog_post_8_testing_monitoring - visit "/blog/testing-monitoring-llm-applications-production/" - - assert_selector "h1", text: "Testing and Monitoring LLM Applications", wait: 5 - assert_selector "article", wait: 3 - end - - def test_recent_blog_post_9_fractional_cto_roi - visit "/blog/fractional-cto-roi-calculator-startup-decision-framework/" - - assert_selector "h1", text: "Fractional CTO ROI Calculator", wait: 5 - assert_selector "article", wait: 3 - end - - def test_recent_blog_post_10_infrastructure_spending - visit "/blog/infrastructure-spending-evaluation-founders/" - - assert_selector "h1", text: "How to Know If Your Developers Are Wasting Money", wait: 5 - assert_selector "article", wait: 3 - end - - # Test 12: Blog post navigation from index - # Testing behavior: Can users click blog post links from the blog index? - def test_navigate_to_blog_post_from_index - visit "/blog/" - - # User clicks first blog post link - within(".blog") do - first_post = find(".blog-post", match: :first, visible: true, wait: 10) - - # Validate post has title (user can see what they're clicking) - post_title = first_post.find(".post-title", wait: 3) - refute_empty post_title.text, "Blog post should have a title" - - # Click the link wrapping the post - first_post.find(:xpath, "..").click # parent element - end - - # Validate user lands on blog post (after clicking, outside of within block) - assert_selector "article", wait: 5 - end - - # Test 13: Blog pagination navigation - # Testing behavior: Can users navigate through blog pages? - def test_blog_pagination_navigation - visit "/blog/" - - # Check if pagination exists (might not if only one page of posts) - if has_selector?("#pagination", wait: 2) - scroll_to find("#pagination") - - # Test pagination link functionality (if next page exists) - if has_link?("Next", wait: 1) - click_link "Next" - - # Validate user navigated to page 2 - assert_selector "h1", text: "Blog", wait: 3 - assert_selector ".blog-post", minimum: 1, wait: 5 - end - end - end - - # Test 14: Tag page navigation - # Testing behavior: Can users access tag pages and filter blog posts? - def test_tag_page_navigation - visit "/blog/" - - # Find a tag link if tags are displayed on blog index - if has_selector?(".blog-tags a", wait: 2) - within(".blog-tags") do - first_tag = find("a", match: :first, visible: true, wait: 5) - first_tag.click - end - - # Validate user lands on tag page (outside within block) - assert_selector "h1", wait: 5 - - # User should see blog posts filtered by tag - assert_selector ".blog-post", minimum: 1, wait: 5 - else - # If no tags on index, try a known tag page directly - visit "/tags/" - - if has_selector?("h1", wait: 2) - assert_selector "h1", wait: 3 - else - skip "No tag pages available for testing" - end - end - end - - # Test 15: Blog post internal navigation - # Testing behavior: Can users use table of contents or internal links? - def test_blog_post_internal_navigation - visit "/blog/langchain-architecture-production-ready-agents/" - - # Check if table of contents exists - if has_selector?(".table-of-contents", wait: 2) - within(".table-of-contents") do - first_toc_link = find("a", match: :first, visible: true, wait: 5) - first_toc_link.click - - # Validate page scrolled to section (URL should have hash) - assert_match(/#/, current_url) - end - end - end - - # Test 16: Blog search or filter functionality - # Testing behavior: Can users search or filter blog posts? - def test_blog_search_functionality - visit "/blog/" - - # Check if search exists - if has_selector?("input[type='search']", wait: 2) || has_selector?("input[type='text'][placeholder*='Search']", wait: 2) - search_field = find("input[type='search'], input[type='text'][placeholder*='Search']", visible: true, wait: 5) - search_field.fill_in with: "Rails" - - # Some search implementations require submit button - if has_button?("Search", wait: 1) - click_button "Search" - end - - # Validate search results appear - assert_selector ".blog-post", minimum: 1, wait: 5 - else - skip "No search functionality available for testing" - end - end - - # Test 17: Related posts navigation - # Testing behavior: Can users navigate to related posts from a blog post? - def test_related_posts_navigation - visit "/blog/building-rag-applications-rails-pgvector/" - - # Check if related posts section exists - if has_selector?(".related-posts", wait: 2) || has_selector?("[class*='related']", wait: 2) - related_section = find(".related-posts, [class*='related']", match: :first, visible: true, wait: 5) - - within(related_section) do - if has_link?(wait: 2) - first_related_link = find("a", match: :first, visible: true, wait: 5) - first_related_link.click - - # Validate user lands on another blog post - assert_selector "article", wait: 5 - end - end - else - skip "No related posts section available for testing" - end - end - - # Test 18: Blog post sharing links - # Testing behavior: Are social sharing links accessible? - def test_blog_post_sharing_links - visit "/blog/cost-optimization-llm-applications-token-management/" - - # Check if social sharing links exist - if has_selector?(".share", wait: 2) || has_selector?("[class*='share']", wait: 2) - share_section = find(".share, [class*='share']", match: :first, visible: true, wait: 5) - - within(share_section) do - # Validate share links are present (not clicking to avoid external navigation) - assert_selector "a", minimum: 1, wait: 3 - end - else - skip "No social sharing functionality available for testing" - end - end - - # Test 19: Blog RSS/feed accessibility - # Testing behavior: Can users access blog feed? - def test_blog_feed_accessibility - visit "/blog/" - - # Check if RSS link exists - if has_link?("RSS", wait: 2) || has_link?("Feed", wait: 2) - # Don't click (external), just validate link exists - assert_selector "a[href*='rss'], a[href*='feed'], a[href*='xml']", wait: 3 - else - # Try direct feed URLs - visit "/blog/index.xml" - - # XML feeds typically have specific content type - # If accessible, page should load without 404 - refute_text "404", wait: 2 - end - rescue Capybara::ElementNotFound - skip "No blog feed available for testing" - end - - # Test 20: Blog archive/category navigation - # Testing behavior: Can users browse blog by date or category? - def test_blog_archive_navigation - visit "/blog/" - - # Check if archive or category navigation exists - if has_selector?(".archive", wait: 2) || has_selector?(".categories", wait: 2) - archive_section = find(".archive, .categories", match: :first, visible: true, wait: 5) - - within(archive_section) do - if has_link?(wait: 2) - first_archive_link = find("a", match: :first, visible: true, wait: 5) - first_archive_link.click - - # Validate user lands on filtered view - assert_selector "h1", wait: 5 - assert_selector ".blog-post", minimum: 1, wait: 5 - end - end - else - skip "No archive/category navigation available for testing" - end - end -end diff --git a/test/system/blog_recommendation_links_test.rb b/test/system/blog_recommendation_links_test.rb deleted file mode 100644 index 246c63093..000000000 --- a/test/system/blog_recommendation_links_test.rb +++ /dev/null @@ -1,273 +0,0 @@ -# frozen_string_literal: true - -require "application_system_test_case" - -# Blog recommendation and resource links validation -# Focus: User behavior - Can users access recommended posts, free resources, and contact links? -# NOT testing: Implementation details, CSS classes, HTML structure -class BlogRecommendationLinksTest < ApplicationSystemTestCase - def setup - Capybara.current_driver = :desktop_chrome - screenshot_section "desktop" - super - end - - # Test 1: Building RAG Applications - Recommendation block links - # Testing behavior: Can users access recommended posts from RAG article? - def test_rag_applications_recommendation_links_work - visit "/blog/building-rag-applications-rails-pgvector/" - - # Wait for page to fully load - assert_selector "h1", text: "Building RAG Applications", wait: 5 - - # Scroll to recommendation section (typically at bottom) - scroll_to_bottom - - # Find recommendation/related posts section - if has_selector?(".related-posts, [class*='related'], [class*='recommend']", wait: 3) - within(".related-posts, [class*='related'], [class*='recommend']", match: :first) do - # Get all recommendation links - links = all("a[href*='/blog/']", wait: 5) - - assert links.count > 0, "Should have at least one recommendation link" - - # Test each recommendation link is accessible - links.each_with_index do |link, index| - href = link[:href] - - # Visit the recommended post - visit href - - # Validate user lands on a valid blog post (not 404) - refute_text "404", wait: 3 - refute_text "Page Not Found", wait: 2 - assert_selector "article, .blog-post", wait: 5 - - # Return to original post for next link check - go_back if index < links.count - 1 - end - end - else - skip "No recommendation section found on RAG applications post" - end - end - - # Test 2: Hiring Developers Contractors - Recommendation and contact links - # Testing behavior: Can users access recommended resources and contact pages? - def test_hiring_developers_recommendation_and_contact_links_work - visit "/blog/hiring-developers-contractors-budget-guide-founders/" - - assert_selector "h1", text: "hiring", wait: 5, case_sensitive: false - - scroll_to_bottom - - # Test recommendation links - if has_selector?(".related-posts, [class*='related'], [class*='recommend']", wait: 3) - within(".related-posts, [class*='related'], [class*='recommend']", match: :first) do - links = all("a[href]", wait: 5) - - links.each do |link| - href = link[:href] - next unless href.include?('/blog/') || href.include?('/contact') - - visit href - - # Validate link works (not broken) - refute_text "404", wait: 3 - refute_text "Page Not Found", wait: 2 - - go_back - end - end - end - end - - # Test 3: Code Quality Evaluation - Free Resources links - # Testing behavior: Can users access free resources mentioned in post? - def test_code_quality_free_resources_links_work - visit "/blog/code-quality-evaluation-non-technical-founders/" - - assert_selector "h1", wait: 5 - - # Look for "Free Resources" section - if has_text?("Free Resources", wait: 3) || has_text?("Resources", wait: 3) - # Find all links in resources section - links = all("a[href*='/contact'], a[href*='/blog/'], a[href*='resource']", wait: 5) - - links.each do |link| - href = link[:href] - - # Skip external links (focus on internal navigation) - next if href.start_with?('http') && !href.include?('jetthoughts') - - visit href - - # Validate resource link works - refute_text "404", wait: 3 - assert_selector "body", wait: 3 - - go_back - end - else - skip "No Free Resources section found" - end - end - - # Test 4: Infrastructure Spending - Contact section links - # Testing behavior: Can users contact the company from blog post? - def test_infrastructure_spending_contact_links_work - visit "/blog/infrastructure-spending-evaluation-founders/" - - assert_selector "h1", wait: 5 - - scroll_to_bottom - - # Find contact links in various sections - contact_links = all("a[href*='/contact'], a[href*='mailto:']", wait: 5) - - assert contact_links.count > 0, "Should have at least one contact link" - - contact_links.each do |link| - href = link[:href] - - # Skip mailto links (can't validate email behavior in browser) - next if href.start_with?('mailto:') - - visit href - - # Validate contact page loads - refute_text "404", wait: 3 - refute_text "Page Not Found", wait: 2 - assert_selector "body", wait: 3 - - go_back - end - end - - # Test 5: Remote Team Accountability - All link types validation - # Testing behavior: Can users navigate from remote team post to all linked resources? - def test_remote_team_all_links_work - visit "/blog/remote-team-accountability-non-technical-founders/" - - assert_selector "h1", wait: 5 - - scroll_to_bottom - - # Collect all internal links (blog posts, contact, resources) - internal_links = all("a[href^='/']", wait: 5).map { |link| link[:href] }.uniq - - # Filter to relevant link types - relevant_links = internal_links.select do |href| - href.include?('/blog/') || href.include?('/contact') || href.include?('/resource') - end - - assert relevant_links.count > 0, "Should have at least one internal link" - - relevant_links.each do |href| - visit href - - # Validate link destination is accessible - refute_text "404", wait: 3 - refute_text "Page Not Found", wait: 2 - assert_selector "body", wait: 3 - - go_back - end - end - - # Test 6: Comprehensive broken link detection across all specified posts - # Testing behavior: Systematically validate NO broken links exist on these posts - def test_no_broken_links_across_founder_posts - posts = [ - "/blog/building-rag-applications-rails-pgvector/", - "/blog/hiring-developers-contractors-budget-guide-founders/", - "/blog/code-quality-evaluation-non-technical-founders/", - "/blog/infrastructure-spending-evaluation-founders/", - "/blog/remote-team-accountability-non-technical-founders/" - ] - - broken_links_report = [] - - posts.each do |post_url| - visit post_url - - # Wait for page load - assert_selector "h1", wait: 5 - scroll_to_bottom - - # Get all internal links on page - internal_links = all("a[href^='/']", wait: 5) - - internal_links.each do |link| - href = link[:href] - link_text = link.text.strip - - # Skip anchor-only links - next if href == '/' || href.start_with?('/#') - - visit href - - # Check if link is broken - if has_text?("404", wait: 2) || has_text?("Page Not Found", wait: 2) - broken_links_report << { - source_post: post_url, - broken_url: href, - link_text: link_text - } - end - - go_back - end - end - - # Assert NO broken links found - assert_empty broken_links_report, - "Found broken links:\n#{broken_links_report.map { |r| " - Post: #{r[:source_post]}\n Link: #{r[:broken_url]}\n Text: '#{r[:link_text]}'" }.join("\n")}" - end - - # Test 7: Validate recommendation block exists and has accessible links - # Testing behavior: Do recommendation blocks consistently provide working links? - def test_recommendation_blocks_consistently_provide_working_links - posts_with_recommendations = [ - "/blog/building-rag-applications-rails-pgvector/", - "/blog/hiring-developers-contractors-budget-guide-founders/", - "/blog/code-quality-evaluation-non-technical-founders/" - ] - - posts_with_recommendations.each do |post_url| - visit post_url - - assert_selector "h1", wait: 5 - scroll_to_bottom - - # Look for recommendation/related section - if has_selector?(".related-posts, [class*='related'], [class*='recommend']", wait: 3) - within(".related-posts, [class*='related'], [class*='recommend']", match: :first) do - links = all("a[href]", wait: 5) - - # Validate at least one recommendation exists - assert links.count > 0, "Post #{post_url} should have recommendation links" - - # Test first recommendation link works - first_link = links.first - href = first_link[:href] - - visit href - - # Validate recommendation link works - refute_text "404", wait: 3 - refute_text "Page Not Found", wait: 2 - - go_back - end - end - end - end - - private - - def scroll_to_bottom - execute_script "window.scrollTo(0, document.body.scrollHeight)" - sleep 1 # Allow time for lazy-loaded content - end -end