capybara 3.35.3 → 3.39.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (173) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +110 -4
  3. data/README.md +28 -12
  4. data/lib/capybara/config.rb +16 -4
  5. data/lib/capybara/driver/base.rb +4 -0
  6. data/lib/capybara/driver/node.rb +5 -1
  7. data/lib/capybara/dsl.rb +4 -10
  8. data/lib/capybara/helpers.rb +8 -13
  9. data/lib/capybara/minitest/spec.rb +2 -2
  10. data/lib/capybara/node/actions.rb +14 -9
  11. data/lib/capybara/node/base.rb +2 -1
  12. data/lib/capybara/node/document.rb +2 -2
  13. data/lib/capybara/node/element.rb +13 -2
  14. data/lib/capybara/node/finders.rb +11 -2
  15. data/lib/capybara/node/simple.rb +5 -1
  16. data/lib/capybara/node/whitespace_normalizer.rb +81 -0
  17. data/lib/capybara/queries/active_element_query.rb +18 -0
  18. data/lib/capybara/queries/ancestor_query.rb +2 -1
  19. data/lib/capybara/queries/base_query.rb +2 -2
  20. data/lib/capybara/queries/current_path_query.rb +1 -1
  21. data/lib/capybara/queries/selector_query.rb +38 -10
  22. data/lib/capybara/queries/sibling_query.rb +2 -1
  23. data/lib/capybara/queries/text_query.rb +1 -1
  24. data/lib/capybara/rack_test/browser.rb +63 -8
  25. data/lib/capybara/rack_test/driver.rb +4 -4
  26. data/lib/capybara/rack_test/form.rb +29 -7
  27. data/lib/capybara/rack_test/node.rb +28 -22
  28. data/lib/capybara/registration_container.rb +0 -3
  29. data/lib/capybara/registrations/drivers.rb +6 -6
  30. data/lib/capybara/registrations/servers.rb +30 -10
  31. data/lib/capybara/rspec/matcher_proxies.rb +6 -6
  32. data/lib/capybara/rspec/matchers/base.rb +8 -6
  33. data/lib/capybara/rspec/matchers/compound.rb +1 -1
  34. data/lib/capybara/rspec/matchers/have_selector.rb +5 -5
  35. data/lib/capybara/rspec/matchers.rb +14 -14
  36. data/lib/capybara/selector/builders/css_builder.rb +1 -1
  37. data/lib/capybara/selector/builders/xpath_builder.rb +1 -1
  38. data/lib/capybara/selector/css.rb +1 -1
  39. data/lib/capybara/selector/definition/button.rb +9 -4
  40. data/lib/capybara/selector/definition/checkbox.rb +1 -1
  41. data/lib/capybara/selector/definition/file_field.rb +1 -1
  42. data/lib/capybara/selector/definition/fillable_field.rb +1 -1
  43. data/lib/capybara/selector/definition/link.rb +2 -1
  44. data/lib/capybara/selector/definition/radio_button.rb +1 -1
  45. data/lib/capybara/selector/definition.rb +4 -2
  46. data/lib/capybara/selector/filter_set.rb +4 -7
  47. data/lib/capybara/selector/regexp_disassembler.rb +2 -5
  48. data/lib/capybara/selector/selector.rb +5 -1
  49. data/lib/capybara/selector.rb +1 -0
  50. data/lib/capybara/selenium/driver.rb +30 -13
  51. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +1 -1
  52. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +9 -5
  53. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +1 -1
  54. data/lib/capybara/selenium/extensions/html5_drag.rb +5 -4
  55. data/lib/capybara/selenium/logger_suppressor.rb +4 -0
  56. data/lib/capybara/selenium/node.rb +81 -32
  57. data/lib/capybara/selenium/nodes/chrome_node.rb +6 -2
  58. data/lib/capybara/selenium/nodes/edge_node.rb +25 -3
  59. data/lib/capybara/selenium/nodes/firefox_node.rb +3 -3
  60. data/lib/capybara/selenium/nodes/safari_node.rb +4 -4
  61. data/lib/capybara/selenium/patches/action_pauser.rb +3 -3
  62. data/lib/capybara/selenium/patches/atoms.rb +1 -1
  63. data/lib/capybara/selenium/patches/pause_duration_fix.rb +1 -1
  64. data/lib/capybara/server/animation_disabler.rb +40 -23
  65. data/lib/capybara/server/middleware.rb +1 -1
  66. data/lib/capybara/session/config.rb +4 -2
  67. data/lib/capybara/session.rb +31 -32
  68. data/lib/capybara/spec/public/test.js +4 -0
  69. data/lib/capybara/spec/session/active_element_spec.rb +31 -0
  70. data/lib/capybara/spec/session/all_spec.rb +10 -14
  71. data/lib/capybara/spec/session/assert_text_spec.rb +17 -17
  72. data/lib/capybara/spec/session/attach_file_spec.rb +6 -0
  73. data/lib/capybara/spec/session/check_spec.rb +10 -0
  74. data/lib/capybara/spec/session/choose_spec.rb +6 -0
  75. data/lib/capybara/spec/session/click_link_spec.rb +11 -0
  76. data/lib/capybara/spec/session/current_scope_spec.rb +1 -1
  77. data/lib/capybara/spec/session/fill_in_spec.rb +6 -0
  78. data/lib/capybara/spec/session/find_link_spec.rb +10 -0
  79. data/lib/capybara/spec/session/find_spec.rb +7 -1
  80. data/lib/capybara/spec/session/first_spec.rb +1 -1
  81. data/lib/capybara/spec/session/frame/within_frame_spec.rb +2 -0
  82. data/lib/capybara/spec/session/has_all_selectors_spec.rb +5 -5
  83. data/lib/capybara/spec/session/has_ancestor_spec.rb +2 -2
  84. data/lib/capybara/spec/session/has_any_selectors_spec.rb +6 -2
  85. data/lib/capybara/spec/session/has_button_spec.rb +30 -0
  86. data/lib/capybara/spec/session/has_current_path_spec.rb +3 -3
  87. data/lib/capybara/spec/session/has_field_spec.rb +25 -1
  88. data/lib/capybara/spec/session/has_link_spec.rb +40 -0
  89. data/lib/capybara/spec/session/has_none_selectors_spec.rb +7 -7
  90. data/lib/capybara/spec/session/has_select_spec.rb +10 -4
  91. data/lib/capybara/spec/session/has_selector_spec.rb +15 -0
  92. data/lib/capybara/spec/session/has_text_spec.rb +6 -14
  93. data/lib/capybara/spec/session/matches_style_spec.rb +2 -0
  94. data/lib/capybara/spec/session/node_spec.rb +82 -1
  95. data/lib/capybara/spec/session/reset_session_spec.rb +13 -0
  96. data/lib/capybara/spec/session/scroll_spec.rb +7 -5
  97. data/lib/capybara/spec/session/visit_spec.rb +20 -0
  98. data/lib/capybara/spec/session/window/window_spec.rb +1 -1
  99. data/lib/capybara/spec/session/window/windows_spec.rb +1 -1
  100. data/lib/capybara/spec/session/within_spec.rb +13 -0
  101. data/lib/capybara/spec/spec_helper.rb +12 -5
  102. data/lib/capybara/spec/test_app.rb +91 -14
  103. data/lib/capybara/spec/views/animated.erb +1 -1
  104. data/lib/capybara/spec/views/form.erb +28 -3
  105. data/lib/capybara/spec/views/frame_child.erb +1 -1
  106. data/lib/capybara/spec/views/frame_one.erb +1 -1
  107. data/lib/capybara/spec/views/frame_parent.erb +1 -1
  108. data/lib/capybara/spec/views/frame_two.erb +1 -1
  109. data/lib/capybara/spec/views/initial_alert.erb +2 -1
  110. data/lib/capybara/spec/views/layout.erb +10 -0
  111. data/lib/capybara/spec/views/obscured.erb +1 -1
  112. data/lib/capybara/spec/views/offset.erb +2 -1
  113. data/lib/capybara/spec/views/path.erb +2 -2
  114. data/lib/capybara/spec/views/popup_one.erb +1 -1
  115. data/lib/capybara/spec/views/popup_two.erb +1 -1
  116. data/lib/capybara/spec/views/react.erb +2 -2
  117. data/lib/capybara/spec/views/scroll.erb +2 -1
  118. data/lib/capybara/spec/views/spatial.erb +1 -1
  119. data/lib/capybara/spec/views/with_animation.erb +2 -3
  120. data/lib/capybara/spec/views/with_base_tag.erb +2 -2
  121. data/lib/capybara/spec/views/with_dragula.erb +2 -2
  122. data/lib/capybara/spec/views/with_fixed_header_footer.erb +2 -1
  123. data/lib/capybara/spec/views/with_hover.erb +2 -2
  124. data/lib/capybara/spec/views/with_html.erb +3 -3
  125. data/lib/capybara/spec/views/with_jquery_animation.erb +1 -1
  126. data/lib/capybara/spec/views/with_js.erb +2 -3
  127. data/lib/capybara/spec/views/with_jstree.erb +1 -1
  128. data/lib/capybara/spec/views/with_namespace.erb +1 -0
  129. data/lib/capybara/spec/views/with_scope.erb +2 -2
  130. data/lib/capybara/spec/views/with_shadow.erb +31 -0
  131. data/lib/capybara/spec/views/with_slow_unload.erb +2 -1
  132. data/lib/capybara/spec/views/with_sortable_js.erb +2 -2
  133. data/lib/capybara/spec/views/with_unload_alert.erb +1 -0
  134. data/lib/capybara/spec/views/with_windows.erb +1 -1
  135. data/lib/capybara/spec/views/within_frames.erb +1 -1
  136. data/lib/capybara/version.rb +1 -1
  137. data/lib/capybara/window.rb +1 -1
  138. data/lib/capybara.rb +23 -24
  139. data/spec/basic_node_spec.rb +16 -3
  140. data/spec/capybara_spec.rb +12 -0
  141. data/spec/counter_spec.rb +35 -0
  142. data/spec/css_builder_spec.rb +1 -1
  143. data/spec/css_splitter_spec.rb +1 -1
  144. data/spec/dsl_spec.rb +5 -3
  145. data/spec/fixtures/selenium_driver_rspec_failure.rb +2 -2
  146. data/spec/fixtures/selenium_driver_rspec_success.rb +2 -2
  147. data/spec/minitest_spec.rb +4 -0
  148. data/spec/minitest_spec_spec.rb +4 -0
  149. data/spec/per_session_config_spec.rb +1 -1
  150. data/spec/rack_test_spec.rb +30 -12
  151. data/spec/result_spec.rb +32 -35
  152. data/spec/rspec/features_spec.rb +3 -3
  153. data/spec/rspec/scenarios_spec.rb +2 -2
  154. data/spec/rspec/shared_spec_matchers.rb +3 -3
  155. data/spec/rspec_matchers_spec.rb +25 -0
  156. data/spec/rspec_spec.rb +2 -2
  157. data/spec/sauce_spec_chrome.rb +4 -4
  158. data/spec/selector_spec.rb +4 -4
  159. data/spec/selenium_spec_chrome.rb +16 -16
  160. data/spec/selenium_spec_chrome_remote.rb +15 -14
  161. data/spec/selenium_spec_edge.rb +12 -6
  162. data/spec/selenium_spec_firefox.rb +24 -7
  163. data/spec/selenium_spec_firefox_remote.rb +19 -4
  164. data/spec/selenium_spec_ie.rb +7 -8
  165. data/spec/selenium_spec_safari.rb +34 -20
  166. data/spec/server_spec.rb +7 -7
  167. data/spec/shared_selenium_node.rb +0 -4
  168. data/spec/shared_selenium_session.rb +24 -14
  169. data/spec/spec_helper.rb +34 -1
  170. data/spec/whitespace_normalizer_spec.rb +54 -0
  171. data/spec/xpath_builder_spec.rb +1 -1
  172. metadata +40 -14
  173. data/lib/capybara/spec/views/with_title.erb +0 -5
@@ -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
@@ -356,7 +348,7 @@ Capybara::SpecHelper.spec '#has_no_text?' do
356
348
  expect(@session).to have_no_text(/xxxxyzzz/)
357
349
  end
358
350
 
359
- it 'should be false if the text in the page matches given regexp' do
351
+ it 'should be false if the text in the page matches given regexp' do
360
352
  @session.visit('/with_html')
361
353
  expect(@session).not_to have_no_text(/Lorem/)
362
354
  end
@@ -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')
@@ -1115,7 +1134,7 @@ Capybara::SpecHelper.spec 'node' do
1115
1134
  end
1116
1135
 
1117
1136
  describe '#evaluate_script', requires: %i[js es_args] do
1118
- it 'should evaluate the given script in the context of the element and return whatever it produces' do
1137
+ it 'should evaluate the given script in the context of the element and return whatever it produces' do
1119
1138
  @session.visit('/with_js')
1120
1139
  el = @session.find(:css, '#with_change_event')
1121
1140
  expect(el.evaluate_script('this.value')).to eq('default value')
@@ -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
 
@@ -15,7 +15,7 @@ Capybara::SpecHelper.spec '#scroll_to', requires: [:scroll] do
15
15
  el = @session.find(:css, '#scroll')
16
16
  @session.scroll_to(el, align: :bottom)
17
17
  el_bottom = el.evaluate_script('this.getBoundingClientRect().bottom')
18
- viewport_bottom = el.evaluate_script('document.body.clientHeight')
18
+ viewport_bottom = el.evaluate_script('document.documentElement.clientHeight')
19
19
  expect(el_bottom).to be_within(1).of(viewport_bottom)
20
20
  end
21
21
 
@@ -23,7 +23,7 @@ Capybara::SpecHelper.spec '#scroll_to', requires: [:scroll] do
23
23
  el = @session.find(:css, '#scroll')
24
24
  @session.scroll_to(el, align: :center)
25
25
  el_center = el.evaluate_script('(function(rect){return (rect.top + rect.bottom)/2})(this.getBoundingClientRect())')
26
- viewport_bottom = el.evaluate_script('document.body.clientHeight')
26
+ viewport_bottom = el.evaluate_script('document.documentElement.clientHeight')
27
27
  expect(el_center).to be_within(2).of(viewport_bottom / 2)
28
28
  end
29
29
 
@@ -35,13 +35,15 @@ Capybara::SpecHelper.spec '#scroll_to', requires: [:scroll] do
35
35
 
36
36
  it 'can scroll the window to the vertical bottom' do
37
37
  @session.scroll_to :bottom
38
- max_scroll = @session.evaluate_script('document.body.scrollHeight - document.body.clientHeight')
39
- expect(@session.evaluate_script('[window.scrollX || window.pageXOffset, window.scrollY || window.pageYOffset]')).to eq [0, max_scroll]
38
+ max_scroll = @session.evaluate_script('document.documentElement.scrollHeight - document.documentElement.clientHeight')
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
43
45
  @session.scroll_to :center
44
- max_scroll = @session.evaluate_script('document.documentElement.scrollHeight - document.body.clientHeight')
46
+ max_scroll = @session.evaluate_script('document.documentElement.scrollHeight - document.documentElement.clientHeight')
45
47
  expect(@session.evaluate_script('[window.scrollX || window.pageXOffset, window.scrollY || window.pageYOffset]')).to eq [0, max_scroll / 2]
46
48
  end
47
49
 
@@ -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
@@ -36,8 +37,8 @@ module Capybara
36
37
  Capybara.test_id = nil
37
38
  Capybara.predicates_wait = true
38
39
  Capybara.default_normalize_ws = false
39
- Capybara.allow_gumbo = true
40
- Capybara.w3c_click_offset = false
40
+ Capybara.use_html5_parsing = !ENV['HTML5_PARSING'].nil?
41
+ Capybara.w3c_click_offset = true
41
42
  reset_threadsafe
42
43
  end
43
44
 
@@ -117,8 +118,14 @@ module Capybara
117
118
 
118
119
  def extract_results(session)
119
120
  expect(session).to have_xpath("//pre[@id='results']")
120
- # YAML.load Nokogiri::HTML(session.body).xpath("//pre[@id='results']").first.inner_html.lstrip
121
- YAML.load Capybara::HTML(session.body).xpath("//pre[@id='results']").first.inner_html.lstrip
121
+ perms = [(::Sinatra::IndifferentHash if defined? ::Sinatra::IndifferentHash)].compact
122
+ results = Capybara::HTML(session.body).xpath("//pre[@id='results']").first.inner_html.lstrip
123
+ YAML.safe_load results, permitted_classes: perms
124
+ end
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
122
129
  end
123
130
 
124
131
  def be_an_invalid_element_error(session)
@@ -131,4 +138,4 @@ module Capybara
131
138
  end
132
139
  end
133
140
 
134
- Dir["#{File.dirname(__FILE__)}/session/**/*.rb"].each { |file| require_relative file }
141
+ Dir["#{File.dirname(__FILE__)}/session/**/*.rb"].sort.each { |file| require_relative file }
@@ -53,8 +53,8 @@ class TestApp < Sinatra::Base
53
53
 
54
54
  get '/referer_base' do
55
55
  '<a href="/get_referer">direct link</a>' \
56
- '<a href="/redirect_to_get_referer">link via redirect</a>' \
57
- '<form action="/get_referer" method="get"><input type="submit"></form>'
56
+ '<a href="/redirect_to_get_referer">link via redirect</a>' \
57
+ '<form action="/get_referer" method="get"><input type="submit"></form>'
58
58
  end
59
59
 
60
60
  get '/redirect_to_get_referer' do
@@ -163,31 +163,112 @@ class TestApp < Sinatra::Base
163
163
 
164
164
  get '/with_title' do
165
165
  <<-HTML
166
- <title>#{params[:title] || 'Test Title'}</title>
167
- <body>
168
- <svg><title>abcdefg</title></svg>
169
- </body>
166
+ <!DOCTYPE html>
167
+ <html lang="en">
168
+ <head>
169
+ <meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
170
+ <title>#{params[:title] || 'Test Title'}</title>
171
+ </head>
172
+
173
+ <body>
174
+ <svg><title>abcdefg</title></svg>
175
+ </body>
176
+ </html>
177
+ HTML
178
+ end
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>
170
242
  HTML
171
243
  end
172
244
 
173
245
  get '/download.csv' do
174
246
  content_type 'text/csv'
175
247
  'This, is, comma, separated' \
176
- 'Thomas, Walpole, was , here'
248
+ 'Thomas, Walpole, was , here'
249
+ end
250
+
251
+ get '/apple-touch-icon-precomposed.png' do
252
+ halt(404)
177
253
  end
178
254
 
179
255
  get '/:view' do |view|
180
- erb view.to_sym, locals: { referrer: request.referrer }
256
+ view_template = "#{__dir__}/views/#{view}.erb"
257
+ has_layout = File.exist?(view_template) && File.open(view_template) { |f| f.first.downcase.include?('doctype') }
258
+ erb view.to_sym, locals: { referrer: request.referrer }, layout: !has_layout
181
259
  end
182
260
 
183
261
  post '/form' do
184
262
  self.class.form_post_count += 1
185
- %(<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
+ )
186
267
  end
187
268
 
188
269
  post '/upload_empty' do
189
270
  if params[:form][:file].nil?
190
- 'Successfully ignored empty file field.'
271
+ "Successfully ignored empty file field. Content type was #{request.content_type}"
191
272
  else
192
273
  'Something went wrong.'
193
274
  end
@@ -214,10 +295,6 @@ class TestApp < Sinatra::Base
214
295
  'No files uploaded'
215
296
  end
216
297
 
217
- get '/apple-touch-icon-precomposed.png' do
218
- halt(404)
219
- end
220
-
221
298
  class << self
222
299
  attr_accessor :form_post_count
223
300
  end
@@ -1,3 +1,4 @@
1
+ <!DOCTYPE html>
1
2
  <html>
2
3
  <head>
3
4
  <meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
@@ -46,4 +47,3 @@
46
47
 
47
48
  <iframe id="frameOne" src="/frame_one"></iframe>
48
49
  </html>
49
-
@@ -1,11 +1,17 @@
1
-
2
1
  <h1>Form</h1>
3
2
 
3
+ <button type="button" tabindex="1">A Button</button>
4
+
5
+ <label>
6
+ An Input
7
+ <input type="text" tabindex="2">
8
+ </label>
9
+
4
10
  <form action="/form" method="post" novalidate>
5
11
 
6
12
  <p>
7
13
  <label for="form_title">Title</label>
8
- <select name="form[title]" id="form_title" class="title">
14
+ <select name="form[title]" id="form_title" class="title" tabindex="3">
9
15
  <option class="title">Mrs</option>
10
16
  <option class="title">Mr</option>
11
17
  <option>Miss</option>
@@ -170,7 +176,7 @@ New line after and before textarea tag
170
176
  <label for="gender_male">Male</label>
171
177
  <input type="radio" name="form[gender]" value="female" id="gender_female" checked="checked"/>
172
178
  <label for="gender_female">Female</label>
173
- <input type="radio" name="form[gender]" value="both" id="gender_both"/>
179
+ <input type="radio" name="form[gender]" value="both" id="gender_both" readonly="readonly"/>
174
180
  <label for="gender_both">Both</label>
175
181
  </p>
176
182
 
@@ -223,6 +229,8 @@ New line after and before textarea tag
223
229
  </label>
224
230
  <input type="checkbox" value="maserati" name="form[cars][]" id="form_cars_maserati" disabled="disabled"/>
225
231
  <label for="form_cars_maserati">Maserati</label>
232
+ <input type="checkbox" value="lotus" name="form[cars][]" id="form_cars_lotus" style="display: none"/>
233
+ <label for="form_cars_lotus"><a href="#" onclick="return false;">Link here</a>Lotus</label>
226
234
  </p>
227
235
 
228
236
  <p>
@@ -578,6 +586,19 @@ New line after and before textarea tag
578
586
  <p>
579
587
  </form>
580
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
+
581
602
  <form action="/upload_multiple" method="post" enctype="multipart/form-data">
582
603
  <p>
583
604
  <label for="form_multiple_file_name">File Name</label>
@@ -674,6 +695,10 @@ New line after and before textarea tag
674
695
  </p>
675
696
  </form>
676
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
+
677
702
  <label>Confusion
678
703
  <input type="checkbox" id="confusion_checkbox" class="confusion-checkbox confusion"/>
679
704
  </label>
@@ -1,4 +1,4 @@
1
-
1
+ <!DOCTYPE html>
2
2
  <html>
3
3
  <head>
4
4
  <title>This is the child frame title</title>
@@ -1,4 +1,4 @@
1
-
1
+ <!DOCTYPE html>
2
2
  <html>
3
3
  <head>
4
4
  <title>This is the title of frame one</title>
@@ -1,4 +1,4 @@
1
-
1
+ <!DOCTYPE html>
2
2
  <html>
3
3
  <head>
4
4
  <title>This is the parent frame title</title>
@@ -1,4 +1,4 @@
1
-
1
+ <!DOCTYPE html>
2
2
  <html>
3
3
  <head>
4
4
  <title>This is the title of frame two</title>