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
@@ -6,7 +6,7 @@ require 'addressable/uri'
6
6
  module Capybara
7
7
  ##
8
8
  #
9
- # The Session class represents a single user's interaction with the system. The Session can use
9
+ # The {Session} class represents a single user's interaction with the system. The {Session} can use
10
10
  # any of the underlying drivers. A session can be initialized manually like this:
11
11
  #
12
12
  # session = Capybara::Session.new(:culerity, MyRackApp)
@@ -17,39 +17,41 @@ module Capybara
17
17
  # session = Capybara::Session.new(:culerity)
18
18
  # session.visit('http://www.google.com')
19
19
  #
20
- # When Capybara.threadsafe == true the sessions options will be initially set to the
20
+ # When {Capybara.configure threadsafe} is `true` the sessions options will be initially set to the
21
21
  # current values of the global options and a configuration block can be passed to the session initializer.
22
- # For available options see {Capybara::SessionConfig::OPTIONS}
22
+ # For available options see {Capybara::SessionConfig::OPTIONS}:
23
23
  #
24
24
  # session = Capybara::Session.new(:driver, MyRackApp) do |config|
25
25
  # config.app_host = "http://my_host.dev"
26
26
  # end
27
27
  #
28
- # Session provides a number of methods for controlling the navigation of the page, such as +visit+,
29
- # +current_path, and so on. It also delegates a number of methods to a Capybara::Document, representing
28
+ # The {Session} provides a number of methods for controlling the navigation of the page, such as {#visit},
29
+ # {#current_path}, and so on. It also delegates a number of methods to a {Capybara::Document}, representing
30
30
  # the current HTML document. This allows interaction:
31
31
  #
32
32
  # session.fill_in('q', with: 'Capybara')
33
33
  # session.click_button('Search')
34
34
  # expect(session).to have_content('Capybara')
35
35
  #
36
- # When using capybara/dsl, the Session is initialized automatically for you.
36
+ # When using `capybara/dsl`, the {Session} is initialized automatically for you.
37
37
  #
38
38
  class Session
39
39
  include Capybara::SessionMatchers
40
40
 
41
41
  NODE_METHODS = %i[
42
- all first attach_file text check choose
42
+ all first attach_file text check choose scroll_to scroll_by
43
+ click double_click right_click
43
44
  click_link_or_button click_button click_link
44
45
  fill_in find find_all find_button find_by_id find_field find_link
45
46
  has_content? has_text? has_css? has_no_content? has_no_text?
46
- has_no_css? has_no_xpath? resolve has_xpath? select uncheck
47
+ has_no_css? has_no_xpath? has_xpath? select uncheck
48
+ has_element? has_no_element?
47
49
  has_link? has_no_link? has_button? has_no_button? has_field?
48
50
  has_no_field? has_checked_field? has_unchecked_field?
49
51
  has_no_table? has_table? unselect has_select? has_no_select?
50
52
  has_selector? has_no_selector? click_on has_no_checked_field?
51
53
  has_no_unchecked_field? query assert_selector assert_no_selector
52
- assert_all_of_selectors assert_none_of_selectors
54
+ assert_all_of_selectors assert_none_of_selectors assert_any_of_selectors
53
55
  refute_selector assert_text assert_no_text
54
56
  ].freeze
55
57
  # @api private
@@ -58,7 +60,7 @@ module Capybara
58
60
  ].freeze
59
61
  SESSION_METHODS = %i[
60
62
  body html source current_url current_host current_path
61
- execute_script evaluate_script visit refresh go_back go_forward
63
+ execute_script evaluate_script evaluate_async_script visit refresh go_back go_forward send_keys
62
64
  within within_element within_fieldset within_table within_frame switch_to_frame
63
65
  current_window windows open_new_window switch_to_window within_window window_opened_by
64
66
  save_page save_and_open_page save_screenshot
@@ -75,26 +77,30 @@ module Capybara
75
77
  attr_accessor :synchronized
76
78
 
77
79
  def initialize(mode, app = nil)
78
- raise TypeError, "The second parameter to Session::new should be a rack app if passed." if app && !app.respond_to?(:call)
79
- @@instance_created = true
80
+ if app && !app.respond_to?(:call)
81
+ raise TypeError, 'The second parameter to Session::new should be a rack app if passed.'
82
+ end
83
+
84
+ @@instance_created = true # rubocop:disable Style/ClassVars
80
85
  @mode = mode
81
86
  @app = app
82
87
  if block_given?
83
- raise "A configuration block is only accepted when Capybara.threadsafe == true" unless Capybara.threadsafe
88
+ raise 'A configuration block is only accepted when Capybara.threadsafe == true' unless Capybara.threadsafe
89
+
84
90
  yield config
85
91
  end
86
92
  @server = if config.run_server && @app && driver.needs_server?
87
93
  server_options = { port: config.server_port, host: config.server_host, reportable_errors: config.server_errors }
88
94
  server_options[:extra_middleware] = [Capybara::Server::AnimationDisabler] if config.disable_animation
89
- Capybara::Server.new(@app, server_options).boot
95
+ Capybara::Server.new(@app, **server_options).boot
90
96
  end
91
97
  @touched = false
92
98
  end
93
99
 
94
100
  def driver
95
101
  @driver ||= begin
96
- unless Capybara.drivers.key?(mode)
97
- other_drivers = Capybara.drivers.keys.map(&:inspect)
102
+ unless Capybara.drivers[mode]
103
+ other_drivers = Capybara.drivers.names.map(&:inspect)
98
104
  raise Capybara::DriverNotFoundError, "no driver called #{mode.inspect} was found, available drivers: #{other_drivers.join(', ')}"
99
105
  end
100
106
  driver = Capybara.drivers[mode].call(app)
@@ -105,26 +111,28 @@ module Capybara
105
111
 
106
112
  ##
107
113
  #
108
- # Reset the session (i.e. remove cookies and navigate to blank page)
114
+ # Reset the session (i.e. remove cookies and navigate to blank page).
109
115
  #
110
116
  # This method does not:
111
117
  #
112
- # * accept modal dialogs if they are present (Selenium driver now does, others may not)
113
- # * clear browser cache/HTML 5 local storage/IndexedDB/Web SQL database/etc.
114
- # * modify state of the driver/underlying browser in any other way
118
+ # * accept modal dialogs if they are present (Selenium driver now does, others may not)
119
+ # * clear browser cache/HTML 5 local storage/IndexedDB/Web SQL database/etc.
120
+ # * modify state of the driver/underlying browser in any other way
115
121
  #
116
122
  # as doing so will result in performance downsides and it's not needed to do everything from the list above for most apps.
117
123
  #
118
124
  # If you want to do anything from the list above on a general basis you can:
119
125
  #
120
- # * write RSpec/Cucumber/etc. after hook
121
- # * monkeypatch this method
122
- # * use Ruby's `prepend` method
126
+ # * write RSpec/Cucumber/etc. after hook
127
+ # * monkeypatch this method
128
+ # * use Ruby's `prepend` method
123
129
  #
124
130
  def reset!
125
131
  if @touched
126
132
  driver.reset!
127
133
  @touched = false
134
+ switch_to_frame(:top) rescue nil # rubocop:disable Style/RescueModifier
135
+ @scopes = [nil]
128
136
  end
129
137
  @server&.wait_for_pending_requests
130
138
  raise_server_error!
@@ -134,18 +142,29 @@ module Capybara
134
142
 
135
143
  ##
136
144
  #
137
- # Raise errors encountered in the server
145
+ # Disconnect from the current driver. A new driver will be instantiated on the next interaction.
146
+ #
147
+ def quit
148
+ @driver.quit if @driver.respond_to? :quit
149
+ @document = @driver = nil
150
+ @touched = false
151
+ @server&.reset_error!
152
+ end
153
+
154
+ ##
155
+ #
156
+ # Raise errors encountered in the server.
138
157
  #
139
158
  def raise_server_error!
140
- return if @server.nil? || !@server.error
159
+ return unless @server&.error
160
+
141
161
  # Force an explanation for the error being raised as the exception cause
142
162
  begin
143
163
  if config.raise_server_errors
144
- raise CapybaraError, "Your application server raised an error - It has been raised in your test code because Capybara.raise_server_errors == true"
164
+ raise CapybaraError, 'Your application server raised an error - It has been raised in your test code because Capybara.raise_server_errors == true'
145
165
  end
146
- rescue CapybaraError
147
- # needed to get the cause set correctly in JRuby -- otherwise we could just do raise @server.error
148
- raise @server.error, @server.error.message, @server.error.backtrace
166
+ rescue CapybaraError => capy_error # rubocop:disable Naming/RescuedExceptionsVariableName
167
+ raise @server.error, cause: capy_error
149
168
  ensure
150
169
  @server.reset_error!
151
170
  end
@@ -153,9 +172,9 @@ module Capybara
153
172
 
154
173
  ##
155
174
  #
156
- # Returns a hash of response headers. Not supported by all drivers (e.g. Selenium)
175
+ # Returns a hash of response headers. Not supported by all drivers (e.g. Selenium).
157
176
  #
158
- # @return [Hash{String => String}] A hash of response headers.
177
+ # @return [Hash<String, String>] A hash of response headers.
159
178
  #
160
179
  def response_headers
161
180
  driver.response_headers
@@ -163,7 +182,7 @@ module Capybara
163
182
 
164
183
  ##
165
184
  #
166
- # Returns the current HTTP status code as an Integer. Not supported by all drivers (e.g. Selenium)
185
+ # Returns the current HTTP status code as an integer. Not supported by all drivers (e.g. Selenium).
167
186
  #
168
187
  # @return [Integer] Current HTTP status code
169
188
  #
@@ -176,7 +195,7 @@ module Capybara
176
195
  # @return [String] A snapshot of the DOM of the current document, as it looks right now (potentially modified by JavaScript).
177
196
  #
178
197
  def html
179
- driver.html
198
+ driver.html || ''
180
199
  end
181
200
  alias_method :body, :html
182
201
  alias_method :source, :html
@@ -190,7 +209,7 @@ module Capybara
190
209
  uri = ::Addressable::URI.parse(current_url)
191
210
 
192
211
  # Addressable doesn't support opaque URIs - we want nil here
193
- return nil if uri&.scheme == "about"
212
+ return nil if uri&.scheme == 'about'
194
213
 
195
214
  path = uri&.path
196
215
  path unless path&.empty?
@@ -223,13 +242,13 @@ module Capybara
223
242
  #
224
243
  # For drivers which can run against an external application, such as the selenium driver
225
244
  # giving an absolute URL will navigate to that page. This allows testing applications
226
- # running on remote servers. For these drivers, setting {Capybara.app_host} will make the
245
+ # running on remote servers. For these drivers, setting {Capybara.configure app_host} will make the
227
246
  # remote server the default. For example:
228
247
  #
229
248
  # Capybara.app_host = 'http://google.com'
230
249
  # session.visit('/') # visits the google homepage
231
250
  #
232
- # If {Capybara.always_include_port} is set to true and this session is running against
251
+ # If {Capybara.configure always_include_port} is set to `true` and this session is running against
233
252
  # a rack application, then the port that the rack application is running on will automatically
234
253
  # be inserted into the URL. Supposing the app is running on port `4567`, doing something like:
235
254
  #
@@ -244,26 +263,19 @@ module Capybara
244
263
  @touched = true
245
264
 
246
265
  visit_uri = ::Addressable::URI.parse(visit_uri.to_s)
266
+ base_uri = ::Addressable::URI.parse(config.app_host || server_url)
247
267
 
248
- base = config.app_host
249
- base ||= "http#{'s' if @server.using_ssl?}://#{@server.host}:#{@server.port}" if @server
250
-
251
- uri_base = ::Addressable::URI.parse(base)
252
-
253
- if uri_base && [nil, 'http', 'https'].include?(visit_uri.scheme)
268
+ if base_uri && [nil, 'http', 'https'].include?(visit_uri.scheme)
254
269
  if visit_uri.relative?
255
- uri_base.port ||= @server.port if @server && config.always_include_port
256
-
257
- visit_uri_parts = visit_uri.to_hash.delete_if { |_k, v| v.nil? }
270
+ visit_uri_parts = visit_uri.to_hash.compact
258
271
 
259
272
  # Useful to people deploying to a subdirectory
260
273
  # and/or single page apps where only the url fragment changes
261
- visit_uri_parts[:path] = uri_base.path + visit_uri.path
274
+ visit_uri_parts[:path] = base_uri.path + visit_uri.path
262
275
 
263
- visit_uri = uri_base.merge(visit_uri_parts)
264
- elsif @server && config.always_include_port
265
- visit_uri.port ||= @server.port
276
+ visit_uri = base_uri.merge(visit_uri_parts)
266
277
  end
278
+ adjust_server_port(visit_uri)
267
279
  end
268
280
 
269
281
  driver.visit(visit_uri.to_s)
@@ -271,7 +283,7 @@ module Capybara
271
283
 
272
284
  ##
273
285
  #
274
- # Refresh the page
286
+ # Refresh the page.
275
287
  #
276
288
  def refresh
277
289
  raise_server_error!
@@ -294,10 +306,28 @@ module Capybara
294
306
  driver.go_forward
295
307
  end
296
308
 
309
+ ##
310
+ # @!method send_keys
311
+ # @see Capybara::Node::Element#send_keys
312
+ #
313
+ def send_keys(...)
314
+ driver.send_keys(...)
315
+ end
316
+
317
+ ##
318
+ #
319
+ # Returns the element with focus.
320
+ #
321
+ # Not supported by Rack Test
322
+ #
323
+ def active_element
324
+ Capybara::Queries::ActiveElementQuery.new.resolve_for(self)[0].tap(&:allow_reload!)
325
+ end
326
+
297
327
  ##
298
328
  #
299
- # Executes the given block within the context of a node. `within` takes the
300
- # same options as `find`, as well as a block. For the duration of the
329
+ # Executes the given block within the context of a node. {#within} takes the
330
+ # same options as {Capybara::Node::Finders#find #find}, as well as a block. For the duration of the
301
331
  # block, any command to Capybara will be handled as though it were scoped
302
332
  # to the given element.
303
333
  #
@@ -305,18 +335,18 @@ module Capybara
305
335
  # fill_in('Street', with: '12 Main Street')
306
336
  # end
307
337
  #
308
- # Just as with `find`, if multiple elements match the selector given to
309
- # `within`, an error will be raised, and just as with `find`, this
338
+ # Just as with `#find`, if multiple elements match the selector given to
339
+ # {#within}, an error will be raised, and just as with `#find`, this
310
340
  # behaviour can be controlled through the `:match` and `:exact` options.
311
341
  #
312
342
  # It is possible to omit the first parameter, in that case, the selector is
313
- # assumed to be of the type set in Capybara.default_selector.
343
+ # assumed to be of the type set in {Capybara.configure default_selector}.
314
344
  #
315
345
  # within('div#delivery-address') do
316
346
  # fill_in('Street', with: '12 Main Street')
317
347
  # end
318
348
  #
319
- # Note that a lot of uses of `within` can be replaced more succinctly with
349
+ # Note that a lot of uses of {#within} can be replaced more succinctly with
320
350
  # chaining:
321
351
  #
322
352
  # find('div#delivery-address').fill_in('Street', with: '12 Main Street')
@@ -329,11 +359,11 @@ module Capybara
329
359
  #
330
360
  # @raise [Capybara::ElementNotFound] If the scope can't be found before time expires
331
361
  #
332
- def within(*args)
333
- new_scope = args.first.respond_to?(:to_capybara_node) ? args.first.to_capybara_node : find(*args)
362
+ def within(*args, **kw_args)
363
+ new_scope = args.first.respond_to?(:to_capybara_node) ? args.first.to_capybara_node : find(*args, **kw_args)
334
364
  begin
335
365
  scopes.push(new_scope)
336
- yield if block_given?
366
+ yield new_scope if block_given?
337
367
  ensure
338
368
  scopes.pop
339
369
  end
@@ -346,8 +376,8 @@ module Capybara
346
376
  #
347
377
  # @param [String] locator Id or legend of the fieldset
348
378
  #
349
- def within_fieldset(locator)
350
- within(:fieldset, locator) { yield }
379
+ def within_fieldset(locator, &block)
380
+ within(:fieldset, locator, &block)
351
381
  end
352
382
 
353
383
  ##
@@ -356,24 +386,24 @@ module Capybara
356
386
  #
357
387
  # @param [String] locator Id or caption of the table
358
388
  #
359
- def within_table(locator)
360
- within(:table, locator) { yield }
389
+ def within_table(locator, &block)
390
+ within(:table, locator, &block)
361
391
  end
362
392
 
363
393
  ##
364
394
  #
365
- # Switch to the given frame
395
+ # Switch to the given frame.
366
396
  #
367
397
  # If you use this method you are responsible for making sure you switch back to the parent frame when done in the frame changed to.
368
- # Capybara::Session#within_frame is preferred over this method and should be used when possible.
398
+ # {#within_frame} is preferred over this method and should be used when possible.
369
399
  # May not be supported by all drivers.
370
400
  #
371
401
  # @overload switch_to_frame(element)
372
- # @param [Capybara::Node::Element] iframe/frame element to switch to
373
- # @overload switch_to_frame(:parent)
374
- # Switch to the parent element
375
- # @overload switch_to_frame(:top)
376
- # Switch to the top level document
402
+ # @param [Capybara::Node::Element] element iframe/frame element to switch to
403
+ # @overload switch_to_frame(location)
404
+ # @param [Symbol] location relative location of the frame to switch to
405
+ # * :parent - the parent frame
406
+ # * :top - the top level document
377
407
  #
378
408
  def switch_to_frame(frame)
379
409
  case frame
@@ -382,21 +412,24 @@ module Capybara
382
412
  scopes.push(:frame)
383
413
  when :parent
384
414
  if scopes.last != :frame
385
- raise Capybara::ScopeError, "`switch_to_frame(:parent)` cannot be called from inside a descendant frame's "\
386
- "`within` block."
415
+ raise Capybara::ScopeError, "`switch_to_frame(:parent)` cannot be called from inside a descendant frame's " \
416
+ '`within` block.'
387
417
  end
388
418
  scopes.pop
389
419
  driver.switch_to_frame(:parent)
390
420
  when :top
391
421
  idx = scopes.index(:frame)
422
+ top_level_scopes = [:frame, nil]
392
423
  if idx
393
- if scopes.slice(idx..-1).any? { |scope| ![:frame, nil].include?(scope) }
394
- raise Capybara::ScopeError, "`switch_to_frame(:top)` cannot be called from inside a descendant frame's "\
395
- "`within` block."
424
+ if scopes.slice(idx..).any? { |scope| !top_level_scopes.include?(scope) }
425
+ raise Capybara::ScopeError, "`switch_to_frame(:top)` cannot be called from inside a descendant frame's " \
426
+ '`within` block.'
396
427
  end
397
- scopes.slice!(idx..-1)
428
+ scopes.slice!(idx..)
398
429
  driver.switch_to_frame(:top)
399
430
  end
431
+ else
432
+ raise ArgumentError, 'You must provide a frame element, :parent, or :top when calling switch_to_frame'
400
433
  end
401
434
  end
402
435
 
@@ -408,12 +441,12 @@ module Capybara
408
441
  # @overload within_frame(element)
409
442
  # @param [Capybara::Node::Element] frame element
410
443
  # @overload within_frame([kind = :frame], locator, **options)
411
- # @param [Symbol] kind Optional selector type (:css, :xpath, :field, etc.) - Defaults to :frame
444
+ # @param [Symbol] kind Optional selector type (:frame, :css, :xpath, etc.) - Defaults to :frame
412
445
  # @param [String] locator The locator for the given selector kind. For :frame this is the name/id of a frame/iframe element
413
446
  # @overload within_frame(index)
414
447
  # @param [Integer] index index of a frame (0 based)
415
- def within_frame(*args)
416
- switch_to_frame(_find_frame(*args))
448
+ def within_frame(*args, **kw_args)
449
+ switch_to_frame(_find_frame(*args, **kw_args))
417
450
  begin
418
451
  yield if block_given?
419
452
  ensure
@@ -442,22 +475,28 @@ module Capybara
442
475
  end
443
476
 
444
477
  ##
445
- # Open new window.
446
- # Current window doesn't change as the result of this call.
478
+ # Open a new window.
479
+ # The current window doesn't change as the result of this call.
447
480
  # It should be switched to explicitly.
448
481
  #
449
482
  # @return [Capybara::Window] window that has been opened
450
483
  #
451
- def open_new_window
484
+ def open_new_window(kind = :tab)
452
485
  window_opened_by do
453
- driver.open_new_window
486
+ if driver.method(:open_new_window).arity.zero?
487
+ driver.open_new_window
488
+ else
489
+ driver.open_new_window(kind)
490
+ end
454
491
  end
455
492
  end
456
493
 
457
494
  ##
495
+ # Switch to the given window.
496
+ #
458
497
  # @overload switch_to_window(&block)
459
498
  # Switches to the first window for which given block returns a value other than false or nil.
460
- # If window that matches block can't be found, the window will be switched back and `WindowError` will be raised.
499
+ # If window that matches block can't be found, the window will be switched back and {Capybara::WindowError} will be raised.
461
500
  # @example
462
501
  # window = switch_to_window { title == 'Page title' }
463
502
  # @raise [Capybara::WindowError] if no window matches given block
@@ -466,19 +505,20 @@ module Capybara
466
505
  # @raise [Capybara::Driver::Base#no_such_window_error] if nonexistent (e.g. closed) window was passed
467
506
  #
468
507
  # @return [Capybara::Window] window that has been switched to
469
- # @raise [Capybara::ScopeError] if this method is invoked inside `within` or
470
- # `within_frame` methods
508
+ # @raise [Capybara::ScopeError] if this method is invoked inside {#within} or
509
+ # {#within_frame} methods
471
510
  # @raise [ArgumentError] if both or neither arguments were provided
472
511
  #
473
512
  def switch_to_window(window = nil, **options, &window_locator)
474
- raise ArgumentError, "`switch_to_window` can take either a block or a window, not both" if window && block_given?
475
- raise ArgumentError, "`switch_to_window`: either window or block should be provided" if !window && !block_given?
513
+ raise ArgumentError, '`switch_to_window` can take either a block or a window, not both' if window && window_locator
514
+ raise ArgumentError, '`switch_to_window`: either window or block should be provided' if !window && !window_locator
515
+
476
516
  unless scopes.last.nil?
477
- raise Capybara::ScopeError, "`switch_to_window` is not supposed to be invoked from "\
478
- "`within` or `within_frame` blocks."
517
+ raise Capybara::ScopeError, '`switch_to_window` is not supposed to be invoked from ' \
518
+ '`within` or `within_frame` blocks.'
479
519
  end
480
520
 
481
- _switch_to_window(window, options, &window_locator)
521
+ _switch_to_window(window, **options, &window_locator)
482
522
  end
483
523
 
484
524
  ##
@@ -486,20 +526,20 @@ module Capybara
486
526
  #
487
527
  # 1. Switches to the given window (it can be located by window instance/lambda/string).
488
528
  # 2. Executes the given block (within window located at previous step).
489
- # 3. Switches back (this step will be invoked even if exception will happen at second step)
529
+ # 3. Switches back (this step will be invoked even if an exception occurs at the second step).
490
530
  #
491
531
  # @overload within_window(window) { do_something }
492
- # @param window [Capybara::Window] instance of `Capybara::Window` class
532
+ # @param window [Capybara::Window] instance of {Capybara::Window} class
493
533
  # that will be switched to
494
534
  # @raise [driver#no_such_window_error] if nonexistent (e.g. closed) window was passed
495
535
  # @overload within_window(proc_or_lambda) { do_something }
496
- # @param lambda [Proc] lambda. First window for which lambda
536
+ # @param lambda [Proc] First window for which lambda
497
537
  # returns a value other than false or nil will be switched to.
498
538
  # @example
499
539
  # within_window(->{ page.title == 'Page title' }) { click_button 'Submit' }
500
540
  # @raise [Capybara::WindowError] if no window matching lambda was found
501
541
  #
502
- # @raise [Capybara::ScopeError] if this method is invoked inside `within_frame` method
542
+ # @raise [Capybara::ScopeError] if this method is invoked inside {#within_frame} method
503
543
  # @return value returned by the block
504
544
  #
505
545
  def within_window(window_or_proc)
@@ -512,7 +552,7 @@ module Capybara
512
552
  when Proc
513
553
  _switch_to_window { window_or_proc.call }
514
554
  else
515
- raise ArgumentError("`#within_window` requires a `Capybara::Window` instance or a lambda")
555
+ raise ArgumentError, '`#within_window` requires a `Capybara::Window` instance or a lambda'
516
556
  end
517
557
 
518
558
  begin
@@ -529,11 +569,11 @@ module Capybara
529
569
  # Get the window that has been opened by the passed block.
530
570
  # It will wait for it to be opened (in the same way as other Capybara methods wait).
531
571
  # It's better to use this method than `windows.last`
532
- # {https://dvcs.w3.org/hg/webdriver/raw-file/default/webdriver-spec.html#h_note_10 as order of windows isn't defined in some drivers}
572
+ # {https://dvcs.w3.org/hg/webdriver/raw-file/default/webdriver-spec.html#h_note_10 as order of windows isn't defined in some drivers}.
533
573
  #
534
574
  # @overload window_opened_by(**options, &block)
535
575
  # @param options [Hash]
536
- # @option options [Numeric] :wait (Capybara.default_max_wait_time) maximum wait time
576
+ # @option options [Numeric] :wait maximum wait time. Defaults to {Capybara.configure default_max_wait_time}
537
577
  # @return [Capybara::Window] the window that has been opened within a block
538
578
  # @raise [Capybara::WindowError] if block passed to window hasn't opened window
539
579
  # or opened more than one window
@@ -542,11 +582,10 @@ module Capybara
542
582
  old_handles = driver.window_handles
543
583
  yield
544
584
 
545
- wait_time = Capybara::Queries::BaseQuery.wait(options, config.default_max_wait_time)
546
- document.synchronize(wait_time, errors: [Capybara::WindowError]) do
585
+ synchronize_windows(options) do
547
586
  opened_handles = (driver.window_handles - old_handles)
548
587
  if opened_handles.size != 1
549
- raise Capybara::WindowError, "block passed to #window_opened_by "\
588
+ raise Capybara::WindowError, 'block passed to #window_opened_by ' \
550
589
  "opened #{opened_handles.size} windows instead of 1"
551
590
  end
552
591
  Window.new(self, opened_handles.first)
@@ -556,11 +595,11 @@ module Capybara
556
595
  ##
557
596
  #
558
597
  # Execute the given script, not returning a result. This is useful for scripts that return
559
- # complex objects, such as jQuery statements. +execute_script+ should be used over
560
- # +evaluate_script+ whenever possible.
598
+ # complex objects, such as jQuery statements. {#execute_script} should be used over
599
+ # {#evaluate_script} whenever possible.
561
600
  #
562
601
  # @param [String] script A string of JavaScript to execute
563
- # @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
602
+ # @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
564
603
  #
565
604
  def execute_script(script, *args)
566
605
  @touched = true
@@ -570,15 +609,16 @@ module Capybara
570
609
  ##
571
610
  #
572
611
  # Evaluate the given JavaScript and return the result. Be careful when using this with
573
- # scripts that return complex objects, such as jQuery statements. +execute_script+ might
612
+ # scripts that return complex objects, such as jQuery statements. {#execute_script} might
574
613
  # be a better alternative.
575
614
  #
576
615
  # @param [String] script A string of JavaScript to evaluate
616
+ # @param args Optional arguments that will be passed to the script
577
617
  # @return [Object] The result of the evaluated JavaScript (may be driver specific)
578
618
  #
579
619
  def evaluate_script(script, *args)
580
620
  @touched = true
581
- result = driver.evaluate_script(script, *driver_args(args))
621
+ result = driver.evaluate_script(script.strip, *driver_args(args))
582
622
  element_script_result(result)
583
623
  end
584
624
 
@@ -587,6 +627,7 @@ module Capybara
587
627
  # Evaluate the given JavaScript and obtain the result from a callback function which will be passed as the last argument to the script.
588
628
  #
589
629
  # @param [String] script A string of JavaScript to evaluate
630
+ # @param args Optional arguments that will be passed to the script
590
631
  # @return [Object] The result of the evaluated JavaScript (may be driver specific)
591
632
  #
592
633
  def evaluate_async_script(script, *args)
@@ -600,17 +641,17 @@ module Capybara
600
641
  # Execute the block, accepting a alert.
601
642
  #
602
643
  # @!macro modal_params
603
- # Expects a block whose actions will trigger the display modal to appear
644
+ # Expects a block whose actions will trigger the display modal to appear.
604
645
  # @example
605
646
  # $0 do
606
647
  # click_link('link that triggers appearance of system modal')
607
648
  # end
608
649
  # @overload $0(text, **options, &blk)
609
- # @param text [String, Regexp] Text or regex to match against the text in the modal. If not provided any modal is matched
610
- # @option options [Numeric] :wait (Capybara.default_max_wait_time) Maximum time to wait for the modal to appear after executing the block.
650
+ # @param text [String, Regexp] Text or regex to match against the text in the modal. If not provided any modal is matched.
651
+ # @option options [Numeric] :wait Maximum time to wait for the modal to appear after executing the block. Defaults to {Capybara.configure default_max_wait_time}.
611
652
  # @yield Block whose actions will trigger the system modal
612
653
  # @overload $0(**options, &blk)
613
- # @option options [Numeric] :wait (Capybara.default_max_wait_time) Maximum time to wait for the modal to appear after executing the block.
654
+ # @option options [Numeric] :wait Maximum time to wait for the modal to appear after executing the block. Defaults to {Capybara.configure default_max_wait_time}.
614
655
  # @yield Block whose actions will trigger the system modal
615
656
  # @return [String] the message shown in the modal
616
657
  # @raise [Capybara::ModalNotFound] if modal dialog hasn't been found
@@ -662,19 +703,19 @@ module Capybara
662
703
 
663
704
  ##
664
705
  #
665
- # Save a snapshot of the page. If `Capybara.asset_host` is set it will inject `base` tag
666
- # pointing to `asset_host`.
706
+ # Save a snapshot of the page. If {Capybara.configure asset_host} is set it will inject `base` tag
707
+ # pointing to {Capybara.configure asset_host}.
667
708
  #
668
- # If invoked without arguments it will save file to `Capybara.save_path`
669
- # and file will be given randomly generated filename. If invoked with a relative path
670
- # the path will be relative to `Capybara.save_path`
709
+ # If invoked without arguments it will save file to {Capybara.configure save_path}
710
+ # and file will be given randomly generated filename. If invoked with a relative path
711
+ # the path will be relative to {Capybara.configure save_path}.
671
712
  #
672
713
  # @param [String] path the path to where it should be saved
673
714
  # @return [String] the path to which the file was saved
674
715
  #
675
716
  def save_page(path = nil)
676
- prepare_path(path, 'html').tap do |p|
677
- File.write(p, Capybara::Helpers.inject_asset_host(body, host: config.asset_host), mode: 'wb')
717
+ prepare_path(path, 'html').tap do |p_path|
718
+ File.write(p_path, Capybara::Helpers.inject_asset_host(body, host: config.asset_host), mode: 'wb')
678
719
  end
679
720
  end
680
721
 
@@ -682,46 +723,44 @@ module Capybara
682
723
  #
683
724
  # Save a snapshot of the page and open it in a browser for inspection.
684
725
  #
685
- # If invoked without arguments it will save file to `Capybara.save_path`
686
- # and file will be given randomly generated filename. If invoked with a relative path
687
- # the path will be relative to `Capybara.save_path`
726
+ # If invoked without arguments it will save file to {Capybara.configure save_path}
727
+ # and file will be given randomly generated filename. If invoked with a relative path
728
+ # the path will be relative to {Capybara.configure save_path}.
688
729
  #
689
730
  # @param [String] path the path to where it should be saved
690
731
  #
691
732
  def save_and_open_page(path = nil)
692
- save_page(path).tap { |p| open_file(p) }
733
+ save_page(path).tap { |s_path| open_file(s_path) }
693
734
  end
694
735
 
695
736
  ##
696
737
  #
697
738
  # Save a screenshot of page.
698
739
  #
699
- # If invoked without arguments it will save file to `Capybara.save_path`
700
- # and file will be given randomly generated filename. If invoked with a relative path
701
- # the path will be relative to `Capybara.save_path`
740
+ # If invoked without arguments it will save file to {Capybara.configure save_path}
741
+ # and file will be given randomly generated filename. If invoked with a relative path
742
+ # the path will be relative to {Capybara.configure save_path}.
702
743
  #
703
744
  # @param [String] path the path to where it should be saved
704
745
  # @param [Hash] options a customizable set of options
705
746
  # @return [String] the path to which the file was saved
706
747
  def save_screenshot(path = nil, **options)
707
- prepare_path(path, 'png').tap { |p| driver.save_screenshot(p, options) }
748
+ prepare_path(path, 'png').tap { |p_path| driver.save_screenshot(p_path, **options) }
708
749
  end
709
750
 
710
751
  ##
711
752
  #
712
753
  # Save a screenshot of the page and open it for inspection.
713
754
  #
714
- # If invoked without arguments it will save file to `Capybara.save_path`
715
- # and file will be given randomly generated filename. If invoked with a relative path
716
- # the path will be relative to `Capybara.save_path`
755
+ # If invoked without arguments it will save file to {Capybara.configure save_path}
756
+ # and file will be given randomly generated filename. If invoked with a relative path
757
+ # the path will be relative to {Capybara.configure save_path}.
717
758
  #
718
759
  # @param [String] path the path to where it should be saved
719
760
  # @param [Hash] options a customizable set of options
720
761
  #
721
762
  def save_and_open_screenshot(path = nil, **options)
722
- # rubocop:disable Lint/Debugger
723
- save_screenshot(path, options).tap { |p| open_file(p) }
724
- # rubocop:enable Lint/Debugger
763
+ save_screenshot(path, **options).tap { |s_path| open_file(s_path) }
725
764
  end
726
765
 
727
766
  def document
@@ -729,16 +768,20 @@ module Capybara
729
768
  end
730
769
 
731
770
  NODE_METHODS.each do |method|
732
- define_method method do |*args, &block|
733
- @touched = true
734
- current_scope.send(method, *args, &block)
735
- end
771
+ class_eval <<~METHOD, __FILE__, __LINE__ + 1
772
+ def #{method}(...)
773
+ @touched = true
774
+ current_scope.#{method}(...)
775
+ end
776
+ METHOD
736
777
  end
737
778
 
738
779
  DOCUMENT_METHODS.each do |method|
739
- define_method method do |*args, &block|
740
- document.send(method, *args, &block)
741
- end
780
+ class_eval <<~METHOD, __FILE__, __LINE__ + 1
781
+ def #{method}(...)
782
+ document.#{method}(...)
783
+ end
784
+ METHOD
742
785
  end
743
786
 
744
787
  def inspect
@@ -752,9 +795,9 @@ module Capybara
752
795
 
753
796
  ##
754
797
  #
755
- # Yield a block using a specific wait time
798
+ # Yield a block using a specific maximum wait time.
756
799
  #
757
- def using_wait_time(seconds)
800
+ def using_wait_time(seconds, &block)
758
801
  if Capybara.threadsafe
759
802
  begin
760
803
  previous_wait_time = config.default_max_wait_time
@@ -764,17 +807,18 @@ module Capybara
764
807
  config.default_max_wait_time = previous_wait_time
765
808
  end
766
809
  else
767
- Capybara.using_wait_time(seconds) { yield }
810
+ Capybara.using_wait_time(seconds, &block)
768
811
  end
769
812
  end
770
813
 
771
814
  ##
772
815
  #
773
- # Accepts a block to set the configuration options if Capybara.threadsafe == true. Note that some options only have an effect
774
- # if set at initialization time, so look at the configuration block that can be passed to the initializer too
816
+ # Accepts a block to set the configuration options if {Capybara.configure threadsafe} is `true`. Note that some options only have an effect
817
+ # if set at initialization time, so look at the configuration block that can be passed to the initializer too.
775
818
  #
776
819
  def configure
777
- raise "Session configuration is only supported when Capybara.threadsafe == true" unless Capybara.threadsafe
820
+ raise 'Session configuration is only supported when Capybara.threadsafe == true' unless Capybara.threadsafe
821
+
778
822
  yield config
779
823
  end
780
824
 
@@ -790,20 +834,24 @@ module Capybara
790
834
  end
791
835
  end
792
836
 
837
+ def server_url
838
+ @server&.base_url
839
+ end
840
+
793
841
  private
794
842
 
795
- @@instance_created = false
843
+ @@instance_created = false # rubocop:disable Style/ClassVars
796
844
 
797
845
  def driver_args(args)
798
846
  args.map { |arg| arg.is_a?(Capybara::Node::Element) ? arg.base : arg }
799
847
  end
800
848
 
801
849
  def accept_modal(type, text_or_options, options, &blk)
802
- driver.accept_modal(type, modal_options(text_or_options, options), &blk)
850
+ driver.accept_modal(type, **modal_options(text_or_options, **options), &blk)
803
851
  end
804
852
 
805
853
  def dismiss_modal(type, text_or_options, options, &blk)
806
- driver.dismiss_modal(type, modal_options(text_or_options, options), &blk)
854
+ driver.dismiss_modal(type, **modal_options(text_or_options, **options), &blk)
807
855
  end
808
856
 
809
857
  def modal_options(text = nil, **options)
@@ -813,18 +861,20 @@ module Capybara
813
861
  end
814
862
 
815
863
  def open_file(path)
816
- require "launchy"
864
+ require 'launchy'
817
865
  Launchy.open(path)
818
866
  rescue LoadError
819
867
  warn "File saved to #{path}.\nPlease install the launchy gem to open the file automatically."
820
868
  end
821
869
 
822
870
  def prepare_path(path, extension)
823
- File.expand_path(path || default_fn(extension), config.save_path).tap { |p| FileUtils.mkdir_p(File.dirname(p)) }
871
+ File.expand_path(path || default_fn(extension), config.save_path).tap do |p_path|
872
+ FileUtils.mkdir_p(File.dirname(p_path))
873
+ end
824
874
  end
825
875
 
826
876
  def default_fn(extension)
827
- timestamp = Time.new.strftime("%Y%m%d%H%M%S")
877
+ timestamp = Time.new.strftime('%Y%m%d%H%M%S')
828
878
  "capybara-#{timestamp}#{rand(10**10)}.#{extension}"
829
879
  end
830
880
 
@@ -835,9 +885,9 @@ module Capybara
835
885
  def element_script_result(arg)
836
886
  case arg
837
887
  when Array
838
- arg.map { |e| element_script_result(e) }
888
+ arg.map { |subarg| element_script_result(subarg) }
839
889
  when Hash
840
- arg.each { |k, v| arg[k] = element_script_result(v) }
890
+ arg.transform_values! { |value| element_script_result(value) }
841
891
  when Capybara::Driver::Node
842
892
  Capybara::Node::Element.new(self, arg, nil, nil)
843
893
  else
@@ -845,14 +895,18 @@ module Capybara
845
895
  end
846
896
  end
847
897
 
848
- def _find_frame(*args)
898
+ def adjust_server_port(uri)
899
+ uri.port ||= @server.port if @server && config.always_include_port
900
+ end
901
+
902
+ def _find_frame(*args, **kw_args)
849
903
  case args[0]
850
904
  when Capybara::Node::Element
851
905
  args[0]
852
- when String, Hash
853
- find(:frame, *args)
906
+ when String, nil
907
+ find(:frame, *args, **kw_args)
854
908
  when Symbol
855
- find(*args)
909
+ find(*args, **kw_args)
856
910
  when Integer
857
911
  idx = args[0]
858
912
  all(:frame, minimum: idx + 1)[idx]
@@ -861,31 +915,37 @@ module Capybara
861
915
  end
862
916
  end
863
917
 
864
- def _switch_to_window(window = nil, **options)
865
- raise Capybara::ScopeError, "Window cannot be switched inside a `within_frame` block" if scopes.include?(:frame)
866
- raise Capybara::ScopeError, "Window cannot be switch inside a `within` block" unless scopes.last.nil?
918
+ def _switch_to_window(window = nil, **options, &window_locator)
919
+ raise Capybara::ScopeError, 'Window cannot be switched inside a `within_frame` block' if scopes.include?(:frame)
920
+ raise Capybara::ScopeError, 'Window cannot be switched inside a `within` block' unless scopes.last.nil?
867
921
 
868
922
  if window
869
923
  driver.switch_to_window(window.handle)
870
924
  window
871
925
  else
872
- wait_time = Capybara::Queries::BaseQuery.wait(options, config.default_max_wait_time)
873
- document.synchronize(wait_time, errors: [Capybara::WindowError]) do
926
+ synchronize_windows(options) do
874
927
  original_window_handle = driver.current_window_handle
875
928
  begin
876
- driver.window_handles.each do |handle|
877
- driver.switch_to_window handle
878
- return Window.new(self, handle) if yield
879
- end
880
- rescue StandardError => e
881
- driver.switch_to_window(original_window_handle)
882
- raise e
883
- else
929
+ _switch_to_window_by_locator(&window_locator)
930
+ rescue StandardError
884
931
  driver.switch_to_window(original_window_handle)
885
- raise Capybara::WindowError, "Could not find a window matching block/lambda"
932
+ raise
886
933
  end
887
934
  end
888
935
  end
889
936
  end
937
+
938
+ def _switch_to_window_by_locator
939
+ driver.window_handles.each do |handle|
940
+ driver.switch_to_window handle
941
+ return Window.new(self, handle) if yield
942
+ end
943
+ raise Capybara::WindowError, 'Could not find a window matching block/lambda'
944
+ end
945
+
946
+ def synchronize_windows(options, &block)
947
+ wait_time = Capybara::Queries::BaseQuery.wait(options, config.default_max_wait_time)
948
+ document.synchronize(wait_time, errors: [Capybara::WindowError], &block)
949
+ end
890
950
  end
891
951
  end