`has_text?` intermittently raises Chrome "Node with given id does not belong to the document" after form navigation
Environment
Capybara versions:
- 3.40.0
- Current
masterat15b5fdb76e972e9623d5af2123ed3755594f9732
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:
ruby reproduce.rbThe 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.
#!/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
endRelated 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
Source: teamcapybara/capybara