capybara 3.37.1 → 3.38.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|