teamcapybara/capybara · error · Capybara::ElementNotFound

Unable to find #{query.applied_description}

Error message

Unable to find #{query.applied_description}

What it means

The canonical Capybara::ElementNotFound from Finders#find (synced_resolve): after retrying for the query's wait time, the selector plus applied filters (:text, :visible, etc.) resolved to zero elements. The message embeds query.applied_description, e.g. "Unable to find visible css 'a' with text 'Continue'". find is an assertion-grade API: it blocks and retries, then fails hard instead of returning nil.

Source

Thrown at lib/capybara/node/finders.rb:312

        options = { minimum: 1 }.merge(options) unless options_include_minimum?(options)
        all(*args, **options, &optional_filter_block).first
      end

    private

      def synced_resolve(query)
        synchronize(query.wait) do
          if prefer_exact?(query)
            result = query.resolve_for(self, true)
            result = query.resolve_for(self, false) if result.empty? && query.supports_exact? && !query.exact?
          else
            result = query.resolve_for(self)
          end

          if ambiguous?(query, result)
            raise Capybara::Ambiguous, "Ambiguous match, found #{result.size} elements matching #{query.applied_description}"
          end
          raise Capybara::ElementNotFound, "Unable to find #{query.applied_description}" if result.empty?

          result.first
        end.tap(&:allow_reload!)
      end

      def ambiguous?(query, result)
        %i[one smart].include?(query.match) && (result.size > 1)
      end

      def prefer_exact?(query)
        %i[smart prefer_exact].include?(query.match)
      end

      def options_include_minimum?(opts)
        %i[count minimum between].any? { |key| opts.key?(key) }
      end

      def parent

View on GitHub (pinned to 15b5fdb76e)

Solutions

  1. Raise the wait: find('#notice', wait: 10) or Capybara.default_max_wait_time = 5
  2. Copy the exact locator from the failure message and check it against current DOM (save_and_open_page or browser devtools)
  3. Drop or adjust mismatched filters: visible: false for hidden nodes, exact_text: false for partial text
  4. Ensure the element is genuinely exposed first (click the tab, scroll, switch frame via within_frame)

Example fix

# before
find('#status-message').text

# after
find('#status-message', wait: 10).text
# or assert with waiting semantics first
expect(page).to have_css('#status-message', wait: 10)
find('#status-message').text
Defensive patterns

Strategy: retry

Validate before calling

if page.has_selector?('#notice', wait: 10)
  find('#notice').text
else
  save_and_open_page # debug the actual DOM
end

Try / catch

begin
  find('#notice', wait: 10)
rescue Capybara::ElementNotFound => e
  raise unless page.has_selector?('.fallback-notice', wait: 2)
  find('.fallback-notice')
end

Prevention

When it happens

Trigger: find('#notice') before an AJAX action has rendered it; find('a', text: 'Sign out') when the text differs by whitespace/case; find('.item', visible: true) when the element exists but is hidden; a typo'd locator find('#sucess'); find on a page that hasn't finished navigating.

Common situations: Race between test step and async rendering when default_max_wait_time is short (common on slow CI), elements behind tabs/accordions that are display:none until opened, React/Vue re-rendering replacing a node mid-find, iframe scope mistakes (find inside page when the element is in a frame).

Related errors


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