capybara 3.0.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 (312) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -1
  3. data/History.md +891 -12
  4. data/License.txt +1 -1
  5. data/README.md +257 -84
  6. data/lib/capybara/config.rb +29 -10
  7. data/lib/capybara/cucumber.rb +1 -1
  8. data/lib/capybara/driver/base.rb +22 -4
  9. data/lib/capybara/driver/node.rb +38 -9
  10. data/lib/capybara/dsl.rb +9 -7
  11. data/lib/capybara/helpers.rb +57 -8
  12. data/lib/capybara/minitest/spec.rb +185 -84
  13. data/lib/capybara/minitest.rb +264 -145
  14. data/lib/capybara/node/actions.rb +248 -124
  15. data/lib/capybara/node/base.rb +35 -20
  16. data/lib/capybara/node/document.rb +14 -2
  17. data/lib/capybara/node/document_matchers.rb +13 -15
  18. data/lib/capybara/node/element.rb +350 -113
  19. data/lib/capybara/node/finders.rb +104 -82
  20. data/lib/capybara/node/matchers.rb +363 -157
  21. data/lib/capybara/node/simple.rb +54 -15
  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 +9 -10
  25. data/lib/capybara/queries/base_query.rb +25 -18
  26. data/lib/capybara/queries/current_path_query.rb +16 -6
  27. data/lib/capybara/queries/match_query.rb +11 -0
  28. data/lib/capybara/queries/selector_query.rb +617 -104
  29. data/lib/capybara/queries/sibling_query.rb +9 -7
  30. data/lib/capybara/queries/style_query.rb +45 -0
  31. data/lib/capybara/queries/text_query.rb +40 -22
  32. data/lib/capybara/queries/title_query.rb +2 -2
  33. data/lib/capybara/rack_test/browser.rb +106 -31
  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 +74 -49
  37. data/lib/capybara/rack_test/node.rb +120 -47
  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 +87 -53
  44. data/lib/capybara/rspec/features.rb +8 -10
  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 +142 -315
  60. data/lib/capybara/rspec.rb +3 -2
  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 +85 -8
  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 +72 -28
  88. data/lib/capybara/selector/filters/base.rb +45 -2
  89. data/lib/capybara/selector/filters/expression_filter.rb +5 -6
  90. data/lib/capybara/selector/filters/locator_filter.rb +29 -0
  91. data/lib/capybara/selector/filters/node_filter.rb +18 -4
  92. data/lib/capybara/selector/regexp_disassembler.rb +211 -0
  93. data/lib/capybara/selector/selector.rb +89 -200
  94. data/lib/capybara/selector/xpath_extensions.rb +17 -0
  95. data/lib/capybara/selector.rb +474 -534
  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 +270 -245
  101. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +117 -0
  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 +460 -170
  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 +80 -0
  123. data/lib/capybara/server/checker.rb +44 -0
  124. data/lib/capybara/server/middleware.rb +71 -0
  125. data/lib/capybara/server.rb +58 -67
  126. data/lib/capybara/session/config.rb +40 -6
  127. data/lib/capybara/session/matchers.rb +26 -19
  128. data/lib/capybara/session.rb +252 -194
  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 +126 -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 +135 -44
  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.rb → assert_current_path_spec.rb} +20 -18
  140. data/lib/capybara/spec/session/assert_selector_spec.rb +143 -0
  141. data/lib/capybara/spec/session/assert_style_spec.rb +26 -0
  142. data/lib/capybara/spec/session/{assert_text.rb → assert_text_spec.rb} +76 -52
  143. data/lib/capybara/spec/session/{assert_title.rb → 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 +11 -13
  146. data/lib/capybara/spec/session/check_spec.rb +112 -51
  147. data/lib/capybara/spec/session/choose_spec.rb +62 -30
  148. data/lib/capybara/spec/session/click_button_spec.rb +227 -161
  149. data/lib/capybara/spec/session/click_link_or_button_spec.rb +49 -30
  150. data/lib/capybara/spec/session/click_link_spec.rb +89 -55
  151. data/lib/capybara/spec/session/current_scope_spec.rb +8 -8
  152. data/lib/capybara/spec/session/current_url_spec.rb +44 -37
  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.rb → assert_match_selector_spec.rb} +11 -9
  156. data/lib/capybara/spec/session/element/match_css_spec.rb +18 -10
  157. data/lib/capybara/spec/session/element/match_xpath_spec.rb +8 -6
  158. data/lib/capybara/spec/session/element/matches_selector_spec.rb +70 -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 +110 -44
  163. data/lib/capybara/spec/session/find_button_spec.rb +23 -23
  164. data/lib/capybara/spec/session/find_by_id_spec.rb +8 -8
  165. data/lib/capybara/spec/session/find_field_spec.rb +33 -31
  166. data/lib/capybara/spec/session/find_link_spec.rb +42 -14
  167. data/lib/capybara/spec/session/find_spec.rb +251 -142
  168. data/lib/capybara/spec/session/first_spec.rb +45 -44
  169. data/lib/capybara/spec/session/frame/frame_title_spec.rb +6 -6
  170. data/lib/capybara/spec/session/frame/frame_url_spec.rb +6 -6
  171. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +32 -20
  172. data/lib/capybara/spec/session/frame/within_frame_spec.rb +46 -19
  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 +98 -12
  179. data/lib/capybara/spec/session/has_css_spec.rb +271 -137
  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 +46 -6
  184. data/lib/capybara/spec/session/has_none_selectors_spec.rb +33 -31
  185. data/lib/capybara/spec/session/has_select_spec.rb +84 -50
  186. data/lib/capybara/spec/session/has_selector_spec.rb +117 -69
  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.rb → headers_spec.rb} +1 -1
  193. data/lib/capybara/spec/session/html_spec.rb +13 -6
  194. data/lib/capybara/spec/session/matches_style_spec.rb +37 -0
  195. data/lib/capybara/spec/session/node_spec.rb +958 -122
  196. data/lib/capybara/spec/session/node_wrapper_spec.rb +15 -12
  197. data/lib/capybara/spec/session/refresh_spec.rb +9 -7
  198. data/lib/capybara/spec/session/reset_session_spec.rb +65 -37
  199. data/lib/capybara/spec/session/{response_code.rb → response_code_spec.rb} +1 -1
  200. data/lib/capybara/spec/session/save_and_open_page_spec.rb +2 -2
  201. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +5 -4
  202. data/lib/capybara/spec/session/save_page_spec.rb +41 -38
  203. data/lib/capybara/spec/session/save_screenshot_spec.rb +13 -11
  204. data/lib/capybara/spec/session/screenshot_spec.rb +2 -2
  205. data/lib/capybara/spec/session/scroll_spec.rb +119 -0
  206. data/lib/capybara/spec/session/select_spec.rb +102 -76
  207. data/lib/capybara/spec/session/selectors_spec.rb +51 -18
  208. data/lib/capybara/spec/session/sibling_spec.rb +9 -9
  209. data/lib/capybara/spec/session/text_spec.rb +26 -24
  210. data/lib/capybara/spec/session/title_spec.rb +8 -6
  211. data/lib/capybara/spec/session/uncheck_spec.rb +41 -22
  212. data/lib/capybara/spec/session/unselect_spec.rb +37 -37
  213. data/lib/capybara/spec/session/visit_spec.rb +79 -53
  214. data/lib/capybara/spec/session/window/become_closed_spec.rb +22 -19
  215. data/lib/capybara/spec/session/window/current_window_spec.rb +4 -3
  216. data/lib/capybara/spec/session/window/open_new_window_spec.rb +4 -3
  217. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +25 -21
  218. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +10 -5
  219. data/lib/capybara/spec/session/window/window_spec.rb +88 -54
  220. data/lib/capybara/spec/session/window/windows_spec.rb +11 -8
  221. data/lib/capybara/spec/session/window/within_window_spec.rb +17 -16
  222. data/lib/capybara/spec/session/within_spec.rb +82 -44
  223. data/lib/capybara/spec/spec_helper.rb +46 -52
  224. data/lib/capybara/spec/test_app.rb +148 -41
  225. data/lib/capybara/spec/views/animated.erb +49 -0
  226. data/lib/capybara/spec/views/form.erb +156 -42
  227. data/lib/capybara/spec/views/frame_child.erb +4 -3
  228. data/lib/capybara/spec/views/frame_one.erb +2 -1
  229. data/lib/capybara/spec/views/frame_parent.erb +1 -1
  230. data/lib/capybara/spec/views/frame_two.erb +1 -1
  231. data/lib/capybara/spec/views/initial_alert.erb +2 -1
  232. data/lib/capybara/spec/views/layout.erb +10 -0
  233. data/lib/capybara/spec/views/obscured.erb +47 -0
  234. data/lib/capybara/spec/views/offset.erb +33 -0
  235. data/lib/capybara/spec/views/path.erb +2 -2
  236. data/lib/capybara/spec/views/popup_one.erb +1 -1
  237. data/lib/capybara/spec/views/popup_two.erb +1 -1
  238. data/lib/capybara/spec/views/react.erb +45 -0
  239. data/lib/capybara/spec/views/scroll.erb +21 -0
  240. data/lib/capybara/spec/views/spatial.erb +31 -0
  241. data/lib/capybara/spec/views/tables.erb +68 -1
  242. data/lib/capybara/spec/views/with_animation.erb +81 -0
  243. data/lib/capybara/spec/views/with_base_tag.erb +2 -2
  244. data/lib/capybara/spec/views/with_dragula.erb +24 -0
  245. data/lib/capybara/spec/views/with_fixed_header_footer.erb +2 -1
  246. data/lib/capybara/spec/views/with_hover.erb +3 -2
  247. data/lib/capybara/spec/views/with_hover1.erb +10 -0
  248. data/lib/capybara/spec/views/with_html.erb +46 -11
  249. data/lib/capybara/spec/views/with_html5_svg.erb +20 -0
  250. data/lib/capybara/spec/views/with_jquery_animation.erb +24 -0
  251. data/lib/capybara/spec/views/with_js.erb +30 -5
  252. data/lib/capybara/spec/views/with_jstree.erb +26 -0
  253. data/lib/capybara/spec/views/with_namespace.erb +21 -0
  254. data/lib/capybara/spec/views/with_scope.erb +2 -2
  255. data/lib/capybara/spec/views/with_scope_other.erb +6 -0
  256. data/lib/capybara/spec/views/with_shadow.erb +31 -0
  257. data/lib/capybara/spec/views/with_slow_unload.erb +2 -1
  258. data/lib/capybara/spec/views/with_sortable_js.erb +21 -0
  259. data/lib/capybara/spec/views/with_unload_alert.erb +1 -0
  260. data/lib/capybara/spec/views/with_windows.erb +1 -1
  261. data/lib/capybara/spec/views/within_frames.erb +1 -1
  262. data/lib/capybara/version.rb +1 -1
  263. data/lib/capybara/window.rb +32 -26
  264. data/lib/capybara.rb +128 -104
  265. data/spec/basic_node_spec.rb +59 -34
  266. data/spec/capybara_spec.rb +65 -51
  267. data/spec/counter_spec.rb +35 -0
  268. data/spec/css_builder_spec.rb +101 -0
  269. data/spec/css_splitter_spec.rb +38 -0
  270. data/spec/dsl_spec.rb +84 -55
  271. data/spec/filter_set_spec.rb +24 -7
  272. data/spec/fixtures/certificate.pem +25 -0
  273. data/spec/fixtures/key.pem +27 -0
  274. data/spec/fixtures/selenium_driver_rspec_failure.rb +5 -5
  275. data/spec/fixtures/selenium_driver_rspec_success.rb +5 -5
  276. data/spec/minitest_spec.rb +49 -5
  277. data/spec/minitest_spec_spec.rb +92 -62
  278. data/spec/per_session_config_spec.rb +6 -6
  279. data/spec/rack_test_spec.rb +183 -115
  280. data/spec/regexp_dissassembler_spec.rb +250 -0
  281. data/spec/result_spec.rb +99 -39
  282. data/spec/rspec/features_spec.rb +28 -25
  283. data/spec/rspec/scenarios_spec.rb +10 -6
  284. data/spec/rspec/shared_spec_matchers.rb +418 -364
  285. data/spec/rspec/views_spec.rb +4 -3
  286. data/spec/rspec_matchers_spec.rb +35 -10
  287. data/spec/rspec_spec.rb +109 -85
  288. data/spec/sauce_spec_chrome.rb +43 -0
  289. data/spec/selector_spec.rb +392 -62
  290. data/spec/selenium_spec_chrome.rb +183 -41
  291. data/spec/selenium_spec_chrome_remote.rb +96 -0
  292. data/spec/selenium_spec_edge.rb +41 -8
  293. data/spec/selenium_spec_firefox.rb +228 -0
  294. data/spec/selenium_spec_firefox_remote.rb +94 -0
  295. data/spec/selenium_spec_ie.rb +129 -11
  296. data/spec/selenium_spec_safari.rb +162 -0
  297. data/spec/server_spec.rb +192 -81
  298. data/spec/session_spec.rb +52 -16
  299. data/spec/shared_selenium_node.rb +79 -0
  300. data/spec/shared_selenium_session.rb +460 -123
  301. data/spec/spec_helper.rb +124 -2
  302. data/spec/whitespace_normalizer_spec.rb +54 -0
  303. data/spec/xpath_builder_spec.rb +93 -0
  304. metadata +344 -45
  305. data/.yard/templates_custom/default/class/html/selectors.erb +0 -38
  306. data/.yard/templates_custom/default/class/html/setup.rb +0 -17
  307. data/.yard/yard_extensions.rb +0 -78
  308. data/lib/capybara/rspec/compound.rb +0 -90
  309. data/lib/capybara/spec/session/assert_selector.rb +0 -149
  310. data/lib/capybara/spec/session/source_spec.rb +0 -0
  311. data/lib/capybara/spec/views/with_title.erb +0 -5
  312. data/spec/selenium_spec_marionette.rb +0 -143
@@ -7,9 +7,9 @@ module Capybara
7
7
  # Asserts that the page has the given title.
8
8
  #
9
9
  # @!macro title_query_params
10
- # @overload $0(string, options = {})
10
+ # @overload $0(string, **options)
11
11
  # @param string [String] The string that title should include
12
- # @overload $0(regexp, options = {})
12
+ # @overload $0(regexp, **options)
13
13
  # @param regexp [Regexp] The regexp that title should match to
14
14
  # @option options [Numeric] :wait (Capybara.default_max_wait_time) Maximum time that Capybara will wait for title to eq/match given string/regexp argument
15
15
  # @option options [Boolean] :exact (false) When passed a string should the match be exact or just substring
@@ -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
- return false
45
+ make_predicate(options) { assert_title(title, **options) }
44
46
  end
45
47
 
46
48
  ##
@@ -50,19 +52,15 @@ 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
- return 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
65
- return true
61
+ query = Capybara::Queries::TitleQuery.new(title, **options)
62
+ synchronize(query.wait) { yield(query) }
63
+ true
66
64
  end
67
65
  end
68
66
  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,30 +45,25 @@ 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
50
52
  # ignored. This behaviour can be overridden by passing `:all` to this
51
53
  # method.
52
54
  #
53
- # @param [:all, :visible] type Whether to return only visible or all text
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)
57
- type ||= :all unless session_options.ignore_hidden_elements or session_options.visible_text_only
58
- synchronize do
59
- if type == :all
60
- base.all_text
61
- else
62
- base.visible_text
63
- end
64
- end
58
+ def text(type = nil, normalize_ws: false)
59
+ type ||= :all unless session_options.ignore_hidden_elements || session_options.visible_text_only
60
+ txt = synchronize { type == :all ? base.all_text : base.visible_text }
61
+ normalize_ws ? txt.gsub(/[[:space:]]+/, ' ').strip : txt
65
62
  end
66
63
 
67
64
  ##
68
65
  #
69
- # Retrieve the given attribute
66
+ # Retrieve the given attribute.
70
67
  #
71
68
  # element[:title] # => HTML title attribute
72
69
  #
@@ -77,6 +74,30 @@ module Capybara
77
74
  synchronize { base[attribute] }
78
75
  end
79
76
 
77
+ ##
78
+ #
79
+ # Retrieve the given CSS styles.
80
+ #
81
+ # element.style('color', 'font-size') # => Computed values of CSS 'color' and 'font-size' styles
82
+ #
83
+ # @param [Array<String>] styles Names of the desired CSS properties
84
+ # @return [Hash] Hash of the CSS property names to computed values
85
+ #
86
+ def style(*styles)
87
+ styles = styles.flatten.map(&:to_s)
88
+ raise ArgumentError, 'You must specify at least one CSS style' if styles.empty?
89
+
90
+ begin
91
+ synchronize { base.style(styles) }
92
+ rescue NotImplementedError => e
93
+ begin
94
+ evaluate_script(STYLE_SCRIPT, *styles)
95
+ rescue Capybara::NotSupportedByDriverError
96
+ raise e
97
+ end
98
+ end
99
+ end
100
+
80
101
  ##
81
102
  #
82
103
  # @return [String] The value of the form element
@@ -90,141 +111,164 @@ module Capybara
90
111
  # Set the value of the form element to the given value.
91
112
  #
92
113
  # @param [String] value The new value
93
- # @param [Hash{}] options Driver specific options for how to set the value
114
+ # @param [Hash] options Driver specific options for how to set the value. Take default values from {Capybara.configure default_set_options}.
94
115
  #
95
116
  # @return [Capybara::Node::Element] The element
96
117
  def set(value, **options)
97
- raise Capybara::ReadOnlyElementError, "Attempt to set readonly element with value: #{value}" if readonly?
98
- synchronize { base.set(value, options) }
118
+ if ENV.fetch('CAPYBARA_THOROUGH', nil) && readonly?
119
+ raise Capybara::ReadOnlyElementError, "Attempt to set readonly element with value: #{value}"
120
+ end
121
+
122
+ options = session_options.default_set_options.to_h.merge(options)
123
+ synchronize { base.set(value, **options) }
99
124
  self
100
125
  end
101
126
 
102
127
  ##
103
128
  #
104
- # 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.
105
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}.
106
138
  # @return [Capybara::Node::Element] The element
107
- def select_option
108
- warn "Attempt to select disabled option: #{value || text}" if disabled?
109
- synchronize { base.select_option }
139
+ def select_option(wait: nil)
140
+ synchronize(wait) { base.select_option }
110
141
  self
111
142
  end
112
143
 
113
144
  ##
114
145
  #
115
- # 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.
116
147
  #
148
+ # @macro action_waiting_behavior
117
149
  # @return [Capybara::Node::Element] The element
118
- def unselect_option
119
- synchronize { base.unselect_option }
150
+ def unselect_option(wait: nil)
151
+ synchronize(wait) { base.unselect_option }
120
152
  self
121
153
  end
122
154
 
123
155
  ##
124
156
  #
125
- # Click the Element
157
+ # Click the Element.
126
158
  #
159
+ # @macro action_waiting_behavior
127
160
  # @!macro click_modifiers
128
- # @overload $0(*key_modifiers=[], offset={x: nil, y: nil})
129
- # @param [Array<:alt, :control, :meta, :shift>] *key_modifiers Keys to be held down when clicking
130
- # @param [Hash] offset x and y coordinates to offset the click location from the top left corner of the element. If not specified will click the middle of the element.
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)
163
+ # @param *modifier_keys [:alt, :control, :meta, :shift] ([]) Keys to be held down when clicking
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)
131
169
  # @return [Capybara::Node::Element] The element
132
- def click(*keys, **offset)
133
- synchronize { base.click(keys, offset) }
134
- self
170
+ def click(*keys, **options)
171
+ perform_click_action(keys, **options) do |k, opts|
172
+ base.click(k, **opts)
173
+ end
135
174
  end
136
175
 
137
176
  ##
138
177
  #
139
- # Right Click the Element
178
+ # Right Click the Element.
140
179
  #
180
+ # @macro action_waiting_behavior
141
181
  # @macro click_modifiers
182
+ # @option options [Float] delay Delay between the mouse down and mouse up events in seconds (0)
142
183
  # @return [Capybara::Node::Element] The element
143
- def right_click(*keys, **offset)
144
- synchronize { base.right_click(keys, offset) }
145
- self
184
+ def right_click(*keys, **options)
185
+ perform_click_action(keys, **options) do |k, opts|
186
+ base.right_click(k, **opts)
187
+ end
146
188
  end
147
189
 
148
190
  ##
149
191
  #
150
- # Double Click the Element
192
+ # Double Click the Element.
151
193
  #
194
+ # @macro action_waiting_behavior
152
195
  # @macro click_modifiers
153
196
  # @return [Capybara::Node::Element] The element
154
- def double_click(*keys, **offset)
155
- synchronize { base.double_click(keys, offset) }
156
- self
197
+ def double_click(*keys, **options)
198
+ perform_click_action(keys, **options) do |k, opts|
199
+ base.double_click(k, **opts)
200
+ end
157
201
  end
158
202
 
159
203
  ##
160
204
  #
161
- # Send Keystrokes to the Element
205
+ # Send Keystrokes to the Element.
162
206
  #
163
207
  # @overload send_keys(keys, ...)
164
- # @param [String, Symbol, Array<String,Symbol>] keys
208
+ # @param keys [String, Symbol, Array<String,Symbol>]
165
209
  #
166
210
  # Examples:
167
211
  #
168
212
  # element.send_keys "foo" #=> value: 'foo'
169
- # element.send_keys "tet", :left, "s" #=> value: 'test'
213
+ # element.send_keys "tet", :left, "s" #=> value: 'test'
170
214
  # element.send_keys [:control, 'a'], :space #=> value: ' ' - assuming ctrl-a selects all contents
171
215
  #
172
- # Symbols supported for keys
173
- # :cancel
174
- # :help
175
- # :backspace
176
- # :tab
177
- # :clear
178
- # :return
179
- # :enter
180
- # :shift
181
- # :control
182
- # :alt
183
- # :pause
184
- # :escape
185
- # :space
186
- # :page_up
187
- # :page_down
188
- # :end
189
- # :home
190
- # :left
191
- # :up
192
- # :right
193
- # :down
194
- # :insert
195
- # :delete
196
- # :semicolon
197
- # :equals
198
- # :numpad0
199
- # :numpad1
200
- # :numpad2
201
- # :numpad3
202
- # :numpad4
203
- # :numpad5
204
- # :numpad6
205
- # :numpad7
206
- # :numpad8
207
- # :numpad9
208
- # :multiply - numeric keypad *
209
- # :add - numeric keypad +
210
- # :separator - numeric keypad 'separator' key ??
211
- # :subtract - numeric keypad -
212
- # :decimal - numeric keypad .
213
- # :divide - numeric keypad /
214
- # :f1
215
- # :f2
216
- # :f3
217
- # :f4
218
- # :f5
219
- # :f6
220
- # :f7
221
- # :f8
222
- # :f9
223
- # :f10
224
- # :f11
225
- # :f12
226
- # :meta
227
- # :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
228
272
  #
229
273
  # @return [Capybara::Node::Element] The element
230
274
  def send_keys(*args)
@@ -234,7 +278,7 @@ module Capybara
234
278
 
235
279
  ##
236
280
  #
237
- # Hover on the Element
281
+ # Hover on the Element.
238
282
  #
239
283
  # @return [Capybara::Node::Element] The element
240
284
  def hover
@@ -247,7 +291,8 @@ module Capybara
247
291
  # @return [String] The tag name of the element
248
292
  #
249
293
  def tag_name
250
- synchronize { base.tag_name }
294
+ # Element type is immutable so cache it
295
+ @tag_name ||= initial_cache[:tag_name] || synchronize { base.tag_name }
251
296
  end
252
297
 
253
298
  ##
@@ -261,6 +306,17 @@ module Capybara
261
306
  synchronize { base.visible? }
262
307
  end
263
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
+
264
320
  ##
265
321
  #
266
322
  # Whether or not the element is checked.
@@ -313,7 +369,7 @@ module Capybara
313
369
 
314
370
  ##
315
371
  #
316
- # 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.
317
373
  #
318
374
  # @return [String] An XPath expression
319
375
  #
@@ -321,10 +377,16 @@ module Capybara
321
377
  synchronize { base.path }
322
378
  end
323
379
 
380
+ def rect
381
+ synchronize { base.rect }
382
+ end
383
+
324
384
  ##
325
385
  #
326
386
  # Trigger any event on the current element, for example mouseover or focus
327
- # 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.
328
390
  #
329
391
  # @param [String] event The name of the event to trigger
330
392
  #
@@ -343,34 +405,209 @@ module Capybara
343
405
  # source.drag_to(target)
344
406
  #
345
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
346
460
  #
347
461
  # @return [Capybara::Node::Element] The element
348
- def drag_to(node)
349
- 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?
472
+ self
473
+ end
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
+
486
+ ##
487
+ #
488
+ # Execute the given JS in the context of the element not returning a result. This is useful for scripts that return
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.
491
+ #
492
+ # @param [String] script A string of JavaScript to execute
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
494
+ #
495
+ def execute_script(script, *args)
496
+ session.execute_script(<<~JS, self, *args)
497
+ (function (){
498
+ #{script}
499
+ }).apply(arguments[0], Array.prototype.slice.call(arguments,1));
500
+ JS
501
+ end
502
+
503
+ ##
504
+ #
505
+ # Evaluate the given JS in the context of the element and return the result. Be careful when using this with
506
+ # scripts that return complex objects, such as jQuery statements. {#execute_script} might
507
+ # be a better alternative. `this` in the script will refer to the element this is called on.
508
+ #
509
+ # @param [String] script A string of JavaScript to evaluate
510
+ # @return [Object] The result of the evaluated JavaScript (may be driver specific)
511
+ #
512
+ def evaluate_script(script, *args)
513
+ session.evaluate_script(<<~JS, self, *args)
514
+ (function(){
515
+ return #{script.strip}
516
+ }).apply(arguments[0], Array.prototype.slice.call(arguments,1));
517
+ JS
518
+ end
519
+
520
+ ##
521
+ #
522
+ # Evaluate the given JavaScript in the context of the element and obtain the result from a
523
+ # callback function which will be passed as the last argument to the script. `this` in the
524
+ # script will refer to the element this is called on.
525
+ #
526
+ # @param [String] script A string of JavaScript to evaluate
527
+ # @return [Object] The result of the evaluated JavaScript (may be driver specific)
528
+ #
529
+ def evaluate_async_script(script, *args)
530
+ session.evaluate_async_script(<<~JS, self, *args)
531
+ (function (){
532
+ #{script}
533
+ }).apply(arguments[0], Array.prototype.slice.call(arguments,1));
534
+ JS
535
+ end
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
+
350
556
  self
351
557
  end
352
558
 
559
+ # @api private
353
560
  def reload
354
- if @allow_reload
355
- begin
356
- reloaded = query_scope.reload.first(@query.name, @query.locator, @query.options)
357
- @base = reloaded.base if reloaded
358
- rescue => e
359
- raise e unless catch_error?(e)
360
- 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)
361
568
  end
362
569
  self
363
570
  end
364
571
 
572
+ ##
573
+ #
574
+ # A human-readable representation of the element.
575
+ #
576
+ # @return [String] A string representation
365
577
  def inspect
366
578
  %(#<Capybara::Node::Element tag="#{base.tag_name}" path="#{base.path}">)
367
579
  rescue NotSupportedByDriverError
368
580
  %(#<Capybara::Node::Element tag="#{base.tag_name}">)
369
- rescue => e
370
- raise unless session.driver.invalid_element_errors.any? { |et| e.is_a?(et) }
371
-
581
+ rescue *session.driver.invalid_element_errors
372
582
  %(Obsolete #<Capybara::Node::Element>)
373
583
  end
584
+
585
+ # @api private
586
+ def initial_cache
587
+ base.respond_to?(:initial_cache) ? base.initial_cache : {}
588
+ end
589
+
590
+ STYLE_SCRIPT = <<~JS
591
+ (function(){
592
+ var s = window.getComputedStyle(this);
593
+ var result = {};
594
+ for (var i = arguments.length; i--; ) {
595
+ var property_name = arguments[i];
596
+ result[property_name] = s.getPropertyValue(property_name);
597
+ }
598
+ return result;
599
+ }).apply(this, arguments)
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
374
611
  end
375
612
  end
376
613
  end