capybara 3.3.0 → 3.40.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (308) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -0
  3. data/History.md +803 -13
  4. data/License.txt +1 -1
  5. data/README.md +257 -84
  6. data/lib/capybara/config.rb +25 -9
  7. data/lib/capybara/cucumber.rb +1 -1
  8. data/lib/capybara/driver/base.rb +17 -3
  9. data/lib/capybara/driver/node.rb +31 -6
  10. data/lib/capybara/dsl.rb +9 -7
  11. data/lib/capybara/helpers.rb +31 -7
  12. data/lib/capybara/minitest/spec.rb +180 -88
  13. data/lib/capybara/minitest.rb +262 -149
  14. data/lib/capybara/node/actions.rb +202 -116
  15. data/lib/capybara/node/base.rb +34 -19
  16. data/lib/capybara/node/document.rb +14 -2
  17. data/lib/capybara/node/document_matchers.rb +10 -12
  18. data/lib/capybara/node/element.rb +269 -115
  19. data/lib/capybara/node/finders.rb +99 -77
  20. data/lib/capybara/node/matchers.rb +327 -151
  21. data/lib/capybara/node/simple.rb +48 -13
  22. data/lib/capybara/node/whitespace_normalizer.rb +81 -0
  23. data/lib/capybara/queries/active_element_query.rb +18 -0
  24. data/lib/capybara/queries/ancestor_query.rb +8 -9
  25. data/lib/capybara/queries/base_query.rb +23 -16
  26. data/lib/capybara/queries/current_path_query.rb +16 -6
  27. data/lib/capybara/queries/match_query.rb +1 -0
  28. data/lib/capybara/queries/selector_query.rb +587 -130
  29. data/lib/capybara/queries/sibling_query.rb +8 -6
  30. data/lib/capybara/queries/style_query.rb +6 -2
  31. data/lib/capybara/queries/text_query.rb +28 -14
  32. data/lib/capybara/queries/title_query.rb +2 -2
  33. data/lib/capybara/rack_test/browser.rb +92 -25
  34. data/lib/capybara/rack_test/driver.rb +16 -7
  35. data/lib/capybara/rack_test/errors.rb +6 -0
  36. data/lib/capybara/rack_test/form.rb +68 -41
  37. data/lib/capybara/rack_test/node.rb +106 -39
  38. data/lib/capybara/rails.rb +1 -1
  39. data/lib/capybara/registration_container.rb +41 -0
  40. data/lib/capybara/registrations/drivers.rb +42 -0
  41. data/lib/capybara/registrations/patches/puma_ssl.rb +29 -0
  42. data/lib/capybara/registrations/servers.rb +66 -0
  43. data/lib/capybara/result.rb +75 -52
  44. data/lib/capybara/rspec/features.rb +7 -7
  45. data/lib/capybara/rspec/matcher_proxies.rb +39 -18
  46. data/lib/capybara/rspec/matchers/base.rb +113 -0
  47. data/lib/capybara/rspec/matchers/become_closed.rb +33 -0
  48. data/lib/capybara/rspec/matchers/compound.rb +88 -0
  49. data/lib/capybara/rspec/matchers/count_sugar.rb +37 -0
  50. data/lib/capybara/rspec/matchers/have_ancestor.rb +28 -0
  51. data/lib/capybara/rspec/matchers/have_current_path.rb +29 -0
  52. data/lib/capybara/rspec/matchers/have_selector.rb +69 -0
  53. data/lib/capybara/rspec/matchers/have_sibling.rb +27 -0
  54. data/lib/capybara/rspec/matchers/have_text.rb +33 -0
  55. data/lib/capybara/rspec/matchers/have_title.rb +29 -0
  56. data/lib/capybara/rspec/matchers/match_selector.rb +27 -0
  57. data/lib/capybara/rspec/matchers/match_style.rb +43 -0
  58. data/lib/capybara/rspec/matchers/spatial_sugar.rb +39 -0
  59. data/lib/capybara/rspec/matchers.rb +141 -339
  60. data/lib/capybara/rspec.rb +2 -0
  61. data/lib/capybara/selector/builders/css_builder.rb +84 -0
  62. data/lib/capybara/selector/builders/xpath_builder.rb +71 -0
  63. data/lib/capybara/selector/css.rb +27 -25
  64. data/lib/capybara/selector/definition/button.rb +68 -0
  65. data/lib/capybara/selector/definition/checkbox.rb +26 -0
  66. data/lib/capybara/selector/definition/css.rb +10 -0
  67. data/lib/capybara/selector/definition/datalist_input.rb +35 -0
  68. data/lib/capybara/selector/definition/datalist_option.rb +25 -0
  69. data/lib/capybara/selector/definition/element.rb +28 -0
  70. data/lib/capybara/selector/definition/field.rb +40 -0
  71. data/lib/capybara/selector/definition/fieldset.rb +14 -0
  72. data/lib/capybara/selector/definition/file_field.rb +13 -0
  73. data/lib/capybara/selector/definition/fillable_field.rb +33 -0
  74. data/lib/capybara/selector/definition/frame.rb +17 -0
  75. data/lib/capybara/selector/definition/id.rb +6 -0
  76. data/lib/capybara/selector/definition/label.rb +62 -0
  77. data/lib/capybara/selector/definition/link.rb +55 -0
  78. data/lib/capybara/selector/definition/link_or_button.rb +16 -0
  79. data/lib/capybara/selector/definition/option.rb +27 -0
  80. data/lib/capybara/selector/definition/radio_button.rb +27 -0
  81. data/lib/capybara/selector/definition/select.rb +81 -0
  82. data/lib/capybara/selector/definition/table.rb +109 -0
  83. data/lib/capybara/selector/definition/table_row.rb +21 -0
  84. data/lib/capybara/selector/definition/xpath.rb +5 -0
  85. data/lib/capybara/selector/definition.rb +280 -0
  86. data/lib/capybara/selector/filter.rb +1 -0
  87. data/lib/capybara/selector/filter_set.rb +73 -25
  88. data/lib/capybara/selector/filters/base.rb +24 -5
  89. data/lib/capybara/selector/filters/expression_filter.rb +3 -3
  90. data/lib/capybara/selector/filters/locator_filter.rb +29 -0
  91. data/lib/capybara/selector/filters/node_filter.rb +16 -2
  92. data/lib/capybara/selector/regexp_disassembler.rb +211 -0
  93. data/lib/capybara/selector/selector.rb +85 -348
  94. data/lib/capybara/selector/xpath_extensions.rb +17 -0
  95. data/lib/capybara/selector.rb +474 -447
  96. data/lib/capybara/selenium/atoms/getAttribute.min.js +1 -0
  97. data/lib/capybara/selenium/atoms/isDisplayed.min.js +1 -0
  98. data/lib/capybara/selenium/atoms/src/getAttribute.js +161 -0
  99. data/lib/capybara/selenium/atoms/src/isDisplayed.js +454 -0
  100. data/lib/capybara/selenium/driver.rb +255 -143
  101. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +93 -11
  102. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +128 -0
  103. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +84 -0
  104. data/lib/capybara/selenium/driver_specializations/internet_explorer_driver.rb +26 -0
  105. data/lib/capybara/selenium/driver_specializations/safari_driver.rb +24 -0
  106. data/lib/capybara/selenium/extensions/file_input_click_emulation.rb +34 -0
  107. data/lib/capybara/selenium/extensions/find.rb +110 -0
  108. data/lib/capybara/selenium/extensions/html5_drag.rb +229 -0
  109. data/lib/capybara/selenium/extensions/modifier_keys_stack.rb +28 -0
  110. data/lib/capybara/selenium/extensions/scroll.rb +76 -0
  111. data/lib/capybara/selenium/node.rb +436 -134
  112. data/lib/capybara/selenium/nodes/chrome_node.rb +125 -0
  113. data/lib/capybara/selenium/nodes/edge_node.rb +110 -0
  114. data/lib/capybara/selenium/nodes/firefox_node.rb +136 -0
  115. data/lib/capybara/selenium/nodes/ie_node.rb +22 -0
  116. data/lib/capybara/selenium/nodes/safari_node.rb +118 -0
  117. data/lib/capybara/selenium/patches/atoms.rb +18 -0
  118. data/lib/capybara/selenium/patches/is_displayed.rb +16 -0
  119. data/lib/capybara/selenium/patches/logs.rb +45 -0
  120. data/lib/capybara/selenium/patches/pause_duration_fix.rb +9 -0
  121. data/lib/capybara/selenium/patches/persistent_client.rb +20 -0
  122. data/lib/capybara/server/animation_disabler.rb +56 -19
  123. data/lib/capybara/server/checker.rb +9 -3
  124. data/lib/capybara/server/middleware.rb +28 -12
  125. data/lib/capybara/server.rb +33 -10
  126. data/lib/capybara/session/config.rb +34 -10
  127. data/lib/capybara/session/matchers.rb +23 -16
  128. data/lib/capybara/session.rb +230 -170
  129. data/lib/capybara/spec/public/jquery.js +5 -5
  130. data/lib/capybara/spec/public/offset.js +6 -0
  131. data/lib/capybara/spec/public/test.js +121 -8
  132. data/lib/capybara/spec/session/accept_alert_spec.rb +11 -11
  133. data/lib/capybara/spec/session/accept_confirm_spec.rb +3 -3
  134. data/lib/capybara/spec/session/accept_prompt_spec.rb +9 -10
  135. data/lib/capybara/spec/session/active_element_spec.rb +31 -0
  136. data/lib/capybara/spec/session/all_spec.rb +127 -40
  137. data/lib/capybara/spec/session/ancestor_spec.rb +24 -19
  138. data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +67 -38
  139. data/lib/capybara/spec/session/assert_current_path_spec.rb +21 -18
  140. data/lib/capybara/spec/session/assert_selector_spec.rb +52 -58
  141. data/lib/capybara/spec/session/assert_style_spec.rb +7 -7
  142. data/lib/capybara/spec/session/assert_text_spec.rb +74 -50
  143. data/lib/capybara/spec/session/assert_title_spec.rb +12 -12
  144. data/lib/capybara/spec/session/attach_file_spec.rb +126 -72
  145. data/lib/capybara/spec/session/body_spec.rb +6 -6
  146. data/lib/capybara/spec/session/check_spec.rb +102 -47
  147. data/lib/capybara/spec/session/choose_spec.rb +58 -32
  148. data/lib/capybara/spec/session/click_button_spec.rb +219 -163
  149. data/lib/capybara/spec/session/click_link_or_button_spec.rb +49 -23
  150. data/lib/capybara/spec/session/click_link_spec.rb +77 -54
  151. data/lib/capybara/spec/session/current_scope_spec.rb +8 -8
  152. data/lib/capybara/spec/session/current_url_spec.rb +38 -29
  153. data/lib/capybara/spec/session/dismiss_confirm_spec.rb +3 -3
  154. data/lib/capybara/spec/session/dismiss_prompt_spec.rb +2 -2
  155. data/lib/capybara/spec/session/element/assert_match_selector_spec.rb +8 -8
  156. data/lib/capybara/spec/session/element/match_css_spec.rb +16 -10
  157. data/lib/capybara/spec/session/element/match_xpath_spec.rb +6 -6
  158. data/lib/capybara/spec/session/element/matches_selector_spec.rb +68 -56
  159. data/lib/capybara/spec/session/evaluate_async_script_spec.rb +7 -7
  160. data/lib/capybara/spec/session/evaluate_script_spec.rb +28 -8
  161. data/lib/capybara/spec/session/execute_script_spec.rb +8 -7
  162. data/lib/capybara/spec/session/fill_in_spec.rb +101 -46
  163. data/lib/capybara/spec/session/find_button_spec.rb +23 -23
  164. data/lib/capybara/spec/session/find_by_id_spec.rb +7 -7
  165. data/lib/capybara/spec/session/find_field_spec.rb +32 -30
  166. data/lib/capybara/spec/session/find_link_spec.rb +31 -21
  167. data/lib/capybara/spec/session/find_spec.rb +244 -141
  168. data/lib/capybara/spec/session/first_spec.rb +43 -43
  169. data/lib/capybara/spec/session/frame/frame_title_spec.rb +5 -5
  170. data/lib/capybara/spec/session/frame/frame_url_spec.rb +5 -5
  171. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +30 -18
  172. data/lib/capybara/spec/session/frame/within_frame_spec.rb +45 -18
  173. data/lib/capybara/spec/session/go_back_spec.rb +1 -1
  174. data/lib/capybara/spec/session/go_forward_spec.rb +1 -1
  175. data/lib/capybara/spec/session/has_all_selectors_spec.rb +23 -23
  176. data/lib/capybara/spec/session/has_ancestor_spec.rb +46 -0
  177. data/lib/capybara/spec/session/has_any_selectors_spec.rb +29 -0
  178. data/lib/capybara/spec/session/has_button_spec.rb +94 -13
  179. data/lib/capybara/spec/session/has_css_spec.rb +272 -132
  180. data/lib/capybara/spec/session/has_current_path_spec.rb +50 -35
  181. data/lib/capybara/spec/session/has_element_spec.rb +47 -0
  182. data/lib/capybara/spec/session/has_field_spec.rb +137 -58
  183. data/lib/capybara/spec/session/has_link_spec.rb +44 -4
  184. data/lib/capybara/spec/session/has_none_selectors_spec.rb +31 -31
  185. data/lib/capybara/spec/session/has_select_spec.rb +84 -50
  186. data/lib/capybara/spec/session/has_selector_spec.rb +111 -71
  187. data/lib/capybara/spec/session/has_sibling_spec.rb +50 -0
  188. data/lib/capybara/spec/session/has_table_spec.rb +181 -4
  189. data/lib/capybara/spec/session/has_text_spec.rb +101 -53
  190. data/lib/capybara/spec/session/has_title_spec.rb +19 -14
  191. data/lib/capybara/spec/session/has_xpath_spec.rb +56 -38
  192. data/lib/capybara/spec/session/headers_spec.rb +1 -1
  193. data/lib/capybara/spec/session/html_spec.rb +13 -6
  194. data/lib/capybara/spec/session/matches_style_spec.rb +37 -0
  195. data/lib/capybara/spec/session/node_spec.rb +894 -142
  196. data/lib/capybara/spec/session/node_wrapper_spec.rb +10 -7
  197. data/lib/capybara/spec/session/refresh_spec.rb +9 -7
  198. data/lib/capybara/spec/session/reset_session_spec.rb +63 -35
  199. data/lib/capybara/spec/session/response_code_spec.rb +1 -1
  200. data/lib/capybara/spec/session/save_and_open_page_spec.rb +2 -2
  201. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +2 -2
  202. data/lib/capybara/spec/session/save_page_spec.rb +37 -37
  203. data/lib/capybara/spec/session/save_screenshot_spec.rb +10 -10
  204. data/lib/capybara/spec/session/screenshot_spec.rb +2 -2
  205. data/lib/capybara/spec/session/scroll_spec.rb +119 -0
  206. data/lib/capybara/spec/session/select_spec.rb +85 -85
  207. data/lib/capybara/spec/session/selectors_spec.rb +49 -18
  208. data/lib/capybara/spec/session/sibling_spec.rb +9 -9
  209. data/lib/capybara/spec/session/text_spec.rb +25 -24
  210. data/lib/capybara/spec/session/title_spec.rb +7 -6
  211. data/lib/capybara/spec/session/uncheck_spec.rb +33 -21
  212. data/lib/capybara/spec/session/unselect_spec.rb +37 -37
  213. data/lib/capybara/spec/session/visit_spec.rb +68 -49
  214. data/lib/capybara/spec/session/window/become_closed_spec.rb +20 -17
  215. data/lib/capybara/spec/session/window/current_window_spec.rb +1 -1
  216. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +20 -16
  217. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +6 -2
  218. data/lib/capybara/spec/session/window/window_spec.rb +62 -63
  219. data/lib/capybara/spec/session/window/windows_spec.rb +5 -1
  220. data/lib/capybara/spec/session/window/within_window_spec.rb +14 -14
  221. data/lib/capybara/spec/session/within_spec.rb +79 -42
  222. data/lib/capybara/spec/spec_helper.rb +41 -53
  223. data/lib/capybara/spec/test_app.rb +132 -43
  224. data/lib/capybara/spec/views/animated.erb +49 -0
  225. data/lib/capybara/spec/views/form.erb +139 -42
  226. data/lib/capybara/spec/views/frame_child.erb +4 -3
  227. data/lib/capybara/spec/views/frame_one.erb +2 -1
  228. data/lib/capybara/spec/views/frame_parent.erb +1 -1
  229. data/lib/capybara/spec/views/frame_two.erb +1 -1
  230. data/lib/capybara/spec/views/initial_alert.erb +2 -1
  231. data/lib/capybara/spec/views/layout.erb +10 -0
  232. data/lib/capybara/spec/views/obscured.erb +47 -0
  233. data/lib/capybara/spec/views/offset.erb +33 -0
  234. data/lib/capybara/spec/views/path.erb +2 -2
  235. data/lib/capybara/spec/views/popup_one.erb +1 -1
  236. data/lib/capybara/spec/views/popup_two.erb +1 -1
  237. data/lib/capybara/spec/views/react.erb +45 -0
  238. data/lib/capybara/spec/views/scroll.erb +21 -0
  239. data/lib/capybara/spec/views/spatial.erb +31 -0
  240. data/lib/capybara/spec/views/tables.erb +67 -0
  241. data/lib/capybara/spec/views/with_animation.erb +39 -4
  242. data/lib/capybara/spec/views/with_base_tag.erb +2 -2
  243. data/lib/capybara/spec/views/with_dragula.erb +24 -0
  244. data/lib/capybara/spec/views/with_fixed_header_footer.erb +2 -1
  245. data/lib/capybara/spec/views/with_hover.erb +3 -2
  246. data/lib/capybara/spec/views/with_hover1.erb +10 -0
  247. data/lib/capybara/spec/views/with_html.erb +37 -9
  248. data/lib/capybara/spec/views/with_html5_svg.erb +20 -0
  249. data/lib/capybara/spec/views/with_jquery_animation.erb +24 -0
  250. data/lib/capybara/spec/views/with_js.erb +26 -5
  251. data/lib/capybara/spec/views/with_jstree.erb +26 -0
  252. data/lib/capybara/spec/views/with_namespace.erb +1 -0
  253. data/lib/capybara/spec/views/with_scope.erb +2 -2
  254. data/lib/capybara/spec/views/with_scope_other.erb +6 -0
  255. data/lib/capybara/spec/views/with_shadow.erb +31 -0
  256. data/lib/capybara/spec/views/with_slow_unload.erb +2 -1
  257. data/lib/capybara/spec/views/with_sortable_js.erb +21 -0
  258. data/lib/capybara/spec/views/with_unload_alert.erb +1 -0
  259. data/lib/capybara/spec/views/with_windows.erb +1 -1
  260. data/lib/capybara/spec/views/within_frames.erb +1 -1
  261. data/lib/capybara/version.rb +1 -1
  262. data/lib/capybara/window.rb +19 -25
  263. data/lib/capybara.rb +126 -111
  264. data/spec/basic_node_spec.rb +59 -34
  265. data/spec/capybara_spec.rb +56 -44
  266. data/spec/counter_spec.rb +35 -0
  267. data/spec/css_builder_spec.rb +101 -0
  268. data/spec/css_splitter_spec.rb +8 -8
  269. data/spec/dsl_spec.rb +79 -52
  270. data/spec/filter_set_spec.rb +9 -9
  271. data/spec/fixtures/selenium_driver_rspec_failure.rb +4 -4
  272. data/spec/fixtures/selenium_driver_rspec_success.rb +4 -4
  273. data/spec/minitest_spec.rb +45 -7
  274. data/spec/minitest_spec_spec.rb +87 -64
  275. data/spec/per_session_config_spec.rb +6 -6
  276. data/spec/rack_test_spec.rb +172 -116
  277. data/spec/regexp_dissassembler_spec.rb +250 -0
  278. data/spec/result_spec.rb +80 -72
  279. data/spec/rspec/features_spec.rb +21 -16
  280. data/spec/rspec/scenarios_spec.rb +10 -6
  281. data/spec/rspec/shared_spec_matchers.rb +407 -365
  282. data/spec/rspec/views_spec.rb +3 -3
  283. data/spec/rspec_matchers_spec.rb +35 -10
  284. data/spec/rspec_spec.rb +63 -41
  285. data/spec/sauce_spec_chrome.rb +43 -0
  286. data/spec/selector_spec.rb +334 -89
  287. data/spec/selenium_spec_chrome.rb +176 -62
  288. data/spec/selenium_spec_chrome_remote.rb +54 -14
  289. data/spec/selenium_spec_edge.rb +41 -8
  290. data/spec/selenium_spec_firefox.rb +228 -0
  291. data/spec/selenium_spec_firefox_remote.rb +94 -0
  292. data/spec/selenium_spec_ie.rb +129 -11
  293. data/spec/selenium_spec_safari.rb +162 -0
  294. data/spec/server_spec.rb +171 -97
  295. data/spec/session_spec.rb +34 -18
  296. data/spec/shared_selenium_node.rb +79 -0
  297. data/spec/shared_selenium_session.rb +344 -80
  298. data/spec/spec_helper.rb +124 -2
  299. data/spec/whitespace_normalizer_spec.rb +54 -0
  300. data/spec/xpath_builder_spec.rb +93 -0
  301. metadata +326 -28
  302. data/lib/capybara/rspec/compound.rb +0 -94
  303. data/lib/capybara/selenium/driver_specializations/marionette_driver.rb +0 -31
  304. data/lib/capybara/selenium/nodes/marionette_node.rb +0 -31
  305. data/lib/capybara/spec/session/has_style_spec.rb +0 -25
  306. data/lib/capybara/spec/session/source_spec.rb +0 -0
  307. data/lib/capybara/spec/views/with_title.erb +0 -5
  308. data/spec/selenium_spec_marionette.rb +0 -167
@@ -1,23 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'capybara/rack_test/errors'
4
+ require 'capybara/node/whitespace_normalizer'
5
+
3
6
  class Capybara::RackTest::Node < Capybara::Driver::Node
7
+ include Capybara::Node::WhitespaceNormalizer
8
+
4
9
  BLOCK_ELEMENTS = %w[p h1 h2 h3 h4 h5 h6 ol ul pre address blockquote dl div fieldset form hr noscript table].freeze
5
10
 
6
11
  def all_text
7
- native.text
8
- .gsub(/[\u200b\u200e\u200f]/, '')
9
- .gsub(/[\ \n\f\t\v\u2028\u2029]+/, ' ')
10
- .gsub(/\A[[:space:]&&[^\u00a0]]+/, "")
11
- .gsub(/[[:space:]&&[^\u00a0]]+\z/, "")
12
- .tr("\u00a0", ' ')
12
+ normalize_spacing(native.text)
13
13
  end
14
14
 
15
15
  def visible_text
16
- displayed_text.gsub(/\ +/, ' ')
17
- .gsub(/[\ \n]*\n[\ \n]*/, "\n")
18
- .gsub(/\A[[:space:]&&[^\u00a0]]+/, "")
19
- .gsub(/[[:space:]&&[^\u00a0]]+\z/, "")
20
- .tr("\u00a0", ' ')
16
+ normalize_visible_spacing(displayed_text)
21
17
  end
22
18
 
23
19
  def [](name)
@@ -25,7 +21,7 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
25
21
  end
26
22
 
27
23
  def style(_styles)
28
- raise NotImplementedError, "The rack_test driver does not process CSS"
24
+ raise NotImplementedError, 'The rack_test driver does not process CSS'
29
25
  end
30
26
 
31
27
  def value
@@ -43,6 +39,7 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
43
39
 
44
40
  if radio? then set_radio(value)
45
41
  elsif checkbox? then set_checkbox(value)
42
+ elsif range? then set_range(value)
46
43
  elsif input_field? then set_input(value)
47
44
  elsif textarea? then native['_capybara_raw_value'] = value.to_s
48
45
  end
@@ -50,17 +47,20 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
50
47
 
51
48
  def select_option
52
49
  return if disabled?
50
+
53
51
  deselect_options unless select_node.multiple?
54
- native["selected"] = 'selected'
52
+ native['selected'] = 'selected'
55
53
  end
56
54
 
57
55
  def unselect_option
58
- raise Capybara::UnselectNotAllowed, "Cannot unselect option from single select box." unless select_node.multiple?
56
+ raise Capybara::UnselectNotAllowed, 'Cannot unselect option from single select box.' unless select_node.multiple?
57
+
59
58
  native.remove_attribute('selected')
60
59
  end
61
60
 
62
- def click(keys = [], **offset)
63
- raise ArgumentError, "The RackTest driver does not support click options" unless keys.empty? && offset.empty?
61
+ def click(keys = [], **options)
62
+ options.delete(:offset)
63
+ raise ArgumentError, 'The RackTest driver does not support click options' unless keys.empty? && options.empty?
64
64
 
65
65
  if link?
66
66
  follow_link
@@ -71,6 +71,8 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
71
71
  set(!checked?)
72
72
  elsif tag_name == 'label'
73
73
  click_label
74
+ elsif (details = native.xpath('.//ancestor-or-self::details').last)
75
+ toggle_details(details)
74
76
  end
75
77
  end
76
78
 
@@ -94,26 +96,41 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
94
96
  return true if string_node.disabled?
95
97
 
96
98
  if %w[option optgroup].include? tag_name
97
- find_xpath("parent::*[self::optgroup or self::select or self::datalist]")[0].disabled?
99
+ find_xpath(OPTION_OWNER_XPATH)[0].disabled?
98
100
  else
99
- !find_xpath("parent::fieldset[@disabled] | ancestor::*[not(self::legend) or preceding-sibling::legend][parent::fieldset[@disabled]]").empty?
101
+ !find_xpath(DISABLED_BY_FIELDSET_XPATH).empty?
100
102
  end
101
103
  end
102
104
 
105
+ def readonly?
106
+ # readonly attribute not valid on these input types
107
+ return false if input_field? && %w[hidden range color checkbox radio file submit image reset button].include?(type)
108
+
109
+ super
110
+ end
111
+
103
112
  def path
104
113
  native.path
105
114
  end
106
115
 
107
- def find_xpath(locator)
108
- native.xpath(locator).map { |n| self.class.new(driver, n) }
116
+ def find_xpath(locator, **_hints)
117
+ native.xpath(locator).map { |el| self.class.new(driver, el) }
109
118
  end
110
119
 
111
- def find_css(locator)
112
- native.css(locator, Capybara::RackTest::CSSHandlers.new).map { |n| self.class.new(driver, n) }
120
+ def find_css(locator, **_hints)
121
+ native.css(locator, Capybara::RackTest::CSSHandlers.new).map { |el| self.class.new(driver, el) }
113
122
  end
114
123
 
115
- def ==(other)
116
- native == other.native
124
+ public_instance_methods(false).each do |meth_name|
125
+ alias_method "unchecked_#{meth_name}", meth_name
126
+ private "unchecked_#{meth_name}" # rubocop:disable Style/AccessModifierDeclarations
127
+
128
+ class_eval <<~METHOD, __FILE__, __LINE__ + 1
129
+ def #{meth_name}(...)
130
+ stale_check
131
+ method(:"unchecked_#{meth_name}").call(...)
132
+ end
133
+ METHOD
117
134
  end
118
135
 
119
136
  protected
@@ -123,24 +140,30 @@ protected
123
140
  if !string_node.visible?(check_ancestor)
124
141
  ''
125
142
  elsif native.text?
126
- native.text
127
- .gsub(/[\u200b\u200e\u200f]/, '')
128
- .gsub(/[\ \n\f\t\v\u2028\u2029]+/, ' ')
143
+ native
144
+ .text
145
+ .delete(REMOVED_CHARACTERS)
146
+ .tr(SQUEEZED_SPACES, ' ')
147
+ .squeeze(' ')
129
148
  elsif native.element?
130
149
  text = native.children.map do |child|
131
150
  Capybara::RackTest::Node.new(driver, child).displayed_text(check_ancestor: false)
132
151
  end.join || ''
133
152
  text = "\n#{text}\n" if BLOCK_ELEMENTS.include?(tag_name)
134
153
  text
135
- else
154
+ else # rubocop:disable Lint/DuplicateBranch
136
155
  ''
137
156
  end
138
157
  end
139
158
 
140
159
  private
141
160
 
161
+ def stale_check
162
+ raise Capybara::RackTest::Errors::StaleElementReferenceError unless native.document == driver.dom
163
+ end
164
+
142
165
  def deselect_options
143
- select_node.find_xpath(".//option[@selected]").each { |node| node.native.remove_attribute("selected") }
166
+ select_node.find_xpath('.//option[@selected]').each { |node| node.native.remove_attribute('selected') }
144
167
  end
145
168
 
146
169
  def string_node
@@ -165,8 +188,8 @@ private
165
188
  end
166
189
 
167
190
  def set_radio(_value) # rubocop:disable Naming/AccessorMethodName
168
- other_radios_xpath = XPath.generate { |x| x.anywhere(:input)[x.attr(:name) == self[:name]] }.to_s
169
- driver.dom.xpath(other_radios_xpath).each { |node| node.remove_attribute("checked") }
191
+ other_radios_xpath = XPath.generate { |xp| xp.anywhere(:input)[xp.attr(:name) == self[:name]] }.to_s
192
+ driver.dom.xpath(other_radios_xpath).each { |node| node.remove_attribute('checked') }
170
193
  native['checked'] = 'checked'
171
194
  end
172
195
 
@@ -178,6 +201,14 @@ private
178
201
  end
179
202
  end
180
203
 
204
+ def set_range(value) # rubocop:disable Naming/AccessorMethodName
205
+ min, max, step = (native['min'] || 0).to_f, (native['max'] || 100).to_f, (native['step'] || 1).to_f
206
+ value = value.to_f
207
+ value = value.clamp(min, max)
208
+ value = (((value - min) / step).round * step) + min
209
+ native['value'] = value.clamp(min, max)
210
+ end
211
+
181
212
  def set_input(value) # rubocop:disable Naming/AccessorMethodName
182
213
  if text_or_password? && attribute_is_not_blank?(:maxlength)
183
214
  # Browser behavior for maxlength="0" is inconsistent, so we stick with
@@ -185,15 +216,22 @@ private
185
216
  value = value.to_s[0...self[:maxlength].to_i]
186
217
  end
187
218
  if value.is_a?(Array) # Assert multiple attribute is present
188
- value.each do |v|
219
+ value.each do |val|
189
220
  new_native = native.clone
190
221
  new_native.remove_attribute('value')
191
222
  native.add_next_sibling(new_native)
192
- new_native['value'] = v.to_s
223
+ new_native['value'] = val.to_s
193
224
  end
194
225
  native.remove
195
226
  else
196
- native['value'] = value.to_s
227
+ value.to_s.tap do |set_value|
228
+ if set_value.end_with?("\n") && form&.css('input, textarea')&.count == 1
229
+ native['value'] = set_value.to_s.chop
230
+ Capybara::RackTest::Form.new(driver, form).submit(self)
231
+ else
232
+ native['value'] = set_value
233
+ end
234
+ end
197
235
  end
198
236
  end
199
237
 
@@ -202,7 +240,7 @@ private
202
240
  end
203
241
 
204
242
  def follow_link
205
- method = self["data-method"] if driver.options[:respect_data_method]
243
+ method = self['data-method'] || self['data-turbo-method'] if driver.options[:respect_data_method]
206
244
  method ||= :get
207
245
  driver.follow(method, self[:href].to_s)
208
246
  end
@@ -211,18 +249,29 @@ private
211
249
  labelled_control = if native[:for]
212
250
  find_xpath("//input[@id='#{native[:for]}']")
213
251
  else
214
- find_xpath(".//input")
252
+ find_xpath('.//input')
215
253
  end.first
216
254
 
217
255
  labelled_control.set(!labelled_control.checked?) if checkbox_or_radio?(labelled_control)
218
256
  end
219
257
 
258
+ def toggle_details(details = nil)
259
+ details ||= native.xpath('.//ancestor-or-self::details').last
260
+ return unless details
261
+
262
+ if details.has_attribute?('open')
263
+ details.remove_attribute('open')
264
+ else
265
+ details.set_attribute('open', 'open')
266
+ end
267
+ end
268
+
220
269
  def link?
221
270
  tag_name == 'a' && !self[:href].nil?
222
271
  end
223
272
 
224
273
  def submits?
225
- (tag_name == 'input' && %w[submit image].include?(type)) || (tag_name == 'button' && [nil, "submit"].include?(type))
274
+ (tag_name == 'input' && %w[submit image].include?(type)) || (tag_name == 'button' && [nil, 'submit'].include?(type))
226
275
  end
227
276
 
228
277
  def checkable?
@@ -252,6 +301,24 @@ protected
252
301
  end
253
302
 
254
303
  def textarea?
255
- tag_name == "textarea"
256
- end
304
+ tag_name == 'textarea'
305
+ end
306
+
307
+ def range?
308
+ input_field? && type == 'range'
309
+ end
310
+
311
+ OPTION_OWNER_XPATH = XPath.parent(:optgroup, :select, :datalist).to_s.freeze
312
+ DISABLED_BY_FIELDSET_XPATH = XPath.generate do |x|
313
+ x.parent(:fieldset)[
314
+ XPath.attr(:disabled)
315
+ ] + x.ancestor[
316
+ ~x.self(:legend) |
317
+ x.preceding_sibling(:legend)
318
+ ][
319
+ x.parent(:fieldset)[
320
+ x.attr(:disabled)
321
+ ]
322
+ ]
323
+ end.to_s.freeze
257
324
  end
@@ -3,7 +3,7 @@
3
3
  require 'capybara/dsl'
4
4
 
5
5
  Capybara.app = Rack::Builder.new do
6
- map "/" do
6
+ map '/' do
7
7
  run Rails.application
8
8
  end
9
9
  end.to_app
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capybara
4
+ # @api private
5
+ class RegistrationContainer
6
+ def names
7
+ @registered.keys
8
+ end
9
+
10
+ def [](name)
11
+ @registered[name]
12
+ end
13
+
14
+ def []=(name, value)
15
+ Capybara::Helpers.warn 'DEPRECATED: Directly setting drivers/servers is deprecated, please use Capybara.register_driver/register_server instead'
16
+ @registered[name] = value
17
+ end
18
+
19
+ def method_missing(method_name, ...)
20
+ if @registered.respond_to?(method_name)
21
+ Capybara::Helpers.warn "DEPRECATED: Calling '#{method_name}' on the drivers/servers container is deprecated without replacement"
22
+ return @registered.public_send(method_name, ...)
23
+ end
24
+ super
25
+ end
26
+
27
+ def respond_to_missing?(method_name, include_all)
28
+ @registered.respond_to?(method_name) || super
29
+ end
30
+
31
+ private
32
+
33
+ def initialize
34
+ @registered = {}
35
+ end
36
+
37
+ def register(name, block)
38
+ @registered[name] = block
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ Capybara.register_driver :rack_test do |app|
4
+ Capybara::RackTest::Driver.new(app)
5
+ end
6
+
7
+ Capybara.register_driver :selenium do |app|
8
+ Capybara::Selenium::Driver.new(app)
9
+ end
10
+
11
+ Capybara.register_driver :selenium_headless do |app|
12
+ version = Capybara::Selenium::Driver.load_selenium
13
+ options_key = Capybara::Selenium::Driver::CAPS_VERSION.satisfied_by?(version) ? :capabilities : :options
14
+ browser_options = Selenium::WebDriver::Firefox::Options.new.tap do |opts|
15
+ opts.add_argument '-headless'
16
+ end
17
+ Capybara::Selenium::Driver.new(app, **{ :browser => :firefox, options_key => browser_options })
18
+ end
19
+
20
+ Capybara.register_driver :selenium_chrome do |app|
21
+ version = Capybara::Selenium::Driver.load_selenium
22
+ options_key = Capybara::Selenium::Driver::CAPS_VERSION.satisfied_by?(version) ? :capabilities : :options
23
+ browser_options = Selenium::WebDriver::Chrome::Options.new.tap do |opts|
24
+ # Workaround https://bugs.chromium.org/p/chromedriver/issues/detail?id=2650&q=load&sort=-id&colspec=ID%20Status%20Pri%20Owner%20Summary
25
+ opts.add_argument('--disable-site-isolation-trials')
26
+ end
27
+
28
+ Capybara::Selenium::Driver.new(app, **{ :browser => :chrome, options_key => browser_options })
29
+ end
30
+
31
+ Capybara.register_driver :selenium_chrome_headless do |app|
32
+ version = Capybara::Selenium::Driver.load_selenium
33
+ options_key = Capybara::Selenium::Driver::CAPS_VERSION.satisfied_by?(version) ? :capabilities : :options
34
+ browser_options = Selenium::WebDriver::Chrome::Options.new.tap do |opts|
35
+ opts.add_argument('--headless=new')
36
+ opts.add_argument('--disable-gpu') if Gem.win_platform?
37
+ # Workaround https://bugs.chromium.org/p/chromedriver/issues/detail?id=2650&q=load&sort=-id&colspec=ID%20Status%20Pri%20Owner%20Summary
38
+ opts.add_argument('--disable-site-isolation-trials')
39
+ end
40
+
41
+ Capybara::Selenium::Driver.new(app, **{ :browser => :chrome, options_key => browser_options })
42
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Puma
4
+ module MiniSSL
5
+ class Socket
6
+ def read_nonblock(size, *_)
7
+ wait_states = %i[wait_readable wait_writable]
8
+
9
+ loop do
10
+ output = engine_read_all
11
+ return output if output
12
+
13
+ data = @socket.read_nonblock(size, exception: false)
14
+ raise IO::EAGAINWaitReadable if wait_states.include? data
15
+ return nil if data.nil?
16
+
17
+ @engine.inject(data)
18
+ output = engine_read_all
19
+
20
+ return output if output
21
+
22
+ while (neg_data = @engine.extract)
23
+ @socket.write neg_data
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ Capybara.register_server :default do |app, port, _host|
4
+ Capybara.run_default_server(app, port)
5
+ end
6
+
7
+ Capybara.register_server :webrick do |app, port, host, **options|
8
+ base_class = begin
9
+ require 'rack/handler/webrick'
10
+ Rack
11
+ rescue LoadError
12
+ # Rack 3 separated out the webrick handle - no way test currently in Capybaras automated
13
+ # tests due to Sinatra not yet supporting Rack 3 - experimental
14
+ require 'rackup/handler/webrick'
15
+ Rackup
16
+ end
17
+ options = { Host: host, Port: port, AccessLog: [], Logger: WEBrick::Log.new(nil, 0) }.merge(options)
18
+ base_class::Handler::WEBrick.run(app, **options)
19
+ end
20
+
21
+ Capybara.register_server :puma do |app, port, host, **options| # rubocop:disable Metrics/BlockLength
22
+ begin
23
+ require 'rackup'
24
+ rescue LoadError # rubocop:disable Lint/SuppressedException
25
+ end
26
+ begin
27
+ require 'rack/handler/puma'
28
+ rescue LoadError
29
+ raise LoadError, 'Capybara is unable to load `puma` for its server, please add `puma` to your project or specify a different server via something like `Capybara.server = :webrick`.'
30
+ end
31
+ puma_rack_handler = defined?(Rackup::Handler::Puma) ? Rackup::Handler::Puma : Rack::Handler::Puma
32
+
33
+ unless puma_rack_handler.respond_to?(:config)
34
+ raise LoadError, 'Capybara requires `puma` version 3.8.0 or higher, please upgrade `puma` or register and specify your own server block'
35
+ end
36
+
37
+ # If we just run the Puma Rack handler it installs signal handlers which prevent us from being able to interrupt tests.
38
+ # Therefore construct and run the Server instance ourselves.
39
+ # puma_rack_handler.run(app, { Host: host, Port: port, Threads: "0:4", workers: 0, daemon: false }.merge(options))
40
+ default_options = { Host: host, Port: port, Threads: '0:4', workers: 0, daemon: false }
41
+ options = default_options.merge(options)
42
+
43
+ conf = puma_rack_handler.config(app, options)
44
+ conf.clamp
45
+
46
+ puma_ver = Gem::Version.new(Puma::Const::PUMA_VERSION)
47
+ require_relative 'patches/puma_ssl' if Gem::Requirement.new('>=4.0.0', '< 4.1.0').satisfied_by?(puma_ver)
48
+
49
+ logger = (defined?(Puma::LogWriter) ? Puma::LogWriter : Puma::Events).then do |cls|
50
+ conf.options[:Silent] ? cls.strings : cls.stdio
51
+ end
52
+ conf.options[:log_writer] = logger
53
+
54
+ logger.log 'Capybara starting Puma...'
55
+ logger.log "* Version #{Puma::Const::PUMA_VERSION}, codename: #{Puma::Const::CODE_NAME}"
56
+ logger.log "* Min threads: #{conf.options[:min_threads]}, max threads: #{conf.options[:max_threads]}"
57
+
58
+ Puma::Server.new(
59
+ conf.app,
60
+ defined?(Puma::LogWriter) ? nil : logger,
61
+ conf.options
62
+ ).tap do |s|
63
+ s.binder.parse conf.options[:binds], (s.log_writer rescue s.events) # rubocop:disable Style/RescueModifier
64
+ s.min_threads, s.max_threads = conf.options[:min_threads], conf.options[:max_threads] if s.respond_to? :min_threads=
65
+ end.run.join
66
+ end
@@ -7,7 +7,7 @@ module Capybara
7
7
  # A {Capybara::Result} represents a collection of {Capybara::Node::Element} on the page. It is possible to interact with this
8
8
  # collection similar to an Array because it implements Enumerable and offers the following Array methods through delegation:
9
9
  #
10
- # * []
10
+ # * \[\]
11
11
  # * each()
12
12
  # * at()
13
13
  # * size()
@@ -16,6 +16,8 @@ module Capybara
16
16
  # * first()
17
17
  # * last()
18
18
  # * empty?()
19
+ # * values_at()
20
+ # * sample()
19
21
  #
20
22
  # @see Capybara::Node::Element
21
23
  #
@@ -26,21 +28,23 @@ module Capybara
26
28
  def initialize(elements, query)
27
29
  @elements = elements
28
30
  @result_cache = []
29
- @results_enum = lazy_select_elements { |node| query.matches_filters?(node) }
31
+ @filter_errors = []
32
+ @results_enum = lazy_select_elements { |node| query.matches_filters?(node, @filter_errors) }
30
33
  @query = query
34
+ @allow_reload = false
31
35
  end
32
36
 
33
- def_delegators :full_results, :size, :length, :last, :values_at, :inspect, :sample
37
+ def_delegators :full_results, :size, :length, :last, :values_at, :inspect, :sample, :to_ary
34
38
 
35
- alias :index :find_index
39
+ alias index find_index
36
40
 
37
41
  def each(&block)
38
- return enum_for(:each) unless block_given?
42
+ return enum_for(:each) unless block
39
43
 
40
44
  @result_cache.each(&block)
41
45
  loop do
42
46
  next_result = @results_enum.next
43
- @result_cache << next_result
47
+ add_to_cache(next_result)
44
48
  yield next_result
45
49
  end
46
50
  self
@@ -50,22 +54,24 @@ module Capybara
50
54
  idx, length = args
51
55
  max_idx = case idx
52
56
  when Integer
53
- if !idx.negative?
54
- length.nil? ? idx : idx + length - 1
55
- else
57
+ if idx.negative?
56
58
  nil
59
+ else
60
+ length.nil? ? idx : idx + length - 1
57
61
  end
58
62
  when Range
59
- idx.max
63
+ # idx.max is broken with beginless ranges
64
+ # idx.end && idx.max # endless range will have end == nil
65
+ max = idx.end
66
+ max = nil if max&.negative?
67
+ max -= 1 if max && idx.exclude_end?
68
+ max
60
69
  end
61
70
 
62
71
  if max_idx.nil?
63
72
  full_results[*args]
64
73
  else
65
- loop do
66
- break if @result_cache.size > max_idx
67
- @result_cache << @results_enum.next
68
- end
74
+ load_up_to(max_idx + 1)
69
75
  @result_cache[*args]
70
76
  end
71
77
  end
@@ -76,41 +82,25 @@ module Capybara
76
82
  end
77
83
 
78
84
  def compare_count
85
+ return 0 unless @query
86
+
87
+ count, min, max, between = @query.options.values_at(:count, :minimum, :maximum, :between)
88
+
79
89
  # Only check filters for as many elements as necessary to determine result
80
- if @query.options[:count]
81
- count_opt = Integer(@query.options[:count])
82
- loop do
83
- break if @result_cache.size > count_opt
84
- @result_cache << @results_enum.next
85
- end
86
- return @result_cache.size <=> count_opt
90
+ if count && (count = Integer(count))
91
+ return load_up_to(count + 1) <=> count
87
92
  end
88
93
 
89
- if @query.options[:minimum]
90
- min_opt = Integer(@query.options[:minimum])
91
- begin
92
- @result_cache << @results_enum.next while @result_cache.size < min_opt
93
- rescue StopIteration
94
- return -1
95
- end
96
- end
94
+ return -1 if min && (min = Integer(min)) && (load_up_to(min) < min)
97
95
 
98
- if @query.options[:maximum]
99
- max_opt = Integer(@query.options[:maximum])
100
- loop do
101
- return 1 if @result_cache.size > max_opt
102
- @result_cache << @results_enum.next
103
- end
104
- end
96
+ return 1 if max && (max = Integer(max)) && (load_up_to(max + 1) > max)
105
97
 
106
- if @query.options[:between]
107
- min, max = @query.options[:between].minmax
108
- loop do
109
- break if @result_cache.size > max
110
- @result_cache << @results_enum.next
111
- end
112
- return 0 if @query.options[:between].include? @result_cache.size
113
- return @result_cache.size <=> min
98
+ if between
99
+ min, max = (between.begin && between.min) || 1, between.end
100
+ max -= 1 if max && between.exclude_end?
101
+
102
+ size = load_up_to(max ? max + 1 : min)
103
+ return size <=> min unless between.include?(size)
114
104
  end
115
105
 
116
106
  0
@@ -123,13 +113,15 @@ module Capybara
123
113
  def failure_message
124
114
  message = @query.failure_message
125
115
  if count.zero?
126
- message << " but there were no matches"
116
+ message << ' but there were no matches'
127
117
  else
128
- message << ", found #{count} #{Capybara::Helpers.declension('match', 'matches', count)}: " << full_results.map(&:text).map(&:inspect).join(", ")
118
+ message << ", found #{count} #{Capybara::Helpers.declension('match', 'matches', count)}: " \
119
+ << full_results.map { |r| r.text.inspect }.join(', ')
129
120
  end
130
121
  unless rest.empty?
131
- elements = rest.map { |el| el.text rescue "<<ERROR>>" }.map(&:inspect).join(", ") # rubocop:disable Style/RescueModifier
132
- message << ". Also found " << elements << ", which matched the selector but not all filters."
122
+ elements = rest.map { |el| el.text rescue '<<ERROR>>' }.map(&:inspect).join(', ') # rubocop:disable Style/RescueModifier
123
+ message << '. Also found ' << elements << ', which matched the selector but not all filters. '
124
+ message << @filter_errors.join('. ') if (rest.size == 1) && count.zero?
133
125
  end
134
126
  message
135
127
  end
@@ -138,8 +130,34 @@ module Capybara
138
130
  failure_message.sub(/(to find)/, 'not \1')
139
131
  end
140
132
 
133
+ def unfiltered_size
134
+ @elements.length
135
+ end
136
+
137
+ ##
138
+ # @api private
139
+ #
140
+ def allow_reload!
141
+ @allow_reload = true
142
+ self
143
+ end
144
+
141
145
  private
142
146
 
147
+ def add_to_cache(elem)
148
+ elem.allow_reload!(@result_cache.size) if @allow_reload
149
+ @result_cache << elem
150
+ end
151
+
152
+ def load_up_to(num)
153
+ loop do
154
+ break if @result_cache.size >= num
155
+
156
+ add_to_cache(@results_enum.next)
157
+ end
158
+ @result_cache.size
159
+ end
160
+
143
161
  def full_results
144
162
  loop { @result_cache << @results_enum.next }
145
163
  @result_cache
@@ -149,13 +167,18 @@ module Capybara
149
167
  @rest ||= @elements - full_results
150
168
  end
151
169
 
152
- def lazy_select_elements(&block)
153
- # JRuby has an issue with lazy enumerators which
170
+ if RUBY_PLATFORM == 'java'
171
+ # JRuby < 9.2.8.0 has an issue with lazy enumerators which
154
172
  # causes a concurrency issue with network requests here
155
173
  # https://github.com/jruby/jruby/issues/4212
156
- if RUBY_PLATFORM == 'java'
174
+ # while JRuby >= 9.2.8.0 leaks threads when using lazy enumerators
175
+ # https://github.com/teamcapybara/capybara/issues/2349
176
+ # so disable the use and JRuby users will need to pay a performance penalty
177
+ def lazy_select_elements(&block)
157
178
  @elements.select(&block).to_enum # non-lazy evaluation
158
- else
179
+ end
180
+ else
181
+ def lazy_select_elements(&block)
159
182
  @elements.lazy.select(&block)
160
183
  end
161
184
  end