teamcapybara / capybara

Acceptance test framework for web applications
http://teamcapybara.github.io/capybara/
MIT License
10.03k stars 1.45k forks source link

NoMethodError: undefined method `gsub' for nil:NilClass #2477

Closed jaynetics closed 3 years ago

jaynetics commented 3 years ago

Meta

Capybara Version: 3.34.0 Driver Information (and browser if relevant): selenium-webdriver 4.0.0beta3 with Firefox 68.10

Expected Behavior

this spec should either pass or not:

# click button to remove labels via JS (with confirm dialog)
click_on 'label remover'
accept_alert
# labels should be gone
expect(page).not_to have_selector('label')

Actual Behavior

the spec mostly passes, but in some rare cases it raises:

NoMethodError: undefined method `gsub' for nil:NilClass

gems/capybara-3.34.0/lib/capybara/selenium/node.rb:18:in `all_text'
gems/capybara-3.34.0/lib/capybara/node/element.rb:60:in `block in text'
gems/capybara-3.34.0/lib/capybara/node/base.rb:77:in `synchronize'
gems/capybara-3.34.0/lib/capybara/node/element.rb:60:in `text'
gems/capybara-3.34.0/lib/capybara/result.rb:119:in `map'
gems/capybara-3.34.0/lib/capybara/result.rb:119:in `failure_message'
gems/capybara-3.34.0/lib/capybara/result.rb:130:in `negative_failure_message'
gems/capybara-3.34.0/lib/capybara/node/matchers.rb:236:in `block in assert_no_selector'
gems/capybara-3.34.0/lib/capybara/node/matchers.rb:844:in `block in _verify_selector_result'
gems/capybara-3.34.0/lib/capybara/node/base.rb:83:in `synchronize'
gems/capybara-3.34.0/lib/capybara/node/matchers.rb:843:in `_verify_selector_result'
gems/capybara-3.34.0/lib/capybara/node/matchers.rb:234:in `assert_no_selector'
gems/capybara-3.34.0/lib/capybara/session.rb:762:in `assert_no_selector'
gems/capybara-3.34.0/lib/capybara/rspec/matchers/have_selector.rb:22:in `element_does_not_match?'
gems/capybara-3.34.0/lib/capybara/rspec/matchers/base.rb:58:in `does_not_match?'
gems/rspec-expectations-3.10.1/lib/rspec/expectations/handler.rb:90:in `does_not_match?'
gems/rspec-expectations-3.10.1/lib/rspec/expectations/handler.rb:79:in `block in handle_matcher'
gems/rspec-expectations-3.10.1/lib/rspec/expectations/handler.rb:27:in `with_matcher'
gems/rspec-expectations-3.10.1/lib/rspec/expectations/handler.rb:76:in `handle_matcher'
gems/rspec-expectations-3.10.1/lib/rspec/expectations/expectation_target.rb:78:in `not_to'
the_spec.rb:10:in `block (2 levels) in <top (required)>'

Steps to reproduce

this is hard to reproduce reliably, because it seems to be a race condition of miliseconds.

i think this is what happens in the error case:

the DOM element is still there when #assert_no_selector happens, but it's gone when #failure_message tries to get the element text to generate a nice detailed failure message as follows:

# lib/capybara/result.rb:118
message << ", found #{count} #{Capybara::Helpers.declension('match', 'matches', count)}: " \
        << full_results.map(&:text).map(&:inspect).join(', ')
twalpole commented 3 years ago

Your thoughts on the issue are definitely possible, I will take a look. In the meantime to make your test more reliable - since the labels do appear to be vanishing as the error message is being built it sounds like you need to increase the maximum wait time in your expectation, and you really should be using the block form of accept_confirm (the blockless form is driver specific and will stop working at some point)

accept_confirm do
  # click button to remove labels and trigger confirm dialog
  click_on 'label remover'
end
# labels should be gone
expect(page).not_to have_selector('label', wait: 5) # increase the maximum wait time enough for the labels to reliably be removed
twalpole commented 3 years ago

Fixed via 76912ce0e8746072162b15cf7fc8894805aac917