capybara 3.36.0 → 3.39.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (121) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +94 -4
  3. data/README.md +24 -12
  4. data/lib/capybara/driver/node.rb +4 -0
  5. data/lib/capybara/dsl.rb +4 -10
  6. data/lib/capybara/helpers.rb +6 -2
  7. data/lib/capybara/minitest/spec.rb +2 -2
  8. data/lib/capybara/node/actions.rb +4 -4
  9. data/lib/capybara/node/base.rb +2 -1
  10. data/lib/capybara/node/element.rb +12 -1
  11. data/lib/capybara/node/finders.rb +10 -1
  12. data/lib/capybara/node/whitespace_normalizer.rb +81 -0
  13. data/lib/capybara/queries/base_query.rb +2 -2
  14. data/lib/capybara/queries/selector_query.rb +24 -9
  15. data/lib/capybara/queries/text_query.rb +1 -1
  16. data/lib/capybara/rack_test/browser.rb +63 -8
  17. data/lib/capybara/rack_test/driver.rb +4 -4
  18. data/lib/capybara/rack_test/form.rb +29 -7
  19. data/lib/capybara/rack_test/node.rb +19 -16
  20. data/lib/capybara/registration_container.rb +0 -3
  21. data/lib/capybara/registrations/drivers.rb +3 -3
  22. data/lib/capybara/registrations/servers.rb +30 -10
  23. data/lib/capybara/rspec/matcher_proxies.rb +3 -3
  24. data/lib/capybara/rspec/matchers/base.rb +8 -6
  25. data/lib/capybara/rspec/matchers/compound.rb +1 -1
  26. data/lib/capybara/rspec/matchers/have_selector.rb +4 -4
  27. data/lib/capybara/rspec/matchers.rb +14 -14
  28. data/lib/capybara/selector/definition/link.rb +2 -1
  29. data/lib/capybara/selector/definition.rb +3 -2
  30. data/lib/capybara/selector/filter_set.rb +4 -5
  31. data/lib/capybara/selector/regexp_disassembler.rb +2 -5
  32. data/lib/capybara/selector/selector.rb +5 -1
  33. data/lib/capybara/selenium/driver.rb +11 -4
  34. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +8 -4
  35. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +1 -1
  36. data/lib/capybara/selenium/extensions/html5_drag.rb +5 -4
  37. data/lib/capybara/selenium/logger_suppressor.rb +6 -2
  38. data/lib/capybara/selenium/node.rb +72 -25
  39. data/lib/capybara/selenium/nodes/chrome_node.rb +5 -1
  40. data/lib/capybara/selenium/nodes/edge_node.rb +24 -2
  41. data/lib/capybara/selenium/nodes/firefox_node.rb +2 -2
  42. data/lib/capybara/selenium/nodes/safari_node.rb +2 -2
  43. data/lib/capybara/selenium/patches/action_pauser.rb +3 -3
  44. data/lib/capybara/selenium/patches/atoms.rb +1 -1
  45. data/lib/capybara/selenium/patches/pause_duration_fix.rb +1 -1
  46. data/lib/capybara/server/animation_disabler.rb +36 -25
  47. data/lib/capybara/server/middleware.rb +1 -1
  48. data/lib/capybara/session/config.rb +4 -2
  49. data/lib/capybara/session.rb +19 -30
  50. data/lib/capybara/spec/public/test.js +4 -0
  51. data/lib/capybara/spec/session/all_spec.rb +6 -8
  52. data/lib/capybara/spec/session/assert_text_spec.rb +17 -17
  53. data/lib/capybara/spec/session/attach_file_spec.rb +6 -0
  54. data/lib/capybara/spec/session/check_spec.rb +1 -0
  55. data/lib/capybara/spec/session/click_link_spec.rb +11 -0
  56. data/lib/capybara/spec/session/current_scope_spec.rb +1 -1
  57. data/lib/capybara/spec/session/fill_in_spec.rb +6 -0
  58. data/lib/capybara/spec/session/find_link_spec.rb +10 -0
  59. data/lib/capybara/spec/session/find_spec.rb +7 -1
  60. data/lib/capybara/spec/session/first_spec.rb +1 -1
  61. data/lib/capybara/spec/session/frame/within_frame_spec.rb +2 -0
  62. data/lib/capybara/spec/session/has_all_selectors_spec.rb +5 -5
  63. data/lib/capybara/spec/session/has_ancestor_spec.rb +2 -2
  64. data/lib/capybara/spec/session/has_any_selectors_spec.rb +2 -2
  65. data/lib/capybara/spec/session/has_button_spec.rb +6 -0
  66. data/lib/capybara/spec/session/has_current_path_spec.rb +3 -3
  67. data/lib/capybara/spec/session/has_field_spec.rb +1 -1
  68. data/lib/capybara/spec/session/has_link_spec.rb +16 -0
  69. data/lib/capybara/spec/session/has_none_selectors_spec.rb +7 -7
  70. data/lib/capybara/spec/session/has_select_spec.rb +10 -4
  71. data/lib/capybara/spec/session/has_selector_spec.rb +15 -0
  72. data/lib/capybara/spec/session/has_text_spec.rb +5 -13
  73. data/lib/capybara/spec/session/matches_style_spec.rb +2 -0
  74. data/lib/capybara/spec/session/node_spec.rb +81 -0
  75. data/lib/capybara/spec/session/reset_session_spec.rb +13 -0
  76. data/lib/capybara/spec/session/scroll_spec.rb +3 -1
  77. data/lib/capybara/spec/session/visit_spec.rb +20 -0
  78. data/lib/capybara/spec/session/window/window_spec.rb +1 -1
  79. data/lib/capybara/spec/session/window/windows_spec.rb +1 -1
  80. data/lib/capybara/spec/session/within_spec.rb +13 -0
  81. data/lib/capybara/spec/spec_helper.rb +8 -2
  82. data/lib/capybara/spec/test_app.rb +74 -6
  83. data/lib/capybara/spec/views/form.erb +17 -0
  84. data/lib/capybara/spec/views/with_html.erb +3 -3
  85. data/lib/capybara/spec/views/with_scope.erb +2 -2
  86. data/lib/capybara/spec/views/with_shadow.erb +31 -0
  87. data/lib/capybara/version.rb +1 -1
  88. data/lib/capybara/window.rb +1 -1
  89. data/lib/capybara.rb +5 -2
  90. data/spec/capybara_spec.rb +12 -0
  91. data/spec/counter_spec.rb +35 -0
  92. data/spec/css_builder_spec.rb +1 -1
  93. data/spec/css_splitter_spec.rb +1 -1
  94. data/spec/dsl_spec.rb +4 -2
  95. data/spec/fixtures/selenium_driver_rspec_failure.rb +2 -2
  96. data/spec/fixtures/selenium_driver_rspec_success.rb +2 -2
  97. data/spec/minitest_spec.rb +4 -0
  98. data/spec/minitest_spec_spec.rb +4 -0
  99. data/spec/per_session_config_spec.rb +1 -1
  100. data/spec/rack_test_spec.rb +16 -2
  101. data/spec/result_spec.rb +27 -29
  102. data/spec/rspec/features_spec.rb +2 -2
  103. data/spec/rspec/scenarios_spec.rb +2 -2
  104. data/spec/rspec/shared_spec_matchers.rb +1 -1
  105. data/spec/rspec_matchers_spec.rb +25 -0
  106. data/spec/rspec_spec.rb +2 -2
  107. data/spec/sauce_spec_chrome.rb +4 -4
  108. data/spec/selector_spec.rb +4 -4
  109. data/spec/selenium_spec_chrome.rb +8 -7
  110. data/spec/selenium_spec_chrome_remote.rb +9 -9
  111. data/spec/selenium_spec_edge.rb +12 -6
  112. data/spec/selenium_spec_firefox.rb +21 -5
  113. data/spec/selenium_spec_firefox_remote.rb +19 -4
  114. data/spec/selenium_spec_ie.rb +4 -2
  115. data/spec/selenium_spec_safari.rb +8 -2
  116. data/spec/server_spec.rb +7 -7
  117. data/spec/shared_selenium_session.rb +13 -6
  118. data/spec/spec_helper.rb +35 -2
  119. data/spec/whitespace_normalizer_spec.rb +54 -0
  120. data/spec/xpath_builder_spec.rb +1 -1
  121. metadata +9 -4
@@ -179,10 +179,16 @@ 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
- expect(@session.has_select?(with_options: %w[Norway Sweden])).to eq true
189
+ expect(@session.has_select?(with_options: %w[Norway Sweden])).to be true
184
190
  expect(@session).to have_select(with_options: ['London'])
185
- expect(@session.has_select?(with_selected: %w[Commando Boxerbriefs])).to eq true
191
+ expect(@session.has_select?(with_selected: %w[Commando Boxerbriefs])).to be true
186
192
  expect(@session).to have_select(with_selected: ['Briefs'])
187
193
  end
188
194
  end
@@ -302,9 +308,9 @@ Capybara::SpecHelper.spec '#has_no_select?' do
302
308
  end
303
309
 
304
310
  it 'should support locator-less usage' do
305
- expect(@session.has_no_select?(with_options: %w[Norway Sweden Finland Latvia])).to eq true
311
+ expect(@session.has_no_select?(with_options: %w[Norway Sweden Finland Latvia])).to be true
306
312
  expect(@session).to have_no_select(with_options: ['New London'])
307
- expect(@session.has_no_select?(id: 'form_underwear', with_selected: ['Boxers'])).to eq true
313
+ expect(@session.has_no_select?(id: 'form_underwear', with_selected: ['Boxers'])).to be true
308
314
  expect(@session).to have_no_select(id: 'form_underwear', with_selected: %w[Commando Boxers])
309
315
  end
310
316
  end
@@ -117,6 +117,21 @@ Capybara::SpecHelper.spec '#has_selector?' do
117
117
  expect(@session).to have_selector(:id, 'h2one', text: 'Header Class Test One', exact_text: false)
118
118
  expect(@session).to have_selector(:id, 'h2one', text: 'Header Class Test', exact_text: false)
119
119
  end
120
+
121
+ it 'should warn if text option is a regexp that it is ignoring exact_text' do
122
+ allow(Capybara::Helpers).to receive(:warn)
123
+ expect(@session).to have_selector(:id, 'h2one', text: /Class Test/, exact_text: true)
124
+ expect(Capybara::Helpers).to have_received(:warn).with(/'exact_text' option is not supported/)
125
+ end
126
+ end
127
+
128
+ context 'regexp' do
129
+ it 'should only match when it fully matches' do
130
+ expect(@session).to have_selector(:id, 'h2one', exact_text: /Header Class Test One/)
131
+ expect(@session).to have_no_selector(:id, 'h2one', exact_text: /Header Class Test/)
132
+ expect(@session).to have_no_selector(:id, 'h2one', exact_text: /Class Test One/)
133
+ expect(@session).to have_no_selector(:id, 'h2one', exact_text: /Class Test/)
134
+ end
120
135
  end
121
136
  end
122
137
 
@@ -127,11 +127,7 @@ Capybara::SpecHelper.spec '#has_text?' do
127
127
  def to_hash; { value: 'Other hash' } end
128
128
  end.new
129
129
  @session.visit('/with_html')
130
- if RUBY_VERSION >= '2.7'
131
- expect(@session).to have_text(:visible, with_to_hash, **{})
132
- else
133
- expect(@session).to have_text(:visible, with_to_hash, {})
134
- end
130
+ expect(@session).to have_text(:visible, with_to_hash, **{})
135
131
  end
136
132
  end
137
133
 
@@ -157,16 +153,12 @@ Capybara::SpecHelper.spec '#has_text?' do
157
153
  expect(@session).to have_text(42)
158
154
  end
159
155
 
160
- it 'should be true when passed nil' do
156
+ it 'should be true when passed nil, and warn about it' do
161
157
  # nil is converted to '' when to_s is invoked
162
158
  @session.visit('/with_html')
163
- expect(@session).to have_text(nil)
164
- end
165
-
166
- it 'should warn when passed nil' do
167
- @session.visit('/with_html')
168
- expect_any_instance_of(Kernel).to receive(:warn).with(/Checking for expected text of nil is confusing/) # rubocop:disable RSpec/AnyInstance
169
- 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
170
162
  end
171
163
 
172
164
  it 'should wait for text to appear', requires: [:js] do
@@ -23,6 +23,7 @@ Capybara::SpecHelper.spec '#matches_style?', requires: [:css] do
23
23
  expect(@session.find(:css, '#first').matches_style?(display: /^in/)).to be false
24
24
  end
25
25
 
26
+ # rubocop:disable Capybara/MatchStyle
26
27
  it 'deprecated has_style?' do
27
28
  expect { have_style(display: /^bl/) }.to \
28
29
  output(/have_style is deprecated/).to_stderr
@@ -32,4 +33,5 @@ Capybara::SpecHelper.spec '#matches_style?', requires: [:css] do
32
33
  el.has_style?('display' => /^bl/)
33
34
  expect(Capybara::Helpers).to have_received(:warn)
34
35
  end
36
+ # rubocop:enable Capybara/MatchStyle
35
37
  end
@@ -157,6 +157,12 @@ Capybara::SpecHelper.spec 'node' do
157
157
  expect(@session.find(:css, '#existing_content_editable_child_parent').text).to eq("Some content\nWYSIWYG")
158
158
  end
159
159
  end
160
+
161
+ it 'should submit single text input forms if ended with \n' do
162
+ @session.visit('/form')
163
+ @session.find(:css, '#single_input').set("my entry\n")
164
+ expect(extract_results(@session)['single_input']).to eq('my entry')
165
+ end
160
166
  end
161
167
 
162
168
  describe '#tag_name' do
@@ -651,6 +657,16 @@ Capybara::SpecHelper.spec 'node' do
651
657
  expect(target).to have_text(%r{^HTML5 Dropped string: text/plain drag_html5-#{key}$}m, exact: true)
652
658
  end
653
659
  end
660
+
661
+ it 'should trigger a dragenter event, before the first dragover event' do
662
+ @session.visit('/with_js')
663
+ element = @session.find('//div[@id="drag_html5"]')
664
+ target = @session.find('//div[@id="drop_html5"]')
665
+ element.drag_to(target)
666
+
667
+ # Events are listed in reverse chronological order
668
+ expect(@session).to have_text(/DragOver.*DragEnter/m)
669
+ end
654
670
  end
655
671
  end
656
672
 
@@ -775,6 +791,7 @@ Capybara::SpecHelper.spec 'node' do
775
791
  end
776
792
 
777
793
  it 'should allow to adjust the click offset', requires: [:js] do
794
+ Capybara.w3c_click_offset = false
778
795
  @session.visit('with_js')
779
796
  @session.find(:css, '#click-test').click(x: 5, y: 5)
780
797
  link = @session.find(:link, 'has-been-clicked')
@@ -901,6 +918,7 @@ Capybara::SpecHelper.spec 'node' do
901
918
  end
902
919
 
903
920
  it 'should allow to adjust the offset', requires: [:js] do
921
+ Capybara.w3c_click_offset = false
904
922
  @session.visit('with_js')
905
923
  @session.find(:css, '#click-test').double_click(x: 10, y: 5)
906
924
  link = @session.find(:link, 'has-been-double-clicked')
@@ -987,6 +1005,7 @@ Capybara::SpecHelper.spec 'node' do
987
1005
  end
988
1006
 
989
1007
  it 'should allow to adjust the offset', requires: [:js] do
1008
+ Capybara.w3c_click_offset = false
990
1009
  @session.visit('with_js')
991
1010
  @session.find(:css, '#click-test').right_click(x: 10, y: 10)
992
1011
  link = @session.find(:link, 'has-been-right-clicked')
@@ -1176,6 +1195,68 @@ Capybara::SpecHelper.spec 'node' do
1176
1195
  end
1177
1196
  end
1178
1197
 
1198
+ describe '#shadow_root', requires: %i[js] do
1199
+ it 'should get the shadow root' do
1200
+ @session.visit('/with_shadow')
1201
+ expect do
1202
+ shadow_root = @session.find(:css, '#shadow_host').shadow_root
1203
+ expect(shadow_root).to be_a(Capybara::Node::Element)
1204
+ end.not_to raise_error
1205
+ end
1206
+
1207
+ it 'should find elements inside the shadow dom using CSS' do
1208
+ @session.visit('/with_shadow')
1209
+ shadow_root = @session.find(:css, '#shadow_host').shadow_root
1210
+ expect(shadow_root).to have_css('#shadow_content', text: 'some text')
1211
+ end
1212
+
1213
+ it 'should find nested shadow roots' do
1214
+ @session.visit('/with_shadow')
1215
+ shadow_root = @session.find(:css, '#shadow_host').shadow_root
1216
+ nested_shadow_root = shadow_root.find(:css, '#nested_shadow_host').shadow_root
1217
+ expect(nested_shadow_root).to have_css('#nested_shadow_content', text: 'nested text')
1218
+ end
1219
+
1220
+ it 'should click on elements' do
1221
+ @session.visit('/with_shadow')
1222
+ shadow_root = @session.find(:css, '#shadow_host').shadow_root
1223
+ checkbox = shadow_root.find(:css, 'input[type="checkbox"]')
1224
+ expect(checkbox).not_to be_checked
1225
+ checkbox.click
1226
+ expect(checkbox).to be_checked
1227
+ end
1228
+
1229
+ it 'should use convenience methods once moved to a descendant of the shadow root' do
1230
+ @session.visit('/with_shadow')
1231
+ shadow_root = @session.find(:css, '#shadow_host').shadow_root
1232
+ descendant = shadow_root.find(:css, '#controls_wrapper')
1233
+ expect do
1234
+ descendant.check('shadow_checkbox')
1235
+ end.not_to raise_error
1236
+ expect(descendant).to have_checked_field('shadow_checkbox')
1237
+ end
1238
+
1239
+ it 'should produce error messages when failing' do
1240
+ @session.visit('/with_shadow')
1241
+ shadow_root = @session.find(:css, '#shadow_host').shadow_root
1242
+ expect do
1243
+ expect(shadow_root).to have_css('#shadow_content', text: 'Not in the document')
1244
+ end.to raise_error(/tag="ShadowRoot"/)
1245
+ end
1246
+
1247
+ it 'should get visible text' do
1248
+ @session.visit('/with_shadow')
1249
+ shadow_root = @session.find(:css, '#shadow_host').shadow_root
1250
+ expect(shadow_root).to have_text('some text scroll.html')
1251
+ end
1252
+
1253
+ it 'should get all text' do
1254
+ @session.visit('/with_shadow')
1255
+ shadow_root = @session.find(:css, '#shadow_host').shadow_root
1256
+ expect(shadow_root).to have_text(:all, 'some text scroll.html')
1257
+ end
1258
+ end
1259
+
1179
1260
  describe '#reload', requires: [:js] do
1180
1261
  it 'should reload elements found via ancestor with CSS' do
1181
1262
  @session.visit('/with_js')
@@ -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
 
@@ -36,7 +36,9 @@ Capybara::SpecHelper.spec '#scroll_to', requires: [:scroll] do
36
36
  it 'can scroll the window to the vertical bottom' do
37
37
  @session.scroll_to :bottom
38
38
  max_scroll = @session.evaluate_script('document.documentElement.scrollHeight - document.documentElement.clientHeight')
39
- expect(@session.evaluate_script('[window.scrollX || window.pageXOffset, window.scrollY || window.pageYOffset]')).to eq [0, max_scroll]
39
+ scrolled_location_x, scrolled_location_y = @session.evaluate_script('[window.scrollX || window.pageXOffset, window.scrollY || window.pageYOffset]')
40
+ expect(scrolled_location_x).to be_within(0.5).of(0)
41
+ expect(scrolled_location_y).to be_within(0.5).of(max_scroll)
40
42
  end
41
43
 
42
44
  it 'can scroll the window to the vertical center' do
@@ -201,4 +201,24 @@ Capybara::SpecHelper.spec '#visit' do
201
201
  @session.visit('/get_cookie')
202
202
  expect(@session).to have_content('root cookie')
203
203
  end
204
+
205
+ context 'with base element' do
206
+ it 'should use base href with relative links' do
207
+ @session.visit('/base/with_base')
208
+ @session.click_link('Title page')
209
+ expect(@session).to have_current_path('/with_title')
210
+ end
211
+
212
+ it 'should use base href with bare queries' do
213
+ @session.visit('/base/with_base')
214
+ @session.click_link('Bare query')
215
+ expect(@session).to have_current_path('/?a=3')
216
+ end
217
+
218
+ it 'should not use the base href with a new visit call' do
219
+ @session.visit('/base/with_other_base')
220
+ @session.visit('with_html')
221
+ expect(@session).to have_current_path('/with_html')
222
+ end
223
+ end
204
224
  end
@@ -59,7 +59,7 @@ Capybara::SpecHelper.spec Capybara::Window, requires: [:windows] do
59
59
  it 'should return false if window is closed' do
60
60
  @session.switch_to_window(other_window)
61
61
  other_window.close
62
- expect(other_window.current?).to eq(false)
62
+ expect(other_window.current?).to be(false)
63
63
  end
64
64
  end
65
65
 
@@ -29,6 +29,6 @@ Capybara::SpecHelper.spec '#windows', requires: [:windows] do
29
29
  titles = @session.windows.map do |window|
30
30
  @session.within_window(window) { @session.title }
31
31
  end
32
- expect(titles).to match_array(['With Windows', 'Title of the first popup', 'Title of popup two'])
32
+ expect(titles).to match_array(['With Windows', 'Title of the first popup', 'Title of popup two']) # rubocop:disable RSpec/MatchArray
33
33
  end
34
34
  end
@@ -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,12 +177,81 @@ 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
+
196
+ get '/base/with_base' do
197
+ <<-HTML
198
+ <!DOCTYPE html>
199
+ <html>
200
+ <head>
201
+ <base href="/">
202
+ <title>Origin</title>
203
+ </head>
204
+ <body>
205
+ <a href="with_title">Title page</a>
206
+ <a href="?a=3">Bare query</a>
207
+ </body>
208
+ </html>
209
+ HTML
210
+ end
211
+
212
+ get '/base/with_other_base' do
213
+ <<-HTML
214
+ <!DOCTYPE html>
215
+ <html>
216
+ <head>
217
+ <base href="/base/">
218
+ <title>Origin</title>
219
+ </head>
220
+ <body>
221
+ <a href="with_title">Title page</a>
222
+ <a href="?a=3">Bare query</a>
223
+ </body>
224
+ </html>
225
+ HTML
226
+ end
227
+
228
+ get '/csp' do
229
+ response.headers['Content-Security-Policy'] = "default-src 'none'; connect-src 'self'; base-uri 'none'; font-src 'self'; img-src 'self' data:; object-src 'none'; script-src 'self' 'nonce-jAviMuMisoTisVXjgLoWdA=='; style-src 'self' 'nonce-jAviMuMisoTisVXjgLoWdA=='; form-action 'self';"
230
+ <<-HTML
231
+ <!DOCTYPE html>
232
+ <html lang="en">
233
+ <head>
234
+ <meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
235
+ <title>CSP</title>
236
+ </head>
237
+
238
+ <body>
239
+ <div>CSP</div>
240
+ </body>
241
+ </html>
242
+ HTML
243
+ end
244
+
180
245
  get '/download.csv' do
181
246
  content_type 'text/csv'
182
247
  'This, is, comma, separated' \
183
248
  'Thomas, Walpole, was , here'
184
249
  end
185
250
 
251
+ get '/apple-touch-icon-precomposed.png' do
252
+ halt(404)
253
+ end
254
+
186
255
  get '/:view' do |view|
187
256
  view_template = "#{__dir__}/views/#{view}.erb"
188
257
  has_layout = File.exist?(view_template) && File.open(view_template) { |f| f.first.downcase.include?('doctype') }
@@ -191,12 +260,15 @@ class TestApp < Sinatra::Base
191
260
 
192
261
  post '/form' do
193
262
  self.class.form_post_count += 1
194
- %(<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
+ )
195
267
  end
196
268
 
197
269
  post '/upload_empty' do
198
270
  if params[:form][:file].nil?
199
- 'Successfully ignored empty file field.'
271
+ "Successfully ignored empty file field. Content type was #{request.content_type}"
200
272
  else
201
273
  'Something went wrong.'
202
274
  end
@@ -223,10 +295,6 @@ class TestApp < Sinatra::Base
223
295
  'No files uploaded'
224
296
  end
225
297
 
226
- get '/apple-touch-icon-precomposed.png' do
227
- halt(404)
228
- end
229
-
230
298
  class << self
231
299
  attr_accessor :form_post_count
232
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>
@@ -682,6 +695,10 @@ New line after and before textarea tag
682
695
  </p>
683
696
  </form>
684
697
 
698
+ <form id="single_input_form" action="/form" method="post">
699
+ <input type="text" name="form[single_input]" id="single_input"/>
700
+ </form>
701
+
685
702
  <label>Confusion
686
703
  <input type="checkbox" id="confusion_checkbox" class="confusion-checkbox confusion"/>
687
704
  </label>
@@ -19,7 +19,7 @@
19
19
 
20
20
  <p class="para" id="first" data-random="abc\def" style="line-height: 25px;">
21
21
  Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
22
- tempor incididunt ut <a href="/with_simple_html" title="awesome title" class="simple" tabindex="1">labore</a>
22
+ tempor incididunt ut <a href="/with_simple_html" title="awesome title" class="simple" tabindex="1" target="_self">labore</a>
23
23
  et dolore magna aliqua. Ut enim ad minim veniam,
24
24
  quis nostrud exercitation <a href="/foo" id="foo" data-test-id="test-foo">ullamco</a> laboris nisi
25
25
  ut aliquip ex ea commodo consequat.
@@ -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>
@@ -0,0 +1,31 @@
1
+ <!DOCTYPE html>
2
+ <%# Borrowed from Titus Fortner %>
3
+ <html>
4
+ <head>
5
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
6
+ <title>Shadow DOM</title>
7
+ </head>
8
+ <body>
9
+ <div id="no_host"></div>
10
+ <div id="shadow_host"></div>
11
+ <a href="scroll.html">scroll.html</a>
12
+ <script>
13
+ let shadowRoot = document.getElementById('shadow_host').attachShadow({mode: 'open'});
14
+ shadowRoot.innerHTML = `
15
+ <span class="wrapper" id="shadow_content"><span class="info">some text</span></span>
16
+ <div id="nested_shadow_host"></div>
17
+ <a href="scroll.html">scroll.html</a>
18
+ <div id="controls_wrapper">
19
+ <input type="text" />
20
+ <input type="checkbox" id="shadow_checkbox" />
21
+ <input type="file" />
22
+ </div>
23
+ `;
24
+
25
+ let nestedShadowRoot = shadowRoot.getElementById('nested_shadow_host').attachShadow({mode: 'open'});
26
+ nestedShadowRoot.innerHTML = `
27
+ <div id="nested_shadow_content"><div>nested text</div></div>
28
+ `;
29
+ </script>
30
+ </body>
31
+ </html>
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Capybara
4
- VERSION = '3.36.0'
4
+ VERSION = '3.39.2'
5
5
  end
@@ -118,7 +118,7 @@ module Capybara
118
118
  alias_method :==, :eql?
119
119
 
120
120
  def hash
121
- @session.hash ^ @handle.hash
121
+ [@session, @handle].hash
122
122
  end
123
123
 
124
124
  def inspect
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.
@@ -96,11 +97,12 @@ module Capybara
96
97
  # {Capybara::Session#save_and_open_page save_and_open_page}, or {Capybara::Session#save_and_open_screenshot save_and_open_screenshot}.
97
98
  # - **server** (Symbol = `:default` (which uses puma)) - The name of the registered server to use when running the app under test.
98
99
  # - **server_port** (Integer) - The port Capybara will run the application server on, if not specified a random port will be used.
100
+ # - **server_host** (String = "127.0.0.1") - The IP address Capybara will bind the application server to. If the test application is to be accessed from an external host, you will want to change this to "0.0.0.0" or to a more specific IP address that your test client can reach.
99
101
  # - **server_errors** (Array\<Class> = `[Exception]`) - Error classes that should be raised in the tests if they are raised in the server
100
102
  # and {configure raise_server_errors} is `true`.
101
103
  # - **test_id** (Symbol, String, `nil` = `nil`) - Optional attribute to match locator against with built-in selectors along with id.
102
104
  # - **threadsafe** (Boolean = `false`) - Whether sessions can be configured individually.
103
- # - **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)
104
106
  #
105
107
  # #### DSL Options
106
108
  #
@@ -495,6 +497,7 @@ Capybara.configure do |config|
495
497
  config.server = :default
496
498
  config.default_selector = :css
497
499
  config.default_max_wait_time = 2
500
+ config.default_retry_interval = 0.01
498
501
  config.ignore_hidden_elements = true
499
502
  config.default_host = 'http://www.example.com'
500
503
  config.automatic_reload = true
@@ -513,5 +516,5 @@ Capybara.configure do |config|
513
516
  config.predicates_wait = true
514
517
  config.default_normalize_ws = false
515
518
  config.use_html5_parsing = false
516
- config.w3c_click_offset = false
519
+ config.w3c_click_offset = true
517
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|