capybara 3.3.0 → 3.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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