teamcapybara/capybara · error · Capybara::InfiniteRedirectError

redirected more than #{driver.redirect_limit} times, check f

Error message

redirected more than #{driver.redirect_limit} times, check for infinite redirects.

What it means

The rack_test browser follows HTTP redirects up to driver.redirect_limit times (default 5, see DEFAULT_OPTIONS in lib/capybara/rack_test/driver.rb). 307/308 redirects replay the original method with params; other redirects become GET. If the response is still a redirect after the full budget, Capybara raises Capybara::InfiniteRedirectError. It means either the app is genuinely looping or the chain legitimately exceeds 5 hops.

Source

Thrown at lib/capybara/rack_test/browser.rb:70

  end

  def process_and_follow_redirects(method, path, attributes = {}, env = {})
    @current_fragment = build_uri(path).fragment
    process(method, path, attributes, env)
    return unless driver.follow_redirects?

    driver.redirect_limit.times do
      if last_response.redirect?
        if [307, 308].include? last_response.status
          process(last_request.request_method, last_response['Location'], last_request.params, env)
        else
          process(:get, last_response['Location'], {}, env)
        end
      end
    end

    if last_response.redirect? # rubocop:disable Style/GuardClause
      raise Capybara::InfiniteRedirectError, "redirected more than #{driver.redirect_limit} times, check for infinite redirects."
    end
  end

  def process(method, path, attributes = {}, env = {})
    method = method.downcase
    new_uri = build_uri(path)
    @current_scheme, @current_host, @current_port = new_uri.select(:scheme, :host, :port)
    @current_fragment = new_uri.fragment || @current_fragment
    reset_cache!
    @new_visit_request = false
    send(method, new_uri.to_s, attributes, env.merge(options[:headers] || {}))
  end

  def build_uri(path)
    uri = URI.parse(path)
    base_uri = base_relative_uri_for(uri)

    uri.path = base_uri.path + uri.path unless uri.absolute? || uri.path.start_with?('/')

View on GitHub (pinned to 15b5fdb76e)

Solutions

  1. Reproduce the loop outside Capybara (curl -v or a bare Rack::Test session) and fix the app-side cause - most often the session cookie is lost because the redirect changes host, so pin the test host (e.g. Rack::Test::Session host or Rails default_url_options / Capybara.app_host).
  2. If the chain is legitimately long, raise the limit by registering the driver yourself: Capybara::RackTest::Driver.new(app, redirect_limit: 10).
  3. Check middleware order for redirect loops (SSL, locale, authentication redirects referencing each other).
  4. If the redirect depends on JavaScript (location.href), rack_test cannot execute it - move the spec to a JS driver.

Example fix

# before
Capybara.register_driver :rack_test do |app|
  Capybara::RackTest::Driver.new(app)
end

# after
Capybara.register_driver :rack_test do |app|
  Capybara::RackTest::Driver.new(app, redirect_limit: 10)
end
Defensive patterns

Strategy: try-catch

Try / catch

begin
  visit '/start'
rescue Capybara::InfiniteRedirectError => e
  save_and_open_page # or log last_response status/Location chain
  raise
end

Prevention

When it happens

Trigger: visit '/login' under the rack_test driver where the Rack app responds 302 -> 302 -> ... forever (e.g. session cookie dropped each hop because the host changes, or a before_action bounces between / and /en); or a valid A->B->C->D->E->F chain of 6 redirects with the default redirect_limit: 5.

Common situations: Rack::Test not sending cookies after a redirect to a different host/port in the test environment; Rails force_ssl or host-canonicalization middleware ping-ponging in test config; omniauth/callback flows with long redirect chains under the default limit; an actual app regression that only manifests in integration tests.

Related errors


AI-assisted analysis of teamcapybara/capybara@15b5fdb76e (2026-08-21). Data as JSON: /api/errors/44da23cc33dfb4d4. Report an issue: GitHub.