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
- Match the invalid name from the message against the selector's filter list and fix the typo - e.g. :placaholder/:placeholder_text -> :placeholder.
- Use the correct built-in key for the intent: :wait for timing (not :timeout), :count/:minimum/:maximum/:between for occurrence counts, :text for content.
- Confirm the filter exists for the selector kind you passed it to (the :field selector accepts :placeholder, a raw :css/'...' query does not).
- 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
- Centralize query options in typed helpers so stray keys (e.g. :timeout) are caught at one place with a clear message.
- Check the selector's documentation (rdoc for Capybara::Selector) for its filter list before adding new options.
- After Capybara upgrades, run the suite once loudly - renamed filters surface through this error.
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
- Invalid option #{match.inspect} for :match, should be one of
- #{@type} is not a valid type for a text query
- :cols must be an Array of Arrays
- redirected more than #{driver.redirect_limit} times, check f
- rack-test requires a rack application, but none was given
AI-assisted analysis of teamcapybara/capybara@15b5fdb76e (2026-08-21).
Data as JSON: /api/errors/c61b7833252294f2.
Report an issue: GitHub.