Skip to content

System Tests - Turbo & Stimulus Integration

Created: December 2, 2025 Status: ✅ Test Suite Created


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

Playwright provides fast, reliable JavaScript testing:

Terminal window
./scripts/setup_playwright

This will:

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

Tests for Bootstrap dropdown and collapse components after Turbo navigation:

TestDescription
desktop dropdown menu works after Turbo navigationVerifies dropdowns expand after clicking through Turbo links
dropdown menu works after navigating back with browser historyTests Turbo cache restoration
multiple dropdowns work after multiple Turbo navigationsStress test for repeated navigation
mobile menu collapse works after Turbo navigationTests offcanvas/collapse on mobile

test/system/stimulus_controller_race_condition_test.rb

Section titled “test/system/stimulus_controller_race_condition_test.rb”

Tests for Stimulus controller race conditions:

TestDescription
quantity increment fires exactly once after Turbo navigationDetects double-firing bug
quantity decrement fires exactly once after Turbo navigationSame for decrement
rapid clicking doesn't cause quantity driftTests rapid click handling
quantity controller works correctly after multiple Turbo navigationsTests back/forward navigation
add to cart fires exactly once after Turbo navigationCart controller race condition
add to cart button becomes disabled during processingUI locking test
double clicking add to cart only adds onceDouble-click protection
stimulus controllers are initialized exactly onceLazy loader race condition
controllers reconnect properly after Turbo cache restorationCache restoration test

Terminal window
bundle exec rails test:system
Terminal window
bundle exec rails test test/system/turbo_navigation_test.rb
bundle exec rails test test/system/stimulus_controller_race_condition_test.rb
Terminal window
bundle exec rails test test/system/turbo_navigation_test.rb -n "test_desktop_dropdown_menu_works_after_Turbo_navigation"
Terminal window
# 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

Test Fails: “Dropdown didn’t expand”

Section titled “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”

Section titled “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”

Section titled “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
}

The ApplicationSystemTestCase provides these helpers:

MethodDescription
wait_for_turboWaits for Turbo navigation to complete
wait_for_stimulus_controller(name)Waits for controller to connect
get_quantity_valueGets current quantity input value
turbo_visit(path)Visit with Turbo wait
turbo_click(locator)Click with Turbo wait

Terminal window
HEADFUL=1 bundle exec rails test test/system/turbo_navigation_test.rb
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
test "check console" do
visit "/page"
# Get console messages
messages = page.driver.browser.logs.get(:browser)
puts messages.map(&:message)
end
test "screenshot example" do
visit "/page"
save_screenshot("debug_screenshot.png")
end

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


AreaTestsStatus
Dropdown menus4✅ Created
Mobile collapse1✅ Created
Quantity controller4✅ Created
Cart controller3✅ Created
Lazy loader2✅ 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