teamcapybara/capybara · error · Capybara::Ambiguous

Ambiguous match, found #{result.size} elements matching #{qu

Error message

Ambiguous match, found #{result.size} elements matching #{query.applied_description}

What it means

Finders#find (via synced_resolve) raises Capybara::Ambiguous when more than one element matches and the query's match mode is :one or :smart (smart is the default). With :smart, find first tries an exact locator match and only falls back to partial matching, so the error means even the exact pass returned multiple elements. It is the counterpart of ElementNotFound: one locator must resolve to exactly one element.

Source

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

      #
      def first(*args, **options, &optional_filter_block)
        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

View on GitHub (pinned to 15b5fdb76e)

Solutions

  1. Scope the search to a unique container: find('#user-1').find('a', text: 'Delete')
  2. Make the locator exact and unique: find('a.exact-class', text: 'Delete', exact_text: true)
  3. Accept the first match explicitly: find('a', text: 'Delete', match: :first) or all('a', text: 'Delete').first
  4. Pick by index/attributes only when order is stable: all('a', text: 'Delete')[1]

Example fix

# before
find('a', text: 'Delete').click  # two Delete links exist

# after
find('#row-42').find('a', text: 'Delete').click
# or
all('a', text: 'Delete').first.click
Defensive patterns

Strategy: validation

Validate before calling

matches = page.all('a', text: 'Delete')
raise 'ambiguous: scope the selector' if matches.size > 1
matches.first&.click

Type guard

def unique_match?(node, *args, **opts, &block)
  node.all(*args, **opts, &block).size == 1
end

Try / catch

begin
  find('a', text: 'Delete')
rescue Capybara::Ambiguous => e
  all('a', text: 'Delete').first # deliberate first-match policy
end

Prevention

When it happens

Trigger: find('a', text: 'Delete') on a page with two 'Delete' links (smart mode tries exact text first, still finds 2); find('li') on a multi-item list; find('button') with multiple buttons under match: :one; find(field: 'Name') when both a label and an aria-label produce separate matches.

Common situations: Repeated UI components (rows each containing an Edit button), icons with identical accessible names, test data duplicated in the DOM, switching default_match to :one globally and having previously-tolerated multiple matches now fail.

Related errors


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