teamcapybara/capybara · error · Capybara::ScopeError

`switch_to_window` is not supposed to be invoked from `withi

Error message

`switch_to_window` is not supposed to be invoked from `within` or `within_frame` blocks.

What it means

switch_to_window permanently changes which window subsequent finders query, so Capybara forbids it while a within or within_frame scope is active (scopes.last non-nil) and raises Capybara::ScopeError naming those blocks as the cause.

Source

Thrown at lib/capybara/session.rb:517

    #   If window that matches block can't be found, the window will be switched back and {Capybara::WindowError} will be raised.
    #   @example
    #     window = switch_to_window { title == 'Page title' }
    #   @raise [Capybara::WindowError]     if no window matches given block
    # @overload switch_to_window(window)
    #   @param window [Capybara::Window]   window that should be switched to
    #   @raise [Capybara::Driver::Base#no_such_window_error] if nonexistent (e.g. closed) window was passed
    #
    # @return [Capybara::Window]         window that has been switched to
    # @raise [Capybara::ScopeError]        if this method is invoked inside {#within} or
    #   {#within_frame} methods
    # @raise [ArgumentError]               if both or neither arguments were provided
    #
    def switch_to_window(window = nil, **options, &window_locator)
      raise ArgumentError, '`switch_to_window` can take either a block or a window, not both' if window && window_locator
      raise ArgumentError, '`switch_to_window`: either window or block should be provided' if !window && !window_locator

      unless scopes.last.nil?
        raise Capybara::ScopeError, '`switch_to_window` is not supposed to be invoked from ' \
                                    '`within` or `within_frame` blocks.'
      end

      _switch_to_window(window, **options, &window_locator)
    end

    ##
    # This method does the following:
    #
    # 1. Switches to the given window (it can be located by window instance/lambda/string).
    # 2. Executes the given block (within window located at previous step).
    # 3. Switches back (this step will be invoked even if an exception occurs at the second step).
    #
    # @overload within_window(window) { do_something }
    #   @param window [Capybara::Window]       instance of {Capybara::Window} class
    #     that will be switched to
    #   @raise [driver#no_such_window_error] if nonexistent (e.g. closed) window was passed
    # @overload within_window(proc_or_lambda) { do_something }

View on GitHub (pinned to 15b5fdb76e)

Solutions

  1. Move the switch outside the within block, then re-scope: switch_to_window(w); within('.modal') { ... }
  2. Use within_window(w) { ... } for temporary window scoping - it restores the original window on exit
  3. Restructure page objects so window switches happen at top level, never inside scoped helpers

Example fix

# before
within('.modal') do
  switch_to_window(chat_window) # Capybara::ScopeError
end

# after
switch_to_window(chat_window)
within('.modal') { click_button 'Send' }
Defensive patterns

Strategy: validation

Validate before calling

switch_to_window(chat_window) if page.send(:scopes).last.nil?

Try / catch

begin
  switch_to_window(w)
rescue Capybara::ScopeError
  within_window(w) { yield } # temporary scope instead of a permanent switch
end

Prevention

When it happens

Trigger: within('.modal') { switch_to_window(chat_window) }; within_frame { switch_to_window { title == 'Chat' } }; page-object methods called from inside a scoped block that switch windows.

Common situations: Popups triggered from scoped sections (clicking a button within a modal opens a new window); feature flows mixing within with window management; helpers that switch windows reused from scoped contexts.

Related errors


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