#2850·capybara

`has_text?` intermittently raises Chrome "Node with given id does not belong to the document" after form navigation

Author: jcoyneCreated Aug 31, 2026Updated Aug 31, 2026

Environment

Capybara versions:

  • 3.40.0
  • Current master at 15b5fdb76e972e9623d5af2123ed3755594f9732

Driver information:

  • selenium-webdriver 4.48.0
  • Chrome/ChromeDriver 152.0.7977.65
  • Headless Chrome

Runtime information:

  • Ruby 4.0.1
  • macOS on arm64

We have also seen the same inspector error in GitHub Actions with Ruby 4.0.6, selenium-webdriver 4.48.0, and Chrome 151.0.7922.173 on Linux.

Expected Behavior

After clicking a form submit button that redirects to another page, session.has_text? should wait for the replacement document and return true when the expected text is present.

If Chrome reports the transient inspector error below while replacing the document, Capybara should retry the query within default_max_wait_time:

unknown error: unhandled inspector error: {"code":-32000,"message":"Node with given id does not belong to the document"}

Actual Behavior

session.has_text? intermittently raises Selenium::WebDriver::Error::UnknownError instead of retrying.

The failure occurs while Capybara is checking the text or visibility of an element selected by the page-wide text query. Immediately after catching the error, the reproducer reports:

Current URL: http://127.0.0.1:<port>/groups
Document readyState: complete
Alerts in DOM: 1
  visible=true html="<div class=\"alert\">The browse group was created.</div>"

This reproduced with the released Capybara 3.40.0 and with the current master revision above. In one local run, the released version failed on attempt 25; the pinned master revision failed on attempt 1.

The relevant part of the backtrace from master is:

selenium-webdriver-4.48.0/lib/selenium/webdriver/remote/response.rb:63:in `add_cause'
selenium-webdriver-4.48.0/lib/selenium/webdriver/remote/bridge.rb:465:in `element_displayed?'
selenium-webdriver-4.48.0/lib/selenium/webdriver/common/element.rb:253:in `displayed?'
capybara/lib/capybara/selenium/node.rb:187:in `visible?'
capybara/lib/capybara/selenium/nodes/chrome_node.rb:59:in `visible?'
capybara/lib/capybara/node/element.rb:306:in `block in visible?'
capybara/lib/capybara/node/base.rb:77:in `synchronize'
capybara/lib/capybara/queries/selector_query.rb:572:in `matches_visibility_filters?'
capybara/lib/capybara/queries/selector_query.rb:122:in `matches_filters?'
capybara/lib/capybara/result.rb:32:in `block in initialize'

The underlying race may be in Chrome/ChromeDriver. However, Capybara master already contains a message-specific retry for this error in ChromeNode, and the error still escapes from this page-wide query path.

Steps to reproduce

Save the script below as reproduce.rb and run:

bash
ruby reproduce.rb

The race is intermittent. The script performs up to 100 attempts and exits on the first failure. Running it again or increasing MAX_ATTEMPTS may be needed.

Standalone reproducer
ruby
#!/usr/bin/env ruby
# frozen_string_literal: true

require 'bundler/inline'
require 'uri'

$stdout.sync = true
$stderr.sync = true

gemfile(true) do
  source 'https://rubygems.org'
  gem 'capybara',
      github: 'teamcapybara/capybara',
      ref: '15b5fdb76e972e9623d5af2123ed3755594f9732'
  gem 'puma'
  gem 'selenium-webdriver', '4.48.0'
end

require 'capybara'
require 'selenium-webdriver'

class NavigationRaceApp
  def call(environment)
    request = Rack::Request.new(environment)

    case [request.request_method, request.path_info]
    when ['GET', '/new']
      html_response(<<~HTML)
        <!doctype html>
        <html lang="en">
          <body>
            <form action="/groups" method="post">
              <input type="submit" value="Save">
            </form>
          </body>
        </html>
      HTML
    when ['POST', '/groups']
      [302, { 'location' => '/groups', 'content-type' => 'text/html' }, []]
    when ['GET', '/groups']
      html_response(<<~HTML)
        <!doctype html>
        <html lang="en">
          <body>
            <div class="alert">The browse group was created.</div>
          </body>
        </html>
      HTML
    else
      [404, { 'content-type' => 'text/plain' }, ['Not found']]
    end
  end

  private

  def html_response(body)
    [200, { 'content-type' => 'text/html; charset=utf-8' }, [body]]
  end
end

Capybara.register_driver :selenium_chrome_headless do |app|
  options = Selenium::WebDriver::Chrome::Options.new
  options.add_argument('--headless')
  options.add_argument('--window-size=1920,1080')

  Capybara::Selenium::Driver.new(app, browser: :chrome, options: options)
end

Capybara.default_max_wait_time = 2
Capybara.server = :puma, { Silent: true }

session = Capybara::Session.new(:selenium_chrome_headless, NavigationRaceApp.new)
max_attempts = ENV.fetch('MAX_ATTEMPTS', '100').to_i

begin
  session.visit '/new'
  capabilities = session.driver.browser.capabilities
  puts "Ruby #{RUBY_VERSION}"
  puts "Capybara #{Capybara::VERSION}"
  puts "Selenium #{Selenium::WebDriver::VERSION}"
  puts "#{capabilities.browser_name} #{capabilities.browser_version}"

  1.upto(max_attempts) do |attempt|
    puts "Attempt #{attempt}"
    session.visit '/new'
    session.click_button 'Save'

    next if session.has_text?('The browse group was created.')

    warn 'has_text? returned false'
    exit 1
  rescue Selenium::WebDriver::Error::WebDriverError => error
    warn error.full_message
    warn "Current URL: #{session.current_url}"
    warn "Document readyState: #{session.driver.browser.execute_script('return document.readyState')}"

    alert = session.find('.alert', visible: :all, wait: 0)
    warn "Alert visible after failure: #{alert.visible?}"
    warn "Alert HTML after failure: #{alert.native.attribute('outerHTML')}"
    exit 1
  end
ensure
  session.quit
end

Related issue

This appears related to #2800, especially the report that page-wide text queries use a Document path not covered by the Chrome-specific handling: https://github.com/teamcapybara/capybara/issues/2800#issuecomment-4980740951

#2800 was closed with a request to open a new issue when a reproducible case was available: https://github.com/teamcapybara/capybara/issues/2800#issuecomment-4919275427