teamcapybara/capybara · error · ArgumentError

Invalid option(s) #{invalid_names}, should be one of #{valid

Error message

Invalid option(s) #{invalid_names}, should be one of #{valid_names}

What it means

BaseQuery#assert_valid_keys raises ArgumentError listing the offending keys and the permitted ones whenever a query receives option keys outside valid_keys. For SelectorQuery, valid_keys are the spatial keys (above/below/left_of/right_of/near), COUNT_KEYS (count/minimum/maximum/between), text/id/class/style/visible/obscured/exact/exact_text/normalize_ws/match/wait/filter_set/focused, plus the chosen selector's own filter options — so a typo or an option the selector does not define fails fast at query construction, before any waiting happens.

Source

Thrown at lib/capybara/queries/base_query.rb:102

        elsif maximum
          message << " at most #{occurrences maximum}"
        elsif minimum
          message << " at least #{occurrences minimum}"
        end
        message
      end

      def occurrences(count)
        "#{count} #{Capybara::Helpers.declension('time', 'times', count)}"
      end

      def assert_valid_keys
        invalid_keys = @options.keys - valid_keys
        return if invalid_keys.empty?

        invalid_names = invalid_keys.map(&:inspect).join(', ')
        valid_names = valid_keys.map(&:inspect).join(', ')
        raise ArgumentError, "Invalid option(s) #{invalid_names}, should be one of #{valid_names}"
      end
    end
  end
end

View on GitHub (pinned to 15b5fdb76e)

Solutions

  1. Diff the invalid names from the message against the valid list it prints and fix the typo
  2. Use the selector whose built-in filters cover the option (e.g. :field supports checked/disabled/placeholder) instead of :css
  3. Check the Capybara version you run against its documented option list; upgrade or drop newer-only options
  4. Pass node filters through a filter block if you need custom matching: find('div') { |n| n[:role] == 'navigation' }

Example fix

# before
find('a', visble: true, role: 'button')

# after
find('a', visible: true) { |n| n[:role] == 'button' }
# or use a selector with the filter built in
find(:link_or_button, 'Save', visible: true)
Defensive patterns

Strategy: validation

Validate before calling

ALLOWED = %i[above below left_of right_of near count minimum maximum between text id class style visible obscured exact exact_text normalize_ws match wait filter_set focused]
opts.slice!(*ALLOWED) # drop unknown keys before calling find/all

Type guard

def valid_query_opts?(opts, selector = :css)
  allowed = %i[above below left_of right_of near count minimum maximum between text id class style visible obscured exact exact_text normalize_ws match wait filter_set focused]
  (opts.keys - allowed).empty?
end

Try / catch

begin
  find('a', **opts)
rescue ArgumentError => e
  raise unless e.message.start_with?('Invalid option(s)')
  # message lists invalid vs valid keys — strip and retry once
  bad = e.message[/Invalid option..s. (.*), should be/, 1].to_s.split(', ').map { |s| s.delete(':"') }.map(&:to_sym)
  find('a', **opts.except(*bad))
end

Prevention

When it happens

Trigger: find('a', visble: true) (typo); page.assert_selector('div', role: 'navigation') when the css selector defines no role filter; passing a filter that only exists on another selector, e.g. find(:css, 'input', checked: true) instead of find(:field, ..., checked: true); count: 'many' (value type is not checked here, but unknown keys are); options renamed or removed when upgrading Capybara.

Common situations: Typos in option names, copying filter options between selector types, using options introduced in a newer Capybara on an older version (e.g. obscured/focused), passing driver-specific options into query options.

Related errors


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