Skip to content

Turbo Stream issue when open and close modals that uses turbo_frame, only happens when headless is activated #94

Description

@lporras

Turbo Stream redirects fail in headless mode when multiple modal tests run together

Environment

  • Apparition: 0.6.0
  • Capybara: 3.37.1
  • Rails: 7.0.4
  • Turbo Rails: 1.3.2
  • Ruby: 3.0.6
  • Platform: macOS (ARM64 Darwin 24.6.0)

Description

When running multiple RSpec feature tests that test modals with Turbo Stream form submissions, the tests pass individually but fail when run together only in headless mode. With headless: false, all tests pass even when run together.

The modals themselves open correctly in both tests. However, the second test's form submission and Turbo Stream redirect fail to work properly in headless mode. The issue appears to be related to JavaScript/Turbo event handler state pollution between tests in headless Chrome. The first test leaves residual browser state that prevents the second modal's form submission from triggering the Turbo Stream redirect.

Expected Behavior

Tests using Turbo Stream redirects in modals should work consistently in both headless and non-headless modes, with proper test isolation between consecutive test runs.

Actual Behavior

In headless mode (headless: true):

  • First modal test: ✅ Passes (modal opens, form submits, redirect works)
  • Second modal test: ❌ Fails
    • Modal opens correctly ✅
    • Form submission doesn't trigger properly ❌
    • Turbo Stream redirect doesn't occur ❌
    • Test stays on the current page instead of redirecting

In non-headless mode (headless: false):

  • First test: ✅ Passes
  • Second test: ✅ Passes

Running individually:

  • Both tests pass in both modes

Reproduction Steps

  1. Create two similar feature tests that:

    • Open a modal with a unique ID (e.g., #modal_a, #modal_b)
    • Submit a form inside the modal using Turbo
    • Expect a Turbo Stream redirect response from the controller
  2. Configure Apparition with headless: true

  3. Run both tests together: bundle exec rspec spec/features/first_modal_spec.rb spec/features/second_modal_spec.rb

Result: Second test fails with redirect not happening

  1. Change to headless: false and run again

Result: Both tests pass

Minimal Test Code

# spec/features/first_modal_spec.rb
RSpec.feature 'First Modal Test', type: :feature do
  let(:resource_a) { create(:resource_a) }

  before do
    visit resource_a_path(resource_a)
  end

  describe 'modal with turbo stream redirect', js: true do
    scenario 'submits form and redirects' do
      # Open modal
      find('#open-modal-a').click

      # Wait for modal to appear
      expect(page).to have_css('#modal_a.show', wait: 5)

      within '#modal_a' do
        click_button 'Submit'
      end

      # Should redirect via Turbo Stream
      expect(page).to have_current_path(index_path, wait: 10)
    end
  end
end

# spec/features/second_modal_spec.rb
RSpec.feature 'Second Modal Test', type: :feature do
  let(:resource_b) { create(:resource_b) }

  before do
    visit resource_b_path(resource_b)
  end

  describe 'modal with turbo stream redirect', js: true do
    scenario 'submits form and redirects' do
      # Open modal
      find('#open-modal-b').click

      # Wait for modal to appear
      expect(page).to have_css('#modal_b.show', wait: 5)

      within '#modal_b' do
        click_button 'Submit'
      end

      # FAILS HERE in headless mode when run after first test
      expect(page).to have_current_path(index_path, wait: 10)
      # Error: expected "/resource_b/123" to equal "/index"
    end
  end
end

Controller Code (Turbo Stream Response)

def destroy
  @resource.destroy

  respond_to do |format|
    format.turbo_stream do
      render turbo_stream: turbo_stream.action(:redirect, index_path)
    end
  end
end

Capybara Configuration

Capybara.register_driver :apparition do |app|
  Capybara::Apparition::Driver.new(app, {
    headless: true,  # Tests fail when true, pass when false
    window_size: [1400, 1400],
    browser_options: {
      'no-sandbox' => nil,
      'disable-gpu' => nil,
      'disable-dev-shm-usage' => nil
    },
    inspector: true,
    js_errors: true
  })
end

Capybara.javascript_driver = :apparition
Capybara.default_max_wait_time = 5

What We've Tried

  1. ✅ Adding explicit waits for modals and redirects
  2. ✅ Using unique modal IDs to prevent conflicts
  3. ✅ Adding window.location.reload(true) between tests
  4. ✅ Using requestSubmit() directly on forms
  5. ✅ Adding cleanup after blocks
  6. ✅ Capturing camera/subscription IDs before deletion

None of these workarounds fixed the issue in headless mode.

Current Workaround

We're currently using headless: false as a workaround, but this is not ideal for CI/CD environments where headless execution is required.

headless: ENV.fetch('HEADLESS', 'false') == 'true'

Additional Context

  • Important: The modals open correctly in both tests. The issue is specifically with the form submission/redirect behavior in the second test.
  • The issue only occurs with Turbo Stream form submissions that trigger redirects
  • It appears to be Turbo event handler state pollution specific to headless Chrome
  • Regular form submissions (non-Turbo) work fine
  • The modals work perfectly in development (manual testing)
  • Both tests pass when run with --order defined or individually
  • When examining the second test in headless mode, clicking the submit button appears to do nothing (no form submission is triggered)

Questions

  1. Is there a known issue with Turbo/Stimulus event handlers being cached between page visits in headless mode?
  2. Does Apparition need special cleanup for Turbo-enabled applications?
  3. Is there a way to force a complete browser state reset between tests in headless mode?

Any guidance would be greatly appreciated! Happy to provide more details or a full reproduction repository if needed.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions