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:
- Turbo caches pages and restores them from cache
- Stimulus controllers reconnect after cache restoration
- Event listeners accumulate due to improper cleanup
- Bootstrap components interfere with Turbo's lifecycle
๐ง Setup
Install Playwright
Playwright provides fast, reliable JavaScript testing:
./scripts/setup_playwright
This will:
- Install the
capybara-playwright-drivergem - Download Chromium browser
- 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
= page.driver.browser.logs.get(:browser)
puts .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
- Stimulus Controllers - Controller documentation
- Skills Index - Coding conventions by area
- Turbo Documentation - Official Turbo docs
- Stimulus Documentation - Official Stimulus docs
๐งช 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