Skip to content

Commit 3d9c699

Browse files
committed
Enable rack_test driver to reload nodes if stale
1 parent 8930306 commit 3d9c699

File tree

7 files changed

+71
-10
lines changed

7 files changed

+71
-10
lines changed

lib/capybara/node/base.rb

+11-4
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,18 @@ def synchronize(seconds = nil, errors: nil)
8383
yield
8484
rescue StandardError => err
8585
session.raise_server_error!
86-
raise err unless driver.wait? && catch_error?(err, errors)
87-
raise err if timer.expired?
86+
raise err unless catch_error?(err, errors)
8887

89-
sleep(0.01)
90-
reload if session_options.automatic_reload
88+
if driver.wait?
89+
raise err if timer.expired?
90+
91+
sleep(0.01)
92+
reload if session_options.automatic_reload
93+
else
94+
old_base = @base
95+
reload if session_options.automatic_reload
96+
raise err if old_base == @base
97+
end
9198
retry
9299
ensure
93100
session.synchronized = false

lib/capybara/rack_test/driver.rb

+4
Original file line numberDiff line numberDiff line change
@@ -102,4 +102,8 @@ def post(*args, &block); browser.post(*args, &block); end
102102
def put(*args, &block); browser.put(*args, &block); end
103103
def delete(*args, &block); browser.delete(*args, &block); end
104104
def header(key, value); browser.header(key, value); end
105+
106+
def invalid_element_errors
107+
[ Capybara::RackTest::Errors::StaleElementReferenceError ]
108+
end
105109
end

lib/capybara/rack_test/errors.rb

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# frozen_string_literal: true
2+
3+
module Capybara::RackTest::Errors
4+
class StaleElementReferenceError < StandardError
5+
end
6+
end

lib/capybara/rack_test/node.rb

+18-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# frozen_string_literal: true
22

3+
require 'capybara/rack_test/errors'
4+
35
class Capybara::RackTest::Node < Capybara::Driver::Node
46
BLOCK_ELEMENTS = %w[p h1 h2 h3 h4 h5 h6 ol ul pre address blockquote dl div fieldset form hr noscript table].freeze
57

@@ -106,18 +108,27 @@ def path
106108
native.path
107109
end
108110

109-
def find_xpath(locator)
111+
def find_xpath(locator, **_hints)
110112
native.xpath(locator).map { |el| self.class.new(driver, el) }
111113
end
112114

113-
def find_css(locator)
115+
def find_css(locator, **_hints)
114116
native.css(locator, Capybara::RackTest::CSSHandlers.new).map { |el| self.class.new(driver, el) }
115117
end
116118

119+
self.public_instance_methods(false).each do |meth_name|
120+
alias_method "unchecked_#{meth_name}", meth_name
121+
private "unchecked_#{meth_name}"
122+
define_method meth_name do |*args|
123+
stale_check
124+
send("unchecked_#{meth_name}", *args)
125+
end
126+
end
127+
117128
def ==(other)
118129
native == other.native
119130
end
120-
131+
121132
protected
122133

123134
# @api private
@@ -141,6 +152,10 @@ def displayed_text(check_ancestor: true)
141152

142153
private
143154

155+
def stale_check
156+
raise Capybara::RackTest::Errors::StaleElementReferenceError unless native.document == driver.dom
157+
end
158+
144159
def deselect_options
145160
select_node.find_xpath('.//option[@selected]').each { |node| node.native.remove_attribute('selected') }
146161
end

lib/capybara/selenium/extensions/find.rb

+3-3
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,9 @@ def es_context
7777
def is_displayed_atom # rubocop:disable Naming/PredicateName
7878
@@is_displayed_atom ||= begin
7979
browser.send(:bridge).send(:read_atom, 'isDisplayed')
80-
rescue StandardError
81-
# If the atom doesn't exist or other error
82-
""
80+
rescue StandardError
81+
# If the atom doesn't exist or other error
82+
''
8383
end
8484
end
8585
end

lib/capybara/spec/session/within_spec.rb

+23
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,29 @@
2626
end
2727
expect(@session).to have_content('Bar')
2828
end
29+
30+
it 'should reload the node if the page is changed' do
31+
@session.within(:css, '#for_foo') do
32+
@session.visit('/with_scope_other')
33+
expect(@session).to have_content('Different text')
34+
end
35+
end
36+
37+
it 'should reload multiple nodes if the page is changed' do
38+
@session.within(:css, '#for_bar') do
39+
@session.within(:css, 'form[action="/redirect"]') do
40+
@session.refresh
41+
expect(@session).to have_content('First Name')
42+
end
43+
end
44+
end
45+
46+
it 'should error if the page is changed and a matching node no longer exists' do
47+
@session.within(:css, '#for_foo') do
48+
@session.visit('/')
49+
expect { @session.text }.to raise_error(StandardError)
50+
end
51+
end
2952
end
3053

3154
context 'with XPath selector' do
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
<h1>This page is used for testing various scopes</h1>
3+
4+
<p id="for_foo">
5+
Different text same wrapper id
6+
</p>

0 commit comments

Comments
 (0)