capybara 3.13.2 → 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 (260) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -0
  3. data/History.md +587 -16
  4. data/README.md +240 -90
  5. data/lib/capybara/config.rb +24 -11
  6. data/lib/capybara/cucumber.rb +1 -1
  7. data/lib/capybara/driver/base.rb +8 -0
  8. data/lib/capybara/driver/node.rb +20 -4
  9. data/lib/capybara/dsl.rb +5 -3
  10. data/lib/capybara/helpers.rb +25 -4
  11. data/lib/capybara/minitest/spec.rb +174 -90
  12. data/lib/capybara/minitest.rb +256 -142
  13. data/lib/capybara/node/actions.rb +123 -77
  14. data/lib/capybara/node/base.rb +20 -12
  15. data/lib/capybara/node/document.rb +2 -2
  16. data/lib/capybara/node/document_matchers.rb +3 -3
  17. data/lib/capybara/node/element.rb +223 -117
  18. data/lib/capybara/node/finders.rb +81 -71
  19. data/lib/capybara/node/matchers.rb +271 -134
  20. data/lib/capybara/node/simple.rb +18 -5
  21. data/lib/capybara/node/whitespace_normalizer.rb +81 -0
  22. data/lib/capybara/queries/active_element_query.rb +18 -0
  23. data/lib/capybara/queries/ancestor_query.rb +8 -9
  24. data/lib/capybara/queries/base_query.rb +3 -2
  25. data/lib/capybara/queries/current_path_query.rb +15 -5
  26. data/lib/capybara/queries/selector_query.rb +364 -54
  27. data/lib/capybara/queries/sibling_query.rb +8 -6
  28. data/lib/capybara/queries/style_query.rb +2 -2
  29. data/lib/capybara/queries/text_query.rb +13 -1
  30. data/lib/capybara/queries/title_query.rb +1 -1
  31. data/lib/capybara/rack_test/browser.rb +76 -11
  32. data/lib/capybara/rack_test/driver.rb +10 -5
  33. data/lib/capybara/rack_test/errors.rb +6 -0
  34. data/lib/capybara/rack_test/form.rb +31 -9
  35. data/lib/capybara/rack_test/node.rb +74 -23
  36. data/lib/capybara/registration_container.rb +41 -0
  37. data/lib/capybara/registrations/drivers.rb +42 -0
  38. data/lib/capybara/registrations/patches/puma_ssl.rb +29 -0
  39. data/lib/capybara/registrations/servers.rb +66 -0
  40. data/lib/capybara/result.rb +44 -20
  41. data/lib/capybara/rspec/matcher_proxies.rb +13 -11
  42. data/lib/capybara/rspec/matchers/base.rb +31 -16
  43. data/lib/capybara/rspec/matchers/compound.rb +1 -1
  44. data/lib/capybara/rspec/matchers/count_sugar.rb +37 -0
  45. data/lib/capybara/rspec/matchers/have_ancestor.rb +28 -0
  46. data/lib/capybara/rspec/matchers/have_current_path.rb +2 -2
  47. data/lib/capybara/rspec/matchers/have_selector.rb +21 -21
  48. data/lib/capybara/rspec/matchers/have_sibling.rb +27 -0
  49. data/lib/capybara/rspec/matchers/have_text.rb +4 -4
  50. data/lib/capybara/rspec/matchers/have_title.rb +2 -2
  51. data/lib/capybara/rspec/matchers/match_selector.rb +3 -3
  52. data/lib/capybara/rspec/matchers/match_style.rb +7 -2
  53. data/lib/capybara/rspec/matchers/spatial_sugar.rb +39 -0
  54. data/lib/capybara/rspec/matchers.rb +111 -68
  55. data/lib/capybara/rspec.rb +2 -0
  56. data/lib/capybara/selector/builders/css_builder.rb +11 -7
  57. data/lib/capybara/selector/builders/xpath_builder.rb +5 -3
  58. data/lib/capybara/selector/css.rb +11 -9
  59. data/lib/capybara/selector/definition/button.rb +68 -0
  60. data/lib/capybara/selector/definition/checkbox.rb +26 -0
  61. data/lib/capybara/selector/definition/css.rb +10 -0
  62. data/lib/capybara/selector/definition/datalist_input.rb +35 -0
  63. data/lib/capybara/selector/definition/datalist_option.rb +25 -0
  64. data/lib/capybara/selector/definition/element.rb +28 -0
  65. data/lib/capybara/selector/definition/field.rb +40 -0
  66. data/lib/capybara/selector/definition/fieldset.rb +14 -0
  67. data/lib/capybara/selector/definition/file_field.rb +13 -0
  68. data/lib/capybara/selector/definition/fillable_field.rb +33 -0
  69. data/lib/capybara/selector/definition/frame.rb +17 -0
  70. data/lib/capybara/selector/definition/id.rb +6 -0
  71. data/lib/capybara/selector/definition/label.rb +62 -0
  72. data/lib/capybara/selector/definition/link.rb +55 -0
  73. data/lib/capybara/selector/definition/link_or_button.rb +16 -0
  74. data/lib/capybara/selector/definition/option.rb +27 -0
  75. data/lib/capybara/selector/definition/radio_button.rb +27 -0
  76. data/lib/capybara/selector/definition/select.rb +81 -0
  77. data/lib/capybara/selector/definition/table.rb +109 -0
  78. data/lib/capybara/selector/definition/table_row.rb +21 -0
  79. data/lib/capybara/selector/definition/xpath.rb +5 -0
  80. data/lib/capybara/selector/definition.rb +280 -0
  81. data/lib/capybara/selector/filter_set.rb +19 -18
  82. data/lib/capybara/selector/filters/base.rb +11 -2
  83. data/lib/capybara/selector/filters/locator_filter.rb +13 -3
  84. data/lib/capybara/selector/regexp_disassembler.rb +11 -7
  85. data/lib/capybara/selector/selector.rb +50 -440
  86. data/lib/capybara/selector/xpath_extensions.rb +17 -0
  87. data/lib/capybara/selector.rb +473 -482
  88. data/lib/capybara/selenium/atoms/getAttribute.min.js +1 -0
  89. data/lib/capybara/selenium/atoms/isDisplayed.min.js +1 -0
  90. data/lib/capybara/selenium/atoms/src/getAttribute.js +161 -0
  91. data/lib/capybara/selenium/atoms/src/isDisplayed.js +454 -0
  92. data/lib/capybara/selenium/driver.rb +174 -62
  93. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +74 -18
  94. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +128 -0
  95. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +37 -3
  96. data/lib/capybara/selenium/driver_specializations/internet_explorer_driver.rb +14 -1
  97. data/lib/capybara/selenium/driver_specializations/safari_driver.rb +24 -0
  98. data/lib/capybara/selenium/extensions/file_input_click_emulation.rb +34 -0
  99. data/lib/capybara/selenium/extensions/find.rb +68 -45
  100. data/lib/capybara/selenium/extensions/html5_drag.rb +192 -22
  101. data/lib/capybara/selenium/extensions/modifier_keys_stack.rb +28 -0
  102. data/lib/capybara/selenium/extensions/scroll.rb +8 -10
  103. data/lib/capybara/selenium/node.rb +268 -72
  104. data/lib/capybara/selenium/nodes/chrome_node.rb +105 -9
  105. data/lib/capybara/selenium/nodes/edge_node.rb +110 -0
  106. data/lib/capybara/selenium/nodes/firefox_node.rb +51 -61
  107. data/lib/capybara/selenium/nodes/ie_node.rb +22 -0
  108. data/lib/capybara/selenium/nodes/safari_node.rb +118 -0
  109. data/lib/capybara/selenium/patches/atoms.rb +18 -0
  110. data/lib/capybara/selenium/patches/is_displayed.rb +16 -0
  111. data/lib/capybara/selenium/patches/logs.rb +45 -0
  112. data/lib/capybara/selenium/patches/pause_duration_fix.rb +1 -1
  113. data/lib/capybara/selenium/patches/persistent_client.rb +20 -0
  114. data/lib/capybara/server/animation_disabler.rb +43 -21
  115. data/lib/capybara/server/checker.rb +6 -2
  116. data/lib/capybara/server/middleware.rb +25 -13
  117. data/lib/capybara/server.rb +20 -4
  118. data/lib/capybara/session/config.rb +15 -11
  119. data/lib/capybara/session/matchers.rb +11 -11
  120. data/lib/capybara/session.rb +162 -131
  121. data/lib/capybara/spec/public/offset.js +6 -0
  122. data/lib/capybara/spec/public/test.js +105 -6
  123. data/lib/capybara/spec/session/accept_alert_spec.rb +1 -1
  124. data/lib/capybara/spec/session/active_element_spec.rb +31 -0
  125. data/lib/capybara/spec/session/all_spec.rb +89 -15
  126. data/lib/capybara/spec/session/ancestor_spec.rb +5 -0
  127. data/lib/capybara/spec/session/assert_current_path_spec.rb +5 -2
  128. data/lib/capybara/spec/session/assert_text_spec.rb +26 -22
  129. data/lib/capybara/spec/session/attach_file_spec.rb +64 -31
  130. data/lib/capybara/spec/session/check_spec.rb +26 -4
  131. data/lib/capybara/spec/session/choose_spec.rb +14 -2
  132. data/lib/capybara/spec/session/click_button_spec.rb +109 -61
  133. data/lib/capybara/spec/session/click_link_or_button_spec.rb +9 -0
  134. data/lib/capybara/spec/session/click_link_spec.rb +23 -1
  135. data/lib/capybara/spec/session/current_scope_spec.rb +1 -1
  136. data/lib/capybara/spec/session/current_url_spec.rb +11 -1
  137. data/lib/capybara/spec/session/element/matches_selector_spec.rb +40 -39
  138. data/lib/capybara/spec/session/evaluate_script_spec.rb +12 -0
  139. data/lib/capybara/spec/session/fill_in_spec.rb +46 -5
  140. data/lib/capybara/spec/session/find_link_spec.rb +10 -0
  141. data/lib/capybara/spec/session/find_spec.rb +80 -7
  142. data/lib/capybara/spec/session/first_spec.rb +2 -2
  143. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +14 -1
  144. data/lib/capybara/spec/session/frame/within_frame_spec.rb +14 -1
  145. data/lib/capybara/spec/session/has_all_selectors_spec.rb +5 -5
  146. data/lib/capybara/spec/session/has_ancestor_spec.rb +46 -0
  147. data/lib/capybara/spec/session/has_any_selectors_spec.rb +6 -2
  148. data/lib/capybara/spec/session/has_button_spec.rb +81 -0
  149. data/lib/capybara/spec/session/has_css_spec.rb +45 -8
  150. data/lib/capybara/spec/session/has_current_path_spec.rb +22 -7
  151. data/lib/capybara/spec/session/has_element_spec.rb +47 -0
  152. data/lib/capybara/spec/session/has_field_spec.rb +59 -1
  153. data/lib/capybara/spec/session/has_link_spec.rb +40 -0
  154. data/lib/capybara/spec/session/has_none_selectors_spec.rb +7 -7
  155. data/lib/capybara/spec/session/has_select_spec.rb +42 -8
  156. data/lib/capybara/spec/session/has_selector_spec.rb +19 -4
  157. data/lib/capybara/spec/session/has_sibling_spec.rb +50 -0
  158. data/lib/capybara/spec/session/has_table_spec.rb +177 -0
  159. data/lib/capybara/spec/session/has_text_spec.rb +31 -3
  160. data/lib/capybara/spec/session/html_spec.rb +1 -1
  161. data/lib/capybara/spec/session/matches_style_spec.rb +6 -4
  162. data/lib/capybara/spec/session/node_spec.rb +697 -23
  163. data/lib/capybara/spec/session/node_wrapper_spec.rb +1 -1
  164. data/lib/capybara/spec/session/refresh_spec.rb +2 -1
  165. data/lib/capybara/spec/session/reset_session_spec.rb +21 -7
  166. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +2 -2
  167. data/lib/capybara/spec/session/save_page_spec.rb +4 -4
  168. data/lib/capybara/spec/session/save_screenshot_spec.rb +4 -4
  169. data/lib/capybara/spec/session/scroll_spec.rb +9 -7
  170. data/lib/capybara/spec/session/select_spec.rb +5 -10
  171. data/lib/capybara/spec/session/selectors_spec.rb +24 -3
  172. data/lib/capybara/spec/session/uncheck_spec.rb +3 -3
  173. data/lib/capybara/spec/session/unselect_spec.rb +1 -1
  174. data/lib/capybara/spec/session/visit_spec.rb +20 -0
  175. data/lib/capybara/spec/session/window/become_closed_spec.rb +20 -17
  176. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +1 -1
  177. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +1 -1
  178. data/lib/capybara/spec/session/window/window_spec.rb +54 -57
  179. data/lib/capybara/spec/session/window/windows_spec.rb +2 -2
  180. data/lib/capybara/spec/session/within_spec.rb +36 -0
  181. data/lib/capybara/spec/spec_helper.rb +30 -19
  182. data/lib/capybara/spec/test_app.rb +122 -34
  183. data/lib/capybara/spec/views/animated.erb +49 -0
  184. data/lib/capybara/spec/views/form.erb +86 -8
  185. data/lib/capybara/spec/views/frame_child.erb +3 -2
  186. data/lib/capybara/spec/views/frame_one.erb +2 -1
  187. data/lib/capybara/spec/views/frame_parent.erb +1 -1
  188. data/lib/capybara/spec/views/frame_two.erb +1 -1
  189. data/lib/capybara/spec/views/initial_alert.erb +2 -1
  190. data/lib/capybara/spec/views/layout.erb +10 -0
  191. data/lib/capybara/spec/views/obscured.erb +10 -10
  192. data/lib/capybara/spec/views/offset.erb +33 -0
  193. data/lib/capybara/spec/views/path.erb +2 -2
  194. data/lib/capybara/spec/views/popup_one.erb +1 -1
  195. data/lib/capybara/spec/views/popup_two.erb +1 -1
  196. data/lib/capybara/spec/views/react.erb +45 -0
  197. data/lib/capybara/spec/views/scroll.erb +2 -1
  198. data/lib/capybara/spec/views/spatial.erb +31 -0
  199. data/lib/capybara/spec/views/tables.erb +67 -0
  200. data/lib/capybara/spec/views/with_animation.erb +39 -4
  201. data/lib/capybara/spec/views/with_base_tag.erb +2 -2
  202. data/lib/capybara/spec/views/with_dragula.erb +24 -0
  203. data/lib/capybara/spec/views/with_fixed_header_footer.erb +2 -1
  204. data/lib/capybara/spec/views/with_hover.erb +3 -2
  205. data/lib/capybara/spec/views/with_hover1.erb +10 -0
  206. data/lib/capybara/spec/views/with_html.erb +34 -6
  207. data/lib/capybara/spec/views/with_jquery_animation.erb +24 -0
  208. data/lib/capybara/spec/views/with_js.erb +7 -4
  209. data/lib/capybara/spec/views/with_jstree.erb +26 -0
  210. data/lib/capybara/spec/views/with_namespace.erb +1 -0
  211. data/lib/capybara/spec/views/with_scope.erb +2 -2
  212. data/lib/capybara/spec/views/with_scope_other.erb +6 -0
  213. data/lib/capybara/spec/views/with_shadow.erb +31 -0
  214. data/lib/capybara/spec/views/with_slow_unload.erb +2 -1
  215. data/lib/capybara/spec/views/with_sortable_js.erb +21 -0
  216. data/lib/capybara/spec/views/with_unload_alert.erb +1 -0
  217. data/lib/capybara/spec/views/with_windows.erb +1 -1
  218. data/lib/capybara/spec/views/within_frames.erb +1 -1
  219. data/lib/capybara/version.rb +1 -1
  220. data/lib/capybara/window.rb +14 -18
  221. data/lib/capybara.rb +91 -126
  222. data/spec/basic_node_spec.rb +30 -16
  223. data/spec/capybara_spec.rb +40 -28
  224. data/spec/counter_spec.rb +35 -0
  225. data/spec/css_builder_spec.rb +3 -1
  226. data/spec/css_splitter_spec.rb +1 -1
  227. data/spec/dsl_spec.rb +33 -22
  228. data/spec/filter_set_spec.rb +5 -5
  229. data/spec/fixtures/selenium_driver_rspec_failure.rb +3 -3
  230. data/spec/fixtures/selenium_driver_rspec_success.rb +3 -3
  231. data/spec/minitest_spec.rb +24 -2
  232. data/spec/minitest_spec_spec.rb +60 -45
  233. data/spec/per_session_config_spec.rb +1 -1
  234. data/spec/rack_test_spec.rb +131 -98
  235. data/spec/regexp_dissassembler_spec.rb +53 -39
  236. data/spec/result_spec.rb +68 -66
  237. data/spec/rspec/features_spec.rb +9 -4
  238. data/spec/rspec/scenarios_spec.rb +6 -2
  239. data/spec/rspec/shared_spec_matchers.rb +137 -98
  240. data/spec/rspec_matchers_spec.rb +25 -0
  241. data/spec/rspec_spec.rb +23 -21
  242. data/spec/sauce_spec_chrome.rb +43 -0
  243. data/spec/selector_spec.rb +77 -21
  244. data/spec/selenium_spec_chrome.rb +141 -39
  245. data/spec/selenium_spec_chrome_remote.rb +32 -17
  246. data/spec/selenium_spec_edge.rb +36 -8
  247. data/spec/selenium_spec_firefox.rb +110 -68
  248. data/spec/selenium_spec_firefox_remote.rb +22 -15
  249. data/spec/selenium_spec_ie.rb +29 -22
  250. data/spec/selenium_spec_safari.rb +162 -0
  251. data/spec/server_spec.rb +153 -81
  252. data/spec/session_spec.rb +11 -4
  253. data/spec/shared_selenium_node.rb +79 -0
  254. data/spec/shared_selenium_session.rb +179 -74
  255. data/spec/spec_helper.rb +80 -5
  256. data/spec/whitespace_normalizer_spec.rb +54 -0
  257. data/spec/xpath_builder_spec.rb +3 -1
  258. metadata +218 -30
  259. data/lib/capybara/spec/session/source_spec.rb +0 -0
  260. data/lib/capybara/spec/views/with_title.erb +0 -5
@@ -6,62 +6,63 @@ module Capybara
6
6
  # @!macro waiting_behavior
7
7
  # If the driver is capable of executing JavaScript, this method will wait for a set amount of time
8
8
  # and continuously retry finding the element until either the element is found or the time
9
- # expires. The length of time +find+ will wait is controlled through {Capybara.default_max_wait_time}
9
+ # expires. The length of time this method will wait is controlled through {Capybara.configure default_max_wait_time}.
10
10
  #
11
- # @option options [false, Numeric] wait (Capybara.default_max_wait_time) Maximum time to wait for matching element to appear.
11
+ # @option options [false, true, Numeric] wait
12
+ # Maximum time to wait for matching element to appear. Defaults to {Capybara.configure default_max_wait_time}.
12
13
 
13
14
  ##
14
15
  #
15
- # Finds a button or link and clicks it. See {Capybara::Node::Actions#click_button} and
16
- # {Capybara::Node::Actions#click_link} for what locator will match against for each type of element
16
+ # Finds a button or link and clicks it. See {#click_button} and
17
+ # {#click_link} for what locator will match against for each type of element.
17
18
  #
18
19
  # @overload click_link_or_button([locator], **options)
19
20
  # @macro waiting_behavior
20
- # @param [String] locator See {Capybara::Node::Actions#click_button} and {Capybara::Node::Actions#click_link}
21
+ # @param [String] locator See {#click_button} and {#click_link}
21
22
  #
22
23
  # @return [Capybara::Node::Element] The element clicked
23
24
  #
24
25
  def click_link_or_button(locator = nil, **options)
25
- find(:link_or_button, locator, options).click
26
+ find(:link_or_button, locator, **options).click
26
27
  end
27
28
  alias_method :click_on, :click_link_or_button
28
29
 
29
30
  ##
30
31
  #
31
- # Finds a link by id, Capybara.test_id attribute, text or title and clicks it. Also looks at image
32
+ # Finds a link by id, {Capybara.configure test_id} attribute, text or title and clicks it. Also looks at image
32
33
  # alt text inside the link.
33
34
  #
34
35
  # @overload click_link([locator], **options)
35
36
  # @macro waiting_behavior
36
- # @param [String] locator text, id, Capybara.test_id attribute, title or nested image's alt attribute
37
- # @param options See {Capybara::Node::Finders#find_link}
37
+ # @param [String] locator text, id, {Capybara.configure test_id} attribute, title or nested image's alt attribute
38
+ # @param [Hash] options See {Capybara::Node::Finders#find_link}
38
39
  #
39
40
  # @return [Capybara::Node::Element] The element clicked
40
41
  def click_link(locator = nil, **options)
41
- find(:link, locator, options).click
42
+ find(:link, locator, **options).click
42
43
  end
43
44
 
44
45
  ##
45
46
  #
46
47
  # Finds a button on the page and clicks it.
47
- # This can be any \<input> element of type submit, reset, image, button or it can be a
48
- # \<button> element. All buttons can be found by their id, Capybara.test_id attribute, value, or title. \<button> elements can also be found
49
- # by their text content, and image \<input> elements by their alt attribute
48
+ # This can be any `<input>` element of type submit, reset, image, button or it can be a
49
+ # `<button>` element. All buttons can be found by their id, name, {Capybara.configure test_id} attribute, value, or title. `<button>` elements can also be found
50
+ # by their text content, and image `<input>` elements by their alt attribute.
50
51
  #
51
52
  # @overload click_button([locator], **options)
52
53
  # @macro waiting_behavior
53
54
  # @param [String] locator Which button to find
54
- # @param options See {Capybara::Node::Finders#find_button}
55
+ # @param [Hash] options See {Capybara::Node::Finders#find_button}
55
56
  # @return [Capybara::Node::Element] The element clicked
56
57
  def click_button(locator = nil, **options)
57
- find(:button, locator, options).click
58
+ find(:button, locator, **options).click
58
59
  end
59
60
 
60
61
  ##
61
62
  #
62
- # Locate a text field or text area and fill it in with the given text
63
- # The field can be found via its name, id, Capybara.test_id attribute, or label text.
64
- # If no locator is provided will operate on self or a descendant
63
+ # Locate a text field or text area and fill it in with the given text.
64
+ # The field can be found via its name, id, {Capybara.configure test_id} attribute, placeholder, or label text.
65
+ # If no locator is provided this will operate on self or a descendant.
65
66
  #
66
67
  # # will fill in a descendant fillable field with name, id, or label text matching 'Name'
67
68
  # page.fill_in 'Name', with: 'Bob'
@@ -73,7 +74,7 @@ module Capybara
73
74
  # @overload fill_in([locator], with:, **options)
74
75
  # @param [String] locator Which field to fill in
75
76
  # @param [Hash] options
76
- # @param with: [String] The value to fill_in
77
+ # @param with: [String] The value to fill in
77
78
  # @macro waiting_behavior
78
79
  # @option options [String] currently_with The current value property of the field to fill in
79
80
  # @option options [Boolean] multiple Match fields that can have multiple values?
@@ -81,23 +82,25 @@ module Capybara
81
82
  # @option options [String] name Match fields that match the name attribute
82
83
  # @option options [String] placeholder Match fields that match the placeholder attribute
83
84
  # @option options [String, Array<String>, Regexp] class Match fields that match the class(es) provided
84
- # @option options [Hash] fill_options Driver specific options regarding how to fill fields (Defaults come from Capybara.default_set_options)
85
+ # @option options [Hash] fill_options Driver specific options regarding how to fill fields (Defaults come from {Capybara.configure default_set_options})
85
86
  #
86
- # @return [Capybara::Node::Element] The element filled_in
87
+ # @return [Capybara::Node::Element] The element filled in
87
88
  def fill_in(locator = nil, with:, currently_with: nil, fill_options: {}, **find_options)
88
89
  find_options[:with] = currently_with if currently_with
89
90
  find_options[:allow_self] = true if locator.nil?
90
- find(:fillable_field, locator, find_options).set(with, fill_options)
91
+ find(:fillable_field, locator, **find_options).set(with, **fill_options)
91
92
  end
92
93
 
93
94
  # @!macro label_click
94
- # @option options [Boolean] allow_label_click (Capybara.automatic_label_click) Attempt to click the label to toggle state if element is non-visible.
95
+ # @option options [Boolean, Hash] allow_label_click
96
+ # Attempt to click the label to toggle state if element is non-visible. Defaults to {Capybara.configure automatic_label_click}.
97
+ # If set to a Hash it is passed as options to the `click` on the label
95
98
 
96
99
  ##
97
100
  #
98
101
  # Find a descendant radio button and mark it as checked. The radio button can be found
99
- # via name, id or label text. If no locator is provided this will match against self or
100
- # a descendant.
102
+ # via name, id, {Capybara.configure test_id} attribute or label text. If no locator is
103
+ # provided this will match against self or a descendant.
101
104
  #
102
105
  # # will choose a descendant radio button with a name, id, or label text matching 'Male'
103
106
  # page.choose('Male')
@@ -117,14 +120,14 @@ module Capybara
117
120
  #
118
121
  # @return [Capybara::Node::Element] The element chosen or the label clicked
119
122
  def choose(locator = nil, **options)
120
- _check_with_label(:radio_button, true, locator, options)
123
+ _check_with_label(:radio_button, true, locator, **options)
121
124
  end
122
125
 
123
126
  ##
124
127
  #
125
128
  # Find a descendant check box and mark it as checked. The check box can be found
126
- # via name, id or label text. If no locator is provided this will match against
127
- # self or a descendant.
129
+ # via name, id, {Capybara.configure test_id} attribute, or label text. If no locator
130
+ # is provided this will match against self or a descendant.
128
131
  #
129
132
  # # will check a descendant checkbox with a name, id, or label text matching 'German'
130
133
  # page.check('German')
@@ -145,14 +148,14 @@ module Capybara
145
148
  #
146
149
  # @return [Capybara::Node::Element] The element checked or the label clicked
147
150
  def check(locator = nil, **options)
148
- _check_with_label(:checkbox, true, locator, options)
151
+ _check_with_label(:checkbox, true, locator, **options)
149
152
  end
150
153
 
151
154
  ##
152
155
  #
153
156
  # Find a descendant check box and uncheck it. The check box can be found
154
- # via name, id or label text. If no locator is provided this will match against
155
- # self or a descendant.
157
+ # via name, id, {Capybara.configure test_id} attribute, or label text. If
158
+ # no locator is provided this will match against self or a descendant.
156
159
  #
157
160
  # # will uncheck a descendant checkbox with a name, id, or label text matching 'German'
158
161
  # page.uncheck('German')
@@ -173,17 +176,18 @@ module Capybara
173
176
  #
174
177
  # @return [Capybara::Node::Element] The element unchecked or the label clicked
175
178
  def uncheck(locator = nil, **options)
176
- _check_with_label(:checkbox, false, locator, options)
179
+ _check_with_label(:checkbox, false, locator, **options)
177
180
  end
178
181
 
179
182
  ##
180
183
  #
181
- # If `:from` option is present, `select` finds a select box, or text input with associated datalist,
184
+ # If `from` option is present, {#select} finds a select box, or text input with associated datalist,
182
185
  # on the page and selects a particular option from it.
183
186
  # Otherwise it finds an option inside current scope and selects it.
184
- # If the select box is a multiple select, +select+ can be called multiple times to select more than
187
+ # If the select box is a multiple select, {#select} can be called multiple times to select more than
185
188
  # one option.
186
- # The select box can be found via its name, id or label text. The option can be found by its text.
189
+ # The select box can be found via its name, id, {Capybara.configure test_id} attribute, or label text.
190
+ # The option can be found by its text.
187
191
  #
188
192
  # page.select 'March', from: 'Month'
189
193
  #
@@ -191,7 +195,7 @@ module Capybara
191
195
  # @macro waiting_behavior
192
196
  #
193
197
  # @param value [String] Which option to select
194
- # @param from [String] The id, Capybara.test_id atrtribute, name or label of the select box
198
+ # @param from [String] The id, {Capybara.configure test_id} attribute, name or label of the select box
195
199
  #
196
200
  # @return [Capybara::Node::Element] The option element selected
197
201
  def select(value = nil, from: nil, **options)
@@ -202,15 +206,16 @@ module Capybara
202
206
  if el.respond_to?(:tag_name) && (el.tag_name == 'input')
203
207
  select_datalist_option(el, value)
204
208
  else
205
- el.find(:option, value, options).select_option
209
+ el.find(:option, value, **options).select_option
206
210
  end
207
211
  end
208
212
 
209
213
  ##
210
214
  #
211
215
  # Find a select box on the page and unselect a particular option from it. If the select
212
- # box is a multiple select, +unselect+ can be called multiple times to unselect more than
213
- # one option. The select box can be found via its name, id or label text.
216
+ # box is a multiple select, {#unselect} can be called multiple times to unselect more than
217
+ # one option. The select box can be found via its name, id, {Capybara.configure test_id} attribute,
218
+ # or label text.
214
219
  #
215
220
  # page.unselect 'March', from: 'Month'
216
221
  #
@@ -218,24 +223,27 @@ module Capybara
218
223
  # @macro waiting_behavior
219
224
  #
220
225
  # @param value [String] Which option to unselect
221
- # @param from [String] The id, Capybara.test_id attribute, name or label of the select box
226
+ # @param from [String] The id, {Capybara.configure test_id} attribute, name or label of the select box
222
227
  #
223
228
  #
224
229
  # @return [Capybara::Node::Element] The option element unselected
225
230
  def unselect(value = nil, from: nil, **options)
226
231
  raise ArgumentError, 'The :from option does not take an element' if from.is_a? Capybara::Node::Element
227
232
 
228
- scope = from ? find(:select, from, options) : self
229
- scope.find(:option, value, options).unselect_option
233
+ scope = from ? find(:select, from, **options) : self
234
+ scope.find(:option, value, **options).unselect_option
230
235
  end
231
236
 
232
237
  ##
233
238
  #
234
- # Find a descendant file field on the page and attach a file given its path. The file field can
235
- # be found via its name, id or label text. In the case of the file field being hidden for
239
+ # Find a descendant file field on the page and attach a file given its path. There are two ways to use
240
+ # {#attach_file}, in the first method the file field can be found via its name, id,
241
+ # {Capybara.configure test_id} attribute, or label text. In the case of the file field being hidden for
236
242
  # styling reasons the `make_visible` option can be used to temporarily change the CSS of
237
243
  # the file field, attach the file, and then revert the CSS back to original. If no locator is
238
244
  # passed this will match self or a descendant.
245
+ # The second method, which is currently in beta and may be changed/removed, involves passing a block
246
+ # which performs whatever actions would trigger the file chooser to appear.
239
247
  #
240
248
  # # will attach file to a descendant file input element that has a name, id, or label_text matching 'My File'
241
249
  # page.attach_file('My File', '/path/to/file.png')
@@ -243,32 +251,57 @@ module Capybara
243
251
  # # will attach file to el if it's a file input element
244
252
  # el.attach_file('/path/to/file.png')
245
253
  #
254
+ # # will attach file to whatever file input is triggered by the block
255
+ # page.attach_file('/path/to/file.png') do
256
+ # page.find('#upload_button').click
257
+ # end
258
+ #
246
259
  # @overload attach_file([locator], paths, **options)
247
260
  # @macro waiting_behavior
248
261
  #
249
262
  # @param [String] locator Which field to attach the file to
250
263
  # @param [String, Array<String>] paths The path(s) of the file(s) that will be attached
251
264
  #
252
- # @option options [Symbol] match (Capybara.match) The matching strategy to use (:one, :first, :prefer_exact, :smart).
253
- # @option options [Boolean] exact (Capybara.exact) Match the exact label name/contents or accept a partial match.
265
+ # @option options [Symbol] match
266
+ # The matching strategy to use (:one, :first, :prefer_exact, :smart). Defaults to {Capybara.configure match}.
267
+ # @option options [Boolean] exact
268
+ # Match the exact label name/contents or accept a partial match. Defaults to {Capybara.configure exact}.
254
269
  # @option options [Boolean] multiple Match field which allows multiple file selection
255
270
  # @option options [String, Regexp] id Match fields that match the id attribute
256
271
  # @option options [String] name Match fields that match the name attribute
257
272
  # @option options [String, Array<String>, Regexp] class Match fields that match the class(es) provided
258
- # @option options [true, Hash] make_visible A Hash of CSS styles to change before attempting to attach the file, if `true` { opacity: 1, display: 'block', visibility: 'visible' } is used (may not be supported by all drivers)
259
- #
260
- # @return [Capybara::Node::Element] The file field element
273
+ # @option options [true, Hash] make_visible
274
+ # A Hash of CSS styles to change before attempting to attach the file, if `true`, `{ opacity: 1, display: 'block', visibility: 'visible' }` is used (may not be supported by all drivers).
275
+ # @overload attach_file(paths, &blk)
276
+ # @param [String, Array<String>] paths The path(s) of the file(s) that will be attached
277
+ # @yield Block whose actions will trigger the system file chooser to be shown
278
+ # @return [Capybara::Node::Element] The file field element
261
279
  def attach_file(locator = nil, paths, make_visible: nil, **options) # rubocop:disable Style/OptionalArguments
280
+ if locator && block_given?
281
+ raise ArgumentError, '`#attach_file` does not support passing both a locator and a block'
282
+ end
283
+
262
284
  Array(paths).each do |path|
263
285
  raise Capybara::FileNotFound, "cannot attach file, #{path} does not exist" unless File.exist?(path.to_s)
264
286
  end
265
287
  options[:allow_self] = true if locator.nil?
288
+
289
+ if block_given?
290
+ begin
291
+ execute_script CAPTURE_FILE_ELEMENT_SCRIPT
292
+ yield
293
+ file_field = evaluate_script 'window._capybara_clicked_file_input'
294
+ raise ArgumentError, "Capybara was unable to determine the file input you're attaching to" unless file_field
295
+ rescue ::Capybara::NotSupportedByDriverError
296
+ warn 'Block mode of `#attach_file` is not supported by the current driver - ignoring.'
297
+ end
298
+ end
266
299
  # Allow user to update the CSS style of the file input since they are so often hidden on a page
267
300
  if make_visible
268
- ff = find(:file_field, locator, options.merge(visible: :all))
301
+ ff = file_field || find(:file_field, locator, **options.merge(visible: :all))
269
302
  while_visible(ff, make_visible) { |el| el.set(paths) }
270
303
  else
271
- find(:file_field, locator, options).set(paths)
304
+ (file_field || find(:file_field, locator, **options)).set(paths)
272
305
  end
273
306
  end
274
307
 
@@ -276,16 +309,14 @@ module Capybara
276
309
 
277
310
  def find_select_or_datalist_input(from, options)
278
311
  synchronize(Capybara::Queries::BaseQuery.wait(options, session_options.default_max_wait_time)) do
312
+ find(:select, from, **options)
313
+ rescue Capybara::ElementNotFound => select_error # rubocop:disable Naming/RescuedExceptionsVariableName
314
+ raise if %i[selected with_selected multiple].any? { |option| options.key?(option) }
315
+
279
316
  begin
280
- find(:select, from, options)
281
- rescue Capybara::ElementNotFound => select_error
282
- raise if %i[selected with_selected multiple].any? { |option| options.key?(option) }
283
-
284
- begin
285
- find(:datalist_input, from, options)
286
- rescue Capybara::ElementNotFound => dlinput_error
287
- raise Capybara::ElementNotFound, "#{select_error.message} and #{dlinput_error.message}"
288
- end
317
+ find(:datalist_input, from, **options)
318
+ rescue Capybara::ElementNotFound => dlinput_error
319
+ raise Capybara::ElementNotFound, "#{select_error.message} and #{dlinput_error.message}"
289
320
  end
290
321
  end
291
322
  end
@@ -304,9 +335,13 @@ module Capybara
304
335
  end
305
336
 
306
337
  def while_visible(element, visible_css)
307
- visible_css = { opacity: 1, display: 'block', visibility: 'visible' } if visible_css == true
338
+ if visible_css == true
339
+ visible_css = { opacity: 1, display: 'block', visibility: 'visible', width: 'auto', height: 'auto' }
340
+ end
308
341
  _update_style(element, visible_css)
309
- raise ExpectationNotMet, 'The style changes in :make_visible did not make the file input visible' unless element.visible?
342
+ unless element.visible?
343
+ raise ExpectationNotMet, 'The style changes in :make_visible did not make the file input visible'
344
+ end
310
345
 
311
346
  begin
312
347
  yield element
@@ -323,31 +358,32 @@ module Capybara
323
358
 
324
359
  def _reset_style(element)
325
360
  element.execute_script(RESET_STYLE_SCRIPT)
326
- rescue StandardError # rubocop:disable Lint/HandleExceptions swallow extra errors
361
+ rescue StandardError # rubocop:disable Lint/SuppressedException swallow extra errors
327
362
  end
328
363
 
329
364
  def _check_with_label(selector, checked, locator,
330
365
  allow_label_click: session_options.automatic_label_click, **options)
331
366
  options[:allow_self] = true if locator.nil?
332
-
333
367
  synchronize(Capybara::Queries::BaseQuery.wait(options, session_options.default_max_wait_time)) do
368
+ el = find(selector, locator, **options)
369
+ el.set(checked)
370
+ rescue StandardError => e
371
+ raise unless allow_label_click && catch_error?(e)
372
+
334
373
  begin
335
- el = find(selector, locator, options)
336
- el.set(checked)
337
- rescue StandardError => err
338
- raise unless allow_label_click && catch_error?(err)
339
-
340
- begin
341
- el ||= find(selector, locator, options.merge(visible: :all))
342
- el.session.find(:label, for: el, visible: true).click unless el.checked? == checked
343
- rescue StandardError # swallow extra errors - raise original
344
- raise err
374
+ el ||= find(selector, locator, **options.merge(visible: :all))
375
+ unless el.checked? == checked
376
+ el.session
377
+ .find(:label, for: el, visible: true, match: :first)
378
+ .click(**(Hash.try_convert(allow_label_click) || {}))
345
379
  end
380
+ rescue StandardError # swallow extra errors - raise original
381
+ raise e
346
382
  end
347
383
  end
348
384
  end
349
385
 
350
- UPDATE_STYLE_SCRIPT = <<~'JS'
386
+ UPDATE_STYLE_SCRIPT = <<~JS
351
387
  this.capybara_style_cache = this.style.cssText;
352
388
  var css = arguments[0];
353
389
  for (var prop in css){
@@ -357,18 +393,28 @@ module Capybara
357
393
  }
358
394
  JS
359
395
 
360
- RESET_STYLE_SCRIPT = <<~'JS'
396
+ RESET_STYLE_SCRIPT = <<~JS
361
397
  if (this.hasOwnProperty('capybara_style_cache')) {
362
398
  this.style.cssText = this.capybara_style_cache;
363
399
  delete this.capybara_style_cache;
364
400
  }
365
401
  JS
366
402
 
367
- DATALIST_OPTIONS_SCRIPT = <<~'JS'
403
+ DATALIST_OPTIONS_SCRIPT = <<~JS
368
404
  Array.prototype.slice.call((this.list||{}).options || []).
369
405
  filter(function(el){ return !el.disabled }).
370
406
  map(function(el){ return { "value": el.value, "label": el.label} })
371
407
  JS
408
+
409
+ CAPTURE_FILE_ELEMENT_SCRIPT = <<~JS
410
+ document.addEventListener('click', function file_catcher(e){
411
+ if (e.target.matches("input[type='file']")) {
412
+ window._capybara_clicked_file_input = e.target;
413
+ this.removeEventListener('click', file_catcher);
414
+ e.preventDefault();
415
+ }
416
+ }, {capture: true})
417
+ JS
372
418
  end
373
419
  end
374
420
  end
@@ -76,18 +76,26 @@ module Capybara
76
76
  def synchronize(seconds = nil, errors: nil)
77
77
  return yield if session.synchronized
78
78
 
79
- seconds = session_options.default_max_wait_time if seconds.nil?
79
+ seconds = session_options.default_max_wait_time if [nil, true].include? seconds
80
+ interval = session_options.default_retry_interval
80
81
  session.synchronized = true
81
82
  timer = Capybara::Helpers.timer(expire_in: seconds)
82
83
  begin
83
84
  yield
84
- rescue StandardError => err
85
+ rescue StandardError => e
85
86
  session.raise_server_error!
86
- raise err unless driver.wait? && catch_error?(err, errors)
87
- raise err if timer.expired?
87
+ raise e unless catch_error?(e, errors)
88
88
 
89
- sleep(0.01)
90
- reload if session_options.automatic_reload
89
+ if driver.wait?
90
+ raise e if timer.expired?
91
+
92
+ sleep interval
93
+ reload if session_options.automatic_reload
94
+ else
95
+ old_base = @base
96
+ reload if session_options.automatic_reload
97
+ raise e if old_base == @base
98
+ end
91
99
  retry
92
100
  ensure
93
101
  session.synchronized = false
@@ -96,19 +104,19 @@ module Capybara
96
104
 
97
105
  # @api private
98
106
  def find_css(css, **options)
99
- if base.method(:find_css).arity != 1
100
- base.find_css(css, **options)
101
- else
107
+ if base.method(:find_css).arity == 1
102
108
  base.find_css(css)
109
+ else
110
+ base.find_css(css, **options)
103
111
  end
104
112
  end
105
113
 
106
114
  # @api private
107
115
  def find_xpath(xpath, **options)
108
- if base.method(:find_xpath).arity != 1
109
- base.find_xpath(xpath, **options)
110
- else
116
+ if base.method(:find_xpath).arity == 1
111
117
  base.find_xpath(xpath)
118
+ else
119
+ base.find_xpath(xpath, **options)
112
120
  end
113
121
  end
114
122
 
@@ -40,8 +40,8 @@ module Capybara
40
40
  find(:xpath, '/html').evaluate_script(*args)
41
41
  end
42
42
 
43
- def scroll_to(*args)
44
- find(:xpath, '//body').scroll_to(*args)
43
+ def scroll_to(*args, quirks: false, **options)
44
+ find(:xpath, quirks ? '//body' : '/html').scroll_to(*args, **options)
45
45
  end
46
46
  end
47
47
  end
@@ -42,7 +42,7 @@ module Capybara
42
42
  # @return [Boolean]
43
43
  #
44
44
  def has_title?(title, **options)
45
- make_predicate(options) { assert_title(title, options) }
45
+ make_predicate(options) { assert_title(title, **options) }
46
46
  end
47
47
 
48
48
  ##
@@ -52,13 +52,13 @@ module Capybara
52
52
  # @return [Boolean]
53
53
  #
54
54
  def has_no_title?(title, **options)
55
- make_predicate(options) { assert_no_title(title, options) }
55
+ make_predicate(options) { assert_no_title(title, **options) }
56
56
  end
57
57
 
58
58
  private
59
59
 
60
60
  def _verify_title(title, options)
61
- query = Capybara::Queries::TitleQuery.new(title, options)
61
+ query = Capybara::Queries::TitleQuery.new(title, **options)
62
62
  synchronize(query.wait) { yield(query) }
63
63
  true
64
64
  end