capybara 3.37.1 → 3.38.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +34 -4
  3. data/README.md +23 -11
  4. data/lib/capybara/helpers.rb +5 -1
  5. data/lib/capybara/node/base.rb +2 -1
  6. data/lib/capybara/queries/base_query.rb +2 -2
  7. data/lib/capybara/queries/selector_query.rb +4 -2
  8. data/lib/capybara/queries/text_query.rb +1 -1
  9. data/lib/capybara/rack_test/browser.rb +8 -2
  10. data/lib/capybara/rack_test/form.rb +29 -7
  11. data/lib/capybara/registrations/servers.rb +17 -9
  12. data/lib/capybara/selector/definition.rb +1 -1
  13. data/lib/capybara/selector/filter_set.rb +4 -5
  14. data/lib/capybara/selector/regexp_disassembler.rb +2 -5
  15. data/lib/capybara/selenium/driver.rb +3 -0
  16. data/lib/capybara/selenium/extensions/html5_drag.rb +2 -4
  17. data/lib/capybara/selenium/logger_suppressor.rb +4 -0
  18. data/lib/capybara/selenium/node.rb +52 -15
  19. data/lib/capybara/selenium/nodes/firefox_node.rb +2 -2
  20. data/lib/capybara/selenium/nodes/safari_node.rb +2 -2
  21. data/lib/capybara/server/animation_disabler.rb +20 -20
  22. data/lib/capybara/server/middleware.rb +1 -1
  23. data/lib/capybara/session/config.rb +3 -1
  24. data/lib/capybara/session.rb +11 -9
  25. data/lib/capybara/spec/session/attach_file_spec.rb +6 -0
  26. data/lib/capybara/spec/session/check_spec.rb +1 -0
  27. data/lib/capybara/spec/session/current_scope_spec.rb +1 -1
  28. data/lib/capybara/spec/session/fill_in_spec.rb +6 -0
  29. data/lib/capybara/spec/session/find_spec.rb +1 -1
  30. data/lib/capybara/spec/session/has_ancestor_spec.rb +2 -2
  31. data/lib/capybara/spec/session/has_button_spec.rb +6 -0
  32. data/lib/capybara/spec/session/has_link_spec.rb +6 -0
  33. data/lib/capybara/spec/session/has_select_spec.rb +6 -0
  34. data/lib/capybara/spec/session/has_text_spec.rb +4 -8
  35. data/lib/capybara/spec/session/node_spec.rb +24 -1
  36. data/lib/capybara/spec/session/reset_session_spec.rb +13 -0
  37. data/lib/capybara/spec/session/within_spec.rb +13 -0
  38. data/lib/capybara/spec/spec_helper.rb +8 -2
  39. data/lib/capybara/spec/test_app.rb +25 -6
  40. data/lib/capybara/spec/views/form.erb +13 -0
  41. data/lib/capybara/spec/views/with_html.erb +2 -2
  42. data/lib/capybara/spec/views/with_scope.erb +2 -2
  43. data/lib/capybara/version.rb +1 -1
  44. data/lib/capybara.rb +4 -2
  45. data/spec/capybara_spec.rb +12 -0
  46. data/spec/counter_spec.rb +35 -0
  47. data/spec/dsl_spec.rb +2 -0
  48. data/spec/minitest_spec.rb +4 -0
  49. data/spec/minitest_spec_spec.rb +4 -0
  50. data/spec/per_session_config_spec.rb +1 -1
  51. data/spec/rack_test_spec.rb +8 -0
  52. data/spec/rspec/shared_spec_matchers.rb +1 -1
  53. data/spec/rspec_spec.rb +2 -2
  54. data/spec/selector_spec.rb +2 -2
  55. data/spec/selenium_spec_chrome.rb +2 -0
  56. data/spec/selenium_spec_chrome_remote.rb +4 -2
  57. data/spec/selenium_spec_edge.rb +2 -0
  58. data/spec/selenium_spec_firefox.rb +11 -5
  59. data/spec/selenium_spec_firefox_remote.rb +4 -2
  60. data/spec/selenium_spec_ie.rb +3 -1
  61. data/spec/selenium_spec_safari.rb +2 -0
  62. data/spec/shared_selenium_session.rb +3 -4
  63. metadata +3 -2
@@ -23,14 +23,14 @@ module Capybara
23
23
  end
24
24
 
25
25
  def call(env)
26
- @status, @headers, @body = @app.call(env)
27
- return [@status, @headers, @body] unless html_content?
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([], @status, @headers)
29
+ nonces = directive_nonces(headers).transform_values { |nonce| "nonce=\"#{nonce}\"" if nonce && !nonce.empty? }
30
+ response = Rack::Response.new([], status, headers)
31
31
 
32
- @body.each { |html| response.write insert_disable(html, nonces) }
33
- @body.close if @body.respond_to?(:close)
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?(@headers['Content-Type'])
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
- @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
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
@@ -14,7 +14,7 @@ module Capybara
14
14
  end
15
15
 
16
16
  def decrement(uri)
17
- @mutex.synchronize { @value.delete_at(@value.index(uri) || @value.length) }
17
+ @mutex.synchronize { @value.delete_at(@value.index(uri) || - 1) }
18
18
  end
19
19
 
20
20
  def positive?
@@ -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
@@ -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
- # needed to get the cause set correctly in JRuby -- otherwise we could just do raise @server.error
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 be_kind_of Capybara::Node::Document
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, '#ancestor1_sibiling')
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, '#ancestor1_sibiling')
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(@session).to have_text(nil)
160
- end
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).not_to be_nil
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 = false
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
- %(<pre id="results">#{params[:form].merge('post_count' => self.class.form_post_count).to_yaml}</pre>)
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
- 'Successfully ignored empty file field.'
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="first_invisble" class="hidden">first hidden link</a>
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="ancestor1_sibiling">
141
+ <div id="ancestor1_sibling">
142
142
  ASibling
143
143
  </div>
144
144
  </div>
@@ -35,8 +35,8 @@
35
35
  </ul>
36
36
  </div>
37
37
 
38
- <div id="another_foo">
38
+ <div id="another_foo" onclick="this.innerHTML = 'I was clicked';">
39
39
  <ul>
40
- <li>With Simple HTML: <a href="/">Go</a>
40
+ <li>With Simple HTML: <a href="/">Go</a></li>
41
41
  </ul>
42
42
  </div>
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Capybara
4
- VERSION = '3.37.1'
4
+ VERSION = '3.38.0'
5
5
  end
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 = 'false') - Whether click offsets should be from element center (true) or top left (false)
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 = false
519
+ config.w3c_click_offset = true
518
520
  end
@@ -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