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