System Tests - Turbo & Stimulus Integration

Created: December 2, 2025
Status: โœ… Test Suite Created


๐ŸŽฏ Purpose

This test suite verifies that Turbo Drive navigation and Stimulus controllers work correctly together. These tests specifically target race conditions and integration issues that can occur when:

  1. Turbo caches pages and restores them from cache
  2. Stimulus controllers reconnect after cache restoration
  3. Event listeners accumulate due to improper cleanup
  4. Bootstrap components interfere with Turbo's lifecycle

๐Ÿ”ง Setup

Install Playwright

Playwright provides fast, reliable JavaScript testing:

./scripts/setup_playwright

This will:

  1. Install the capybara-playwright-driver gem
  2. Download Chromium browser
  3. Configure Capybara to use Playwright

๐Ÿ“ Test Files

test/system/turbo_navigation_test.rb

Tests for Bootstrap dropdown and collapse components after Turbo navigation:

Test Description
desktop dropdown menu works after Turbo navigation Verifies dropdowns expand after clicking through Turbo links
dropdown menu works after navigating back with browser history Tests Turbo cache restoration
multiple dropdowns work after multiple Turbo navigations Stress test for repeated navigation
mobile menu collapse works after Turbo navigation Tests offcanvas/collapse on mobile

test/system/stimulus_controller_race_condition_test.rb

Tests for Stimulus controller race conditions:

Test Description
quantity increment fires exactly once after Turbo navigation Detects double-firing bug
quantity decrement fires exactly once after Turbo navigation Same for decrement
rapid clicking doesn't cause quantity drift Tests rapid click handling
quantity controller works correctly after multiple Turbo navigations Tests back/forward navigation
add to cart fires exactly once after Turbo navigation Cart controller race condition
add to cart button becomes disabled during processing UI locking test
double clicking add to cart only adds once Double-click protection
stimulus controllers are initialized exactly once Lazy loader race condition
controllers reconnect properly after Turbo cache restoration Cache restoration test

๐Ÿš€ Running Tests

Run All System Tests

bundle exec rails test:system

Run Specific Test File

bundle exec rails test test/system/turbo_navigation_test.rb
bundle exec rails test test/system/stimulus_controller_race_condition_test.rb

Run Single Test

bundle exec rails test test/system/turbo_navigation_test.rb -n "test_desktop_dropdown_menu_works_after_Turbo_navigation"

Debugging Options

# Run with visible browser (headful mode)
HEADFUL=1 bundle exec rails test:system

# Run with Selenium instead of Playwright
USE_SELENIUM=1 bundle exec rails test:system

# Run with specific test seed
SEED=12345 bundle exec rails test:system

๐Ÿ› Common Issues

Test Fails: "Dropdown didn't expand"

Symptom: Dropdown menu doesn't show after clicking toggle

Cause: Bootstrap's data-api wasn't properly cleaned up before Turbo cached the page

Fix: Ensure turbo:before-cache event disposes Bootstrap instances:

// client/js/www/setup/turbo.js
document.addEventListener('turbo:before-cache', () => {
  document.querySelectorAll('[data-bs-toggle="dropdown"]').forEach(el => {
    const instance = window.bootstrap.Dropdown.getInstance(el);
    if (instance) {
      instance.hide();
      instance.dispose();
    }
  });
});

Test Fails: "Quantity increased by 2 instead of 1"

Symptom: Clicking increment once adds 2+ to quantity

Cause: Event listener firing multiple times due to:

  • Missing event.stopPropagation()
  • Controller not properly cleaned up
  • Lazy loader initializing controller twice

Fix: Ensure controller uses stopPropagation and proper cleanup:

// app/javascript/controllers/quantity_controller.js
increment(event) {
  event.stopPropagation()
  event.preventDefault()
  // ... rest of method
}

Test Fails: "Controller initialized twice"

Symptom: Multiple controller instances on same element

Cause: Dataset guards like element.dataset.controllerConnected break Turbo cache

Fix: Remove dataset guards - Stimulus handles instance management:

// โŒ BAD
connect() {
  if (this.element.dataset.connected === 'true') return;
  this.element.dataset.connected = 'true';
}

// โœ… GOOD
connect() {
  // Just initialize - Stimulus manages instances
}

๐Ÿ“Š Test Helper Methods

The ApplicationSystemTestCase provides these helpers:

Method Description
wait_for_turbo Waits for Turbo navigation to complete
wait_for_stimulus_controller(name) Waits for controller to connect
get_quantity_value Gets current quantity input value
turbo_visit(path) Visit with Turbo wait
turbo_click(locator) Click with Turbo wait

๐Ÿ” Debugging Tips

1. Run with visible browser

HEADFUL=1 bundle exec rails test test/system/turbo_navigation_test.rb

2. Add debugging pauses

test "debugging example" do
  visit "/en-US/floor-heating/underlayment"
  
  # Pause to inspect
  binding.pry  # or
  sleep 30     # gives you 30 seconds to inspect
end

3. Check browser console

test "check console" do
  visit "/page"
  
  # Get console messages
  messages = page.driver.browser.logs.get(:browser)
  puts messages.map(&:message)
end

4. Take screenshots

test "screenshot example" do
  visit "/page"
  save_screenshot("debug_screenshot.png")
end

โœ… CI/CD Integration

Add to your CI workflow:

# .github/workflows/test.yml
system_tests:
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@v4
    
    - name: Install Playwright
      run: npx playwright install chromium --with-deps
    
    - name: Run system tests
      run: bundle exec rails test:system

๐Ÿ“š Related Documentation


๐Ÿงช Test Coverage Goals

Area Tests Status
Dropdown menus 4 โœ… Created
Mobile collapse 1 โœ… Created
Quantity controller 4 โœ… Created
Cart controller 3 โœ… Created
Lazy loader 2 โœ… Created

Total: 14 tests covering the most critical Turbo/Stimulus integration points.


Created: December 2, 2025
Test Files: 2
Test Cases: 14
Framework: Minitest + Capybara + Playwright