SeleniumHQ/selenium · error · ArgumentError
argument should be a Hash or implement #capabilities
Error message
argument should be a Hash or implement #capabilities
What it means
Raised by Remote::Capabilities#merge! when the argument is neither a Hash nor an object that responds to #capabilities with a Hash result. The method uses respond_to?(:capabilities, true) to detect capabilities-bearing objects and falls back to is_a?(Hash); anything else is rejected to prevent silent corruption of the internal @capabilities hash.
Source
Thrown at rb/lib/selenium/webdriver/remote/capabilities.rb:151
#
# Allows setting arbitrary capabilities.
#
def []=(key, value)
@capabilities[key] = value
end
def [](key)
@capabilities[key]
end
def merge!(other)
if other.respond_to?(:capabilities, true) && other.capabilities.is_a?(Hash)
@capabilities.merge! other.capabilities
elsif other.is_a? Hash
@capabilities.merge! other
else
raise ArgumentError, 'argument should be a Hash or implement #capabilities'
end
end
def proxy
@capabilities[:proxy]
end
def proxy=(proxy)
case proxy
when Hash
@capabilities[:proxy] = Proxy.new(proxy)
when Proxy, nil
@capabilities[:proxy] = proxy
else
raise TypeError, "expected Hash or #{Proxy.name}, got #{proxy.inspect}:#{proxy.class}"
end
end
View on GitHub (pinned to aa36b38e69)
Solutions
- Ensure the argument is a plain Hash: capabilities.merge!({browserName: 'chrome'}).
- If merging another Capabilities object, confirm it responds to #capabilities and that method returns a Hash — or call .capabilities on it explicitly first.
- If you have an Options object, call .as_json or extract its hash form before merging.
- Wrap the argument in a Hash constructor: capabilities.merge!(Hash(other)) only if the object is safely coercible.
Example fix
# before caps.merge!(driver.options) # after caps.merge!(driver.options.as_json)
Defensive patterns
Strategy: validation
Validate before calling
# Validate before merge!: unless other.is_a?(Hash) || (other.respond_to?(:capabilities, true) && other.capabilities.is_a?(Hash)) raise ArgumentError, 'merge! requires a Hash or object with #capabilities returning a Hash' end capabilities.merge!(other)
Type guard
# Check the argument is mergeable before calling: def mergeable?(obj) obj.is_a?(Hash) || (obj.respond_to?(:capabilities, true) && obj.capabilities.is_a?(Hash)) end
Try / catch
begin capabilities.merge!(other) rescue ArgumentError => e # coerce if possible, else report capabilities.merge!(other.to_h) if other.respond_to?(:to_h) end
Prevention
- Always pass a Hash literal or another Capabilities object to merge!.
- If accepting external input, coerce to Hash first: Hash(obj) or obj.to_h.
- Do not pass Options objects to merge! — use .as_json or extract capabilities first.
When it happens
Trigger: Calling capabilities.merge!(some_string), capabilities.merge!(an_array), or capabilities.merge!(a_driver_instance). Passing a struct or Data object that holds capability data but does not expose a #capabilities method. Passing nil or an OpenStruct that lacks #capabilities.
Common situations: Attempting to merge a single key-value pair as a two-element array instead of a Hash. Passing a WebDriver::Options instance expecting it to work (Options does not expose #capabilities — it exposes #as_json). Confusing merge! with the constructor which accepts different shapes. Refactoring that changed what object flows into merge!.
Related errors
- no capabilities provided for merge
- expected Hash or #{Proxy.name}, got #{proxy.inspect}:#{proxy
- Don't use both :options and :capabilities when initializing
- :capabilities parameter only accepts objects responding to #
- Safari does not support options that are not namespaced
AI-assisted analysis of SeleniumHQ/selenium@aa36b38e69 (2026-08-14).
Data as JSON: /api/errors/c55cb92238fed601.
Report an issue: GitHub.