SeleniumHQ/selenium · error · ArgumentError

:capabilities parameter only accepts objects responding to #

Error message

:capabilities parameter only accepts objects responding to #as_json which #{cap.class} does not

What it means

Raised by Remote::Driver#generate_capabilities when an element of the :capabilities array does not respond to #as_json. The method iterates the array, resolves Symbols to Options via WebDriver::Options.send(cap), and calls cap.as_json on everything else to produce the JSON capabilities payload; objects without as_json cannot be serialized for the W3C new-session request.

Source

Thrown at rb/lib/selenium/webdriver/remote/driver.rb:85

        end

        def process_options(options, capabilities)
          if options && capabilities
            msg = "Don't use both :options and :capabilities when initializing #{self.class}, prefer :options"
            raise ArgumentError, msg
          elsif options.nil? && capabilities.nil?
            raise ArgumentError, "#{self.class} needs :options to be set"
          end
          options ? options.as_json : generate_capabilities(capabilities)
        end

        def generate_capabilities(capabilities)
          Array(capabilities).map { |cap|
            if cap.is_a? Symbol
              cap = WebDriver::Options.send(cap)
            elsif !cap.respond_to? :as_json
              msg = ":capabilities parameter only accepts objects responding to #as_json which #{cap.class} does not"
              raise ArgumentError, msg
            end
            cap.as_json
          }.inject(:merge)
        end
      end # Driver
    end # Remote
  end # WebDriver
end # Selenium

View on GitHub (pinned to aa36b38e69)

Solutions

  1. Use Symbols that map to Options: capabilities: [:chrome] (resolved via WebDriver::Options.send).
  2. Use Options or Capabilities objects that implement as_json: capabilities: [Selenium::WebDriver::Options.chrome].
  3. If passing custom objects, ensure they implement a valid #as_json method returning a Hash.
  4. Migrate to the :options parameter which is type-checked and clearer.

Example fix

# before
driver = Selenium::WebDriver.for(:remote, url: url,
  capabilities: [{browserName: 'chrome'}])  # raw hash may fail depending on JSON gem

# after
driver = Selenium::WebDriver.for(:remote, url: url,
  options: Selenium::WebDriver::Options.chrome)
Defensive patterns

Strategy: type-guard

Validate before calling

# Validate each capability object before passing:
caps_array.each do |cap|
  next if cap.is_a?(Symbol)
  raise ArgumentError, "#{cap.class} does not respond to #as_json" unless cap.respond_to?(:as_json)
end

Type guard

def valid_capabilities_array?(arr)
  arr.all? { |c| c.is_a?(Symbol) || c.respond_to?(:as_json) }
end

Try / catch

begin
  Selenium::WebDriver.for(:remote, url: url, capabilities: caps)
rescue ArgumentError
  # fall back to :options with a single Options object
  Selenium::WebDriver.for(:remote, url: url, options: Selenium::WebDriver::Options.chrome)
end

Prevention

When it happens

Trigger: Passing capabilities: [some_string] or capabilities: [an_integer]. Passing a raw Hash inside the array (Hash does respond to as_json via the JSON gem, so this typically triggers for truly non-serializable objects). Passing a custom class instance that doesn't implement as_json. Passing a Capabilities object from an incompatible library version.

Common situations: Using the legacy :capabilities array API with objects that were valid in Selenium 3 but whose as_json contract changed. Passing driver-specific config objects instead of Options/Capabilities. Mixed-up capability arrays containing strings parsed from config files.

Related errors


AI-assisted analysis of SeleniumHQ/selenium@aa36b38e69 (2026-08-14). Data as JSON: /api/errors/6595c620ff5557be. Report an issue: GitHub.