teamcapybara/capybara · error · Capybara::WindowError
block passed to #window_opened_by opened #{opened_handles.si
Error message
block passed to #window_opened_by opened #{opened_handles.size} windows instead of 1 What it means
window_opened_by snapshots driver.window_handles, runs the block, then - retrying until the wait time expires - requires exactly one new handle. Zero or multiple new windows raise Capybara::WindowError reporting the observed count. The retry loop means slow popups eventually resolve, but a popup that never appears or a burst of several windows is a hard failure.
Source
Thrown at lib/capybara/session.rb:588
# It will wait for it to be opened (in the same way as other Capybara methods wait).
# It's better to use this method than `windows.last`
# {https://dvcs.w3.org/hg/webdriver/raw-file/default/webdriver-spec.html#h_note_10 as order of windows isn't defined in some drivers}.
#
# @overload window_opened_by(**options, &block)
# @param options [Hash]
# @option options [Numeric] :wait maximum wait time. Defaults to {Capybara.configure default_max_wait_time}
# @return [Capybara::Window] the window that has been opened within a block
# @raise [Capybara::WindowError] if block passed to window hasn't opened window
# or opened more than one window
#
def window_opened_by(**options)
old_handles = driver.window_handles
yield
synchronize_windows(options) do
opened_handles = (driver.window_handles - old_handles)
if opened_handles.size != 1
raise Capybara::WindowError, 'block passed to #window_opened_by ' \
"opened #{opened_handles.size} windows instead of 1"
end
Window.new(self, opened_handles.first)
end
end
##
#
# Execute the given script, not returning a result. This is useful for scripts that return
# complex objects, such as jQuery statements. {#execute_script} should be used over
# {#evaluate_script} whenever possible.
#
# @param [String] script A string of JavaScript to execute
# @param args Optional arguments that will be passed to the script. Driver support for this is optional and types of objects supported may differ between drivers
#
def execute_script(script, *args)
@touched = true
driver.execute_script(script, *driver_args(args))View on GitHub (pinned to 15b5fdb76e)
Solutions
- Verify the action really opens a window (run visibly in a real browser; assert the trigger element exists and the click fired)
- Raise the wait: window_opened_by(wait: 5) { click_link 'Share' }
- If several windows legitimately open, diff manually: snapshot page.windows before/after and select the one you need
- For target=_blank links or window.open in the app, make sure driver options are not blocking popups
Example fix
# before
window_opened_by { click_link 'Tweet' } # WindowError: opened 0 windows
# after
window = window_opened_by(wait: 5) { click_link 'Tweet' }
within_window(window) { click_link 'Authorize' } Defensive patterns
Strategy: retry
Validate before calling
before_handles = page.driver.window_handles
click_link 'Share'
new_handles = page.driver.window_handles - before_handles
switch_to_window(page.windows.find { |w| new_handles.include?(w.handle) }) if new_handles.size == 1 Try / catch
begin
window_opened_by(wait: Capybara.default_max_wait_time) { click_link 'Share' }
rescue Capybara::WindowError => e
raise "#{e.message} - open windows: #{page.windows.map(&:title).inspect}"
end Prevention
- Pass wait: explicitly for OAuth/popup flows
- Assert the trigger element exists and is clickable before the block
- For multi-window flows, use manual handle diffing instead of window_opened_by
When it happens
Trigger: window_opened_by { click_link 'Share' } where the click does not open a window (popup blocked, JS error, wrong or overlapped element), or the action opens several tabs at once; OAuth/SSO popups that exceed default_max_wait_time.
Common situations: Headless Chrome with the click intercepted by an overlay or popups effectively blocked; popup opening after the default wait (slow third-party JS); app code legitimately opening two windows; clicks matching the wrong element so nothing happens.
Related errors
- Could not find a window matching block/lambda
- `switch_to_window` can take either a block or a window, not
- `switch_to_window`: either window or block should be provide
- `switch_to_window` is not supposed to be invoked from `withi
- `#within_window` requires a `Capybara::Window` instance or a
AI-assisted analysis of teamcapybara/capybara@15b5fdb76e (2026-08-21).
Data as JSON: /api/errors/82d9397c0d215d7b.
Report an issue: GitHub.