capybara 3.37.1 → 3.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/History.md +34 -4
- data/README.md +23 -11
- data/lib/capybara/helpers.rb +5 -1
- data/lib/capybara/node/base.rb +2 -1
- data/lib/capybara/queries/base_query.rb +2 -2
- data/lib/capybara/queries/selector_query.rb +4 -2
- data/lib/capybara/queries/text_query.rb +1 -1
- data/lib/capybara/rack_test/browser.rb +8 -2
- data/lib/capybara/rack_test/form.rb +29 -7
- data/lib/capybara/registrations/servers.rb +17 -9
- data/lib/capybara/selector/definition.rb +1 -1
- data/lib/capybara/selector/filter_set.rb +4 -5
- data/lib/capybara/selector/regexp_disassembler.rb +2 -5
- data/lib/capybara/selenium/driver.rb +3 -0
- data/lib/capybara/selenium/extensions/html5_drag.rb +2 -4
- data/lib/capybara/selenium/logger_suppressor.rb +4 -0
- data/lib/capybara/selenium/node.rb +52 -15
- data/lib/capybara/selenium/nodes/firefox_node.rb +2 -2
- data/lib/capybara/selenium/nodes/safari_node.rb +2 -2
- data/lib/capybara/server/animation_disabler.rb +20 -20
- data/lib/capybara/server/middleware.rb +1 -1
- data/lib/capybara/session/config.rb +3 -1
- data/lib/capybara/session.rb +11 -9
- data/lib/capybara/spec/session/attach_file_spec.rb +6 -0
- data/lib/capybara/spec/session/check_spec.rb +1 -0
- data/lib/capybara/spec/session/current_scope_spec.rb +1 -1
- data/lib/capybara/spec/session/fill_in_spec.rb +6 -0
- data/lib/capybara/spec/session/find_spec.rb +1 -1
- data/lib/capybara/spec/session/has_ancestor_spec.rb +2 -2
- data/lib/capybara/spec/session/has_button_spec.rb +6 -0
- data/lib/capybara/spec/session/has_link_spec.rb +6 -0
- data/lib/capybara/spec/session/has_select_spec.rb +6 -0
- data/lib/capybara/spec/session/has_text_spec.rb +4 -8
- data/lib/capybara/spec/session/node_spec.rb +24 -1
- data/lib/capybara/spec/session/reset_session_spec.rb +13 -0
- data/lib/capybara/spec/session/within_spec.rb +13 -0
- data/lib/capybara/spec/spec_helper.rb +8 -2
- data/lib/capybara/spec/test_app.rb +25 -6
- data/lib/capybara/spec/views/form.erb +13 -0
- data/lib/capybara/spec/views/with_html.erb +2 -2
- data/lib/capybara/spec/views/with_scope.erb +2 -2
- data/lib/capybara/version.rb +1 -1
- data/lib/capybara.rb +4 -2
- data/spec/capybara_spec.rb +12 -0
- data/spec/counter_spec.rb +35 -0
- data/spec/dsl_spec.rb +2 -0
- data/spec/minitest_spec.rb +4 -0
- data/spec/minitest_spec_spec.rb +4 -0
- data/spec/per_session_config_spec.rb +1 -1
- data/spec/rack_test_spec.rb +8 -0
- data/spec/rspec/shared_spec_matchers.rb +1 -1
- data/spec/rspec_spec.rb +2 -2
- data/spec/selector_spec.rb +2 -2
- data/spec/selenium_spec_chrome.rb +2 -0
- data/spec/selenium_spec_chrome_remote.rb +4 -2
- data/spec/selenium_spec_edge.rb +2 -0
- data/spec/selenium_spec_firefox.rb +11 -5
- data/spec/selenium_spec_firefox_remote.rb +4 -2
- data/spec/selenium_spec_ie.rb +3 -1
- data/spec/selenium_spec_safari.rb +2 -0
- data/spec/shared_selenium_session.rb +3 -4
- metadata +3 -2
@@ -23,14 +23,14 @@ module Capybara
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def call(env)
|
26
|
-
|
27
|
-
return [
|
26
|
+
status, headers, body = @app.call(env)
|
27
|
+
return [status, headers, body] unless html_content?(headers)
|
28
28
|
|
29
|
-
nonces = directive_nonces.transform_values { |nonce| "nonce=\"#{nonce}\"" if nonce && !nonce.empty? }
|
30
|
-
response = Rack::Response.new([],
|
29
|
+
nonces = directive_nonces(headers).transform_values { |nonce| "nonce=\"#{nonce}\"" if nonce && !nonce.empty? }
|
30
|
+
response = Rack::Response.new([], status, headers)
|
31
31
|
|
32
|
-
|
33
|
-
|
32
|
+
body.each { |html| response.write insert_disable(html, nonces) }
|
33
|
+
body.close if body.respond_to?(:close)
|
34
34
|
|
35
35
|
response.finish
|
36
36
|
end
|
@@ -39,8 +39,8 @@ module Capybara
|
|
39
39
|
|
40
40
|
attr_reader :disable_css_markup, :disable_js_markup
|
41
41
|
|
42
|
-
def html_content?
|
43
|
-
/html/.match?(
|
42
|
+
def html_content?(headers)
|
43
|
+
/html/.match?(headers['Content-Type'])
|
44
44
|
end
|
45
45
|
|
46
46
|
def insert_disable(html, nonces)
|
@@ -48,18 +48,18 @@ module Capybara
|
|
48
48
|
.sub(%r{(</body>)}, "<script #{nonces['script-src']}>#{disable_js_markup}</script>\\1")
|
49
49
|
end
|
50
50
|
|
51
|
-
def directive_nonces
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
51
|
+
def directive_nonces(headers)
|
52
|
+
headers.fetch('Content-Security-Policy', '')
|
53
|
+
.split(';')
|
54
|
+
.map(&:split)
|
55
|
+
.to_h do |s|
|
56
|
+
[
|
57
|
+
s[0], s[1..].filter_map do |value|
|
58
|
+
/^'nonce-(?<nonce>.+)'/ =~ value
|
59
|
+
nonce
|
60
|
+
end[0]
|
61
|
+
]
|
62
|
+
end
|
63
63
|
end
|
64
64
|
|
65
65
|
DISABLE_CSS_MARKUP_TEMPLATE = <<~CSS
|
@@ -8,7 +8,7 @@ module Capybara
|
|
8
8
|
automatic_reload match exact exact_text raise_server_errors visible_text_only
|
9
9
|
automatic_label_click enable_aria_label save_path asset_host default_host app_host
|
10
10
|
server_host server_port server_errors default_set_options disable_animation test_id
|
11
|
-
predicates_wait default_normalize_ws w3c_click_offset enable_aria_role].freeze
|
11
|
+
predicates_wait default_normalize_ws w3c_click_offset enable_aria_role default_retry_interval].freeze
|
12
12
|
|
13
13
|
attr_accessor(*OPTIONS)
|
14
14
|
|
@@ -21,6 +21,8 @@ module Capybara
|
|
21
21
|
# See {Capybara.configure}
|
22
22
|
# @!method default_max_wait_time
|
23
23
|
# See {Capybara.configure}
|
24
|
+
# @!method default_retry_interval
|
25
|
+
# See {Capybara.configure}
|
24
26
|
# @!method ignore_hidden_elements
|
25
27
|
# See {Capybara.configure}
|
26
28
|
# @!method automatic_reload
|
data/lib/capybara/session.rb
CHANGED
@@ -40,6 +40,7 @@ module Capybara
|
|
40
40
|
|
41
41
|
NODE_METHODS = %i[
|
42
42
|
all first attach_file text check choose scroll_to scroll_by
|
43
|
+
click double_click right_click
|
43
44
|
click_link_or_button click_button click_link
|
44
45
|
fill_in find find_all find_button find_by_id find_field find_link
|
45
46
|
has_content? has_text? has_css? has_no_content? has_no_text?
|
@@ -58,7 +59,7 @@ module Capybara
|
|
58
59
|
].freeze
|
59
60
|
SESSION_METHODS = %i[
|
60
61
|
body html source current_url current_host current_path
|
61
|
-
execute_script evaluate_script visit refresh go_back go_forward send_keys
|
62
|
+
execute_script evaluate_script evaluate_async_script visit refresh go_back go_forward send_keys
|
62
63
|
within within_element within_fieldset within_table within_frame switch_to_frame
|
63
64
|
current_window windows open_new_window switch_to_window within_window window_opened_by
|
64
65
|
save_page save_and_open_page save_screenshot
|
@@ -129,6 +130,8 @@ module Capybara
|
|
129
130
|
if @touched
|
130
131
|
driver.reset!
|
131
132
|
@touched = false
|
133
|
+
switch_to_frame(:top) rescue nil # rubocop:disable Style/RescueModifier
|
134
|
+
@scopes = [nil]
|
132
135
|
end
|
133
136
|
@server&.wait_for_pending_requests
|
134
137
|
raise_server_error!
|
@@ -159,9 +162,8 @@ module Capybara
|
|
159
162
|
if config.raise_server_errors
|
160
163
|
raise CapybaraError, 'Your application server raised an error - It has been raised in your test code because Capybara.raise_server_errors == true'
|
161
164
|
end
|
162
|
-
rescue CapybaraError
|
163
|
-
|
164
|
-
raise @server.error, @server.error.message, @server.error.backtrace
|
165
|
+
rescue CapybaraError => capy_error # rubocop:disable Naming/RescuedExceptionsVariableName
|
166
|
+
raise @server.error, cause: capy_error
|
165
167
|
ensure
|
166
168
|
@server.reset_error!
|
167
169
|
end
|
@@ -360,7 +362,7 @@ module Capybara
|
|
360
362
|
new_scope = args.first.respond_to?(:to_capybara_node) ? args.first.to_capybara_node : find(*args, **kw_args)
|
361
363
|
begin
|
362
364
|
scopes.push(new_scope)
|
363
|
-
yield if block_given?
|
365
|
+
yield new_scope if block_given?
|
364
366
|
ensure
|
365
367
|
scopes.pop
|
366
368
|
end
|
@@ -409,7 +411,7 @@ module Capybara
|
|
409
411
|
scopes.push(:frame)
|
410
412
|
when :parent
|
411
413
|
if scopes.last != :frame
|
412
|
-
raise Capybara::ScopeError, "`switch_to_frame(:parent)` cannot be called from inside a descendant frame's "\
|
414
|
+
raise Capybara::ScopeError, "`switch_to_frame(:parent)` cannot be called from inside a descendant frame's " \
|
413
415
|
'`within` block.'
|
414
416
|
end
|
415
417
|
scopes.pop
|
@@ -419,7 +421,7 @@ module Capybara
|
|
419
421
|
top_level_scopes = [:frame, nil]
|
420
422
|
if idx
|
421
423
|
if scopes.slice(idx..).any? { |scope| !top_level_scopes.include?(scope) }
|
422
|
-
raise Capybara::ScopeError, "`switch_to_frame(:top)` cannot be called from inside a descendant frame's "\
|
424
|
+
raise Capybara::ScopeError, "`switch_to_frame(:top)` cannot be called from inside a descendant frame's " \
|
423
425
|
'`within` block.'
|
424
426
|
end
|
425
427
|
scopes.slice!(idx..)
|
@@ -511,7 +513,7 @@ module Capybara
|
|
511
513
|
raise ArgumentError, '`switch_to_window`: either window or block should be provided' if !window && !window_locator
|
512
514
|
|
513
515
|
unless scopes.last.nil?
|
514
|
-
raise Capybara::ScopeError, '`switch_to_window` is not supposed to be invoked from '\
|
516
|
+
raise Capybara::ScopeError, '`switch_to_window` is not supposed to be invoked from ' \
|
515
517
|
'`within` or `within_frame` blocks.'
|
516
518
|
end
|
517
519
|
|
@@ -582,7 +584,7 @@ module Capybara
|
|
582
584
|
synchronize_windows(options) do
|
583
585
|
opened_handles = (driver.window_handles - old_handles)
|
584
586
|
if opened_handles.size != 1
|
585
|
-
raise Capybara::WindowError, 'block passed to #window_opened_by '\
|
587
|
+
raise Capybara::WindowError, 'block passed to #window_opened_by ' \
|
586
588
|
"opened #{opened_handles.size} windows instead of 1"
|
587
589
|
end
|
588
590
|
Window.new(self, opened_handles.first)
|
@@ -55,6 +55,12 @@ Capybara::SpecHelper.spec '#attach_file' do
|
|
55
55
|
expect(@session).to have_content('No file uploaded')
|
56
56
|
end
|
57
57
|
|
58
|
+
it 'should send prior hidden field if no file submitted' do
|
59
|
+
@session.click_button('Upload Empty With Hidden')
|
60
|
+
expect(extract_results(@session)['document2']).to eq('hidden_field')
|
61
|
+
expect(extract_content_type(@session)).to start_with('multipart/form-data;')
|
62
|
+
end
|
63
|
+
|
58
64
|
it 'should send content type text/plain when uploading a text file' do
|
59
65
|
@session.attach_file 'Single Document', with_os_path_separators(test_file_path)
|
60
66
|
@session.click_button 'Upload Single'
|
@@ -239,6 +239,7 @@ Capybara::SpecHelper.spec '#check' do
|
|
239
239
|
|
240
240
|
context 'with allow_label_click options', requires: [:js] do
|
241
241
|
it 'should allow offsets to click location on label' do
|
242
|
+
Capybara.w3c_click_offset = false
|
242
243
|
expect(@session.find(:checkbox, 'form_cars_lotus', unchecked: true, visible: :hidden)).to be_truthy
|
243
244
|
@session.check('form_cars_lotus', allow_label_click: { x: 90, y: 10 })
|
244
245
|
@session.click_button('awesome')
|
@@ -7,7 +7,7 @@ Capybara::SpecHelper.spec '#current_scope' do
|
|
7
7
|
|
8
8
|
context 'when not in a #within block' do
|
9
9
|
it 'should return the document' do
|
10
|
-
expect(@session.current_scope).to
|
10
|
+
expect(@session.current_scope).to be_a Capybara::Node::Document
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
@@ -98,6 +98,12 @@ Capybara::SpecHelper.spec '#fill_in' do
|
|
98
98
|
expect(extract_results(@session)['description']).to eq("\r\nSome text\r\n")
|
99
99
|
end
|
100
100
|
|
101
|
+
it 'should handle carriage returns with line feeds in a textarea correctly' do
|
102
|
+
@session.fill_in('form_description', with: "\r\nSome text\r\n")
|
103
|
+
@session.click_button('awesome')
|
104
|
+
expect(extract_results(@session)['description']).to eq("\r\nSome text\r\n")
|
105
|
+
end
|
106
|
+
|
101
107
|
it 'should fill in a color field' do
|
102
108
|
@session.fill_in('Html5 Color', with: '#112233')
|
103
109
|
@session.click_button('html5_submit')
|
@@ -528,7 +528,7 @@ Capybara::SpecHelper.spec '#find' do
|
|
528
528
|
expect(@session.find(:link, 'test-foo')[:id]).to eq 'foo'
|
529
529
|
end
|
530
530
|
end
|
531
|
-
|
531
|
+
|
532
532
|
it 'should warn if passed count options' do
|
533
533
|
allow(Capybara::Helpers).to receive(:warn)
|
534
534
|
@session.find('//h1', count: 44)
|
@@ -39,8 +39,8 @@ Capybara::SpecHelper.spec '#have_no_ancestor' do
|
|
39
39
|
it 'should assert no matching ancestor' do
|
40
40
|
el = @session.find(:css, '#ancestor1')
|
41
41
|
expect(el).to have_no_ancestor(:css, '#child')
|
42
|
-
expect(el).to have_no_ancestor(:css, '#
|
42
|
+
expect(el).to have_no_ancestor(:css, '#ancestor1_sibling')
|
43
43
|
expect(el).not_to have_ancestor(:css, '#child')
|
44
|
-
expect(el).not_to have_ancestor(:css, '#
|
44
|
+
expect(el).not_to have_ancestor(:css, '#ancestor1_sibling')
|
45
45
|
end
|
46
46
|
end
|
@@ -77,6 +77,12 @@ Capybara::SpecHelper.spec '#has_button?' do
|
|
77
77
|
expect(@session).to have_button('A Button', focused: false)
|
78
78
|
end
|
79
79
|
end
|
80
|
+
|
81
|
+
it 'should raise an error if an invalid option is passed' do
|
82
|
+
expect do
|
83
|
+
expect(@session).to have_button('A Button', invalid: true)
|
84
|
+
end.to raise_error(ArgumentError, 'Invalid option(s) :invalid, should be one of :above, :below, :left_of, :right_of, :near, :count, :minimum, :maximum, :between, :text, :id, :class, :style, :visible, :obscured, :exact, :exact_text, :normalize_ws, :match, :wait, :filter_set, :focused, :disabled, :name, :value, :title, :type')
|
85
|
+
end
|
80
86
|
end
|
81
87
|
|
82
88
|
Capybara::SpecHelper.spec '#has_no_button?' do
|
@@ -36,6 +36,12 @@ Capybara::SpecHelper.spec '#has_link?' do
|
|
36
36
|
expect(@session).to have_link('labore', focused: false)
|
37
37
|
end
|
38
38
|
end
|
39
|
+
|
40
|
+
it 'should raise an error if an invalid option is passed' do
|
41
|
+
expect do
|
42
|
+
expect(@session).to have_link('labore', invalid: true)
|
43
|
+
end.to raise_error(ArgumentError, 'Invalid option(s) :invalid, should be one of :above, :below, :left_of, :right_of, :near, :count, :minimum, :maximum, :between, :text, :id, :class, :style, :visible, :obscured, :exact, :exact_text, :normalize_ws, :match, :wait, :filter_set, :focused, :href, :alt, :title, :download')
|
44
|
+
end
|
39
45
|
end
|
40
46
|
|
41
47
|
Capybara::SpecHelper.spec '#has_no_link?' do
|
@@ -179,6 +179,12 @@ Capybara::SpecHelper.spec '#has_select?' do
|
|
179
179
|
end
|
180
180
|
end
|
181
181
|
|
182
|
+
it 'should raise an error if an invalid option is passed' do
|
183
|
+
expect do
|
184
|
+
expect(@session).to have_select('form_languages', invalid: true)
|
185
|
+
end.to raise_error(ArgumentError, 'Invalid option(s) :invalid, should be one of :above, :below, :left_of, :right_of, :near, :count, :minimum, :maximum, :between, :text, :id, :class, :style, :visible, :obscured, :exact, :exact_text, :normalize_ws, :match, :wait, :filter_set, :focused, :disabled, :name, :placeholder, :options, :enabled_options, :disabled_options, :selected, :with_selected, :multiple, :with_options')
|
186
|
+
end
|
187
|
+
|
182
188
|
it 'should support locator-less usage' do
|
183
189
|
expect(@session.has_select?(with_options: %w[Norway Sweden])).to be true
|
184
190
|
expect(@session).to have_select(with_options: ['London'])
|
@@ -153,16 +153,12 @@ Capybara::SpecHelper.spec '#has_text?' do
|
|
153
153
|
expect(@session).to have_text(42)
|
154
154
|
end
|
155
155
|
|
156
|
-
it 'should be true when passed nil' do
|
156
|
+
it 'should be true when passed nil, and warn about it' do
|
157
157
|
# nil is converted to '' when to_s is invoked
|
158
158
|
@session.visit('/with_html')
|
159
|
-
expect
|
160
|
-
|
161
|
-
|
162
|
-
it 'should warn when passed nil' do
|
163
|
-
@session.visit('/with_html')
|
164
|
-
expect_any_instance_of(Kernel).to receive(:warn).with(/Checking for expected text of nil is confusing/) # rubocop:disable RSpec/AnyInstance
|
165
|
-
expect(@session).to have_text(nil)
|
159
|
+
expect do
|
160
|
+
expect(@session).to have_text(nil)
|
161
|
+
end.to output(/Checking for expected text of nil is confusing/).to_stderr
|
166
162
|
end
|
167
163
|
|
168
164
|
it 'should wait for text to appear', requires: [:js] do
|
@@ -775,6 +775,7 @@ Capybara::SpecHelper.spec 'node' do
|
|
775
775
|
end
|
776
776
|
|
777
777
|
it 'should allow to adjust the click offset', requires: [:js] do
|
778
|
+
Capybara.w3c_click_offset = false
|
778
779
|
@session.visit('with_js')
|
779
780
|
@session.find(:css, '#click-test').click(x: 5, y: 5)
|
780
781
|
link = @session.find(:link, 'has-been-clicked')
|
@@ -901,6 +902,7 @@ Capybara::SpecHelper.spec 'node' do
|
|
901
902
|
end
|
902
903
|
|
903
904
|
it 'should allow to adjust the offset', requires: [:js] do
|
905
|
+
Capybara.w3c_click_offset = false
|
904
906
|
@session.visit('with_js')
|
905
907
|
@session.find(:css, '#click-test').double_click(x: 10, y: 5)
|
906
908
|
link = @session.find(:link, 'has-been-double-clicked')
|
@@ -987,6 +989,7 @@ Capybara::SpecHelper.spec 'node' do
|
|
987
989
|
end
|
988
990
|
|
989
991
|
it 'should allow to adjust the offset', requires: [:js] do
|
992
|
+
Capybara.w3c_click_offset = false
|
990
993
|
@session.visit('with_js')
|
991
994
|
@session.find(:css, '#click-test').right_click(x: 10, y: 10)
|
992
995
|
link = @session.find(:link, 'has-been-right-clicked')
|
@@ -1181,7 +1184,7 @@ Capybara::SpecHelper.spec 'node' do
|
|
1181
1184
|
@session.visit('/with_shadow')
|
1182
1185
|
expect do
|
1183
1186
|
shadow_root = @session.find(:css, '#shadow_host').shadow_root
|
1184
|
-
expect(shadow_root).
|
1187
|
+
expect(shadow_root).to be_a(Capybara::Node::Element)
|
1185
1188
|
end.not_to raise_error
|
1186
1189
|
end
|
1187
1190
|
|
@@ -1216,6 +1219,26 @@ Capybara::SpecHelper.spec 'node' do
|
|
1216
1219
|
end.not_to raise_error
|
1217
1220
|
expect(descendant).to have_checked_field('shadow_checkbox')
|
1218
1221
|
end
|
1222
|
+
|
1223
|
+
it 'should produce error messages when failing' do
|
1224
|
+
@session.visit('/with_shadow')
|
1225
|
+
shadow_root = @session.find(:css, '#shadow_host').shadow_root
|
1226
|
+
expect do
|
1227
|
+
expect(shadow_root).to have_css('#shadow_content', text: 'Not in the document')
|
1228
|
+
end.to raise_error(/tag="ShadowRoot"/)
|
1229
|
+
end
|
1230
|
+
|
1231
|
+
it 'should get visible text' do
|
1232
|
+
@session.visit('/with_shadow')
|
1233
|
+
shadow_root = @session.find(:css, '#shadow_host').shadow_root
|
1234
|
+
expect(shadow_root).to have_text('some text scroll.html')
|
1235
|
+
end
|
1236
|
+
|
1237
|
+
it 'should get all text' do
|
1238
|
+
@session.visit('/with_shadow')
|
1239
|
+
shadow_root = @session.find(:css, '#shadow_host').shadow_root
|
1240
|
+
expect(shadow_root).to have_text(:all, 'some text scroll.html')
|
1241
|
+
end
|
1219
1242
|
end
|
1220
1243
|
|
1221
1244
|
describe '#reload', requires: [:js] do
|
@@ -95,6 +95,19 @@ Capybara::SpecHelper.spec '#reset_session!' do
|
|
95
95
|
expect(@session.windows.size).to eq 1
|
96
96
|
end
|
97
97
|
|
98
|
+
it 'does not block opening a new window after a frame was switched to and not switched back', requires: [:windows] do
|
99
|
+
@session.visit('/with_iframe?id=test_iframe&url=/')
|
100
|
+
@session.switch_to_frame(@session.find(:frame, 'test_iframe'))
|
101
|
+
within_window_test = lambda do
|
102
|
+
@session.within_window(@session.open_new_window) do
|
103
|
+
@session.visit('/')
|
104
|
+
end
|
105
|
+
end
|
106
|
+
expect(&within_window_test).to raise_error(Capybara::ScopeError)
|
107
|
+
@session.reset_session!
|
108
|
+
expect(&within_window_test).not_to raise_error
|
109
|
+
end
|
110
|
+
|
98
111
|
context 'When reuse_server == false' do
|
99
112
|
let!(:orig_reuse_server) { Capybara.reuse_server }
|
100
113
|
|
@@ -49,6 +49,19 @@ Capybara::SpecHelper.spec '#within' do
|
|
49
49
|
expect { @session.text }.to raise_error(StandardError)
|
50
50
|
end
|
51
51
|
end
|
52
|
+
|
53
|
+
it 'should pass scope element to the block' do
|
54
|
+
@session.within(:css, '#another_foo') do |scope|
|
55
|
+
expect(scope).to match_css('#another_foo')
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'should scope click', requires: [:js] do
|
60
|
+
@session.within(:css, '#another_foo') do |scope|
|
61
|
+
@session.click
|
62
|
+
expect(scope).to have_text('I was clicked')
|
63
|
+
end
|
64
|
+
end
|
52
65
|
end
|
53
66
|
|
54
67
|
context 'with XPath selector' do
|
@@ -24,6 +24,7 @@ module Capybara
|
|
24
24
|
Capybara.app_host = nil
|
25
25
|
Capybara.default_selector = :xpath
|
26
26
|
Capybara.default_max_wait_time = 1
|
27
|
+
Capybara.default_retry_interval = 0.01
|
27
28
|
Capybara.ignore_hidden_elements = true
|
28
29
|
Capybara.exact = false
|
29
30
|
Capybara.raise_server_errors = true
|
@@ -37,7 +38,7 @@ module Capybara
|
|
37
38
|
Capybara.predicates_wait = true
|
38
39
|
Capybara.default_normalize_ws = false
|
39
40
|
Capybara.use_html5_parsing = !ENV['HTML5_PARSING'].nil?
|
40
|
-
Capybara.w3c_click_offset =
|
41
|
+
Capybara.w3c_click_offset = true
|
41
42
|
reset_threadsafe
|
42
43
|
end
|
43
44
|
|
@@ -122,6 +123,11 @@ module Capybara
|
|
122
123
|
YAML.safe_load results, permitted_classes: perms
|
123
124
|
end
|
124
125
|
|
126
|
+
def extract_content_type(session)
|
127
|
+
expect(session).to have_xpath("//pre[@id='content_type']")
|
128
|
+
Capybara::HTML(session.body).xpath("//pre[@id='content_type']").first.text
|
129
|
+
end
|
130
|
+
|
125
131
|
def be_an_invalid_element_error(session)
|
126
132
|
satisfy { |error| session.driver.invalid_element_errors.any? { |e| error.is_a? e } }
|
127
133
|
end
|
@@ -132,4 +138,4 @@ module Capybara
|
|
132
138
|
end
|
133
139
|
end
|
134
140
|
|
135
|
-
Dir["#{File.dirname(__FILE__)}/session/**/*.rb"].each { |file| require_relative file }
|
141
|
+
Dir["#{File.dirname(__FILE__)}/session/**/*.rb"].sort.each { |file| require_relative file }
|
@@ -177,6 +177,22 @@ class TestApp < Sinatra::Base
|
|
177
177
|
HTML
|
178
178
|
end
|
179
179
|
|
180
|
+
get '/with_iframe' do
|
181
|
+
<<-HTML
|
182
|
+
<!DOCTYPE html>
|
183
|
+
<html lang="en">
|
184
|
+
<head>
|
185
|
+
<meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
|
186
|
+
<title>Test with Iframe</title>
|
187
|
+
</head>
|
188
|
+
|
189
|
+
<body>
|
190
|
+
<iframe src="#{params[:url]}" id="#{params[:id]}"></iframe>
|
191
|
+
</body>
|
192
|
+
</html>
|
193
|
+
HTML
|
194
|
+
end
|
195
|
+
|
180
196
|
get '/base/with_base' do
|
181
197
|
<<-HTML
|
182
198
|
<!DOCTYPE html>
|
@@ -232,6 +248,10 @@ class TestApp < Sinatra::Base
|
|
232
248
|
'Thomas, Walpole, was , here'
|
233
249
|
end
|
234
250
|
|
251
|
+
get '/apple-touch-icon-precomposed.png' do
|
252
|
+
halt(404)
|
253
|
+
end
|
254
|
+
|
235
255
|
get '/:view' do |view|
|
236
256
|
view_template = "#{__dir__}/views/#{view}.erb"
|
237
257
|
has_layout = File.exist?(view_template) && File.open(view_template) { |f| f.first.downcase.include?('doctype') }
|
@@ -240,12 +260,15 @@ class TestApp < Sinatra::Base
|
|
240
260
|
|
241
261
|
post '/form' do
|
242
262
|
self.class.form_post_count += 1
|
243
|
-
%(
|
263
|
+
%(
|
264
|
+
<pre id="content_type">#{request.content_type}</pre>
|
265
|
+
<pre id="results">#{params.fetch(:form, {}).merge('post_count' => self.class.form_post_count).to_yaml}</pre>
|
266
|
+
)
|
244
267
|
end
|
245
268
|
|
246
269
|
post '/upload_empty' do
|
247
270
|
if params[:form][:file].nil?
|
248
|
-
|
271
|
+
"Successfully ignored empty file field. Content type was #{request.content_type}"
|
249
272
|
else
|
250
273
|
'Something went wrong.'
|
251
274
|
end
|
@@ -272,10 +295,6 @@ class TestApp < Sinatra::Base
|
|
272
295
|
'No files uploaded'
|
273
296
|
end
|
274
297
|
|
275
|
-
get '/apple-touch-icon-precomposed.png' do
|
276
|
-
halt(404)
|
277
|
-
end
|
278
|
-
|
279
298
|
class << self
|
280
299
|
attr_accessor :form_post_count
|
281
300
|
end
|
@@ -586,6 +586,19 @@ New line after and before textarea tag
|
|
586
586
|
<p>
|
587
587
|
</form>
|
588
588
|
|
589
|
+
<form action="/form" method="post" enctype="multipart/form-data">
|
590
|
+
<input type="hidden" name="form[document2]" value="hidden_field"/>
|
591
|
+
|
592
|
+
<p>
|
593
|
+
<label for="form_document">Document with hidden</label>
|
594
|
+
<input type="file" name="form[document2]" id="form_document2"/>
|
595
|
+
</p>
|
596
|
+
|
597
|
+
<p>
|
598
|
+
<input type="submit" value="Upload Empty With Hidden"/>
|
599
|
+
<p>
|
600
|
+
</form>
|
601
|
+
|
589
602
|
<form action="/upload_multiple" method="post" enctype="multipart/form-data">
|
590
603
|
<p>
|
591
604
|
<label for="form_multiple_file_name">File Name</label>
|
@@ -88,7 +88,7 @@ banana</textarea>
|
|
88
88
|
</div>
|
89
89
|
|
90
90
|
<div style="display: none;">
|
91
|
-
<a id="
|
91
|
+
<a id="first_invisible" class="hidden">first hidden link</a>
|
92
92
|
</div>
|
93
93
|
|
94
94
|
<div style="display: none;">
|
@@ -138,7 +138,7 @@ banana</textarea>
|
|
138
138
|
Ancestor
|
139
139
|
<div id="child">Child</div>
|
140
140
|
</div>
|
141
|
-
<div id="
|
141
|
+
<div id="ancestor1_sibling">
|
142
142
|
ASibling
|
143
143
|
</div>
|
144
144
|
</div>
|
data/lib/capybara/version.rb
CHANGED
data/lib/capybara.rb
CHANGED
@@ -79,6 +79,7 @@ module Capybara
|
|
79
79
|
# - **automatic_reload** (Boolean = `true`) - Whether to automatically reload elements as Capybara is waiting.
|
80
80
|
# - **default_max_wait_time** (Numeric = `2`) - The maximum number of seconds to wait for asynchronous processes to finish.
|
81
81
|
# - **default_normalize_ws** (Boolean = `false`) - Whether text predicates and matchers use normalize whitespace behavior.
|
82
|
+
# - **default_retry_interval** (Numeric = `0.01`) - The number of seconds to delay the next check in asynchronous processes.
|
82
83
|
# - **default_selector** (`:css`, `:xpath` = `:css`) - Methods which take a selector use the given type by default. See also {Capybara::Selector}.
|
83
84
|
# - **default_set_options** (Hash = `{}`) - The default options passed to {Capybara::Node::Element#set Element#set}.
|
84
85
|
# - **enable_aria_label** (Boolean = `false`) - Whether fields, links, and buttons will match against `aria-label` attribute.
|
@@ -101,7 +102,7 @@ module Capybara
|
|
101
102
|
# and {configure raise_server_errors} is `true`.
|
102
103
|
# - **test_id** (Symbol, String, `nil` = `nil`) - Optional attribute to match locator against with built-in selectors along with id.
|
103
104
|
# - **threadsafe** (Boolean = `false`) - Whether sessions can be configured individually.
|
104
|
-
# - **w3c_click_offset** (Boolean = '
|
105
|
+
# - **w3c_click_offset** (Boolean = 'true') - Whether click offsets should be from element center (true) or top left (false)
|
105
106
|
#
|
106
107
|
# #### DSL Options
|
107
108
|
#
|
@@ -496,6 +497,7 @@ Capybara.configure do |config|
|
|
496
497
|
config.server = :default
|
497
498
|
config.default_selector = :css
|
498
499
|
config.default_max_wait_time = 2
|
500
|
+
config.default_retry_interval = 0.01
|
499
501
|
config.ignore_hidden_elements = true
|
500
502
|
config.default_host = 'http://www.example.com'
|
501
503
|
config.automatic_reload = true
|
@@ -514,5 +516,5 @@ Capybara.configure do |config|
|
|
514
516
|
config.predicates_wait = true
|
515
517
|
config.default_normalize_ws = false
|
516
518
|
config.use_html5_parsing = false
|
517
|
-
config.w3c_click_offset =
|
519
|
+
config.w3c_click_offset = true
|
518
520
|
end
|
data/spec/capybara_spec.rb
CHANGED
@@ -15,6 +15,18 @@ RSpec.describe Capybara do
|
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
|
+
describe 'default_retry_interval' do
|
19
|
+
before { @previous_default_interval = described_class.default_retry_interval }
|
20
|
+
|
21
|
+
after { described_class.default_retry_interval = @previous_default_interval } # rubocop:disable RSpec/InstanceVariable
|
22
|
+
|
23
|
+
it 'should be changeable' do
|
24
|
+
expect(described_class.default_retry_interval).not_to eq(0.1)
|
25
|
+
described_class.default_retry_interval = 0.1
|
26
|
+
expect(described_class.default_retry_interval).to eq(0.1)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
18
30
|
describe '.register_driver' do
|
19
31
|
it 'should add a new driver' do
|
20
32
|
described_class.register_driver :schmoo do |app|
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe Capybara::Server::Middleware::Counter do
|
6
|
+
let(:counter) { described_class.new }
|
7
|
+
let(:uri) { '/example' }
|
8
|
+
|
9
|
+
describe '#increment' do
|
10
|
+
it 'successfully' do
|
11
|
+
counter.increment(uri)
|
12
|
+
expect(counter).to be_positive
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe '#decrement' do
|
17
|
+
before do
|
18
|
+
counter.increment(uri)
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'successfully' do
|
22
|
+
it 'with same uri' do
|
23
|
+
expect(counter).to be_positive
|
24
|
+
counter.decrement(uri)
|
25
|
+
expect(counter).not_to be_positive
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'with changed uri' do
|
29
|
+
expect(counter).to be_positive
|
30
|
+
counter.decrement('/')
|
31
|
+
expect(counter).not_to be_positive
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|