Updated: Capybara: Quick checking for element presence (without retries or timeout)

Posted . Visible to the public. Auto-destruct in 60 days

I've extended the existing card with more examples of the same pitfall where Capybara spends much time waiting for nothing than needed.

Changes

  • Element finding is a central feature of Capybara. Since `#find` is normally used to get elements from the current page and interact with them, it's a good thing that some Capybara drivers (e.g. Selenium) will wait an amount of time until the expected element shows up. But if Capybara cannot `#find` it at all, you'll get an error.
  • ```ruby
  • if page.find('.that-element')
  • # Do something
  • else
  • # Never happens because #find raises
  • end
  • ```
  • -In order to simply check whether an element is present, without errors raised, you can use `#has_css?`. It will return a Boolean – but will wait as well. If the element _does not_ exist, it will take the configured `Capybara.default_wait_time` for this check to return `false`, which is usually several seconds.
  • +In order to simply check whether an element is present, without errors raised, you can use `#has_css?`. It will return a Boolean – but will wait as well. If the element *does not* exist, it will take the configured `Capybara.default_max_wait_time` for this check to return `false`, which is usually several seconds.
  • +
  • +The same goes for the other direction and for `#all`:
  • +
  • +- `has_no_css?` returns `true` as soon as the element is gone, but `false` only after the full wait time if the element is there.
  • +- Since Capybara 3, `all` waits for a first match, so `all('.that-element').empty?` takes the full wait time whenever nothing matches.
  • +
  • +In a shared helper that branches on the state of the page, this quickly adds up to minutes per test run.
  • +
  • The solution is to disable the waiting for just that check:
  • ```ruby
  • if page.has_css?('.that-element', wait: 0)
  • # Do something
  • else
  • # Do something else
  • end
  • +
  • +page.has_no_css?('.that-element', wait: 0)
  • +page.all('.that-element', wait: false).empty?
  • ```
  • -> [important]
  • +> [!IMPORTANT]
  • > If you opt-out of Capybara's retry mechanism you need to retry yourself or your test will be flaky.
  • -Note the `wait: 0` argument. Actually there is [a host of options](https://www.rubydoc.info/github/jnicklas/capybara/Capybara/Node/Finders#all-instance_method) worth knowing (scroll down to the "Options Hash" section).
  • +..or use a pre-existing matcher depending on your use case:
  • +
  • +```rb
  • +expect(page).to have_no_css('.that-element') # ✅ returns as soon as the element is gone
  • +expect(page.has_css?('.that-element')).to eq(false) # ❌ waits the full time whenever it passes
  • +```
  • +
  • +Note the `wait: 0` argument. Actually there is [a host of options](https://www.rubydoc.info/github/jnicklas/capybara/Capybara/Node/Finders#all-instance_method) worth knowing (scroll down to the "Options Hash" section).
Profile picture of Michael Leimstädtner
Michael Leimstädtner
License
Source code in this card is licensed under the MIT License.
Posted by Michael Leimstädtner to makandra dev (2026-10-19 11:47)