capybara 3.3.0 → 3.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (308) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -0
  3. data/History.md +803 -13
  4. data/License.txt +1 -1
  5. data/README.md +257 -84
  6. data/lib/capybara/config.rb +25 -9
  7. data/lib/capybara/cucumber.rb +1 -1
  8. data/lib/capybara/driver/base.rb +17 -3
  9. data/lib/capybara/driver/node.rb +31 -6
  10. data/lib/capybara/dsl.rb +9 -7
  11. data/lib/capybara/helpers.rb +31 -7
  12. data/lib/capybara/minitest/spec.rb +180 -88
  13. data/lib/capybara/minitest.rb +262 -149
  14. data/lib/capybara/node/actions.rb +202 -116
  15. data/lib/capybara/node/base.rb +34 -19
  16. data/lib/capybara/node/document.rb +14 -2
  17. data/lib/capybara/node/document_matchers.rb +10 -12
  18. data/lib/capybara/node/element.rb +269 -115
  19. data/lib/capybara/node/finders.rb +99 -77
  20. data/lib/capybara/node/matchers.rb +327 -151
  21. data/lib/capybara/node/simple.rb +48 -13
  22. data/lib/capybara/node/whitespace_normalizer.rb +81 -0
  23. data/lib/capybara/queries/active_element_query.rb +18 -0
  24. data/lib/capybara/queries/ancestor_query.rb +8 -9
  25. data/lib/capybara/queries/base_query.rb +23 -16
  26. data/lib/capybara/queries/current_path_query.rb +16 -6
  27. data/lib/capybara/queries/match_query.rb +1 -0
  28. data/lib/capybara/queries/selector_query.rb +587 -130
  29. data/lib/capybara/queries/sibling_query.rb +8 -6
  30. data/lib/capybara/queries/style_query.rb +6 -2
  31. data/lib/capybara/queries/text_query.rb +28 -14
  32. data/lib/capybara/queries/title_query.rb +2 -2
  33. data/lib/capybara/rack_test/browser.rb +92 -25
  34. data/lib/capybara/rack_test/driver.rb +16 -7
  35. data/lib/capybara/rack_test/errors.rb +6 -0
  36. data/lib/capybara/rack_test/form.rb +68 -41
  37. data/lib/capybara/rack_test/node.rb +106 -39
  38. data/lib/capybara/rails.rb +1 -1
  39. data/lib/capybara/registration_container.rb +41 -0
  40. data/lib/capybara/registrations/drivers.rb +42 -0
  41. data/lib/capybara/registrations/patches/puma_ssl.rb +29 -0
  42. data/lib/capybara/registrations/servers.rb +66 -0
  43. data/lib/capybara/result.rb +75 -52
  44. data/lib/capybara/rspec/features.rb +7 -7
  45. data/lib/capybara/rspec/matcher_proxies.rb +39 -18
  46. data/lib/capybara/rspec/matchers/base.rb +113 -0
  47. data/lib/capybara/rspec/matchers/become_closed.rb +33 -0
  48. data/lib/capybara/rspec/matchers/compound.rb +88 -0
  49. data/lib/capybara/rspec/matchers/count_sugar.rb +37 -0
  50. data/lib/capybara/rspec/matchers/have_ancestor.rb +28 -0
  51. data/lib/capybara/rspec/matchers/have_current_path.rb +29 -0
  52. data/lib/capybara/rspec/matchers/have_selector.rb +69 -0
  53. data/lib/capybara/rspec/matchers/have_sibling.rb +27 -0
  54. data/lib/capybara/rspec/matchers/have_text.rb +33 -0
  55. data/lib/capybara/rspec/matchers/have_title.rb +29 -0
  56. data/lib/capybara/rspec/matchers/match_selector.rb +27 -0
  57. data/lib/capybara/rspec/matchers/match_style.rb +43 -0
  58. data/lib/capybara/rspec/matchers/spatial_sugar.rb +39 -0
  59. data/lib/capybara/rspec/matchers.rb +141 -339
  60. data/lib/capybara/rspec.rb +2 -0
  61. data/lib/capybara/selector/builders/css_builder.rb +84 -0
  62. data/lib/capybara/selector/builders/xpath_builder.rb +71 -0
  63. data/lib/capybara/selector/css.rb +27 -25
  64. data/lib/capybara/selector/definition/button.rb +68 -0
  65. data/lib/capybara/selector/definition/checkbox.rb +26 -0
  66. data/lib/capybara/selector/definition/css.rb +10 -0
  67. data/lib/capybara/selector/definition/datalist_input.rb +35 -0
  68. data/lib/capybara/selector/definition/datalist_option.rb +25 -0
  69. data/lib/capybara/selector/definition/element.rb +28 -0
  70. data/lib/capybara/selector/definition/field.rb +40 -0
  71. data/lib/capybara/selector/definition/fieldset.rb +14 -0
  72. data/lib/capybara/selector/definition/file_field.rb +13 -0
  73. data/lib/capybara/selector/definition/fillable_field.rb +33 -0
  74. data/lib/capybara/selector/definition/frame.rb +17 -0
  75. data/lib/capybara/selector/definition/id.rb +6 -0
  76. data/lib/capybara/selector/definition/label.rb +62 -0
  77. data/lib/capybara/selector/definition/link.rb +55 -0
  78. data/lib/capybara/selector/definition/link_or_button.rb +16 -0
  79. data/lib/capybara/selector/definition/option.rb +27 -0
  80. data/lib/capybara/selector/definition/radio_button.rb +27 -0
  81. data/lib/capybara/selector/definition/select.rb +81 -0
  82. data/lib/capybara/selector/definition/table.rb +109 -0
  83. data/lib/capybara/selector/definition/table_row.rb +21 -0
  84. data/lib/capybara/selector/definition/xpath.rb +5 -0
  85. data/lib/capybara/selector/definition.rb +280 -0
  86. data/lib/capybara/selector/filter.rb +1 -0
  87. data/lib/capybara/selector/filter_set.rb +73 -25
  88. data/lib/capybara/selector/filters/base.rb +24 -5
  89. data/lib/capybara/selector/filters/expression_filter.rb +3 -3
  90. data/lib/capybara/selector/filters/locator_filter.rb +29 -0
  91. data/lib/capybara/selector/filters/node_filter.rb +16 -2
  92. data/lib/capybara/selector/regexp_disassembler.rb +211 -0
  93. data/lib/capybara/selector/selector.rb +85 -348
  94. data/lib/capybara/selector/xpath_extensions.rb +17 -0
  95. data/lib/capybara/selector.rb +474 -447
  96. data/lib/capybara/selenium/atoms/getAttribute.min.js +1 -0
  97. data/lib/capybara/selenium/atoms/isDisplayed.min.js +1 -0
  98. data/lib/capybara/selenium/atoms/src/getAttribute.js +161 -0
  99. data/lib/capybara/selenium/atoms/src/isDisplayed.js +454 -0
  100. data/lib/capybara/selenium/driver.rb +255 -143
  101. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +93 -11
  102. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +128 -0
  103. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +84 -0
  104. data/lib/capybara/selenium/driver_specializations/internet_explorer_driver.rb +26 -0
  105. data/lib/capybara/selenium/driver_specializations/safari_driver.rb +24 -0
  106. data/lib/capybara/selenium/extensions/file_input_click_emulation.rb +34 -0
  107. data/lib/capybara/selenium/extensions/find.rb +110 -0
  108. data/lib/capybara/selenium/extensions/html5_drag.rb +229 -0
  109. data/lib/capybara/selenium/extensions/modifier_keys_stack.rb +28 -0
  110. data/lib/capybara/selenium/extensions/scroll.rb +76 -0
  111. data/lib/capybara/selenium/node.rb +436 -134
  112. data/lib/capybara/selenium/nodes/chrome_node.rb +125 -0
  113. data/lib/capybara/selenium/nodes/edge_node.rb +110 -0
  114. data/lib/capybara/selenium/nodes/firefox_node.rb +136 -0
  115. data/lib/capybara/selenium/nodes/ie_node.rb +22 -0
  116. data/lib/capybara/selenium/nodes/safari_node.rb +118 -0
  117. data/lib/capybara/selenium/patches/atoms.rb +18 -0
  118. data/lib/capybara/selenium/patches/is_displayed.rb +16 -0
  119. data/lib/capybara/selenium/patches/logs.rb +45 -0
  120. data/lib/capybara/selenium/patches/pause_duration_fix.rb +9 -0
  121. data/lib/capybara/selenium/patches/persistent_client.rb +20 -0
  122. data/lib/capybara/server/animation_disabler.rb +56 -19
  123. data/lib/capybara/server/checker.rb +9 -3
  124. data/lib/capybara/server/middleware.rb +28 -12
  125. data/lib/capybara/server.rb +33 -10
  126. data/lib/capybara/session/config.rb +34 -10
  127. data/lib/capybara/session/matchers.rb +23 -16
  128. data/lib/capybara/session.rb +230 -170
  129. data/lib/capybara/spec/public/jquery.js +5 -5
  130. data/lib/capybara/spec/public/offset.js +6 -0
  131. data/lib/capybara/spec/public/test.js +121 -8
  132. data/lib/capybara/spec/session/accept_alert_spec.rb +11 -11
  133. data/lib/capybara/spec/session/accept_confirm_spec.rb +3 -3
  134. data/lib/capybara/spec/session/accept_prompt_spec.rb +9 -10
  135. data/lib/capybara/spec/session/active_element_spec.rb +31 -0
  136. data/lib/capybara/spec/session/all_spec.rb +127 -40
  137. data/lib/capybara/spec/session/ancestor_spec.rb +24 -19
  138. data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +67 -38
  139. data/lib/capybara/spec/session/assert_current_path_spec.rb +21 -18
  140. data/lib/capybara/spec/session/assert_selector_spec.rb +52 -58
  141. data/lib/capybara/spec/session/assert_style_spec.rb +7 -7
  142. data/lib/capybara/spec/session/assert_text_spec.rb +74 -50
  143. data/lib/capybara/spec/session/assert_title_spec.rb +12 -12
  144. data/lib/capybara/spec/session/attach_file_spec.rb +126 -72
  145. data/lib/capybara/spec/session/body_spec.rb +6 -6
  146. data/lib/capybara/spec/session/check_spec.rb +102 -47
  147. data/lib/capybara/spec/session/choose_spec.rb +58 -32
  148. data/lib/capybara/spec/session/click_button_spec.rb +219 -163
  149. data/lib/capybara/spec/session/click_link_or_button_spec.rb +49 -23
  150. data/lib/capybara/spec/session/click_link_spec.rb +77 -54
  151. data/lib/capybara/spec/session/current_scope_spec.rb +8 -8
  152. data/lib/capybara/spec/session/current_url_spec.rb +38 -29
  153. data/lib/capybara/spec/session/dismiss_confirm_spec.rb +3 -3
  154. data/lib/capybara/spec/session/dismiss_prompt_spec.rb +2 -2
  155. data/lib/capybara/spec/session/element/assert_match_selector_spec.rb +8 -8
  156. data/lib/capybara/spec/session/element/match_css_spec.rb +16 -10
  157. data/lib/capybara/spec/session/element/match_xpath_spec.rb +6 -6
  158. data/lib/capybara/spec/session/element/matches_selector_spec.rb +68 -56
  159. data/lib/capybara/spec/session/evaluate_async_script_spec.rb +7 -7
  160. data/lib/capybara/spec/session/evaluate_script_spec.rb +28 -8
  161. data/lib/capybara/spec/session/execute_script_spec.rb +8 -7
  162. data/lib/capybara/spec/session/fill_in_spec.rb +101 -46
  163. data/lib/capybara/spec/session/find_button_spec.rb +23 -23
  164. data/lib/capybara/spec/session/find_by_id_spec.rb +7 -7
  165. data/lib/capybara/spec/session/find_field_spec.rb +32 -30
  166. data/lib/capybara/spec/session/find_link_spec.rb +31 -21
  167. data/lib/capybara/spec/session/find_spec.rb +244 -141
  168. data/lib/capybara/spec/session/first_spec.rb +43 -43
  169. data/lib/capybara/spec/session/frame/frame_title_spec.rb +5 -5
  170. data/lib/capybara/spec/session/frame/frame_url_spec.rb +5 -5
  171. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +30 -18
  172. data/lib/capybara/spec/session/frame/within_frame_spec.rb +45 -18
  173. data/lib/capybara/spec/session/go_back_spec.rb +1 -1
  174. data/lib/capybara/spec/session/go_forward_spec.rb +1 -1
  175. data/lib/capybara/spec/session/has_all_selectors_spec.rb +23 -23
  176. data/lib/capybara/spec/session/has_ancestor_spec.rb +46 -0
  177. data/lib/capybara/spec/session/has_any_selectors_spec.rb +29 -0
  178. data/lib/capybara/spec/session/has_button_spec.rb +94 -13
  179. data/lib/capybara/spec/session/has_css_spec.rb +272 -132
  180. data/lib/capybara/spec/session/has_current_path_spec.rb +50 -35
  181. data/lib/capybara/spec/session/has_element_spec.rb +47 -0
  182. data/lib/capybara/spec/session/has_field_spec.rb +137 -58
  183. data/lib/capybara/spec/session/has_link_spec.rb +44 -4
  184. data/lib/capybara/spec/session/has_none_selectors_spec.rb +31 -31
  185. data/lib/capybara/spec/session/has_select_spec.rb +84 -50
  186. data/lib/capybara/spec/session/has_selector_spec.rb +111 -71
  187. data/lib/capybara/spec/session/has_sibling_spec.rb +50 -0
  188. data/lib/capybara/spec/session/has_table_spec.rb +181 -4
  189. data/lib/capybara/spec/session/has_text_spec.rb +101 -53
  190. data/lib/capybara/spec/session/has_title_spec.rb +19 -14
  191. data/lib/capybara/spec/session/has_xpath_spec.rb +56 -38
  192. data/lib/capybara/spec/session/headers_spec.rb +1 -1
  193. data/lib/capybara/spec/session/html_spec.rb +13 -6
  194. data/lib/capybara/spec/session/matches_style_spec.rb +37 -0
  195. data/lib/capybara/spec/session/node_spec.rb +894 -142
  196. data/lib/capybara/spec/session/node_wrapper_spec.rb +10 -7
  197. data/lib/capybara/spec/session/refresh_spec.rb +9 -7
  198. data/lib/capybara/spec/session/reset_session_spec.rb +63 -35
  199. data/lib/capybara/spec/session/response_code_spec.rb +1 -1
  200. data/lib/capybara/spec/session/save_and_open_page_spec.rb +2 -2
  201. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +2 -2
  202. data/lib/capybara/spec/session/save_page_spec.rb +37 -37
  203. data/lib/capybara/spec/session/save_screenshot_spec.rb +10 -10
  204. data/lib/capybara/spec/session/screenshot_spec.rb +2 -2
  205. data/lib/capybara/spec/session/scroll_spec.rb +119 -0
  206. data/lib/capybara/spec/session/select_spec.rb +85 -85
  207. data/lib/capybara/spec/session/selectors_spec.rb +49 -18
  208. data/lib/capybara/spec/session/sibling_spec.rb +9 -9
  209. data/lib/capybara/spec/session/text_spec.rb +25 -24
  210. data/lib/capybara/spec/session/title_spec.rb +7 -6
  211. data/lib/capybara/spec/session/uncheck_spec.rb +33 -21
  212. data/lib/capybara/spec/session/unselect_spec.rb +37 -37
  213. data/lib/capybara/spec/session/visit_spec.rb +68 -49
  214. data/lib/capybara/spec/session/window/become_closed_spec.rb +20 -17
  215. data/lib/capybara/spec/session/window/current_window_spec.rb +1 -1
  216. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +20 -16
  217. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +6 -2
  218. data/lib/capybara/spec/session/window/window_spec.rb +62 -63
  219. data/lib/capybara/spec/session/window/windows_spec.rb +5 -1
  220. data/lib/capybara/spec/session/window/within_window_spec.rb +14 -14
  221. data/lib/capybara/spec/session/within_spec.rb +79 -42
  222. data/lib/capybara/spec/spec_helper.rb +41 -53
  223. data/lib/capybara/spec/test_app.rb +132 -43
  224. data/lib/capybara/spec/views/animated.erb +49 -0
  225. data/lib/capybara/spec/views/form.erb +139 -42
  226. data/lib/capybara/spec/views/frame_child.erb +4 -3
  227. data/lib/capybara/spec/views/frame_one.erb +2 -1
  228. data/lib/capybara/spec/views/frame_parent.erb +1 -1
  229. data/lib/capybara/spec/views/frame_two.erb +1 -1
  230. data/lib/capybara/spec/views/initial_alert.erb +2 -1
  231. data/lib/capybara/spec/views/layout.erb +10 -0
  232. data/lib/capybara/spec/views/obscured.erb +47 -0
  233. data/lib/capybara/spec/views/offset.erb +33 -0
  234. data/lib/capybara/spec/views/path.erb +2 -2
  235. data/lib/capybara/spec/views/popup_one.erb +1 -1
  236. data/lib/capybara/spec/views/popup_two.erb +1 -1
  237. data/lib/capybara/spec/views/react.erb +45 -0
  238. data/lib/capybara/spec/views/scroll.erb +21 -0
  239. data/lib/capybara/spec/views/spatial.erb +31 -0
  240. data/lib/capybara/spec/views/tables.erb +67 -0
  241. data/lib/capybara/spec/views/with_animation.erb +39 -4
  242. data/lib/capybara/spec/views/with_base_tag.erb +2 -2
  243. data/lib/capybara/spec/views/with_dragula.erb +24 -0
  244. data/lib/capybara/spec/views/with_fixed_header_footer.erb +2 -1
  245. data/lib/capybara/spec/views/with_hover.erb +3 -2
  246. data/lib/capybara/spec/views/with_hover1.erb +10 -0
  247. data/lib/capybara/spec/views/with_html.erb +37 -9
  248. data/lib/capybara/spec/views/with_html5_svg.erb +20 -0
  249. data/lib/capybara/spec/views/with_jquery_animation.erb +24 -0
  250. data/lib/capybara/spec/views/with_js.erb +26 -5
  251. data/lib/capybara/spec/views/with_jstree.erb +26 -0
  252. data/lib/capybara/spec/views/with_namespace.erb +1 -0
  253. data/lib/capybara/spec/views/with_scope.erb +2 -2
  254. data/lib/capybara/spec/views/with_scope_other.erb +6 -0
  255. data/lib/capybara/spec/views/with_shadow.erb +31 -0
  256. data/lib/capybara/spec/views/with_slow_unload.erb +2 -1
  257. data/lib/capybara/spec/views/with_sortable_js.erb +21 -0
  258. data/lib/capybara/spec/views/with_unload_alert.erb +1 -0
  259. data/lib/capybara/spec/views/with_windows.erb +1 -1
  260. data/lib/capybara/spec/views/within_frames.erb +1 -1
  261. data/lib/capybara/version.rb +1 -1
  262. data/lib/capybara/window.rb +19 -25
  263. data/lib/capybara.rb +126 -111
  264. data/spec/basic_node_spec.rb +59 -34
  265. data/spec/capybara_spec.rb +56 -44
  266. data/spec/counter_spec.rb +35 -0
  267. data/spec/css_builder_spec.rb +101 -0
  268. data/spec/css_splitter_spec.rb +8 -8
  269. data/spec/dsl_spec.rb +79 -52
  270. data/spec/filter_set_spec.rb +9 -9
  271. data/spec/fixtures/selenium_driver_rspec_failure.rb +4 -4
  272. data/spec/fixtures/selenium_driver_rspec_success.rb +4 -4
  273. data/spec/minitest_spec.rb +45 -7
  274. data/spec/minitest_spec_spec.rb +87 -64
  275. data/spec/per_session_config_spec.rb +6 -6
  276. data/spec/rack_test_spec.rb +172 -116
  277. data/spec/regexp_dissassembler_spec.rb +250 -0
  278. data/spec/result_spec.rb +80 -72
  279. data/spec/rspec/features_spec.rb +21 -16
  280. data/spec/rspec/scenarios_spec.rb +10 -6
  281. data/spec/rspec/shared_spec_matchers.rb +407 -365
  282. data/spec/rspec/views_spec.rb +3 -3
  283. data/spec/rspec_matchers_spec.rb +35 -10
  284. data/spec/rspec_spec.rb +63 -41
  285. data/spec/sauce_spec_chrome.rb +43 -0
  286. data/spec/selector_spec.rb +334 -89
  287. data/spec/selenium_spec_chrome.rb +176 -62
  288. data/spec/selenium_spec_chrome_remote.rb +54 -14
  289. data/spec/selenium_spec_edge.rb +41 -8
  290. data/spec/selenium_spec_firefox.rb +228 -0
  291. data/spec/selenium_spec_firefox_remote.rb +94 -0
  292. data/spec/selenium_spec_ie.rb +129 -11
  293. data/spec/selenium_spec_safari.rb +162 -0
  294. data/spec/server_spec.rb +171 -97
  295. data/spec/session_spec.rb +34 -18
  296. data/spec/shared_selenium_node.rb +79 -0
  297. data/spec/shared_selenium_session.rb +344 -80
  298. data/spec/spec_helper.rb +124 -2
  299. data/spec/whitespace_normalizer_spec.rb +54 -0
  300. data/spec/xpath_builder_spec.rb +93 -0
  301. metadata +326 -28
  302. data/lib/capybara/rspec/compound.rb +0 -94
  303. data/lib/capybara/selenium/driver_specializations/marionette_driver.rb +0 -31
  304. data/lib/capybara/selenium/nodes/marionette_node.rb +0 -31
  305. data/lib/capybara/spec/session/has_style_spec.rb +0 -25
  306. data/lib/capybara/spec/session/source_spec.rb +0 -0
  307. data/lib/capybara/spec/views/with_title.erb +0 -5
  308. data/spec/selenium_spec_marionette.rb +0 -167
@@ -17,7 +17,9 @@ module Capybara
17
17
  # @return [true]
18
18
  #
19
19
  def assert_title(title, **options)
20
- _verify_title(title, options) { |query| raise Capybara::ExpectationNotMet, query.failure_message unless query.resolves_for?(self) }
20
+ _verify_title(title, options) do |query|
21
+ raise Capybara::ExpectationNotMet, query.failure_message unless query.resolves_for?(self)
22
+ end
21
23
  end
22
24
 
23
25
  ##
@@ -28,7 +30,9 @@ module Capybara
28
30
  # @return [true]
29
31
  #
30
32
  def assert_no_title(title, **options)
31
- _verify_title(title, options) { |query| raise Capybara::ExpectationNotMet, query.negative_failure_message if query.resolves_for?(self) }
33
+ _verify_title(title, options) do |query|
34
+ raise Capybara::ExpectationNotMet, query.negative_failure_message if query.resolves_for?(self)
35
+ end
32
36
  end
33
37
 
34
38
  ##
@@ -38,9 +42,7 @@ module Capybara
38
42
  # @return [Boolean]
39
43
  #
40
44
  def has_title?(title, **options)
41
- assert_title(title, options)
42
- rescue Capybara::ExpectationNotMet
43
- false
45
+ make_predicate(options) { assert_title(title, **options) }
44
46
  end
45
47
 
46
48
  ##
@@ -50,18 +52,14 @@ module Capybara
50
52
  # @return [Boolean]
51
53
  #
52
54
  def has_no_title?(title, **options)
53
- assert_no_title(title, options)
54
- rescue Capybara::ExpectationNotMet
55
- false
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)
62
- synchronize(query.wait) do
63
- yield(query)
64
- end
61
+ query = Capybara::Queries::TitleQuery.new(title, **options)
62
+ synchronize(query.wait) { yield(query) }
65
63
  true
66
64
  end
67
65
  end
@@ -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
@@ -53,14 +55,15 @@ module Capybara
53
55
  # @param type [:all, :visible] Whether to return only visible or all text
54
56
  # @return [String] The text of the element
55
57
  #
56
- def text(type = nil)
58
+ def text(type = nil, normalize_ws: false)
57
59
  type ||= :all unless session_options.ignore_hidden_elements || session_options.visible_text_only
58
- synchronize { type == :all ? base.all_text : base.visible_text }
60
+ txt = synchronize { type == :all ? base.all_text : base.visible_text }
61
+ normalize_ws ? txt.gsub(/[[:space:]]+/, ' ').strip : txt
59
62
  end
60
63
 
61
64
  ##
62
65
  #
63
- # Retrieve the given attribute
66
+ # Retrieve the given attribute.
64
67
  #
65
68
  # element[:title] # => HTML title attribute
66
69
  #
@@ -73,16 +76,17 @@ module Capybara
73
76
 
74
77
  ##
75
78
  #
76
- # Retrieve the given CSS styles
79
+ # Retrieve the given CSS styles.
77
80
  #
78
81
  # element.style('color', 'font-size') # => Computed values of CSS 'color' and 'font-size' styles
79
82
  #
80
- # @param [String] Names of the desired CSS properties
81
- # @return [Hash] Hash of the CSS property names to computed values
83
+ # @param [Array<String>] styles Names of the desired CSS properties
84
+ # @return [Hash] Hash of the CSS property names to computed values
82
85
  #
83
86
  def style(*styles)
84
87
  styles = styles.flatten.map(&:to_s)
85
- raise ArgumentError, "You must specify at least one CSS style" if styles.empty?
88
+ raise ArgumentError, 'You must specify at least one CSS style' if styles.empty?
89
+
86
90
  begin
87
91
  synchronize { base.style(styles) }
88
92
  rescue NotImplementedError => e
@@ -107,78 +111,98 @@ module Capybara
107
111
  # Set the value of the form element to the given value.
108
112
  #
109
113
  # @param [String] value The new value
110
- # @param [Hash{}] options Driver specific options for how to set the value. Take default values from {Capybara#default_set_options}
114
+ # @param [Hash] options Driver specific options for how to set the value. Take default values from {Capybara.configure default_set_options}.
111
115
  #
112
116
  # @return [Capybara::Node::Element] The element
113
117
  def set(value, **options)
114
- 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
121
+
115
122
  options = session_options.default_set_options.to_h.merge(options)
116
- synchronize { base.set(value, options) }
123
+ synchronize { base.set(value, **options) }
117
124
  self
118
125
  end
119
126
 
120
127
  ##
121
128
  #
122
- # 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.
123
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.
135
+ #
136
+ # @param [false, Numeric] wait
137
+ # Maximum time to wait for the action to succeed. Defaults to {Capybara.configure default_max_wait_time}.
124
138
  # @return [Capybara::Node::Element] The element
125
- def select_option
126
- warn "Attempt to select disabled option: #{value || text}" if disabled?
127
- synchronize { base.select_option }
139
+ def select_option(wait: nil)
140
+ synchronize(wait) { base.select_option }
128
141
  self
129
142
  end
130
143
 
131
144
  ##
132
145
  #
133
- # 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.
134
147
  #
148
+ # @macro action_waiting_behavior
135
149
  # @return [Capybara::Node::Element] The element
136
- def unselect_option
137
- synchronize { base.unselect_option }
150
+ def unselect_option(wait: nil)
151
+ synchronize(wait) { base.unselect_option }
138
152
  self
139
153
  end
140
154
 
141
155
  ##
142
156
  #
143
- # Click the Element
157
+ # Click the Element.
144
158
  #
159
+ # @macro action_waiting_behavior
145
160
  # @!macro click_modifiers
146
- # 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
147
- # @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)
148
163
  # @param *modifier_keys [:alt, :control, :meta, :shift] ([]) Keys to be held down when clicking
149
- # @option offset [Integer] x X coordinate to offset the click location from the top left corner of the element
150
- # @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)
151
169
  # @return [Capybara::Node::Element] The element
152
- def click(*keys, **offset)
153
- synchronize { base.click(keys, offset) }
154
- self
170
+ def click(*keys, **options)
171
+ perform_click_action(keys, **options) do |k, opts|
172
+ base.click(k, **opts)
173
+ end
155
174
  end
156
175
 
157
176
  ##
158
177
  #
159
- # Right Click the Element
178
+ # Right Click the Element.
160
179
  #
180
+ # @macro action_waiting_behavior
161
181
  # @macro click_modifiers
182
+ # @option options [Float] delay Delay between the mouse down and mouse up events in seconds (0)
162
183
  # @return [Capybara::Node::Element] The element
163
- def right_click(*keys, **offset)
164
- synchronize { base.right_click(keys, offset) }
165
- self
184
+ def right_click(*keys, **options)
185
+ perform_click_action(keys, **options) do |k, opts|
186
+ base.right_click(k, **opts)
187
+ end
166
188
  end
167
189
 
168
190
  ##
169
191
  #
170
- # Double Click the Element
192
+ # Double Click the Element.
171
193
  #
194
+ # @macro action_waiting_behavior
172
195
  # @macro click_modifiers
173
196
  # @return [Capybara::Node::Element] The element
174
- def double_click(*keys, **offset)
175
- synchronize { base.double_click(keys, offset) }
176
- self
197
+ def double_click(*keys, **options)
198
+ perform_click_action(keys, **options) do |k, opts|
199
+ base.double_click(k, **opts)
200
+ end
177
201
  end
178
202
 
179
203
  ##
180
204
  #
181
- # Send Keystrokes to the Element
205
+ # Send Keystrokes to the Element.
182
206
  #
183
207
  # @overload send_keys(keys, ...)
184
208
  # @param keys [String, Symbol, Array<String,Symbol>]
@@ -186,65 +210,65 @@ module Capybara
186
210
  # Examples:
187
211
  #
188
212
  # element.send_keys "foo" #=> value: 'foo'
189
- # element.send_keys "tet", :left, "s" #=> value: 'test'
213
+ # element.send_keys "tet", :left, "s" #=> value: 'test'
190
214
  # element.send_keys [:control, 'a'], :space #=> value: ' ' - assuming ctrl-a selects all contents
191
215
  #
192
- # Symbols supported for keys
193
- # :cancel
194
- # :help
195
- # :backspace
196
- # :tab
197
- # :clear
198
- # :return
199
- # :enter
200
- # :shift
201
- # :control
202
- # :alt
203
- # :pause
204
- # :escape
205
- # :space
206
- # :page_up
207
- # :page_down
208
- # :end
209
- # :home
210
- # :left
211
- # :up
212
- # :right
213
- # :down
214
- # :insert
215
- # :delete
216
- # :semicolon
217
- # :equals
218
- # :numpad0
219
- # :numpad1
220
- # :numpad2
221
- # :numpad3
222
- # :numpad4
223
- # :numpad5
224
- # :numpad6
225
- # :numpad7
226
- # :numpad8
227
- # :numpad9
228
- # :multiply - numeric keypad *
229
- # :add - numeric keypad +
230
- # :separator - numeric keypad 'separator' key ??
231
- # :subtract - numeric keypad -
232
- # :decimal - numeric keypad .
233
- # :divide - numeric keypad /
234
- # :f1
235
- # :f2
236
- # :f3
237
- # :f4
238
- # :f5
239
- # :f6
240
- # :f7
241
- # :f8
242
- # :f9
243
- # :f10
244
- # :f11
245
- # :f12
246
- # :meta
247
- # :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
248
272
  #
249
273
  # @return [Capybara::Node::Element] The element
250
274
  def send_keys(*args)
@@ -254,7 +278,7 @@ module Capybara
254
278
 
255
279
  ##
256
280
  #
257
- # Hover on the Element
281
+ # Hover on the Element.
258
282
  #
259
283
  # @return [Capybara::Node::Element] The element
260
284
  def hover
@@ -267,7 +291,8 @@ module Capybara
267
291
  # @return [String] The tag name of the element
268
292
  #
269
293
  def tag_name
270
- synchronize { base.tag_name }
294
+ # Element type is immutable so cache it
295
+ @tag_name ||= initial_cache[:tag_name] || synchronize { base.tag_name }
271
296
  end
272
297
 
273
298
  ##
@@ -281,6 +306,17 @@ module Capybara
281
306
  synchronize { base.visible? }
282
307
  end
283
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
+
284
320
  ##
285
321
  #
286
322
  # Whether or not the element is checked.
@@ -333,7 +369,7 @@ module Capybara
333
369
 
334
370
  ##
335
371
  #
336
- # 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.
337
373
  #
338
374
  # @return [String] An XPath expression
339
375
  #
@@ -341,10 +377,16 @@ module Capybara
341
377
  synchronize { base.path }
342
378
  end
343
379
 
380
+ def rect
381
+ synchronize { base.rect }
382
+ end
383
+
344
384
  ##
345
385
  #
346
386
  # Trigger any event on the current element, for example mouseover or focus
347
- # events. Does not work in Selenium.
387
+ # events. Not supported with the Selenium driver, and SHOULDN'T BE USED IN TESTING unless you
388
+ # fully understand why you're using it, that it can allow actions a user could never
389
+ # perform, and that it may completely invalidate your test.
348
390
  #
349
391
  # @param [String] event The name of the event to trigger
350
392
  #
@@ -363,21 +405,92 @@ module Capybara
363
405
  # source.drag_to(target)
364
406
  #
365
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" }`
435
+ #
436
+ # @return [Capybara::Node::Element] The element
437
+ def drop(*args)
438
+ options = args.map { |arg| arg.respond_to?(:to_path) ? arg.to_path : arg }
439
+ synchronize { base.drop(*options) }
440
+ self
441
+ end
442
+
443
+ ##
444
+ #
445
+ # Scroll the page or element.
446
+ #
447
+ # @overload scroll_to(position, offset: [0,0])
448
+ # Scroll the page or element to its top, bottom or middle.
449
+ # @param [:top, :bottom, :center, :current] position
450
+ # @param [[Integer, Integer]] offset
451
+ #
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.
454
+ # @param [Capybara::Node::Element] element The element to be scrolled into view
455
+ # @param [:top, :bottom, :center] align Where to align the element being scrolled into view with relation to the current page/element if possible
456
+ #
457
+ # @overload scroll_to(x,y)
458
+ # @param [Integer] x Horizontal scroll offset
459
+ # @param [Integer] y Vertical scroll offset
366
460
  #
367
461
  # @return [Capybara::Node::Element] The element
368
- def drag_to(node)
369
- synchronize { base.drag_to(node.base) }
462
+ def scroll_to(pos_or_el_or_x, y = nil, align: :top, offset: nil)
463
+ case pos_or_el_or_x
464
+ when Symbol
465
+ synchronize { base.scroll_to(nil, pos_or_el_or_x) } unless pos_or_el_or_x == :current
466
+ when Capybara::Node::Element
467
+ synchronize { base.scroll_to(pos_or_el_or_x.base, align) }
468
+ else
469
+ synchronize { base.scroll_to(nil, nil, [pos_or_el_or_x, y]) }
470
+ end
471
+ synchronize { base.scroll_by(*offset) } unless offset.nil?
370
472
  self
371
473
  end
372
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
+
373
486
  ##
374
487
  #
375
488
  # Execute the given JS in the context of the element not returning a result. This is useful for scripts that return
376
- # complex objects, such as jQuery statements. +execute_script+ should be used over
377
- # +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.
378
491
  #
379
492
  # @param [String] script A string of JavaScript to execute
380
- # @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
381
494
  #
382
495
  def execute_script(script, *args)
383
496
  session.execute_script(<<~JS, self, *args)
@@ -390,7 +503,7 @@ module Capybara
390
503
  ##
391
504
  #
392
505
  # Evaluate the given JS in the context of the element and return the result. Be careful when using this with
393
- # 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
394
507
  # be a better alternative. `this` in the script will refer to the element this is called on.
395
508
  #
396
509
  # @param [String] script A string of JavaScript to evaluate
@@ -399,7 +512,7 @@ module Capybara
399
512
  def evaluate_script(script, *args)
400
513
  session.evaluate_script(<<~JS, self, *args)
401
514
  (function(){
402
- return #{script}
515
+ return #{script.strip}
403
516
  }).apply(arguments[0], Array.prototype.slice.call(arguments,1));
404
517
  JS
405
518
  end
@@ -408,7 +521,7 @@ module Capybara
408
521
  #
409
522
  # Evaluate the given JavaScript in the context of the element and obtain the result from a
410
523
  # callback function which will be passed as the last argument to the script. `this` in the
411
- # script will refer to the element this is called on
524
+ # script will refer to the element this is called on.
412
525
  #
413
526
  # @param [String] script A string of JavaScript to evaluate
414
527
  # @return [Object] The result of the evaluated JavaScript (may be driver specific)
@@ -421,28 +534,59 @@ module Capybara
421
534
  JS
422
535
  end
423
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
424
560
  def reload
425
- if @allow_reload
426
- begin
427
- reloaded = query_scope.reload.first(@query.name, @query.locator, @query.options)
428
- @base = reloaded.base if reloaded
429
- rescue StandardError => e
430
- raise e unless catch_error?(e)
431
- 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)
432
568
  end
433
569
  self
434
570
  end
435
571
 
572
+ ##
573
+ #
574
+ # A human-readable representation of the element.
575
+ #
576
+ # @return [String] A string representation
436
577
  def inspect
437
578
  %(#<Capybara::Node::Element tag="#{base.tag_name}" path="#{base.path}">)
438
579
  rescue NotSupportedByDriverError
439
580
  %(#<Capybara::Node::Element tag="#{base.tag_name}">)
440
- rescue StandardError => e
441
- raise unless session.driver.invalid_element_errors.any? { |et| e.is_a?(et) }
442
-
581
+ rescue *session.driver.invalid_element_errors
443
582
  %(Obsolete #<Capybara::Node::Element>)
444
583
  end
445
584
 
585
+ # @api private
586
+ def initial_cache
587
+ base.respond_to?(:initial_cache) ? base.initial_cache : {}
588
+ end
589
+
446
590
  STYLE_SCRIPT = <<~JS
447
591
  (function(){
448
592
  var s = window.getComputedStyle(this);
@@ -454,6 +598,16 @@ module Capybara
454
598
  return result;
455
599
  }).apply(this, arguments)
456
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
457
611
  end
458
612
  end
459
613
  end