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.
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