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

The second check in SelectorQuery#assert_valid_keys rejects any option key the query cannot handle. Valid keys are the built-in ones (VALID_KEYS: :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) plus every node/expression filter registered on the current selector. An unlisted key is almost always a typo'd filter name or a filter that belongs to a different selector.

Source

Thrown at lib/capybara/queries/selector_query.rb:360

        @custom_keys ||= node_filters.keys + expression_filters.keys
      end

      def assert_valid_keys
        unless VALID_MATCH.include?(match)
          raise ArgumentError, "Invalid option #{match.inspect} for :match, should be one of #{VALID_MATCH.map(&:inspect).join(', ')}"
        end

        unhandled_options = @options.keys.reject do |option_name|
          valid_keys.include?(option_name) ||
            expression_filters.any? { |_name, ef| ef.handles_option? option_name } ||
            node_filters.any? { |_name, nf| nf.handles_option? option_name }
        end

        return if unhandled_options.empty?

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

      def filtered_expression(expr)
        conditions = {}
        conditions[:id] = options[:id] if use_default_id_filter?
        conditions[:class] = options[:class] if use_default_class_filter?
        conditions[:style] = options[:style] if use_default_style_filter? && !options[:style].is_a?(Hash)
        builder(expr).add_attribute_conditions(**conditions)
      end

      def use_default_id_filter?
        options.key?(:id) && !custom_keys.include?(:id)
      end

      def use_default_class_filter?
        options.key?(:class) && !custom_keys.include?(:class)
      end

View on GitHub (pinned to 15b5fdb76e)

Solutions

  1. Match the invalid name from the message against the selector's filter list and fix the typo - e.g. :placaholder/:placeholder_text -> :placeholder.
  2. Use the correct built-in key for the intent: :wait for timing (not :timeout), :count/:minimum/:maximum/:between for occurrence counts, :text for content.
  3. Confirm the filter exists for the selector kind you passed it to (the :field selector accepts :placeholder, a raw :css/'...' query does not).
  4. If you genuinely need a new option, register it with Capybara.add_selector or Selector#expression_filter/node_filter so custom_keys covers it.

Example fix

# before
find(:field, 'Email', placaholder: 'Email')

# after
find(:field, 'Email', placeholder: 'Email')
Defensive patterns

Strategy: validation

Validate before calling

options = { text: 'Hi', placeholder: 'Email' }
allowed = %i[text placeholder] # filters that exist for :field
unknown = options.keys - allowed
raise ArgumentError, "unsupported options: #{unknown.inspect}" unless unknown.empty?
find(:field, 'Email', **options)

Type guard

def supported_options?(selector_kind, options)
  filters = Capybara::Selector.new(Capybara.send(:find_selector, selector_kind)).expressions.keys # introspect as needed
  (options.keys - filters).empty?
end

Prevention

When it happens

Trigger: find('.row', text: 'Hi', placaholder: 'Email'); find(:field, 'Email', placeholder_text: 'Email') (the filter is :placeholder); find(:link, href: 'x', disabled: true) where :disabled is not a filter of :link; splatting an options hash with extra keys, e.g. find('.x', **user_params) where user_params contains app-level keys like :timeout.

Common situations: Using :timeout instead of the built-in :wait; passing a filter from one selector to another (e.g. :rows on :css); Capybara upgrades that moved filters into filter_sets or renamed them; custom selectors whose filters were never registered; copy-pasting options from find_field documentation into have_selector calls.

Related errors


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