capybara 3.0.0 → 3.36.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 -1
  3. data/History.md +777 -10
  4. data/License.txt +1 -1
  5. data/README.md +75 -58
  6. data/lib/capybara/config.rb +29 -10
  7. data/lib/capybara/cucumber.rb +1 -1
  8. data/lib/capybara/driver/base.rb +22 -4
  9. data/lib/capybara/driver/node.rb +34 -9
  10. data/lib/capybara/dsl.rb +14 -6
  11. data/lib/capybara/helpers.rb +52 -7
  12. data/lib/capybara/minitest/spec.rb +173 -84
  13. data/lib/capybara/minitest.rb +250 -144
  14. data/lib/capybara/node/actions.rb +248 -124
  15. data/lib/capybara/node/base.rb +34 -20
  16. data/lib/capybara/node/document.rb +14 -2
  17. data/lib/capybara/node/document_matchers.rb +13 -15
  18. data/lib/capybara/node/element.rb +339 -113
  19. data/lib/capybara/node/finders.rb +95 -82
  20. data/lib/capybara/node/matchers.rb +338 -157
  21. data/lib/capybara/node/simple.rb +54 -15
  22. data/lib/capybara/queries/active_element_query.rb +18 -0
  23. data/lib/capybara/queries/ancestor_query.rb +9 -10
  24. data/lib/capybara/queries/base_query.rb +25 -18
  25. data/lib/capybara/queries/current_path_query.rb +16 -6
  26. data/lib/capybara/queries/match_query.rb +11 -0
  27. data/lib/capybara/queries/selector_query.rb +600 -103
  28. data/lib/capybara/queries/sibling_query.rb +9 -7
  29. data/lib/capybara/queries/style_query.rb +45 -0
  30. data/lib/capybara/queries/text_query.rb +40 -22
  31. data/lib/capybara/queries/title_query.rb +2 -2
  32. data/lib/capybara/rack_test/browser.rb +47 -28
  33. data/lib/capybara/rack_test/driver.rb +12 -3
  34. data/lib/capybara/rack_test/errors.rb +6 -0
  35. data/lib/capybara/rack_test/form.rb +53 -50
  36. data/lib/capybara/rack_test/node.rb +114 -37
  37. data/lib/capybara/rails.rb +1 -1
  38. data/lib/capybara/registration_container.rb +44 -0
  39. data/lib/capybara/registrations/drivers.rb +42 -0
  40. data/lib/capybara/registrations/patches/puma_ssl.rb +29 -0
  41. data/lib/capybara/registrations/servers.rb +45 -0
  42. data/lib/capybara/result.rb +86 -52
  43. data/lib/capybara/rspec/features.rb +8 -10
  44. data/lib/capybara/rspec/matcher_proxies.rb +39 -18
  45. data/lib/capybara/rspec/matchers/base.rb +111 -0
  46. data/lib/capybara/rspec/matchers/become_closed.rb +33 -0
  47. data/lib/capybara/rspec/matchers/compound.rb +88 -0
  48. data/lib/capybara/rspec/matchers/count_sugar.rb +37 -0
  49. data/lib/capybara/rspec/matchers/have_ancestor.rb +28 -0
  50. data/lib/capybara/rspec/matchers/have_current_path.rb +29 -0
  51. data/lib/capybara/rspec/matchers/have_selector.rb +77 -0
  52. data/lib/capybara/rspec/matchers/have_sibling.rb +27 -0
  53. data/lib/capybara/rspec/matchers/have_text.rb +33 -0
  54. data/lib/capybara/rspec/matchers/have_title.rb +29 -0
  55. data/lib/capybara/rspec/matchers/match_selector.rb +27 -0
  56. data/lib/capybara/rspec/matchers/match_style.rb +43 -0
  57. data/lib/capybara/rspec/matchers/spatial_sugar.rb +39 -0
  58. data/lib/capybara/rspec/matchers.rb +138 -316
  59. data/lib/capybara/rspec.rb +3 -2
  60. data/lib/capybara/selector/builders/css_builder.rb +84 -0
  61. data/lib/capybara/selector/builders/xpath_builder.rb +71 -0
  62. data/lib/capybara/selector/css.rb +89 -12
  63. data/lib/capybara/selector/definition/button.rb +68 -0
  64. data/lib/capybara/selector/definition/checkbox.rb +26 -0
  65. data/lib/capybara/selector/definition/css.rb +10 -0
  66. data/lib/capybara/selector/definition/datalist_input.rb +35 -0
  67. data/lib/capybara/selector/definition/datalist_option.rb +25 -0
  68. data/lib/capybara/selector/definition/element.rb +28 -0
  69. data/lib/capybara/selector/definition/field.rb +40 -0
  70. data/lib/capybara/selector/definition/fieldset.rb +14 -0
  71. data/lib/capybara/selector/definition/file_field.rb +13 -0
  72. data/lib/capybara/selector/definition/fillable_field.rb +33 -0
  73. data/lib/capybara/selector/definition/frame.rb +17 -0
  74. data/lib/capybara/selector/definition/id.rb +6 -0
  75. data/lib/capybara/selector/definition/label.rb +62 -0
  76. data/lib/capybara/selector/definition/link.rb +54 -0
  77. data/lib/capybara/selector/definition/link_or_button.rb +16 -0
  78. data/lib/capybara/selector/definition/option.rb +27 -0
  79. data/lib/capybara/selector/definition/radio_button.rb +27 -0
  80. data/lib/capybara/selector/definition/select.rb +81 -0
  81. data/lib/capybara/selector/definition/table.rb +109 -0
  82. data/lib/capybara/selector/definition/table_row.rb +21 -0
  83. data/lib/capybara/selector/definition/xpath.rb +5 -0
  84. data/lib/capybara/selector/definition.rb +279 -0
  85. data/lib/capybara/selector/filter.rb +1 -0
  86. data/lib/capybara/selector/filter_set.rb +72 -27
  87. data/lib/capybara/selector/filters/base.rb +45 -2
  88. data/lib/capybara/selector/filters/expression_filter.rb +5 -6
  89. data/lib/capybara/selector/filters/locator_filter.rb +29 -0
  90. data/lib/capybara/selector/filters/node_filter.rb +18 -4
  91. data/lib/capybara/selector/regexp_disassembler.rb +214 -0
  92. data/lib/capybara/selector/selector.rb +85 -200
  93. data/lib/capybara/selector/xpath_extensions.rb +17 -0
  94. data/lib/capybara/selector.rb +226 -537
  95. data/lib/capybara/selenium/atoms/getAttribute.min.js +1 -0
  96. data/lib/capybara/selenium/atoms/isDisplayed.min.js +1 -0
  97. data/lib/capybara/selenium/atoms/src/getAttribute.js +161 -0
  98. data/lib/capybara/selenium/atoms/src/isDisplayed.js +454 -0
  99. data/lib/capybara/selenium/driver.rb +296 -238
  100. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +117 -0
  101. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +124 -0
  102. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +89 -0
  103. data/lib/capybara/selenium/driver_specializations/internet_explorer_driver.rb +26 -0
  104. data/lib/capybara/selenium/driver_specializations/safari_driver.rb +24 -0
  105. data/lib/capybara/selenium/extensions/file_input_click_emulation.rb +34 -0
  106. data/lib/capybara/selenium/extensions/find.rb +110 -0
  107. data/lib/capybara/selenium/extensions/html5_drag.rb +228 -0
  108. data/lib/capybara/selenium/extensions/modifier_keys_stack.rb +28 -0
  109. data/lib/capybara/selenium/extensions/scroll.rb +76 -0
  110. data/lib/capybara/selenium/logger_suppressor.rb +40 -0
  111. data/lib/capybara/selenium/node.rb +436 -166
  112. data/lib/capybara/selenium/nodes/chrome_node.rb +137 -0
  113. data/lib/capybara/selenium/nodes/edge_node.rb +104 -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/action_pauser.rb +26 -0
  118. data/lib/capybara/selenium/patches/atoms.rb +18 -0
  119. data/lib/capybara/selenium/patches/is_displayed.rb +16 -0
  120. data/lib/capybara/selenium/patches/logs.rb +45 -0
  121. data/lib/capybara/selenium/patches/pause_duration_fix.rb +9 -0
  122. data/lib/capybara/selenium/patches/persistent_client.rb +20 -0
  123. data/lib/capybara/server/animation_disabler.rb +69 -0
  124. data/lib/capybara/server/checker.rb +44 -0
  125. data/lib/capybara/server/middleware.rb +71 -0
  126. data/lib/capybara/server.rb +58 -67
  127. data/lib/capybara/session/config.rb +38 -6
  128. data/lib/capybara/session/matchers.rb +26 -19
  129. data/lib/capybara/session.rb +258 -190
  130. data/lib/capybara/spec/public/jquery.js +5 -5
  131. data/lib/capybara/spec/public/offset.js +6 -0
  132. data/lib/capybara/spec/public/test.js +122 -8
  133. data/lib/capybara/spec/session/accept_alert_spec.rb +11 -11
  134. data/lib/capybara/spec/session/accept_confirm_spec.rb +3 -3
  135. data/lib/capybara/spec/session/accept_prompt_spec.rb +9 -10
  136. data/lib/capybara/spec/session/active_element_spec.rb +31 -0
  137. data/lib/capybara/spec/session/all_spec.rb +135 -42
  138. data/lib/capybara/spec/session/ancestor_spec.rb +24 -19
  139. data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +67 -38
  140. data/lib/capybara/spec/session/{assert_current_path.rb → assert_current_path_spec.rb} +20 -18
  141. data/lib/capybara/spec/session/assert_selector_spec.rb +143 -0
  142. data/lib/capybara/spec/session/assert_style_spec.rb +26 -0
  143. data/lib/capybara/spec/session/{assert_text.rb → assert_text_spec.rb} +62 -38
  144. data/lib/capybara/spec/session/{assert_title.rb → assert_title_spec.rb} +12 -12
  145. data/lib/capybara/spec/session/attach_file_spec.rb +120 -72
  146. data/lib/capybara/spec/session/body_spec.rb +11 -13
  147. data/lib/capybara/spec/session/check_spec.rb +111 -51
  148. data/lib/capybara/spec/session/choose_spec.rb +62 -30
  149. data/lib/capybara/spec/session/click_button_spec.rb +227 -161
  150. data/lib/capybara/spec/session/click_link_or_button_spec.rb +49 -30
  151. data/lib/capybara/spec/session/click_link_spec.rb +78 -55
  152. data/lib/capybara/spec/session/current_scope_spec.rb +7 -7
  153. data/lib/capybara/spec/session/current_url_spec.rb +44 -37
  154. data/lib/capybara/spec/session/dismiss_confirm_spec.rb +3 -3
  155. data/lib/capybara/spec/session/dismiss_prompt_spec.rb +2 -2
  156. data/lib/capybara/spec/session/element/{assert_match_selector.rb → assert_match_selector_spec.rb} +11 -9
  157. data/lib/capybara/spec/session/element/match_css_spec.rb +18 -10
  158. data/lib/capybara/spec/session/element/match_xpath_spec.rb +8 -6
  159. data/lib/capybara/spec/session/element/matches_selector_spec.rb +70 -56
  160. data/lib/capybara/spec/session/evaluate_async_script_spec.rb +7 -7
  161. data/lib/capybara/spec/session/evaluate_script_spec.rb +28 -8
  162. data/lib/capybara/spec/session/execute_script_spec.rb +8 -7
  163. data/lib/capybara/spec/session/fill_in_spec.rb +104 -44
  164. data/lib/capybara/spec/session/find_button_spec.rb +23 -23
  165. data/lib/capybara/spec/session/find_by_id_spec.rb +8 -8
  166. data/lib/capybara/spec/session/find_field_spec.rb +33 -31
  167. data/lib/capybara/spec/session/find_link_spec.rb +32 -14
  168. data/lib/capybara/spec/session/find_spec.rb +236 -141
  169. data/lib/capybara/spec/session/first_spec.rb +44 -43
  170. data/lib/capybara/spec/session/frame/frame_title_spec.rb +6 -6
  171. data/lib/capybara/spec/session/frame/frame_url_spec.rb +6 -6
  172. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +32 -20
  173. data/lib/capybara/spec/session/frame/within_frame_spec.rb +44 -19
  174. data/lib/capybara/spec/session/go_back_spec.rb +1 -1
  175. data/lib/capybara/spec/session/go_forward_spec.rb +1 -1
  176. data/lib/capybara/spec/session/has_all_selectors_spec.rb +18 -18
  177. data/lib/capybara/spec/session/has_ancestor_spec.rb +46 -0
  178. data/lib/capybara/spec/session/has_any_selectors_spec.rb +29 -0
  179. data/lib/capybara/spec/session/has_button_spec.rb +92 -12
  180. data/lib/capybara/spec/session/has_css_spec.rb +271 -137
  181. data/lib/capybara/spec/session/has_current_path_spec.rb +48 -33
  182. data/lib/capybara/spec/session/has_field_spec.rb +137 -58
  183. data/lib/capybara/spec/session/has_link_spec.rb +30 -6
  184. data/lib/capybara/spec/session/has_none_selectors_spec.rb +26 -24
  185. data/lib/capybara/spec/session/has_select_spec.rb +75 -47
  186. data/lib/capybara/spec/session/has_selector_spec.rb +102 -69
  187. data/lib/capybara/spec/session/has_sibling_spec.rb +50 -0
  188. data/lib/capybara/spec/session/has_table_spec.rb +170 -4
  189. data/lib/capybara/spec/session/has_text_spec.rb +108 -52
  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.rb → 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 +35 -0
  195. data/lib/capybara/spec/session/node_spec.rb +871 -122
  196. data/lib/capybara/spec/session/node_wrapper_spec.rb +15 -12
  197. data/lib/capybara/spec/session/refresh_spec.rb +9 -7
  198. data/lib/capybara/spec/session/reset_session_spec.rb +52 -37
  199. data/lib/capybara/spec/session/{response_code.rb → 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 +5 -4
  202. data/lib/capybara/spec/session/save_page_spec.rb +41 -38
  203. data/lib/capybara/spec/session/save_screenshot_spec.rb +13 -11
  204. data/lib/capybara/spec/session/screenshot_spec.rb +2 -2
  205. data/lib/capybara/spec/session/scroll_spec.rb +117 -0
  206. data/lib/capybara/spec/session/select_spec.rb +102 -76
  207. data/lib/capybara/spec/session/selectors_spec.rb +51 -18
  208. data/lib/capybara/spec/session/sibling_spec.rb +9 -9
  209. data/lib/capybara/spec/session/text_spec.rb +26 -24
  210. data/lib/capybara/spec/session/title_spec.rb +8 -6
  211. data/lib/capybara/spec/session/uncheck_spec.rb +40 -21
  212. data/lib/capybara/spec/session/unselect_spec.rb +37 -37
  213. data/lib/capybara/spec/session/visit_spec.rb +59 -53
  214. data/lib/capybara/spec/session/window/become_closed_spec.rb +22 -19
  215. data/lib/capybara/spec/session/window/current_window_spec.rb +4 -3
  216. data/lib/capybara/spec/session/window/open_new_window_spec.rb +4 -3
  217. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +25 -21
  218. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +10 -5
  219. data/lib/capybara/spec/session/window/window_spec.rb +88 -54
  220. data/lib/capybara/spec/session/window/windows_spec.rb +10 -7
  221. data/lib/capybara/spec/session/window/within_window_spec.rb +17 -16
  222. data/lib/capybara/spec/session/within_spec.rb +69 -44
  223. data/lib/capybara/spec/spec_helper.rb +41 -53
  224. data/lib/capybara/spec/test_app.rb +79 -40
  225. data/lib/capybara/spec/views/animated.erb +49 -0
  226. data/lib/capybara/spec/views/form.erb +134 -42
  227. data/lib/capybara/spec/views/frame_child.erb +4 -3
  228. data/lib/capybara/spec/views/frame_one.erb +2 -1
  229. data/lib/capybara/spec/views/frame_parent.erb +1 -1
  230. data/lib/capybara/spec/views/frame_two.erb +1 -1
  231. data/lib/capybara/spec/views/initial_alert.erb +2 -1
  232. data/lib/capybara/spec/views/layout.erb +10 -0
  233. data/lib/capybara/spec/views/obscured.erb +47 -0
  234. data/lib/capybara/spec/views/offset.erb +33 -0
  235. data/lib/capybara/spec/views/path.erb +2 -2
  236. data/lib/capybara/spec/views/popup_one.erb +1 -1
  237. data/lib/capybara/spec/views/popup_two.erb +1 -1
  238. data/lib/capybara/spec/views/react.erb +45 -0
  239. data/lib/capybara/spec/views/scroll.erb +21 -0
  240. data/lib/capybara/spec/views/spatial.erb +31 -0
  241. data/lib/capybara/spec/views/tables.erb +68 -1
  242. data/lib/capybara/spec/views/with_animation.erb +81 -0
  243. data/lib/capybara/spec/views/with_base_tag.erb +2 -2
  244. data/lib/capybara/spec/views/with_dragula.erb +24 -0
  245. data/lib/capybara/spec/views/with_fixed_header_footer.erb +2 -1
  246. data/lib/capybara/spec/views/with_hover.erb +3 -2
  247. data/lib/capybara/spec/views/with_hover1.erb +10 -0
  248. data/lib/capybara/spec/views/with_html.erb +43 -10
  249. data/lib/capybara/spec/views/with_html5_svg.erb +20 -0
  250. data/lib/capybara/spec/views/with_jquery_animation.erb +24 -0
  251. data/lib/capybara/spec/views/with_js.erb +30 -5
  252. data/lib/capybara/spec/views/with_jstree.erb +26 -0
  253. data/lib/capybara/spec/views/with_namespace.erb +21 -0
  254. data/lib/capybara/spec/views/with_scope_other.erb +6 -0
  255. data/lib/capybara/spec/views/with_slow_unload.erb +2 -1
  256. data/lib/capybara/spec/views/with_sortable_js.erb +21 -0
  257. data/lib/capybara/spec/views/with_unload_alert.erb +1 -0
  258. data/lib/capybara/spec/views/with_windows.erb +1 -1
  259. data/lib/capybara/spec/views/within_frames.erb +1 -1
  260. data/lib/capybara/version.rb +1 -1
  261. data/lib/capybara/window.rb +31 -25
  262. data/lib/capybara.rb +120 -100
  263. data/spec/basic_node_spec.rb +59 -34
  264. data/spec/capybara_spec.rb +54 -52
  265. data/spec/css_builder_spec.rb +101 -0
  266. data/spec/css_splitter_spec.rb +38 -0
  267. data/spec/dsl_spec.rb +80 -53
  268. data/spec/filter_set_spec.rb +24 -7
  269. data/spec/fixtures/certificate.pem +25 -0
  270. data/spec/fixtures/key.pem +27 -0
  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 +38 -5
  274. data/spec/minitest_spec_spec.rb +88 -62
  275. data/spec/per_session_config_spec.rb +5 -5
  276. data/spec/rack_test_spec.rb +169 -115
  277. data/spec/regexp_dissassembler_spec.rb +250 -0
  278. data/spec/result_spec.rb +86 -33
  279. data/spec/rspec/features_spec.rb +26 -23
  280. data/spec/rspec/scenarios_spec.rb +8 -4
  281. data/spec/rspec/shared_spec_matchers.rb +393 -363
  282. data/spec/rspec/views_spec.rb +4 -3
  283. data/spec/rspec_matchers_spec.rb +10 -10
  284. data/spec/rspec_spec.rb +109 -85
  285. data/spec/sauce_spec_chrome.rb +43 -0
  286. data/spec/selector_spec.rb +391 -61
  287. data/spec/selenium_spec_chrome.rb +180 -41
  288. data/spec/selenium_spec_chrome_remote.rb +101 -0
  289. data/spec/selenium_spec_edge.rb +28 -8
  290. data/spec/selenium_spec_firefox.rb +211 -0
  291. data/spec/selenium_spec_firefox_remote.rb +80 -0
  292. data/spec/selenium_spec_ie.rb +127 -11
  293. data/spec/selenium_spec_safari.rb +156 -0
  294. data/spec/server_spec.rb +177 -78
  295. data/spec/session_spec.rb +52 -16
  296. data/spec/shared_selenium_node.rb +79 -0
  297. data/spec/shared_selenium_session.rb +455 -123
  298. data/spec/spec_helper.rb +91 -2
  299. data/spec/xpath_builder_spec.rb +93 -0
  300. metadata +343 -42
  301. data/.yard/templates_custom/default/class/html/selectors.erb +0 -38
  302. data/.yard/templates_custom/default/class/html/setup.rb +0 -17
  303. data/.yard/yard_extensions.rb +0 -78
  304. data/lib/capybara/rspec/compound.rb +0 -90
  305. data/lib/capybara/spec/session/assert_selector.rb +0 -149
  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 -143
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'capybara/rack_test/errors'
4
+
3
5
  class Capybara::RackTest::Node < Capybara::Driver::Node
4
6
  BLOCK_ELEMENTS = %w[p h1 h2 h3 h4 h5 h6 ol ul pre address blockquote dl div fieldset form hr noscript table].freeze
5
7
 
@@ -7,16 +9,16 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
7
9
  native.text
8
10
  .gsub(/[\u200b\u200e\u200f]/, '')
9
11
  .gsub(/[\ \n\f\t\v\u2028\u2029]+/, ' ')
10
- .gsub(/\A[[:space:]&&[^\u00a0]]+/, "")
11
- .gsub(/[[:space:]&&[^\u00a0]]+\z/, "")
12
+ .gsub(/\A[[:space:]&&[^\u00a0]]+/, '')
13
+ .gsub(/[[:space:]&&[^\u00a0]]+\z/, '')
12
14
  .tr("\u00a0", ' ')
13
15
  end
14
16
 
15
17
  def visible_text
16
- displayed_text.gsub(/\ +/, ' ')
18
+ displayed_text.squeeze(' ')
17
19
  .gsub(/[\ \n]*\n[\ \n]*/, "\n")
18
- .gsub(/\A[[:space:]&&[^\u00a0]]+/, "")
19
- .gsub(/[[:space:]&&[^\u00a0]]+\z/, "")
20
+ .gsub(/\A[[:space:]&&[^\u00a0]]+/, '')
21
+ .gsub(/[[:space:]&&[^\u00a0]]+\z/, '')
20
22
  .tr("\u00a0", ' ')
21
23
  end
22
24
 
@@ -24,6 +26,10 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
24
26
  string_node[name]
25
27
  end
26
28
 
29
+ def style(_styles)
30
+ raise NotImplementedError, 'The rack_test driver does not process CSS'
31
+ end
32
+
27
33
  def value
28
34
  string_node.value
29
35
  end
@@ -39,6 +45,7 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
39
45
 
40
46
  if radio? then set_radio(value)
41
47
  elsif checkbox? then set_checkbox(value)
48
+ elsif range? then set_range(value)
42
49
  elsif input_field? then set_input(value)
43
50
  elsif textarea? then native['_capybara_raw_value'] = value.to_s
44
51
  end
@@ -46,17 +53,20 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
46
53
 
47
54
  def select_option
48
55
  return if disabled?
56
+
49
57
  deselect_options unless select_node.multiple?
50
- native["selected"] = 'selected'
58
+ native['selected'] = 'selected'
51
59
  end
52
60
 
53
61
  def unselect_option
54
- raise Capybara::UnselectNotAllowed, "Cannot unselect option from single select box." unless select_node.multiple?
62
+ raise Capybara::UnselectNotAllowed, 'Cannot unselect option from single select box.' unless select_node.multiple?
63
+
55
64
  native.remove_attribute('selected')
56
65
  end
57
66
 
58
- def click(keys = [], offset = {})
59
- raise ArgumentError, "The RackTest driver does not support click options" unless keys.empty? && offset.empty?
67
+ def click(keys = [], **options)
68
+ options.delete(:offset)
69
+ raise ArgumentError, 'The RackTest driver does not support click options' unless keys.empty? && options.empty?
60
70
 
61
71
  if link?
62
72
  follow_link
@@ -67,6 +77,8 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
67
77
  set(!checked?)
68
78
  elsif tag_name == 'label'
69
79
  click_label
80
+ elsif (details = native.xpath('.//ancestor-or-self::details').last)
81
+ toggle_details(details)
70
82
  end
71
83
  end
72
84
 
@@ -90,26 +102,48 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
90
102
  return true if string_node.disabled?
91
103
 
92
104
  if %w[option optgroup].include? tag_name
93
- find_xpath("parent::*[self::optgroup or self::select]")[0].disabled?
105
+ find_xpath(OPTION_OWNER_XPATH)[0].disabled?
94
106
  else
95
- !find_xpath("parent::fieldset[@disabled] | ancestor::*[not(self::legend) or preceding-sibling::legend][parent::fieldset[@disabled]]").empty?
107
+ !find_xpath(DISABLED_BY_FIELDSET_XPATH).empty?
96
108
  end
97
109
  end
98
110
 
111
+ def readonly?
112
+ # readonly attribute not valid on these input types
113
+ return false if input_field? && %w[hidden range color checkbox radio file submit image reset button].include?(type)
114
+
115
+ super
116
+ end
117
+
99
118
  def path
100
119
  native.path
101
120
  end
102
121
 
103
- def find_xpath(locator)
104
- native.xpath(locator).map { |n| self.class.new(driver, n) }
122
+ def find_xpath(locator, **_hints)
123
+ native.xpath(locator).map { |el| self.class.new(driver, el) }
105
124
  end
106
125
 
107
- def find_css(locator)
108
- native.css(locator, Capybara::RackTest::CSSHandlers.new).map { |n| self.class.new(driver, n) }
126
+ def find_css(locator, **_hints)
127
+ native.css(locator, Capybara::RackTest::CSSHandlers.new).map { |el| self.class.new(driver, el) }
109
128
  end
110
129
 
111
- def ==(other)
112
- native == other.native
130
+ public_instance_methods(false).each do |meth_name|
131
+ alias_method "unchecked_#{meth_name}", meth_name
132
+ private "unchecked_#{meth_name}" # rubocop:disable Style/AccessModifierDeclarations
133
+
134
+ if RUBY_VERSION >= '2.7'
135
+ class_eval <<~METHOD, __FILE__, __LINE__ + 1
136
+ def #{meth_name}(...)
137
+ stale_check
138
+ method(:"unchecked_#{meth_name}").call(...)
139
+ end
140
+ METHOD
141
+ else
142
+ define_method meth_name do |*args|
143
+ stale_check
144
+ send("unchecked_#{meth_name}", *args)
145
+ end
146
+ end
113
147
  end
114
148
 
115
149
  protected
@@ -128,15 +162,19 @@ protected
128
162
  end.join || ''
129
163
  text = "\n#{text}\n" if BLOCK_ELEMENTS.include?(tag_name)
130
164
  text
131
- else
165
+ else # rubocop:disable Lint/DuplicateBranch
132
166
  ''
133
167
  end
134
168
  end
135
169
 
136
170
  private
137
171
 
172
+ def stale_check
173
+ raise Capybara::RackTest::Errors::StaleElementReferenceError unless native.document == driver.dom
174
+ end
175
+
138
176
  def deselect_options
139
- select_node.find_xpath(".//option[@selected]").each { |node| node.native.remove_attribute("selected") }
177
+ select_node.find_xpath('.//option[@selected]').each { |node| node.native.remove_attribute('selected') }
140
178
  end
141
179
 
142
180
  def string_node
@@ -161,8 +199,8 @@ private
161
199
  end
162
200
 
163
201
  def set_radio(_value) # rubocop:disable Naming/AccessorMethodName
164
- other_radios_xpath = XPath.generate { |x| x.anywhere(:input)[x.attr(:name) == self[:name]] }.to_s
165
- driver.dom.xpath(other_radios_xpath).each { |node| node.remove_attribute("checked") }
202
+ other_radios_xpath = XPath.generate { |xp| xp.anywhere(:input)[xp.attr(:name) == self[:name]] }.to_s
203
+ driver.dom.xpath(other_radios_xpath).each { |node| node.remove_attribute('checked') }
166
204
  native['checked'] = 'checked'
167
205
  end
168
206
 
@@ -174,6 +212,14 @@ private
174
212
  end
175
213
  end
176
214
 
215
+ def set_range(value) # rubocop:disable Naming/AccessorMethodName
216
+ min, max, step = (native['min'] || 0).to_f, (native['max'] || 100).to_f, (native['step'] || 1).to_f
217
+ value = value.to_f
218
+ value = value.clamp(min, max)
219
+ value = (((value - min) / step).round * step) + min
220
+ native['value'] = value.clamp(min, max)
221
+ end
222
+
177
223
  def set_input(value) # rubocop:disable Naming/AccessorMethodName
178
224
  if text_or_password? && attribute_is_not_blank?(:maxlength)
179
225
  # Browser behavior for maxlength="0" is inconsistent, so we stick with
@@ -181,11 +227,11 @@ private
181
227
  value = value.to_s[0...self[:maxlength].to_i]
182
228
  end
183
229
  if value.is_a?(Array) # Assert multiple attribute is present
184
- value.each do |v|
230
+ value.each do |val|
185
231
  new_native = native.clone
186
232
  new_native.remove_attribute('value')
187
233
  native.add_next_sibling(new_native)
188
- new_native['value'] = v.to_s
234
+ new_native['value'] = val.to_s
189
235
  end
190
236
  native.remove
191
237
  else
@@ -198,7 +244,7 @@ private
198
244
  end
199
245
 
200
246
  def follow_link
201
- method = self["data-method"] if driver.options[:respect_data_method]
247
+ method = self['data-method'] if driver.options[:respect_data_method]
202
248
  method ||= :get
203
249
  driver.follow(method, self[:href].to_s)
204
250
  end
@@ -207,11 +253,20 @@ private
207
253
  labelled_control = if native[:for]
208
254
  find_xpath("//input[@id='#{native[:for]}']")
209
255
  else
210
- find_xpath(".//input")
256
+ find_xpath('.//input')
211
257
  end.first
212
258
 
213
- if labelled_control && (labelled_control.checkbox? || labelled_control.radio?)
214
- labelled_control.set(!labelled_control.checked?)
259
+ labelled_control.set(!labelled_control.checked?) if checkbox_or_radio?(labelled_control)
260
+ end
261
+
262
+ def toggle_details(details = nil)
263
+ details ||= native.xpath('.//ancestor-or-self::details').last
264
+ return unless details
265
+
266
+ if details.has_attribute?('open')
267
+ details.remove_attribute('open')
268
+ else
269
+ details.set_attribute('open', 'open')
215
270
  end
216
271
  end
217
272
 
@@ -220,32 +275,54 @@ private
220
275
  end
221
276
 
222
277
  def submits?
223
- (tag_name == 'input' and %w[submit image].include?(type)) || (tag_name == 'button' and [nil, "submit"].include?(type))
278
+ (tag_name == 'input' && %w[submit image].include?(type)) || (tag_name == 'button' && [nil, 'submit'].include?(type))
224
279
  end
225
280
 
226
281
  def checkable?
227
- tag_name == 'input' and %w[checkbox radio].include?(type)
282
+ tag_name == 'input' && %w[checkbox radio].include?(type)
228
283
  end
229
284
 
230
285
  protected
231
286
 
232
- def checkbox?
233
- input_field? && type == 'checkbox'
287
+ def checkbox_or_radio?(field = self)
288
+ field&.checkbox? || field&.radio?
234
289
  end
235
290
 
236
- def input_field?
237
- tag_name == 'input'
291
+ def checkbox?
292
+ input_field? && type == 'checkbox'
238
293
  end
239
294
 
240
295
  def radio?
241
296
  input_field? && type == 'radio'
242
297
  end
243
298
 
244
- def textarea?
245
- tag_name == "textarea"
246
- end
247
-
248
299
  def text_or_password?
249
300
  input_field? && (type == 'text' || type == 'password')
250
301
  end
302
+
303
+ def input_field?
304
+ tag_name == 'input'
305
+ end
306
+
307
+ def textarea?
308
+ tag_name == 'textarea'
309
+ end
310
+
311
+ def range?
312
+ input_field? && type == 'range'
313
+ end
314
+
315
+ OPTION_OWNER_XPATH = XPath.parent(:optgroup, :select, :datalist).to_s.freeze
316
+ DISABLED_BY_FIELDSET_XPATH = XPath.generate do |x|
317
+ x.parent(:fieldset)[
318
+ XPath.attr(:disabled)
319
+ ] + x.ancestor[
320
+ ~x.self(:legend) |
321
+ x.preceding_sibling(:legend)
322
+ ][
323
+ x.parent(:fieldset)[
324
+ x.attr(:disabled)
325
+ ]
326
+ ]
327
+ end.to_s.freeze
251
328
  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,44 @@
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, *args, **options, &block)
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
+ # RUBY 2.6 will send an empty hash rather than nothing with **options so fix that
23
+ return @registered.public_send(method_name, *args, &block) if options.empty?
24
+
25
+ return @registered.public_send(method_name, *args, **options, &block)
26
+ end
27
+ super
28
+ end
29
+
30
+ def respond_to_missing?(method_name, include_all)
31
+ @registered.respond_to?(method_name) || super
32
+ end
33
+
34
+ private
35
+
36
+ def initialize
37
+ @registered = {}
38
+ end
39
+
40
+ def register(name, block)
41
+ @registered[name] = block
42
+ end
43
+ end
44
+ 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')
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,45 @@
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
+ require 'rack/handler/webrick'
9
+ options = { Host: host, Port: port, AccessLog: [], Logger: WEBrick::Log.new(nil, 0) }.merge(options)
10
+ Rack::Handler::WEBrick.run(app, **options)
11
+ end
12
+
13
+ Capybara.register_server :puma do |app, port, host, **options|
14
+ begin
15
+ require 'rack/handler/puma'
16
+ rescue LoadError
17
+ 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`.'
18
+ else
19
+ unless Rack::Handler::Puma.respond_to?(:config)
20
+ raise LoadError, 'Capybara requires `puma` version 3.8.0 or higher, please upgrade `puma` or register and specify your own server block'
21
+ end
22
+ end
23
+
24
+ # If we just run the Puma Rack handler it installs signal handlers which prevent us from being able to interrupt tests.
25
+ # Therefore construct and run the Server instance ourselves.
26
+ # Rack::Handler::Puma.run(app, { Host: host, Port: port, Threads: "0:4", workers: 0, daemon: false }.merge(options))
27
+ default_options = { Host: host, Port: port, Threads: '0:4', workers: 0, daemon: false }
28
+ options = default_options.merge(options)
29
+
30
+ conf = Rack::Handler::Puma.config(app, options)
31
+ conf.clamp
32
+ events = conf.options[:Silent] ? ::Puma::Events.strings : ::Puma::Events.stdio
33
+
34
+ puma_ver = Gem::Version.new(Puma::Const::PUMA_VERSION)
35
+ require_relative 'patches/puma_ssl' if Gem::Requirement.new('>=4.0.0', '< 4.1.0').satisfied_by?(puma_ver)
36
+
37
+ events.log 'Capybara starting Puma...'
38
+ events.log "* Version #{Puma::Const::PUMA_VERSION} , codename: #{Puma::Const::CODE_NAME}"
39
+ events.log "* Min threads: #{conf.options[:min_threads]}, max threads: #{conf.options[:max_threads]}"
40
+
41
+ Puma::Server.new(conf.app, events, conf.options).tap do |s|
42
+ s.binder.parse conf.options[:binds], s.events
43
+ s.min_threads, s.max_threads = conf.options[:min_threads], conf.options[:max_threads]
44
+ end.run.join
45
+ 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,35 +28,52 @@ 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
37
  def_delegators :full_results, :size, :length, :last, :values_at, :inspect, :sample
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
47
51
  end
48
52
 
49
53
  def [](*args)
50
- if (args.size == 1) && ((idx = args[0]).is_a? Integer) && (idx >= 0)
51
- @result_cache << @results_enum.next while @result_cache.size <= idx
52
- @result_cache[idx]
53
- else
54
+ idx, length = args
55
+ max_idx = case idx
56
+ when Integer
57
+ if idx.negative?
58
+ nil
59
+ else
60
+ length.nil? ? idx : idx + length - 1
61
+ end
62
+ when Range
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
69
+ end
70
+
71
+ if max_idx.nil?
54
72
  full_results[*args]
73
+ else
74
+ load_up_to(max_idx + 1)
75
+ @result_cache[*args]
55
76
  end
56
- rescue StopIteration
57
- return nil
58
77
  end
59
78
  alias :at :[]
60
79
 
@@ -63,46 +82,28 @@ module Capybara
63
82
  end
64
83
 
65
84
  def compare_count
85
+ return 0 unless @query
86
+
87
+ count, min, max, between = @query.options.values_at(:count, :minimum, :maximum, :between)
88
+
66
89
  # Only check filters for as many elements as necessary to determine result
67
- if @query.options[:count]
68
- count_opt = Integer(@query.options[:count])
69
- loop do
70
- break if @result_cache.size > count_opt
71
- @result_cache << @results_enum.next
72
- end
73
- return @result_cache.size <=> count_opt
90
+ if count && (count = Integer(count))
91
+ return load_up_to(count + 1) <=> count
74
92
  end
75
93
 
76
- if @query.options[:minimum]
77
- min_opt = Integer(@query.options[:minimum])
78
- begin
79
- @result_cache << @results_enum.next while @result_cache.size < min_opt
80
- rescue StopIteration
81
- return -1
82
- end
83
- end
94
+ return -1 if min && (min = Integer(min)) && (load_up_to(min) < min)
84
95
 
85
- if @query.options[:maximum]
86
- max_opt = Integer(@query.options[:maximum])
87
- begin
88
- @result_cache << @results_enum.next while @result_cache.size <= max_opt
89
- return 1
90
- rescue StopIteration
91
- end
92
- end
96
+ return 1 if max && (max = Integer(max)) && (load_up_to(max + 1) > max)
93
97
 
94
- if @query.options[:between]
95
- min, max = @query.options[:between].minmax
96
- loop do
97
- break if @result_cache.size > max
98
- @result_cache << @results_enum.next
99
- end
100
- return 0 if @query.options[:between].include?(@result_cache.size)
101
- return -1 if @result_cache.size < min
102
- return 1
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)
103
104
  end
104
105
 
105
- return 0
106
+ 0
106
107
  end
107
108
 
108
109
  def matches_count?
@@ -112,13 +113,15 @@ module Capybara
112
113
  def failure_message
113
114
  message = @query.failure_message
114
115
  if count.zero?
115
- message << " but there were no matches"
116
+ message << ' but there were no matches'
116
117
  else
117
- 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(&:text).map(&:inspect).join(', ')
118
120
  end
119
121
  unless rest.empty?
120
- elements = rest.map { |el| el.text rescue "<<ERROR>>" }.map(&:inspect).join(", ")
121
- 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?
122
125
  end
123
126
  message
124
127
  end
@@ -127,8 +130,34 @@ module Capybara
127
130
  failure_message.sub(/(to find)/, 'not \1')
128
131
  end
129
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
+
130
145
  private
131
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
+
132
161
  def full_results
133
162
  loop { @result_cache << @results_enum.next }
134
163
  @result_cache
@@ -138,13 +167,18 @@ module Capybara
138
167
  @rest ||= @elements - full_results
139
168
  end
140
169
 
141
- def lazy_select_elements(&block)
142
- # 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
143
172
  # causes a concurrency issue with network requests here
144
173
  # https://github.com/jruby/jruby/issues/4212
145
- 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)
146
178
  @elements.select(&block).to_enum # non-lazy evaluation
147
- else
179
+ end
180
+ else
181
+ def lazy_select_elements(&block)
148
182
  @elements.lazy.select(&block)
149
183
  end
150
184
  end