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
@@ -27,9 +27,11 @@ module Capybara
27
27
  @query_scope = query_scope
28
28
  @query = query
29
29
  @allow_reload = false
30
+ @query_idx = nil
30
31
  end
31
32
 
32
- def allow_reload!
33
+ def allow_reload!(idx = nil)
34
+ @query_idx = idx
33
35
  @allow_reload = true
34
36
  end
35
37
 
@@ -43,7 +45,7 @@ module Capybara
43
45
 
44
46
  ##
45
47
  #
46
- # Retrieve the text of the element. If `Capybara.ignore_hidden_elements`
48
+ # Retrieve the text of the element. If {Capybara.configure ignore_hidden_elements}
47
49
  # is `true`, which it is by default, then this will return only text
48
50
  # which is visible. The exact semantics of this may differ between
49
51
  # drivers, but generally any text within elements with `display:none` is
@@ -61,7 +63,7 @@ module Capybara
61
63
 
62
64
  ##
63
65
  #
64
- # Retrieve the given attribute
66
+ # Retrieve the given attribute.
65
67
  #
66
68
  # element[:title] # => HTML title attribute
67
69
  #
@@ -74,7 +76,7 @@ module Capybara
74
76
 
75
77
  ##
76
78
  #
77
- # Retrieve the given CSS styles
79
+ # Retrieve the given CSS styles.
78
80
  #
79
81
  # element.style('color', 'font-size') # => Computed values of CSS 'color' and 'font-size' styles
80
82
  #
@@ -87,11 +89,11 @@ module Capybara
87
89
 
88
90
  begin
89
91
  synchronize { base.style(styles) }
90
- rescue NotImplementedError => err
92
+ rescue NotImplementedError => e
91
93
  begin
92
94
  evaluate_script(STYLE_SCRIPT, *styles)
93
95
  rescue Capybara::NotSupportedByDriverError
94
- raise err
96
+ raise e
95
97
  end
96
98
  end
97
99
  end
@@ -109,32 +111,41 @@ module Capybara
109
111
  # Set the value of the form element to the given value.
110
112
  #
111
113
  # @param [String] value The new value
112
- # @param [Hash{}] options Driver specific options for how to set the value. Take default values from `Capybara#default_set_options` - See {Capybara::configure}
114
+ # @param [Hash] options Driver specific options for how to set the value. Take default values from {Capybara.configure default_set_options}.
113
115
  #
114
116
  # @return [Capybara::Node::Element] The element
115
117
  def set(value, **options)
116
- raise Capybara::ReadOnlyElementError, "Attempt to set readonly element with value: #{value}" if readonly?
118
+ if ENV.fetch('CAPYBARA_THOROUGH', nil) && readonly?
119
+ raise Capybara::ReadOnlyElementError, "Attempt to set readonly element with value: #{value}"
120
+ end
117
121
 
118
122
  options = session_options.default_set_options.to_h.merge(options)
119
- synchronize { base.set(value, options) }
123
+ synchronize { base.set(value, **options) }
120
124
  self
121
125
  end
122
126
 
123
127
  ##
124
128
  #
125
- # Select this node if is an option element inside a select tag
129
+ # Select this node if it is an option element inside a select tag.
130
+ #
131
+ # @!macro action_waiting_behavior
132
+ # If the driver dynamic pages (JS) and the element is currently non-interactable, this method will
133
+ # continuously retry the action until either the element becomes interactable or the maximum
134
+ # wait time expires.
126
135
  #
136
+ # @param [false, Numeric] wait
137
+ # Maximum time to wait for the action to succeed. Defaults to {Capybara.configure default_max_wait_time}.
127
138
  # @return [Capybara::Node::Element] The element
128
139
  def select_option(wait: nil)
129
- warn "Attempt to select disabled option: #{value || text}" if disabled?
130
140
  synchronize(wait) { base.select_option }
131
141
  self
132
142
  end
133
143
 
134
144
  ##
135
145
  #
136
- # Unselect this node if is an option element inside a multiple select tag
146
+ # Unselect this node if it is an option element inside a multiple select tag.
137
147
  #
148
+ # @macro action_waiting_behavior
138
149
  # @return [Capybara::Node::Element] The element
139
150
  def unselect_option(wait: nil)
140
151
  synchronize(wait) { base.unselect_option }
@@ -143,51 +154,55 @@ module Capybara
143
154
 
144
155
  ##
145
156
  #
146
- # Click the Element
157
+ # Click the Element.
147
158
  #
159
+ # @macro action_waiting_behavior
148
160
  # @!macro click_modifiers
149
- # Both x: and y: must be specified if an offset is wanted, if not specified the click will occur at the middle of the element
150
- # @overload $0(*modifier_keys, **offset)
161
+ # Both x: and y: must be specified if an offset is wanted, if not specified the click will occur at the middle of the element.
162
+ # @overload $0(*modifier_keys, wait: nil, **offset)
151
163
  # @param *modifier_keys [:alt, :control, :meta, :shift] ([]) Keys to be held down when clicking
152
- # @option offset [Integer] x X coordinate to offset the click location from the top left corner of the element
153
- # @option offset [Integer] y Y coordinate to offset the click location from the top left corner of the element
164
+ # @option options [Integer] x X coordinate to offset the click location. If {Capybara.configure w3c_click_offset} is `true` the
165
+ # offset will be from the element center, otherwise it will be from the top left corner of the element
166
+ # @option options [Integer] y Y coordinate to offset the click location. If {Capybara.configure w3c_click_offset} is `true` the
167
+ # offset will be from the element center, otherwise it will be from the top left corner of the element
168
+ # @option options [Float] delay Delay between the mouse down and mouse up events in seconds (0)
154
169
  # @return [Capybara::Node::Element] The element
155
- def click(*keys, wait: nil, **offset)
156
- raise ArgumentError, 'You must specify both x: and y: for a click offset' if nil ^ offset[:x] ^ offset[:y]
157
-
158
- synchronize(wait) { base.click(Array(keys), offset) }
159
- self
170
+ def click(*keys, **options)
171
+ perform_click_action(keys, **options) do |k, opts|
172
+ base.click(k, **opts)
173
+ end
160
174
  end
161
175
 
162
176
  ##
163
177
  #
164
- # Right Click the Element
178
+ # Right Click the Element.
165
179
  #
180
+ # @macro action_waiting_behavior
166
181
  # @macro click_modifiers
182
+ # @option options [Float] delay Delay between the mouse down and mouse up events in seconds (0)
167
183
  # @return [Capybara::Node::Element] The element
168
- def right_click(*keys, wait: nil, **offset)
169
- raise ArgumentError, 'You must specify both x: and y: for a click offset' if nil ^ offset[:x] ^ offset[:y]
170
-
171
- synchronize(wait) { base.right_click(keys, offset) }
172
- self
184
+ def right_click(*keys, **options)
185
+ perform_click_action(keys, **options) do |k, opts|
186
+ base.right_click(k, **opts)
187
+ end
173
188
  end
174
189
 
175
190
  ##
176
191
  #
177
- # Double Click the Element
192
+ # Double Click the Element.
178
193
  #
194
+ # @macro action_waiting_behavior
179
195
  # @macro click_modifiers
180
196
  # @return [Capybara::Node::Element] The element
181
- def double_click(*keys, wait: nil, **offset)
182
- raise ArgumentError, 'You must specify both x: and y: for a click offset' if nil ^ offset[:x] ^ offset[:y]
183
-
184
- synchronize(wait) { base.double_click(keys, offset) }
185
- self
197
+ def double_click(*keys, **options)
198
+ perform_click_action(keys, **options) do |k, opts|
199
+ base.double_click(k, **opts)
200
+ end
186
201
  end
187
202
 
188
203
  ##
189
204
  #
190
- # Send Keystrokes to the Element
205
+ # Send Keystrokes to the Element.
191
206
  #
192
207
  # @overload send_keys(keys, ...)
193
208
  # @param keys [String, Symbol, Array<String,Symbol>]
@@ -195,65 +210,65 @@ module Capybara
195
210
  # Examples:
196
211
  #
197
212
  # element.send_keys "foo" #=> value: 'foo'
198
- # element.send_keys "tet", :left, "s" #=> value: 'test'
213
+ # element.send_keys "tet", :left, "s" #=> value: 'test'
199
214
  # element.send_keys [:control, 'a'], :space #=> value: ' ' - assuming ctrl-a selects all contents
200
215
  #
201
- # Symbols supported for keys
202
- # :cancel
203
- # :help
204
- # :backspace
205
- # :tab
206
- # :clear
207
- # :return
208
- # :enter
209
- # :shift
210
- # :control
211
- # :alt
212
- # :pause
213
- # :escape
214
- # :space
215
- # :page_up
216
- # :page_down
217
- # :end
218
- # :home
219
- # :left
220
- # :up
221
- # :right
222
- # :down
223
- # :insert
224
- # :delete
225
- # :semicolon
226
- # :equals
227
- # :numpad0
228
- # :numpad1
229
- # :numpad2
230
- # :numpad3
231
- # :numpad4
232
- # :numpad5
233
- # :numpad6
234
- # :numpad7
235
- # :numpad8
236
- # :numpad9
237
- # :multiply - numeric keypad *
238
- # :add - numeric keypad +
239
- # :separator - numeric keypad 'separator' key ??
240
- # :subtract - numeric keypad -
241
- # :decimal - numeric keypad .
242
- # :divide - numeric keypad /
243
- # :f1
244
- # :f2
245
- # :f3
246
- # :f4
247
- # :f5
248
- # :f6
249
- # :f7
250
- # :f8
251
- # :f9
252
- # :f10
253
- # :f11
254
- # :f12
255
- # :meta
256
- # :command - alias of :meta
216
+ # Symbols supported for keys:
217
+ # * :cancel
218
+ # * :help
219
+ # * :backspace
220
+ # * :tab
221
+ # * :clear
222
+ # * :return
223
+ # * :enter
224
+ # * :shift
225
+ # * :control
226
+ # * :alt
227
+ # * :pause
228
+ # * :escape
229
+ # * :space
230
+ # * :page_up
231
+ # * :page_down
232
+ # * :end
233
+ # * :home
234
+ # * :left
235
+ # * :up
236
+ # * :right
237
+ # * :down
238
+ # * :insert
239
+ # * :delete
240
+ # * :semicolon
241
+ # * :equals
242
+ # * :numpad0
243
+ # * :numpad1
244
+ # * :numpad2
245
+ # * :numpad3
246
+ # * :numpad4
247
+ # * :numpad5
248
+ # * :numpad6
249
+ # * :numpad7
250
+ # * :numpad8
251
+ # * :numpad9
252
+ # * :multiply - numeric keypad *
253
+ # * :add - numeric keypad +
254
+ # * :separator - numeric keypad 'separator' key ??
255
+ # * :subtract - numeric keypad -
256
+ # * :decimal - numeric keypad .
257
+ # * :divide - numeric keypad /
258
+ # * :f1
259
+ # * :f2
260
+ # * :f3
261
+ # * :f4
262
+ # * :f5
263
+ # * :f6
264
+ # * :f7
265
+ # * :f8
266
+ # * :f9
267
+ # * :f10
268
+ # * :f11
269
+ # * :f12
270
+ # * :meta
271
+ # * :command - alias of :meta
257
272
  #
258
273
  # @return [Capybara::Node::Element] The element
259
274
  def send_keys(*args)
@@ -263,7 +278,7 @@ module Capybara
263
278
 
264
279
  ##
265
280
  #
266
- # Hover on the Element
281
+ # Hover on the Element.
267
282
  #
268
283
  # @return [Capybara::Node::Element] The element
269
284
  def hover
@@ -276,7 +291,8 @@ module Capybara
276
291
  # @return [String] The tag name of the element
277
292
  #
278
293
  def tag_name
279
- synchronize { base.tag_name }
294
+ # Element type is immutable so cache it
295
+ @tag_name ||= initial_cache[:tag_name] || synchronize { base.tag_name }
280
296
  end
281
297
 
282
298
  ##
@@ -290,6 +306,17 @@ module Capybara
290
306
  synchronize { base.visible? }
291
307
  end
292
308
 
309
+ ##
310
+ #
311
+ # Whether or not the element is currently in the viewport and it (or descendants)
312
+ # would be considered clickable at the elements center point.
313
+ #
314
+ # @return [Boolean] Whether the elements center is obscured.
315
+ #
316
+ def obscured?
317
+ synchronize { base.obscured? }
318
+ end
319
+
293
320
  ##
294
321
  #
295
322
  # Whether or not the element is checked.
@@ -342,7 +369,7 @@ module Capybara
342
369
 
343
370
  ##
344
371
  #
345
- # An XPath expression describing where on the page the element can be found
372
+ # An XPath expression describing where on the page the element can be found.
346
373
  #
347
374
  # @return [String] An XPath expression
348
375
  #
@@ -350,6 +377,10 @@ module Capybara
350
377
  synchronize { base.path }
351
378
  end
352
379
 
380
+ def rect
381
+ synchronize { base.rect }
382
+ end
383
+
353
384
  ##
354
385
  #
355
386
  # Trigger any event on the current element, for example mouseover or focus
@@ -374,26 +405,54 @@ module Capybara
374
405
  # source.drag_to(target)
375
406
  #
376
407
  # @param [Capybara::Node::Element] node The element to drag to
408
+ # @param [Hash] options Driver specific options for dragging. May not be supported by all drivers.
409
+ # @option options [Numeric] :delay (0.05) When using Chrome/Firefox with Selenium and HTML5 dragging this is the number
410
+ # of seconds between each stage of the drag.
411
+ # @option options [Boolean] :html5 When using Chrome/Firefox with Selenium enables to force the use of HTML5
412
+ # (true) or legacy (false) dragging. If not specified the driver will attempt to
413
+ # detect the correct method to use.
414
+ # @option options [Array<Symbol>,Symbol] :drop_modifiers Modifier keys which should be held while the dragged element is dropped.
415
+ #
416
+ #
417
+ # @return [Capybara::Node::Element] The dragged element
418
+ def drag_to(node, **options)
419
+ synchronize { base.drag_to(node.base, **options) }
420
+ self
421
+ end
422
+
423
+ ##
424
+ #
425
+ # Drop items on the current element.
426
+ #
427
+ # target = page.find('#foo')
428
+ # target.drop('/some/path/file.csv')
429
+ #
430
+ # @overload drop(path, ...)
431
+ # @param [String, #to_path] path Location of the file to drop on the element
432
+ #
433
+ # @overload drop(strings, ...)
434
+ # @param [Hash] strings A hash of type to data to be dropped - `{ "text/url" => "https://www.google.com" }`
377
435
  #
378
436
  # @return [Capybara::Node::Element] The element
379
- def drag_to(node)
380
- synchronize { base.drag_to(node.base) }
437
+ def drop(*args)
438
+ options = args.map { |arg| arg.respond_to?(:to_path) ? arg.to_path : arg }
439
+ synchronize { base.drop(*options) }
381
440
  self
382
441
  end
383
442
 
384
443
  ##
385
444
  #
386
- # Scroll the page or element
445
+ # Scroll the page or element.
387
446
  #
388
- # Scroll the page or element to its top, bottom or middle
389
447
  # @overload scroll_to(position, offset: [0,0])
448
+ # Scroll the page or element to its top, bottom or middle.
390
449
  # @param [:top, :bottom, :center, :current] position
391
- # @param :offset
450
+ # @param [[Integer, Integer]] offset
392
451
  #
393
- # Scroll the page or current element until the given element is aligned at the top, bottom, or center of it
394
452
  # @overload scroll_to(element, align: :top)
453
+ # Scroll the page or current element until the given element is aligned at the top, bottom, or center of it.
395
454
  # @param [Capybara::Node::Element] element The element to be scrolled into view
396
- # @param [:top, :bottom, :center] :align Where to align the element being scrolled into view with relation to the current page/element if possible
455
+ # @param [:top, :bottom, :center] align Where to align the element being scrolled into view with relation to the current page/element if possible
397
456
  #
398
457
  # @overload scroll_to(x,y)
399
458
  # @param [Integer] x Horizontal scroll offset
@@ -413,14 +472,25 @@ module Capybara
413
472
  self
414
473
  end
415
474
 
475
+ ##
476
+ #
477
+ # Return the shadow_root for the current element
478
+ #
479
+ # @return [Capybara::Node::Element] The shadow root
480
+
481
+ def shadow_root
482
+ root = synchronize { base.shadow_root }
483
+ root && Capybara::Node::Element.new(session, root, nil, nil)
484
+ end
485
+
416
486
  ##
417
487
  #
418
488
  # Execute the given JS in the context of the element not returning a result. This is useful for scripts that return
419
- # complex objects, such as jQuery statements. +execute_script+ should be used over
420
- # +evaluate_script+ whenever possible. `this` in the script will refer to the element this is called on.
489
+ # complex objects, such as jQuery statements. {#execute_script} should be used over
490
+ # {#evaluate_script} whenever a result is not expected or needed. `this` in the script will refer to the element this is called on.
421
491
  #
422
492
  # @param [String] script A string of JavaScript to execute
423
- # @param args Optional arguments that will be passed to the script. Driver support for this is optional and types of objects supported may differ between drivers
493
+ # @param args Optional arguments that will be passed to the script. Driver support for this is optional and types of objects supported may differ between drivers
424
494
  #
425
495
  def execute_script(script, *args)
426
496
  session.execute_script(<<~JS, self, *args)
@@ -433,7 +503,7 @@ module Capybara
433
503
  ##
434
504
  #
435
505
  # Evaluate the given JS in the context of the element and return the result. Be careful when using this with
436
- # scripts that return complex objects, such as jQuery statements. +execute_script+ might
506
+ # scripts that return complex objects, such as jQuery statements. {#execute_script} might
437
507
  # be a better alternative. `this` in the script will refer to the element this is called on.
438
508
  #
439
509
  # @param [String] script A string of JavaScript to evaluate
@@ -451,7 +521,7 @@ module Capybara
451
521
  #
452
522
  # Evaluate the given JavaScript in the context of the element and obtain the result from a
453
523
  # callback function which will be passed as the last argument to the script. `this` in the
454
- # script will refer to the element this is called on
524
+ # script will refer to the element this is called on.
455
525
  #
456
526
  # @param [String] script A string of JavaScript to evaluate
457
527
  # @return [Object] The result of the evaluated JavaScript (may be driver specific)
@@ -464,25 +534,51 @@ module Capybara
464
534
  JS
465
535
  end
466
536
 
537
+ ##
538
+ #
539
+ # Toggle the elements background color between white and black for a period of time.
540
+ #
541
+ # @return [Capybara::Node::Element] The element
542
+ def flash
543
+ execute_script(<<~JS, 100)
544
+ async function flash(el, delay){
545
+ var old_bg = el.style.backgroundColor;
546
+ var colors = ["black", "white"];
547
+ for(var i=0; i<20; i++){
548
+ el.style.backgroundColor = colors[i % colors.length];
549
+ await new Promise(resolve => setTimeout(resolve, delay));
550
+ }
551
+ el.style.backgroundColor = old_bg;
552
+ }
553
+ flash(this, arguments[0]);
554
+ JS
555
+
556
+ self
557
+ end
558
+
559
+ # @api private
467
560
  def reload
468
- if @allow_reload
469
- begin
470
- reloaded = query_scope.reload.first(@query.name, @query.locator, @query.options)
471
- @base = reloaded.base if reloaded
472
- rescue StandardError => err
473
- raise err unless catch_error?(err)
474
- end
561
+ return self unless @allow_reload
562
+
563
+ begin
564
+ reloaded = @query.resolve_for(query_scope ? query_scope.reload : session)[@query_idx.to_i]
565
+ @base = reloaded.base if reloaded
566
+ rescue StandardError => e
567
+ raise e unless catch_error?(e)
475
568
  end
476
569
  self
477
570
  end
478
571
 
572
+ ##
573
+ #
574
+ # A human-readable representation of the element.
575
+ #
576
+ # @return [String] A string representation
479
577
  def inspect
480
578
  %(#<Capybara::Node::Element tag="#{base.tag_name}" path="#{base.path}">)
481
579
  rescue NotSupportedByDriverError
482
580
  %(#<Capybara::Node::Element tag="#{base.tag_name}">)
483
- rescue StandardError => err
484
- raise unless session.driver.invalid_element_errors.any? { |et| err.is_a?(et) }
485
-
581
+ rescue *session.driver.invalid_element_errors
486
582
  %(Obsolete #<Capybara::Node::Element>)
487
583
  end
488
584
 
@@ -502,6 +598,16 @@ module Capybara
502
598
  return result;
503
599
  }).apply(this, arguments)
504
600
  JS
601
+
602
+ private
603
+
604
+ def perform_click_action(keys, wait: nil, **options)
605
+ raise ArgumentError, 'You must specify both x: and y: for a click offset' if nil ^ options[:x] ^ options[:y]
606
+
607
+ options[:offset] ||= :center if session_options.w3c_click_offset
608
+ synchronize(wait) { yield keys, options }
609
+ self
610
+ end
505
611
  end
506
612
  end
507
613
  end