teamcapybara/capybara · error · ArgumentError

You must provide a frame element, :parent, or :top when call

Error message

You must provide a frame element, :parent, or :top when calling switch_to_frame

What it means

switch_to_frame accepts exactly three argument kinds: a Capybara::Node::Element for a located frame, :parent, and :top. Anything else - a String frame name/id, an Integer index, nil - reaches the else branch and raises ArgumentError immediately, before any driver call.

Source

Thrown at lib/capybara/session.rb:432

        if scopes.last != :frame
          raise Capybara::ScopeError, "`switch_to_frame(:parent)` cannot be called from inside a descendant frame's " \
                                      '`within` block.'
        end
        scopes.pop
        driver.switch_to_frame(:parent)
      when :top
        idx = scopes.index(:frame)
        top_level_scopes = [:frame, nil]
        if idx
          if scopes.slice(idx..).any? { |scope| !top_level_scopes.include?(scope) }
            raise Capybara::ScopeError, "`switch_to_frame(:top)` cannot be called from inside a descendant frame's " \
                                        '`within` block.'
          end
          scopes.slice!(idx..)
          driver.switch_to_frame(:top)
        end
      else
        raise ArgumentError, 'You must provide a frame element, :parent, or :top when calling switch_to_frame'
      end
    end

    ##
    #
    # Execute the given block within the given iframe using given frame, frame name/id or index.
    # May not be supported by all drivers.
    #
    # @overload within_frame(element)
    #   @param [Capybara::Node::Element]  frame element
    # @overload within_frame([kind = :frame], locator, **options)
    #   @param [Symbol] kind      Optional selector type (:frame, :css, :xpath, etc.) - Defaults to :frame
    #   @param [String] locator   The locator for the given selector kind.  For :frame this is the name/id of a frame/iframe element
    # @overload within_frame(index)
    #   @param [Integer] index         index of a frame (0 based)
    def within_frame(*args, **kw_args)
      switch_to_frame(_find_frame(*args, **kw_args))
      begin

View on GitHub (pinned to 15b5fdb76e)

Solutions

  1. Use within_frame for locating by name/id/index: within_frame('payment_iframe') { ... } or within_frame(0) { ... }
  2. If switching manually, find the element first: switch_to_frame(find(:frame, 'payment_iframe'))
  3. Remember only :parent/:top symbols and frame elements are valid for the manual API

Example fix

# before
switch_to_frame('payment_iframe') # ArgumentError

# after
within_frame('payment_iframe') { fill_in 'Card number', with: '4242' }
# or manual:
switch_to_frame(find(:frame, 'payment_iframe'))
Defensive patterns

Strategy: type-guard

Validate before calling

switch_to_frame(find(:frame, 'payment_iframe')) if 'payment_iframe'.is_a?(String)

Type guard

def frame_target?(arg)
  arg.is_a?(Capybara::Node::Element) || %i[parent top].include?(arg)
end

switch_to_frame(arg) if frame_target?(arg)

Try / catch

begin
  switch_to_frame(arg)
rescue ArgumentError
  within_frame(arg) { yield } # fall back to the locating API
end

Prevention

When it happens

Trigger: switch_to_frame('payment_iframe') (frame name as String); switch_to_frame(0) (index); switch_to_frame(nil); passing a locator string that would have worked with within_frame.

Common situations: Old Capybara 1.x habits where frame names were strings; reaching for switch_to_frame when within_frame(name_or_index) is the locating API; passing find results for non-frame elements.

Related errors


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