Jump to content

Connect Leeroopedia MCP: Equip your AI agents to search best practices, build plans, verify code, diagnose failures, and look up hyperparameter defaults.

Heuristic:Teamcapybara Capybara Async Waiting And Retry

From Leeroopedia
Knowledge Sources
Domains Testing, Synchronization, Optimization
Last Updated 2026-02-12 06:00 GMT

Overview

Tuning `default_max_wait_time` and `default_retry_interval` to balance test speed against flakiness in asynchronous web applications.

Description

Capybara's `synchronize` method implements a retry loop that catches specific exceptions (primarily `ElementNotFound` and driver-specific stale element errors) and re-executes the block until either it succeeds or the timeout expires. The two key configuration parameters are `default_max_wait_time` (default: 2 seconds) and `default_retry_interval` (default: 0.01 seconds). These defaults represent a balance between fast test execution and tolerance for asynchronous page updates. The retry interval of 10ms means Capybara checks roughly 100 times per second, while the 2-second timeout provides a generous window for most AJAX operations.

Usage

Apply this heuristic when you are experiencing flaky tests due to timing issues, or when you need to optimize test suite execution time. Increase `default_max_wait_time` for slow CI environments or applications with heavy AJAX. Decrease it for fast applications to speed up test failure detection. Override per-query with the `wait:` option for specific slow operations.

The Insight (Rule of Thumb)

  • Action: Configure `Capybara.default_max_wait_time` based on your application's typical async response time. Use per-query `wait:` overrides for known slow operations.
  • Value: Default is 2 seconds / 0.01 second retry interval. Slow apps may need 5-10 seconds. Fast apps can reduce to 1 second.
  • Trade-off: Higher wait time = fewer flaky tests but slower failure detection. Lower wait time = faster failures but more flakiness.
  • Key insight: The RackTest driver's `wait?` returns `false`, bypassing the retry loop entirely — this is why RackTest is faster but cannot handle async content.
  • Override pattern: `find(:css, '#slow-element', wait: 10)` for individual slow operations without changing global timeout.

Reasoning

The synchronize loop in `Node::Base` is Capybara's primary defence against asynchronicity problems (as stated in the source code documentation). It works on the principle that transient failures (element not yet rendered, element briefly removed during re-render) will resolve themselves within a short time window. The 0.01-second retry interval keeps CPU usage low while providing rapid responsiveness. The automatic reload feature (`automatic_reload = true` by default) means that during retries, stale element references are automatically refreshed by re-querying the DOM.

For drivers that do not support async processes (like RackTest), the synchronize method skips the retry loop entirely. This design means the same test code works across both sync and async drivers, but behavior differs: with RackTest, failures are immediate; with Selenium, failures wait up to `default_max_wait_time`.

Code Evidence

Configuration defaults from `lib/capybara.rb:80-82`:

# - **default_max_wait_time** (Numeric = `2`) - The maximum number of seconds to wait for asynchronous processes to finish.
# - **default_normalize_ws** (Boolean = `false`) - Whether text predicates and matchers use normalize whitespace behavior.
# - **default_retry_interval** (Numeric = `0.01`) - The number of seconds to delay the next check in asynchronous processes.

Core synchronize loop from `lib/capybara/node/base.rb:76-102`:

def synchronize(seconds = nil, errors: nil)
  return yield if session.synchronized

  seconds = session_options.default_max_wait_time if [nil, true].include? seconds
  interval = session_options.default_retry_interval
  session.synchronized = true
  timer = Capybara::Helpers.timer(expire_in: seconds)
  begin
    yield
  rescue StandardError => e
    session.raise_server_error!
    raise e unless catch_error?(e, errors)

    if driver.wait?
      raise e if timer.expired?
      sleep interval
      reload if session_options.automatic_reload
    else
      old_base = @base
      reload if session_options.automatic_reload
      raise e if old_base == @base
    end
    retry
  ensure
    session.synchronized = false
  end
end

Retryable error detection from `lib/capybara/node/base.rb:134-136`:

def catch_error?(error, errors = nil)
  errors ||= (driver.invalid_element_errors + [Capybara::ElementNotFound])
  errors.any? { |type| error.is_a?(type) }
end

Related Pages

Page Connections

Double-click a node to navigate. Hold to expand connections.
Principle
Implementation
Heuristic
Environment