capybara 2.18.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 (316) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -1
  3. data/History.md +945 -12
  4. data/License.txt +1 -1
  5. data/README.md +264 -90
  6. data/lib/capybara/config.rb +29 -57
  7. data/lib/capybara/cucumber.rb +2 -3
  8. data/lib/capybara/driver/base.rb +35 -18
  9. data/lib/capybara/driver/node.rb +40 -10
  10. data/lib/capybara/dsl.rb +10 -7
  11. data/lib/capybara/helpers.rb +70 -31
  12. data/lib/capybara/minitest/spec.rb +185 -83
  13. data/lib/capybara/minitest.rb +232 -112
  14. data/lib/capybara/node/actions.rb +274 -171
  15. data/lib/capybara/node/base.rb +42 -34
  16. data/lib/capybara/node/document.rb +15 -3
  17. data/lib/capybara/node/document_matchers.rb +19 -21
  18. data/lib/capybara/node/element.rb +362 -135
  19. data/lib/capybara/node/finders.rb +151 -137
  20. data/lib/capybara/node/matchers.rb +394 -209
  21. data/lib/capybara/node/simple.rb +59 -26
  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 +12 -9
  25. data/lib/capybara/queries/base_query.rb +39 -28
  26. data/lib/capybara/queries/current_path_query.rb +21 -27
  27. data/lib/capybara/queries/match_query.rb +14 -7
  28. data/lib/capybara/queries/selector_query.rb +659 -149
  29. data/lib/capybara/queries/sibling_query.rb +11 -9
  30. data/lib/capybara/queries/style_query.rb +45 -0
  31. data/lib/capybara/queries/text_query.rb +56 -38
  32. data/lib/capybara/queries/title_query.rb +8 -11
  33. data/lib/capybara/rack_test/browser.rb +114 -42
  34. data/lib/capybara/rack_test/css_handlers.rb +6 -4
  35. data/lib/capybara/rack_test/driver.rb +22 -17
  36. data/lib/capybara/rack_test/errors.rb +6 -0
  37. data/lib/capybara/rack_test/form.rb +93 -58
  38. data/lib/capybara/rack_test/node.rb +184 -81
  39. data/lib/capybara/rails.rb +3 -7
  40. data/lib/capybara/registration_container.rb +41 -0
  41. data/lib/capybara/registrations/drivers.rb +42 -0
  42. data/lib/capybara/registrations/patches/puma_ssl.rb +29 -0
  43. data/lib/capybara/registrations/servers.rb +66 -0
  44. data/lib/capybara/result.rb +97 -63
  45. data/lib/capybara/rspec/features.rb +17 -50
  46. data/lib/capybara/rspec/matcher_proxies.rb +52 -15
  47. data/lib/capybara/rspec/matchers/base.rb +113 -0
  48. data/lib/capybara/rspec/matchers/become_closed.rb +33 -0
  49. data/lib/capybara/rspec/matchers/compound.rb +88 -0
  50. data/lib/capybara/rspec/matchers/count_sugar.rb +37 -0
  51. data/lib/capybara/rspec/matchers/have_ancestor.rb +28 -0
  52. data/lib/capybara/rspec/matchers/have_current_path.rb +29 -0
  53. data/lib/capybara/rspec/matchers/have_selector.rb +69 -0
  54. data/lib/capybara/rspec/matchers/have_sibling.rb +27 -0
  55. data/lib/capybara/rspec/matchers/have_text.rb +33 -0
  56. data/lib/capybara/rspec/matchers/have_title.rb +29 -0
  57. data/lib/capybara/rspec/matchers/match_selector.rb +27 -0
  58. data/lib/capybara/rspec/matchers/match_style.rb +43 -0
  59. data/lib/capybara/rspec/matchers/spatial_sugar.rb +39 -0
  60. data/lib/capybara/rspec/matchers.rb +146 -310
  61. data/lib/capybara/rspec.rb +7 -11
  62. data/lib/capybara/selector/builders/css_builder.rb +84 -0
  63. data/lib/capybara/selector/builders/xpath_builder.rb +71 -0
  64. data/lib/capybara/selector/css.rb +85 -13
  65. data/lib/capybara/selector/definition/button.rb +68 -0
  66. data/lib/capybara/selector/definition/checkbox.rb +26 -0
  67. data/lib/capybara/selector/definition/css.rb +10 -0
  68. data/lib/capybara/selector/definition/datalist_input.rb +35 -0
  69. data/lib/capybara/selector/definition/datalist_option.rb +25 -0
  70. data/lib/capybara/selector/definition/element.rb +28 -0
  71. data/lib/capybara/selector/definition/field.rb +40 -0
  72. data/lib/capybara/selector/definition/fieldset.rb +14 -0
  73. data/lib/capybara/selector/definition/file_field.rb +13 -0
  74. data/lib/capybara/selector/definition/fillable_field.rb +33 -0
  75. data/lib/capybara/selector/definition/frame.rb +17 -0
  76. data/lib/capybara/selector/definition/id.rb +6 -0
  77. data/lib/capybara/selector/definition/label.rb +62 -0
  78. data/lib/capybara/selector/definition/link.rb +55 -0
  79. data/lib/capybara/selector/definition/link_or_button.rb +16 -0
  80. data/lib/capybara/selector/definition/option.rb +27 -0
  81. data/lib/capybara/selector/definition/radio_button.rb +27 -0
  82. data/lib/capybara/selector/definition/select.rb +81 -0
  83. data/lib/capybara/selector/definition/table.rb +109 -0
  84. data/lib/capybara/selector/definition/table_row.rb +21 -0
  85. data/lib/capybara/selector/definition/xpath.rb +5 -0
  86. data/lib/capybara/selector/definition.rb +280 -0
  87. data/lib/capybara/selector/filter.rb +2 -17
  88. data/lib/capybara/selector/filter_set.rb +80 -33
  89. data/lib/capybara/selector/filters/base.rb +50 -6
  90. data/lib/capybara/selector/filters/expression_filter.rb +8 -26
  91. data/lib/capybara/selector/filters/locator_filter.rb +29 -0
  92. data/lib/capybara/selector/filters/node_filter.rb +16 -12
  93. data/lib/capybara/selector/regexp_disassembler.rb +211 -0
  94. data/lib/capybara/selector/selector.rb +93 -210
  95. data/lib/capybara/selector/xpath_extensions.rb +17 -0
  96. data/lib/capybara/selector.rb +475 -523
  97. data/lib/capybara/selenium/atoms/getAttribute.min.js +1 -0
  98. data/lib/capybara/selenium/atoms/isDisplayed.min.js +1 -0
  99. data/lib/capybara/selenium/atoms/src/getAttribute.js +161 -0
  100. data/lib/capybara/selenium/atoms/src/isDisplayed.js +454 -0
  101. data/lib/capybara/selenium/driver.rb +298 -267
  102. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +117 -0
  103. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +128 -0
  104. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +84 -0
  105. data/lib/capybara/selenium/driver_specializations/internet_explorer_driver.rb +26 -0
  106. data/lib/capybara/selenium/driver_specializations/safari_driver.rb +24 -0
  107. data/lib/capybara/selenium/extensions/file_input_click_emulation.rb +34 -0
  108. data/lib/capybara/selenium/extensions/find.rb +110 -0
  109. data/lib/capybara/selenium/extensions/html5_drag.rb +229 -0
  110. data/lib/capybara/selenium/extensions/modifier_keys_stack.rb +28 -0
  111. data/lib/capybara/selenium/extensions/scroll.rb +76 -0
  112. data/lib/capybara/selenium/node.rb +517 -145
  113. data/lib/capybara/selenium/nodes/chrome_node.rb +125 -0
  114. data/lib/capybara/selenium/nodes/edge_node.rb +110 -0
  115. data/lib/capybara/selenium/nodes/firefox_node.rb +136 -0
  116. data/lib/capybara/selenium/nodes/ie_node.rb +22 -0
  117. data/lib/capybara/selenium/nodes/safari_node.rb +118 -0
  118. data/lib/capybara/selenium/patches/atoms.rb +18 -0
  119. data/lib/capybara/selenium/patches/is_displayed.rb +16 -0
  120. data/lib/capybara/selenium/patches/logs.rb +45 -0
  121. data/lib/capybara/selenium/patches/pause_duration_fix.rb +9 -0
  122. data/lib/capybara/selenium/patches/persistent_client.rb +20 -0
  123. data/lib/capybara/server/animation_disabler.rb +80 -0
  124. data/lib/capybara/server/checker.rb +44 -0
  125. data/lib/capybara/server/middleware.rb +71 -0
  126. data/lib/capybara/server.rb +59 -67
  127. data/lib/capybara/session/config.rb +81 -67
  128. data/lib/capybara/session/matchers.rb +28 -20
  129. data/lib/capybara/session.rb +337 -365
  130. data/lib/capybara/spec/public/jquery.js +5 -5
  131. data/lib/capybara/spec/public/offset.js +6 -0
  132. data/lib/capybara/spec/public/test.js +151 -12
  133. data/lib/capybara/spec/session/accept_alert_spec.rb +12 -11
  134. data/lib/capybara/spec/session/accept_confirm_spec.rb +6 -5
  135. data/lib/capybara/spec/session/accept_prompt_spec.rb +10 -10
  136. data/lib/capybara/spec/session/active_element_spec.rb +31 -0
  137. data/lib/capybara/spec/session/all_spec.rb +161 -57
  138. data/lib/capybara/spec/session/ancestor_spec.rb +27 -24
  139. data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +68 -38
  140. data/lib/capybara/spec/session/assert_current_path_spec.rb +75 -0
  141. data/lib/capybara/spec/session/assert_selector_spec.rb +143 -0
  142. data/lib/capybara/spec/session/assert_style_spec.rb +26 -0
  143. data/lib/capybara/spec/session/assert_text_spec.rb +258 -0
  144. data/lib/capybara/spec/session/{assert_title.rb → assert_title_spec.rb} +22 -12
  145. data/lib/capybara/spec/session/attach_file_spec.rb +144 -69
  146. data/lib/capybara/spec/session/body_spec.rb +12 -13
  147. data/lib/capybara/spec/session/check_spec.rb +117 -55
  148. data/lib/capybara/spec/session/choose_spec.rb +64 -31
  149. data/lib/capybara/spec/session/click_button_spec.rb +231 -173
  150. data/lib/capybara/spec/session/click_link_or_button_spec.rb +55 -35
  151. data/lib/capybara/spec/session/click_link_spec.rb +93 -58
  152. data/lib/capybara/spec/session/current_scope_spec.rb +12 -11
  153. data/lib/capybara/spec/session/current_url_spec.rb +57 -39
  154. data/lib/capybara/spec/session/dismiss_confirm_spec.rb +4 -4
  155. data/lib/capybara/spec/session/dismiss_prompt_spec.rb +3 -2
  156. data/lib/capybara/spec/session/element/{assert_match_selector.rb → assert_match_selector_spec.rb} +11 -9
  157. data/lib/capybara/spec/session/element/match_css_spec.rb +18 -10
  158. data/lib/capybara/spec/session/element/match_xpath_spec.rb +9 -7
  159. data/lib/capybara/spec/session/element/matches_selector_spec.rb +71 -57
  160. data/lib/capybara/spec/session/evaluate_async_script_spec.rb +8 -7
  161. data/lib/capybara/spec/session/evaluate_script_spec.rb +29 -8
  162. data/lib/capybara/spec/session/execute_script_spec.rb +10 -8
  163. data/lib/capybara/spec/session/fill_in_spec.rb +134 -43
  164. data/lib/capybara/spec/session/find_button_spec.rb +25 -24
  165. data/lib/capybara/spec/session/find_by_id_spec.rb +10 -9
  166. data/lib/capybara/spec/session/find_field_spec.rb +37 -41
  167. data/lib/capybara/spec/session/find_link_spec.rb +46 -17
  168. data/lib/capybara/spec/session/find_spec.rb +260 -145
  169. data/lib/capybara/spec/session/first_spec.rb +80 -52
  170. data/lib/capybara/spec/session/frame/frame_title_spec.rb +23 -0
  171. data/lib/capybara/spec/session/frame/frame_url_spec.rb +23 -0
  172. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +33 -20
  173. data/lib/capybara/spec/session/frame/within_frame_spec.rb +52 -32
  174. data/lib/capybara/spec/session/go_back_spec.rb +2 -1
  175. data/lib/capybara/spec/session/go_forward_spec.rb +2 -1
  176. data/lib/capybara/spec/session/has_all_selectors_spec.rb +31 -31
  177. data/lib/capybara/spec/session/has_ancestor_spec.rb +46 -0
  178. data/lib/capybara/spec/session/has_any_selectors_spec.rb +29 -0
  179. data/lib/capybara/spec/session/has_button_spec.rb +100 -13
  180. data/lib/capybara/spec/session/has_css_spec.rb +272 -137
  181. data/lib/capybara/spec/session/has_current_path_spec.rb +60 -61
  182. data/lib/capybara/spec/session/has_element_spec.rb +47 -0
  183. data/lib/capybara/spec/session/has_field_spec.rb +139 -59
  184. data/lib/capybara/spec/session/has_link_spec.rb +47 -6
  185. data/lib/capybara/spec/session/has_none_selectors_spec.rb +42 -40
  186. data/lib/capybara/spec/session/has_select_spec.rb +107 -72
  187. data/lib/capybara/spec/session/has_selector_spec.rb +120 -71
  188. data/lib/capybara/spec/session/has_sibling_spec.rb +50 -0
  189. data/lib/capybara/spec/session/has_table_spec.rb +183 -5
  190. data/lib/capybara/spec/session/has_text_spec.rb +106 -62
  191. data/lib/capybara/spec/session/has_title_spec.rb +20 -14
  192. data/lib/capybara/spec/session/has_xpath_spec.rb +57 -38
  193. data/lib/capybara/spec/session/{headers.rb → headers_spec.rb} +3 -2
  194. data/lib/capybara/spec/session/html_spec.rb +14 -6
  195. data/lib/capybara/spec/session/matches_style_spec.rb +37 -0
  196. data/lib/capybara/spec/session/node_spec.rb +1024 -153
  197. data/lib/capybara/spec/session/node_wrapper_spec.rb +39 -0
  198. data/lib/capybara/spec/session/refresh_spec.rb +12 -6
  199. data/lib/capybara/spec/session/reset_session_spec.rb +82 -35
  200. data/lib/capybara/spec/session/{response_code.rb → response_code_spec.rb} +2 -1
  201. data/lib/capybara/spec/session/save_and_open_page_spec.rb +3 -2
  202. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +8 -12
  203. data/lib/capybara/spec/session/save_page_spec.rb +42 -55
  204. data/lib/capybara/spec/session/save_screenshot_spec.rb +16 -14
  205. data/lib/capybara/spec/session/screenshot_spec.rb +2 -2
  206. data/lib/capybara/spec/session/scroll_spec.rb +119 -0
  207. data/lib/capybara/spec/session/select_spec.rb +107 -81
  208. data/lib/capybara/spec/session/selectors_spec.rb +52 -19
  209. data/lib/capybara/spec/session/sibling_spec.rb +10 -10
  210. data/lib/capybara/spec/session/text_spec.rb +37 -21
  211. data/lib/capybara/spec/session/title_spec.rb +17 -5
  212. data/lib/capybara/spec/session/uncheck_spec.rb +43 -23
  213. data/lib/capybara/spec/session/unselect_spec.rb +39 -38
  214. data/lib/capybara/spec/session/visit_spec.rb +85 -53
  215. data/lib/capybara/spec/session/window/become_closed_spec.rb +24 -20
  216. data/lib/capybara/spec/session/window/current_window_spec.rb +5 -3
  217. data/lib/capybara/spec/session/window/open_new_window_spec.rb +5 -3
  218. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +27 -22
  219. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +12 -6
  220. data/lib/capybara/spec/session/window/window_spec.rb +97 -63
  221. data/lib/capybara/spec/session/window/windows_spec.rb +12 -10
  222. data/lib/capybara/spec/session/window/within_window_spec.rb +31 -86
  223. data/lib/capybara/spec/session/within_spec.rb +83 -44
  224. data/lib/capybara/spec/spec_helper.rb +54 -44
  225. data/lib/capybara/spec/test_app.rb +158 -43
  226. data/lib/capybara/spec/views/animated.erb +49 -0
  227. data/lib/capybara/spec/views/form.erb +163 -42
  228. data/lib/capybara/spec/views/frame_child.erb +4 -3
  229. data/lib/capybara/spec/views/frame_one.erb +2 -1
  230. data/lib/capybara/spec/views/frame_parent.erb +1 -1
  231. data/lib/capybara/spec/views/frame_two.erb +1 -1
  232. data/lib/capybara/spec/views/initial_alert.erb +2 -1
  233. data/lib/capybara/spec/views/layout.erb +10 -0
  234. data/lib/capybara/spec/views/obscured.erb +47 -0
  235. data/lib/capybara/spec/views/offset.erb +33 -0
  236. data/lib/capybara/spec/views/path.erb +2 -2
  237. data/lib/capybara/spec/views/popup_one.erb +1 -1
  238. data/lib/capybara/spec/views/popup_two.erb +1 -1
  239. data/lib/capybara/spec/views/react.erb +45 -0
  240. data/lib/capybara/spec/views/scroll.erb +21 -0
  241. data/lib/capybara/spec/views/spatial.erb +31 -0
  242. data/lib/capybara/spec/views/tables.erb +68 -1
  243. data/lib/capybara/spec/views/with_animation.erb +81 -0
  244. data/lib/capybara/spec/views/with_base_tag.erb +2 -2
  245. data/lib/capybara/spec/views/with_dragula.erb +24 -0
  246. data/lib/capybara/spec/views/with_fixed_header_footer.erb +2 -1
  247. data/lib/capybara/spec/views/with_hover.erb +3 -2
  248. data/lib/capybara/spec/views/with_hover1.erb +10 -0
  249. data/lib/capybara/spec/views/with_html.erb +69 -12
  250. data/lib/capybara/spec/views/with_html5_svg.erb +20 -0
  251. data/lib/capybara/spec/views/with_jquery_animation.erb +24 -0
  252. data/lib/capybara/spec/views/with_js.erb +30 -5
  253. data/lib/capybara/spec/views/with_jstree.erb +26 -0
  254. data/lib/capybara/spec/views/with_namespace.erb +21 -0
  255. data/lib/capybara/spec/views/with_scope.erb +2 -2
  256. data/lib/capybara/spec/views/with_scope_other.erb +6 -0
  257. data/lib/capybara/spec/views/with_shadow.erb +31 -0
  258. data/lib/capybara/spec/views/with_slow_unload.erb +2 -1
  259. data/lib/capybara/spec/views/with_sortable_js.erb +21 -0
  260. data/lib/capybara/spec/views/with_unload_alert.erb +1 -0
  261. data/lib/capybara/spec/views/with_windows.erb +1 -1
  262. data/lib/capybara/spec/views/within_frames.erb +5 -2
  263. data/lib/capybara/version.rb +2 -1
  264. data/lib/capybara/window.rb +36 -34
  265. data/lib/capybara.rb +134 -107
  266. data/spec/basic_node_spec.rb +60 -34
  267. data/spec/capybara_spec.rb +63 -88
  268. data/spec/counter_spec.rb +35 -0
  269. data/spec/css_builder_spec.rb +101 -0
  270. data/spec/css_splitter_spec.rb +38 -0
  271. data/spec/dsl_spec.rb +85 -64
  272. data/spec/filter_set_spec.rb +27 -9
  273. data/spec/fixtures/certificate.pem +25 -0
  274. data/spec/fixtures/key.pem +27 -0
  275. data/spec/fixtures/selenium_driver_rspec_failure.rb +6 -5
  276. data/spec/fixtures/selenium_driver_rspec_success.rb +6 -5
  277. data/spec/minitest_spec.rb +52 -7
  278. data/spec/minitest_spec_spec.rb +94 -63
  279. data/spec/per_session_config_spec.rb +14 -13
  280. data/spec/rack_test_spec.rb +194 -125
  281. data/spec/regexp_dissassembler_spec.rb +250 -0
  282. data/spec/result_spec.rb +111 -50
  283. data/spec/rspec/features_spec.rb +37 -31
  284. data/spec/rspec/scenarios_spec.rb +10 -8
  285. data/spec/rspec/shared_spec_matchers.rb +473 -422
  286. data/spec/rspec/views_spec.rb +5 -3
  287. data/spec/rspec_matchers_spec.rb +52 -11
  288. data/spec/rspec_spec.rb +109 -89
  289. data/spec/sauce_spec_chrome.rb +43 -0
  290. data/spec/selector_spec.rb +397 -68
  291. data/spec/selenium_spec_chrome.rb +187 -40
  292. data/spec/selenium_spec_chrome_remote.rb +96 -0
  293. data/spec/selenium_spec_edge.rb +60 -0
  294. data/spec/selenium_spec_firefox.rb +201 -41
  295. data/spec/selenium_spec_firefox_remote.rb +94 -0
  296. data/spec/selenium_spec_ie.rb +149 -0
  297. data/spec/selenium_spec_safari.rb +162 -0
  298. data/spec/server_spec.rb +213 -102
  299. data/spec/session_spec.rb +53 -16
  300. data/spec/shared_selenium_node.rb +79 -0
  301. data/spec/shared_selenium_session.rb +473 -122
  302. data/spec/spec_helper.rb +126 -7
  303. data/spec/whitespace_normalizer_spec.rb +54 -0
  304. data/spec/xpath_builder_spec.rb +93 -0
  305. metadata +355 -73
  306. data/.yard/templates_custom/default/class/html/selectors.erb +0 -38
  307. data/.yard/templates_custom/default/class/html/setup.rb +0 -17
  308. data/.yard/yard_extensions.rb +0 -78
  309. data/lib/capybara/query.rb +0 -7
  310. data/lib/capybara/rspec/compound.rb +0 -95
  311. data/lib/capybara/spec/session/assert_current_path.rb +0 -72
  312. data/lib/capybara/spec/session/assert_selector.rb +0 -148
  313. data/lib/capybara/spec/session/assert_text.rb +0 -234
  314. data/lib/capybara/spec/session/source_spec.rb +0 -0
  315. data/lib/capybara/spec/views/with_title.erb +0 -5
  316. data/spec/selenium_spec_marionette.rb +0 -127
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'capybara/session/matchers'
3
4
  require 'addressable/uri'
4
5
 
5
6
  module Capybara
6
-
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,85 +17,90 @@ 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 delegate 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
- NODE_METHODS = [
42
- :all, :first, :attach_file, :text, :check, :choose,
43
- :click_link_or_button, :click_button, :click_link, :field_labeled,
44
- :fill_in, :find, :find_all, :find_button, :find_by_id, :find_field, :find_link,
45
- :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_link?, :has_no_link?, :has_button?, :has_no_button?, :has_field?,
48
- :has_no_field?, :has_checked_field?, :has_unchecked_field?,
49
- :has_no_table?, :has_table?, :unselect, :has_select?, :has_no_select?,
50
- :has_selector?, :has_no_selector?, :click_on, :has_no_checked_field?,
51
- :has_no_unchecked_field?, :query, :assert_selector, :assert_no_selector,
52
- :assert_all_of_selectors, :assert_none_of_selectors,
53
- :refute_selector, :assert_text, :assert_no_text
54
- ]
41
+ NODE_METHODS = %i[
42
+ all first attach_file text check choose scroll_to scroll_by
43
+ click double_click right_click
44
+ click_link_or_button click_button click_link
45
+ fill_in find find_all find_button find_by_id find_field find_link
46
+ has_content? has_text? has_css? has_no_content? has_no_text?
47
+ has_no_css? has_no_xpath? has_xpath? select uncheck
48
+ has_element? has_no_element?
49
+ has_link? has_no_link? has_button? has_no_button? has_field?
50
+ has_no_field? has_checked_field? has_unchecked_field?
51
+ has_no_table? has_table? unselect has_select? has_no_select?
52
+ has_selector? has_no_selector? click_on has_no_checked_field?
53
+ has_no_unchecked_field? query assert_selector assert_no_selector
54
+ assert_all_of_selectors assert_none_of_selectors assert_any_of_selectors
55
+ refute_selector assert_text assert_no_text
56
+ ].freeze
55
57
  # @api private
56
- DOCUMENT_METHODS = [
57
- :title, :assert_title, :assert_no_title, :has_title?, :has_no_title?
58
- ]
59
- SESSION_METHODS = [
60
- :body, :html, :source, :current_url, :current_host, :current_path,
61
- :execute_script, :evaluate_script, :visit, :refresh, :go_back, :go_forward,
62
- :within, :within_element, :within_fieldset, :within_table, :within_frame, :switch_to_frame,
63
- :current_window, :windows, :open_new_window, :switch_to_window, :within_window, :window_opened_by,
64
- :save_page, :save_and_open_page, :save_screenshot,
65
- :save_and_open_screenshot, :reset_session!, :response_headers,
66
- :status_code, :current_scope,
67
- :assert_current_path, :assert_no_current_path, :has_current_path?, :has_no_current_path?
68
- ] + DOCUMENT_METHODS
69
- MODAL_METHODS = [
70
- :accept_alert, :accept_confirm, :dismiss_confirm, :accept_prompt,
71
- :dismiss_prompt
72
- ]
58
+ DOCUMENT_METHODS = %i[
59
+ title assert_title assert_no_title has_title? has_no_title?
60
+ ].freeze
61
+ SESSION_METHODS = %i[
62
+ body html source current_url current_host current_path
63
+ execute_script evaluate_script evaluate_async_script visit refresh go_back go_forward send_keys
64
+ within within_element within_fieldset within_table within_frame switch_to_frame
65
+ current_window windows open_new_window switch_to_window within_window window_opened_by
66
+ save_page save_and_open_page save_screenshot
67
+ save_and_open_screenshot reset_session! response_headers
68
+ status_code current_scope
69
+ assert_current_path assert_no_current_path has_current_path? has_no_current_path?
70
+ ].freeze + DOCUMENT_METHODS
71
+ MODAL_METHODS = %i[
72
+ accept_alert accept_confirm dismiss_confirm accept_prompt dismiss_prompt
73
+ ].freeze
73
74
  DSL_METHODS = NODE_METHODS + SESSION_METHODS + MODAL_METHODS
74
75
 
75
76
  attr_reader :mode, :app, :server
76
77
  attr_accessor :synchronized
77
78
 
78
- def initialize(mode, app=nil)
79
- raise TypeError, "The second parameter to Session::new should be a rack app if passed." if app && !app.respond_to?(:call)
80
- @@instance_created = true
79
+ def initialize(mode, app = nil)
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
81
85
  @mode = mode
82
86
  @app = app
83
87
  if block_given?
84
- raise "A configuration block is only accepted when Capybara.threadsafe == true" unless Capybara.threadsafe
85
- yield config if block_given?
88
+ raise 'A configuration block is only accepted when Capybara.threadsafe == true' unless Capybara.threadsafe
89
+
90
+ yield config
86
91
  end
87
- if config.run_server and @app and driver.needs_server?
88
- @server = Capybara::Server.new(@app, config.server_port, config.server_host, config.server_errors).boot
89
- else
90
- @server = nil
92
+ @server = if config.run_server && @app && driver.needs_server?
93
+ server_options = { port: config.server_port, host: config.server_host, reportable_errors: config.server_errors }
94
+ server_options[:extra_middleware] = [Capybara::Server::AnimationDisabler] if config.disable_animation
95
+ Capybara::Server.new(@app, **server_options).boot
91
96
  end
92
97
  @touched = false
93
98
  end
94
99
 
95
100
  def driver
96
101
  @driver ||= begin
97
- unless Capybara.drivers.has_key?(mode)
98
- other_drivers = Capybara.drivers.keys.map { |key| key.inspect }
102
+ unless Capybara.drivers[mode]
103
+ other_drivers = Capybara.drivers.names.map(&:inspect)
99
104
  raise Capybara::DriverNotFoundError, "no driver called #{mode.inspect} was found, available drivers: #{other_drivers.join(', ')}"
100
105
  end
101
106
  driver = Capybara.drivers[mode].call(app)
@@ -106,28 +111,30 @@ module Capybara
106
111
 
107
112
  ##
108
113
  #
109
- # 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).
110
115
  #
111
116
  # This method does not:
112
117
  #
113
- # * accept modal dialogs if they are present (Selenium driver now does, others may not)
114
- # * clear browser cache/HTML 5 local storage/IndexedDB/Web SQL database/etc.
115
- # * 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
116
121
  #
117
122
  # as doing so will result in performance downsides and it's not needed to do everything from the list above for most apps.
118
123
  #
119
124
  # If you want to do anything from the list above on a general basis you can:
120
125
  #
121
- # * write RSpec/Cucumber/etc. after hook
122
- # * monkeypatch this method
123
- # * use Ruby's `prepend` method
126
+ # * write RSpec/Cucumber/etc. after hook
127
+ # * monkeypatch this method
128
+ # * use Ruby's `prepend` method
124
129
  #
125
130
  def reset!
126
131
  if @touched
127
132
  driver.reset!
128
133
  @touched = false
134
+ switch_to_frame(:top) rescue nil # rubocop:disable Style/RescueModifier
135
+ @scopes = [nil]
129
136
  end
130
- @server.wait_for_pending_requests if @server
137
+ @server&.wait_for_pending_requests
131
138
  raise_server_error!
132
139
  end
133
140
  alias_method :cleanup!, :reset!
@@ -135,29 +142,39 @@ module Capybara
135
142
 
136
143
  ##
137
144
  #
138
- # 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.
139
157
  #
140
158
  def raise_server_error!
141
- if @server and @server.error
142
- # Force an explanation for the error being raised as the exception cause
143
- begin
144
- if config.raise_server_errors
145
- raise CapybaraError, "Your application server raised an error - It has been raised in your test code because Capybara.raise_server_errors == true"
146
- end
147
- rescue CapybaraError
148
- #needed to get the cause set correctly in JRuby -- otherwise we could just do raise @server.error
149
- raise @server.error, @server.error.message, @server.error.backtrace
150
- ensure
151
- @server.reset_error!
159
+ return unless @server&.error
160
+
161
+ # Force an explanation for the error being raised as the exception cause
162
+ begin
163
+ if config.raise_server_errors
164
+ raise CapybaraError, 'Your application server raised an error - It has been raised in your test code because Capybara.raise_server_errors == true'
152
165
  end
166
+ rescue CapybaraError => capy_error # rubocop:disable Naming/RescuedExceptionsVariableName
167
+ raise @server.error, cause: capy_error
168
+ ensure
169
+ @server.reset_error!
153
170
  end
154
171
  end
155
172
 
156
173
  ##
157
174
  #
158
- # 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).
159
176
  #
160
- # @return [Hash{String => String}] A hash of response headers.
177
+ # @return [Hash<String, String>] A hash of response headers.
161
178
  #
162
179
  def response_headers
163
180
  driver.response_headers
@@ -165,7 +182,7 @@ module Capybara
165
182
 
166
183
  ##
167
184
  #
168
- # 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).
169
186
  #
170
187
  # @return [Integer] Current HTTP status code
171
188
  #
@@ -178,7 +195,7 @@ module Capybara
178
195
  # @return [String] A snapshot of the DOM of the current document, as it looks right now (potentially modified by JavaScript).
179
196
  #
180
197
  def html
181
- driver.html
198
+ driver.html || ''
182
199
  end
183
200
  alias_method :body, :html
184
201
  alias_method :source, :html
@@ -191,13 +208,11 @@ module Capybara
191
208
  # Addressable parsing is more lenient than URI
192
209
  uri = ::Addressable::URI.parse(current_url)
193
210
 
194
- # If current_url ends up being nil, won't be able to call .path on a NilClass.
195
- return nil if uri.nil?
196
-
197
211
  # Addressable doesn't support opaque URIs - we want nil here
198
- return nil if uri.scheme == "about"
199
- path = uri.path
200
- path if path and not path.empty?
212
+ return nil if uri&.scheme == 'about'
213
+
214
+ path = uri&.path
215
+ path unless path&.empty?
201
216
  end
202
217
 
203
218
  ##
@@ -227,13 +242,13 @@ module Capybara
227
242
  #
228
243
  # For drivers which can run against an external application, such as the selenium driver
229
244
  # giving an absolute URL will navigate to that page. This allows testing applications
230
- # 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
231
246
  # remote server the default. For example:
232
247
  #
233
248
  # Capybara.app_host = 'http://google.com'
234
249
  # session.visit('/') # visits the google homepage
235
250
  #
236
- # 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
237
252
  # a rack application, then the port that the rack application is running on will automatically
238
253
  # be inserted into the URL. Supposing the app is running on port `4567`, doing something like:
239
254
  #
@@ -248,27 +263,19 @@ module Capybara
248
263
  @touched = true
249
264
 
250
265
  visit_uri = ::Addressable::URI.parse(visit_uri.to_s)
266
+ base_uri = ::Addressable::URI.parse(config.app_host || server_url)
251
267
 
252
- uri_base = if @server
253
- ::Addressable::URI.parse(config.app_host || "http://#{@server.host}:#{@server.port}")
254
- else
255
- config.app_host && ::Addressable::URI.parse(config.app_host)
256
- end
257
-
258
- if uri_base && [nil, 'http', 'https'].include?(visit_uri.scheme)
268
+ if base_uri && [nil, 'http', 'https'].include?(visit_uri.scheme)
259
269
  if visit_uri.relative?
260
- uri_base.port ||= @server.port if @server && config.always_include_port
261
-
262
- visit_uri_parts = visit_uri.to_hash.delete_if { |k,v| v.nil? }
270
+ visit_uri_parts = visit_uri.to_hash.compact
263
271
 
264
272
  # Useful to people deploying to a subdirectory
265
273
  # and/or single page apps where only the url fragment changes
266
- visit_uri_parts[:path] = uri_base.path + visit_uri.path
274
+ visit_uri_parts[:path] = base_uri.path + visit_uri.path
267
275
 
268
- visit_uri = uri_base.merge(visit_uri_parts)
269
- else
270
- visit_uri.port ||= @server.port if @server && config.always_include_port
276
+ visit_uri = base_uri.merge(visit_uri_parts)
271
277
  end
278
+ adjust_server_port(visit_uri)
272
279
  end
273
280
 
274
281
  driver.visit(visit_uri.to_s)
@@ -276,7 +283,7 @@ module Capybara
276
283
 
277
284
  ##
278
285
  #
279
- # Refresh the page
286
+ # Refresh the page.
280
287
  #
281
288
  def refresh
282
289
  raise_server_error!
@@ -300,9 +307,27 @@ module Capybara
300
307
  end
301
308
 
302
309
  ##
310
+ # @!method send_keys
311
+ # @see Capybara::Node::Element#send_keys
303
312
  #
304
- # Executes the given block within the context of a node. `within` takes the
305
- # same options as `find`, as well as a block. For the duration of the
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
+
327
+ ##
328
+ #
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
306
331
  # block, any command to Capybara will be handled as though it were scoped
307
332
  # to the given element.
308
333
  #
@@ -310,18 +335,18 @@ module Capybara
310
335
  # fill_in('Street', with: '12 Main Street')
311
336
  # end
312
337
  #
313
- # Just as with `find`, if multiple elements match the selector given to
314
- # `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
315
340
  # behaviour can be controlled through the `:match` and `:exact` options.
316
341
  #
317
342
  # It is possible to omit the first parameter, in that case, the selector is
318
- # assumed to be of the type set in Capybara.default_selector.
343
+ # assumed to be of the type set in {Capybara.configure default_selector}.
319
344
  #
320
345
  # within('div#delivery-address') do
321
346
  # fill_in('Street', with: '12 Main Street')
322
347
  # end
323
348
  #
324
- # 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
325
350
  # chaining:
326
351
  #
327
352
  # find('div#delivery-address').fill_in('Street', with: '12 Main Street')
@@ -334,11 +359,11 @@ module Capybara
334
359
  #
335
360
  # @raise [Capybara::ElementNotFound] If the scope can't be found before time expires
336
361
  #
337
- def within(*args)
338
- new_scope = if args.first.is_a?(Capybara::Node::Base) then args.first else find(*args) end
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)
339
364
  begin
340
365
  scopes.push(new_scope)
341
- yield
366
+ yield new_scope if block_given?
342
367
  ensure
343
368
  scopes.pop
344
369
  end
@@ -351,10 +376,8 @@ module Capybara
351
376
  #
352
377
  # @param [String] locator Id or legend of the fieldset
353
378
  #
354
- def within_fieldset(locator)
355
- within :fieldset, locator do
356
- yield
357
- end
379
+ def within_fieldset(locator, &block)
380
+ within(:fieldset, locator, &block)
358
381
  end
359
382
 
360
383
  ##
@@ -363,26 +386,24 @@ module Capybara
363
386
  #
364
387
  # @param [String] locator Id or caption of the table
365
388
  #
366
- def within_table(locator)
367
- within :table, locator do
368
- yield
369
- end
389
+ def within_table(locator, &block)
390
+ within(:table, locator, &block)
370
391
  end
371
392
 
372
393
  ##
373
394
  #
374
- # Switch to the given frame
395
+ # Switch to the given frame.
375
396
  #
376
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.
377
- # 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.
378
399
  # May not be supported by all drivers.
379
400
  #
380
401
  # @overload switch_to_frame(element)
381
- # @param [Capybara::Node::Element] iframe/frame element to switch to
382
- # @overload switch_to_frame(:parent)
383
- # Switch to the parent element
384
- # @overload switch_to_frame(:top)
385
- # 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
386
407
  #
387
408
  def switch_to_frame(frame)
388
409
  case frame
@@ -390,18 +411,25 @@ module Capybara
390
411
  driver.switch_to_frame(frame)
391
412
  scopes.push(:frame)
392
413
  when :parent
393
- raise Capybara::ScopeError, "`switch_to_frame(:parent)` cannot be called from inside a descendant frame's "\
394
- "`within` block." if scopes.last() != :frame
414
+ if scopes.last != :frame
415
+ raise Capybara::ScopeError, "`switch_to_frame(:parent)` cannot be called from inside a descendant frame's " \
416
+ '`within` block.'
417
+ end
395
418
  scopes.pop
396
419
  driver.switch_to_frame(:parent)
397
420
  when :top
398
421
  idx = scopes.index(:frame)
422
+ top_level_scopes = [:frame, nil]
399
423
  if idx
400
- raise Capybara::ScopeError, "`switch_to_frame(:top)` cannot be called from inside a descendant frame's "\
401
- "`within` block." if scopes.slice(idx..-1).any? {|scope| ![:frame, nil].include?(scope)}
402
- scopes.slice!(idx..-1)
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.'
427
+ end
428
+ scopes.slice!(idx..)
403
429
  driver.switch_to_frame(:top)
404
430
  end
431
+ else
432
+ raise ArgumentError, 'You must provide a frame element, :parent, or :top when calling switch_to_frame'
405
433
  end
406
434
  end
407
435
 
@@ -412,35 +440,17 @@ module Capybara
412
440
  #
413
441
  # @overload within_frame(element)
414
442
  # @param [Capybara::Node::Element] frame element
415
- # @overload within_frame([kind = :frame], locator, options = {})
416
- # @param [Symobl] kind Optional selector type (:css, :xpath, :field, etc.) - Defaults to :frame
443
+ # @overload within_frame([kind = :frame], locator, **options)
444
+ # @param [Symbol] kind Optional selector type (:frame, :css, :xpath, etc.) - Defaults to :frame
417
445
  # @param [String] locator The locator for the given selector kind. For :frame this is the name/id of a frame/iframe element
418
446
  # @overload within_frame(index)
419
447
  # @param [Integer] index index of a frame (0 based)
420
- def within_frame(*args)
421
- frame = _find_frame(*args)
422
-
448
+ def within_frame(*args, **kw_args)
449
+ switch_to_frame(_find_frame(*args, **kw_args))
423
450
  begin
424
- switch_to_frame(frame)
425
- begin
426
- yield
427
- ensure
428
- switch_to_frame(:parent)
429
- end
430
- rescue Capybara::NotSupportedByDriverError
431
- # Support older driver frame API for now
432
- if driver.respond_to?(:within_frame)
433
- begin
434
- scopes.push(:frame)
435
- driver.within_frame(frame) do
436
- yield
437
- end
438
- ensure
439
- scopes.pop
440
- end
441
- else
442
- raise
443
- end
451
+ yield if block_given?
452
+ ensure
453
+ switch_to_frame(:parent)
444
454
  end
445
455
  end
446
456
 
@@ -465,22 +475,28 @@ module Capybara
465
475
  end
466
476
 
467
477
  ##
468
- # Open new window.
469
- # 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.
470
480
  # It should be switched to explicitly.
471
481
  #
472
482
  # @return [Capybara::Window] window that has been opened
473
483
  #
474
- def open_new_window
484
+ def open_new_window(kind = :tab)
475
485
  window_opened_by do
476
- 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
477
491
  end
478
492
  end
479
493
 
480
494
  ##
495
+ # Switch to the given window.
496
+ #
481
497
  # @overload switch_to_window(&block)
482
498
  # Switches to the first window for which given block returns a value other than false or nil.
483
- # 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.
484
500
  # @example
485
501
  # window = switch_to_window { title == 'Page title' }
486
502
  # @raise [Capybara::WindowError] if no window matches given block
@@ -489,24 +505,20 @@ module Capybara
489
505
  # @raise [Capybara::Driver::Base#no_such_window_error] if nonexistent (e.g. closed) window was passed
490
506
  #
491
507
  # @return [Capybara::Window] window that has been switched to
492
- # @raise [Capybara::ScopeError] if this method is invoked inside `within` or
493
- # `within_frame` methods
508
+ # @raise [Capybara::ScopeError] if this method is invoked inside {#within} or
509
+ # {#within_frame} methods
494
510
  # @raise [ArgumentError] if both or neither arguments were provided
495
511
  #
496
- def switch_to_window(window = nil, options= {}, &window_locator)
497
- options, window = window, nil if window.is_a? Hash
498
-
499
- block_given = block_given?
500
- if window && block_given
501
- raise ArgumentError, "`switch_to_window` can take either a block or a window, not both"
502
- elsif !window && !block_given
503
- raise ArgumentError, "`switch_to_window`: either window or block should be provided"
504
- elsif !scopes.last.nil?
505
- raise Capybara::ScopeError, "`switch_to_window` is not supposed to be invoked from "\
506
- "`within` or `within_frame` blocks."
512
+ def switch_to_window(window = nil, **options, &window_locator)
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
+
516
+ unless scopes.last.nil?
517
+ raise Capybara::ScopeError, '`switch_to_window` is not supposed to be invoked from ' \
518
+ '`within` or `within_frame` blocks.'
507
519
  end
508
520
 
509
- _switch_to_window(window, options, &window_locator)
521
+ _switch_to_window(window, **options, &window_locator)
510
522
  end
511
523
 
512
524
  ##
@@ -514,63 +526,42 @@ module Capybara
514
526
  #
515
527
  # 1. Switches to the given window (it can be located by window instance/lambda/string).
516
528
  # 2. Executes the given block (within window located at previous step).
517
- # 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).
518
530
  #
519
531
  # @overload within_window(window) { do_something }
520
- # @param window [Capybara::Window] instance of `Capybara::Window` class
532
+ # @param window [Capybara::Window] instance of {Capybara::Window} class
521
533
  # that will be switched to
522
534
  # @raise [driver#no_such_window_error] if nonexistent (e.g. closed) window was passed
523
535
  # @overload within_window(proc_or_lambda) { do_something }
524
- # @param lambda [Proc] lambda. First window for which lambda
536
+ # @param lambda [Proc] First window for which lambda
525
537
  # returns a value other than false or nil will be switched to.
526
538
  # @example
527
539
  # within_window(->{ page.title == 'Page title' }) { click_button 'Submit' }
528
540
  # @raise [Capybara::WindowError] if no window matching lambda was found
529
- # @overload within_window(string) { do_something }
530
- # @deprecated Pass window or lambda instead
531
- # @param [String] handle, name, url or title of the window
532
541
  #
533
- # @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
534
543
  # @return value returned by the block
535
544
  #
536
- def within_window(window_or_handle)
537
- if window_or_handle.instance_of?(Capybara::Window)
538
- original = current_window
539
- scopes << nil
540
- begin
541
- _switch_to_window(window_or_handle) unless original == window_or_handle
542
- begin
543
- yield
544
- ensure
545
- _switch_to_window(original) unless original == window_or_handle
546
- end
547
- ensure
548
- scopes.pop
549
- end
550
- elsif window_or_handle.is_a?(Proc)
551
- original = current_window
552
- scopes << nil
553
- begin
554
- _switch_to_window { window_or_handle.call }
555
- begin
556
- yield
557
- ensure
558
- _switch_to_window(original)
559
- end
560
- ensure
561
- scopes.pop
545
+ def within_window(window_or_proc)
546
+ original = current_window
547
+ scopes << nil
548
+ begin
549
+ case window_or_proc
550
+ when Capybara::Window
551
+ _switch_to_window(window_or_proc) unless original == window_or_proc
552
+ when Proc
553
+ _switch_to_window { window_or_proc.call }
554
+ else
555
+ raise ArgumentError, '`#within_window` requires a `Capybara::Window` instance or a lambda'
562
556
  end
563
- else
564
- offending_line = caller.first
565
- file_line = offending_line.match(/^(.+?):(\d+)/)[0]
566
- warn "DEPRECATION WARNING: Passing string argument to #within_window is deprecated. "\
567
- "Pass window object or lambda. (called from #{file_line})"
557
+
568
558
  begin
569
- scopes << nil
570
- driver.within_window(window_or_handle) { yield }
559
+ yield if block_given?
571
560
  ensure
572
- scopes.pop
561
+ _switch_to_window(original) unless original == window_or_proc
573
562
  end
563
+ ensure
564
+ scopes.pop
574
565
  end
575
566
  end
576
567
 
@@ -578,23 +569,23 @@ module Capybara
578
569
  # Get the window that has been opened by the passed block.
579
570
  # It will wait for it to be opened (in the same way as other Capybara methods wait).
580
571
  # It's better to use this method than `windows.last`
581
- # {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}.
582
573
  #
583
- # @param options [Hash]
584
- # @option options [Numeric] :wait (Capybara.default_max_wait_time) maximum wait time
585
- # @return [Capybara::Window] the window that has been opened within a block
586
- # @raise [Capybara::WindowError] if block passed to window hasn't opened window
587
- # or opened more than one window
574
+ # @overload window_opened_by(**options, &block)
575
+ # @param options [Hash]
576
+ # @option options [Numeric] :wait maximum wait time. Defaults to {Capybara.configure default_max_wait_time}
577
+ # @return [Capybara::Window] the window that has been opened within a block
578
+ # @raise [Capybara::WindowError] if block passed to window hasn't opened window
579
+ # or opened more than one window
588
580
  #
589
- def window_opened_by(options = {}, &block)
581
+ def window_opened_by(**options)
590
582
  old_handles = driver.window_handles
591
- block.call
583
+ yield
592
584
 
593
- wait_time = Capybara::Queries::BaseQuery.wait(options, config.default_max_wait_time)
594
- document.synchronize(wait_time, errors: [Capybara::WindowError]) do
585
+ synchronize_windows(options) do
595
586
  opened_handles = (driver.window_handles - old_handles)
596
587
  if opened_handles.size != 1
597
- raise Capybara::WindowError, "block passed to #window_opened_by "\
588
+ raise Capybara::WindowError, 'block passed to #window_opened_by ' \
598
589
  "opened #{opened_handles.size} windows instead of 1"
599
590
  end
600
591
  Window.new(self, opened_handles.first)
@@ -604,39 +595,30 @@ module Capybara
604
595
  ##
605
596
  #
606
597
  # Execute the given script, not returning a result. This is useful for scripts that return
607
- # complex objects, such as jQuery statements. +execute_script+ should be used over
608
- # +evaluate_script+ whenever possible.
598
+ # complex objects, such as jQuery statements. {#execute_script} should be used over
599
+ # {#evaluate_script} whenever possible.
609
600
  #
610
601
  # @param [String] script A string of JavaScript to execute
611
- # @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
612
603
  #
613
604
  def execute_script(script, *args)
614
605
  @touched = true
615
- if args.empty?
616
- driver.execute_script(script)
617
- else
618
- raise Capybara::NotSupportedByDriverError, "The current driver does not support execute_script arguments" if driver.method(:execute_script).arity == 1
619
- driver.execute_script(script, *args.map { |arg| arg.is_a?(Capybara::Node::Element) ? arg.base : arg} )
620
- end
606
+ driver.execute_script(script, *driver_args(args))
621
607
  end
622
608
 
623
609
  ##
624
610
  #
625
611
  # Evaluate the given JavaScript and return the result. Be careful when using this with
626
- # 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
627
613
  # be a better alternative.
628
614
  #
629
615
  # @param [String] script A string of JavaScript to evaluate
616
+ # @param args Optional arguments that will be passed to the script
630
617
  # @return [Object] The result of the evaluated JavaScript (may be driver specific)
631
618
  #
632
619
  def evaluate_script(script, *args)
633
620
  @touched = true
634
- result = if args.empty?
635
- driver.evaluate_script(script)
636
- else
637
- raise Capybara::NotSupportedByDriverError, "The current driver does not support evaluate_script arguments" if driver.method(:evaluate_script).arity == 1
638
- driver.evaluate_script(script, *args.map { |arg| arg.is_a?(Capybara::Node::Element) ? arg.base : arg} )
639
- end
621
+ result = driver.evaluate_script(script.strip, *driver_args(args))
640
622
  element_script_result(result)
641
623
  end
642
624
 
@@ -645,16 +627,12 @@ module Capybara
645
627
  # Evaluate the given JavaScript and obtain the result from a callback function which will be passed as the last argument to the script.
646
628
  #
647
629
  # @param [String] script A string of JavaScript to evaluate
630
+ # @param args Optional arguments that will be passed to the script
648
631
  # @return [Object] The result of the evaluated JavaScript (may be driver specific)
649
632
  #
650
633
  def evaluate_async_script(script, *args)
651
634
  @touched = true
652
- result = if args.empty?
653
- driver.evaluate_async_script(script)
654
- else
655
- raise Capybara::NotSupportedByDriverError, "The current driver does not support evaluate_async_script arguments" if driver.method(:evaluate_async_script).arity == 1
656
- driver.evaluate_async_script(script, *args.map { |arg| arg.is_a?(Capybara::Node::Element) ? arg.base : arg} )
657
- end
635
+ result = driver.evaluate_async_script(script, *driver_args(args))
658
636
  element_script_result(result)
659
637
  end
660
638
 
@@ -663,24 +641,23 @@ module Capybara
663
641
  # Execute the block, accepting a alert.
664
642
  #
665
643
  # @!macro modal_params
666
- # 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.
667
645
  # @example
668
646
  # $0 do
669
647
  # click_link('link that triggers appearance of system modal')
670
648
  # end
671
- # @overload $0(text, options = {}, &blk)
672
- # @param text [String, Regexp] Text or regex to match against the text in the modal. If not provided any modal is matched
673
- # @option options [Numeric] :wait (Capybara.default_max_wait_time) Maximum time to wait for the modal to appear after executing the block.
649
+ # @overload $0(text, **options, &blk)
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}.
674
652
  # @yield Block whose actions will trigger the system modal
675
- # @overload $0(options = {}, &blk)
676
- # @option options [Numeric] :wait (Capybara.default_max_wait_time) Maximum time to wait for the modal to appear after executing the block.
653
+ # @overload $0(**options, &blk)
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}.
677
655
  # @yield Block whose actions will trigger the system modal
678
656
  # @return [String] the message shown in the modal
679
657
  # @raise [Capybara::ModalNotFound] if modal dialog hasn't been found
680
658
  #
681
- #
682
- def accept_alert(text_or_options=nil, options={}, &blk)
683
- accept_modal(:alert, text_or_options, options, &blk)
659
+ def accept_alert(text = nil, **options, &blk)
660
+ accept_modal(:alert, text, options, &blk)
684
661
  end
685
662
 
686
663
  ##
@@ -689,8 +666,8 @@ module Capybara
689
666
  #
690
667
  # @macro modal_params
691
668
  #
692
- def accept_confirm(text_or_options=nil, options={}, &blk)
693
- accept_modal(:confirm, text_or_options, options, &blk)
669
+ def accept_confirm(text = nil, **options, &blk)
670
+ accept_modal(:confirm, text, options, &blk)
694
671
  end
695
672
 
696
673
  ##
@@ -699,8 +676,8 @@ module Capybara
699
676
  #
700
677
  # @macro modal_params
701
678
  #
702
- def dismiss_confirm(text_or_options=nil, options={}, &blk)
703
- dismiss_modal(:confirm, text_or_options, options, &blk)
679
+ def dismiss_confirm(text = nil, **options, &blk)
680
+ dismiss_modal(:confirm, text, options, &blk)
704
681
  end
705
682
 
706
683
  ##
@@ -710,8 +687,8 @@ module Capybara
710
687
  # @macro modal_params
711
688
  # @option options [String] :with Response to provide to the prompt
712
689
  #
713
- def accept_prompt(text_or_options=nil, options={}, &blk)
714
- accept_modal(:prompt, text_or_options, options, &blk)
690
+ def accept_prompt(text = nil, **options, &blk)
691
+ accept_modal(:prompt, text, options, &blk)
715
692
  end
716
693
 
717
694
  ##
@@ -720,82 +697,70 @@ module Capybara
720
697
  #
721
698
  # @macro modal_params
722
699
  #
723
- def dismiss_prompt(text_or_options=nil, options={}, &blk)
724
- dismiss_modal(:prompt, text_or_options, options, &blk)
700
+ def dismiss_prompt(text = nil, **options, &blk)
701
+ dismiss_modal(:prompt, text, options, &blk)
725
702
  end
726
703
 
727
704
  ##
728
705
  #
729
- # Save a snapshot of the page. If `Capybara.asset_host` is set it will inject `base` tag
730
- # 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}.
731
708
  #
732
- # If invoked without arguments it will save file to `Capybara.save_path`
733
- # and file will be given randomly generated filename. If invoked with a relative path
734
- # the path will be relative to `Capybara.save_path`, which is different from
735
- # the previous behavior with `Capybara.save_and_open_page_path` where the relative path was
736
- # relative to Dir.pwd
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}.
737
712
  #
738
713
  # @param [String] path the path to where it should be saved
739
714
  # @return [String] the path to which the file was saved
740
715
  #
741
716
  def save_page(path = nil)
742
- path = prepare_path(path, 'html')
743
- File.write(path, Capybara::Helpers.inject_asset_host(body, config.asset_host), mode: 'wb')
744
- path
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')
719
+ end
745
720
  end
746
721
 
747
722
  ##
748
723
  #
749
724
  # Save a snapshot of the page and open it in a browser for inspection.
750
725
  #
751
- # If invoked without arguments it will save file to `Capybara.save_path`
752
- # and file will be given randomly generated filename. If invoked with a relative path
753
- # the path will be relative to `Capybara.save_path`, which is different from
754
- # the previous behavior with `Capybara.save_and_open_page_path` where the relative path was
755
- # relative to Dir.pwd
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}.
756
729
  #
757
730
  # @param [String] path the path to where it should be saved
758
731
  #
759
732
  def save_and_open_page(path = nil)
760
- path = save_page(path)
761
- open_file(path)
733
+ save_page(path).tap { |s_path| open_file(s_path) }
762
734
  end
763
735
 
764
736
  ##
765
737
  #
766
738
  # Save a screenshot of page.
767
739
  #
768
- # If invoked without arguments it will save file to `Capybara.save_path`
769
- # and file will be given randomly generated filename. If invoked with a relative path
770
- # the path will be relative to `Capybara.save_path`, which is different from
771
- # the previous behavior with `Capybara.save_and_open_page_path` where the relative path was
772
- # relative to Dir.pwd
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}.
773
743
  #
774
744
  # @param [String] path the path to where it should be saved
775
745
  # @param [Hash] options a customizable set of options
776
746
  # @return [String] the path to which the file was saved
777
- def save_screenshot(path = nil, options = {})
778
- path = prepare_path(path, 'png')
779
- driver.save_screenshot(path, options)
780
- path
747
+ def save_screenshot(path = nil, **options)
748
+ prepare_path(path, 'png').tap { |p_path| driver.save_screenshot(p_path, **options) }
781
749
  end
782
750
 
783
751
  ##
784
752
  #
785
753
  # Save a screenshot of the page and open it for inspection.
786
754
  #
787
- # If invoked without arguments it will save file to `Capybara.save_path`
788
- # and file will be given randomly generated filename. If invoked with a relative path
789
- # the path will be relative to `Capybara.save_path`, which is different from
790
- # the previous behavior with `Capybara.save_and_open_page_path` where the relative path was
791
- # relative to Dir.pwd
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}.
792
758
  #
793
759
  # @param [String] path the path to where it should be saved
794
760
  # @param [Hash] options a customizable set of options
795
761
  #
796
- def save_and_open_screenshot(path = nil, options = {})
797
- path = save_screenshot(path, options)
798
- open_file(path)
762
+ def save_and_open_screenshot(path = nil, **options)
763
+ save_screenshot(path, **options).tap { |s_path| open_file(s_path) }
799
764
  end
800
765
 
801
766
  def document
@@ -803,16 +768,20 @@ module Capybara
803
768
  end
804
769
 
805
770
  NODE_METHODS.each do |method|
806
- define_method method do |*args, &block|
807
- @touched = true
808
- current_scope.send(method, *args, &block)
809
- end
771
+ class_eval <<~METHOD, __FILE__, __LINE__ + 1
772
+ def #{method}(...)
773
+ @touched = true
774
+ current_scope.#{method}(...)
775
+ end
776
+ METHOD
810
777
  end
811
778
 
812
779
  DOCUMENT_METHODS.each do |method|
813
- define_method method do |*args, &block|
814
- document.send(method, *args, &block)
815
- end
780
+ class_eval <<~METHOD, __FILE__, __LINE__ + 1
781
+ def #{method}(...)
782
+ document.#{method}(...)
783
+ end
784
+ METHOD
816
785
  end
817
786
 
818
787
  def inspect
@@ -821,15 +790,14 @@ module Capybara
821
790
 
822
791
  def current_scope
823
792
  scope = scopes.last
824
- scope = document if [nil, :frame].include? scope
825
- scope
793
+ [nil, :frame].include?(scope) ? document : scope
826
794
  end
827
795
 
828
796
  ##
829
797
  #
830
- # Yield a block using a specific wait time
798
+ # Yield a block using a specific maximum wait time.
831
799
  #
832
- def using_wait_time(seconds)
800
+ def using_wait_time(seconds, &block)
833
801
  if Capybara.threadsafe
834
802
  begin
835
803
  previous_wait_time = config.default_max_wait_time
@@ -839,17 +807,18 @@ module Capybara
839
807
  config.default_max_wait_time = previous_wait_time
840
808
  end
841
809
  else
842
- Capybara.using_wait_time(seconds) { yield }
810
+ Capybara.using_wait_time(seconds, &block)
843
811
  end
844
812
  end
845
813
 
846
814
  ##
847
815
  #
848
- # Accepts a block to set the configuration options if Capybara.threadsafe == true. Note that some options only have an effect
849
- # 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.
850
818
  #
851
819
  def configure
852
- 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
+
853
822
  yield config
854
823
  end
855
824
 
@@ -864,48 +833,48 @@ module Capybara
864
833
  Capybara::ReadOnlySessionConfig.new(Capybara.session_options)
865
834
  end
866
835
  end
836
+
837
+ def server_url
838
+ @server&.base_url
839
+ end
840
+
867
841
  private
868
842
 
869
- @@instance_created = false
843
+ @@instance_created = false # rubocop:disable Style/ClassVars
844
+
845
+ def driver_args(args)
846
+ args.map { |arg| arg.is_a?(Capybara::Node::Element) ? arg.base : arg }
847
+ end
870
848
 
871
849
  def accept_modal(type, text_or_options, options, &blk)
872
- driver.accept_modal(type, modal_options(text_or_options, options), &blk)
850
+ driver.accept_modal(type, **modal_options(text_or_options, **options), &blk)
873
851
  end
874
852
 
875
853
  def dismiss_modal(type, text_or_options, options, &blk)
876
- driver.dismiss_modal(type, modal_options(text_or_options, options), &blk)
854
+ driver.dismiss_modal(type, **modal_options(text_or_options, **options), &blk)
877
855
  end
878
856
 
879
- def modal_options(text_or_options, options)
880
- text_or_options, options = nil, text_or_options if text_or_options.is_a?(Hash)
881
- options[:text] ||= text_or_options unless text_or_options.nil?
857
+ def modal_options(text = nil, **options)
858
+ options[:text] ||= text unless text.nil?
882
859
  options[:wait] ||= config.default_max_wait_time
883
860
  options
884
861
  end
885
862
 
886
-
887
863
  def open_file(path)
888
- begin
889
- require "launchy"
890
- Launchy.open(path)
891
- rescue LoadError
892
- warn "File saved to #{path}."
893
- warn "Please install the launchy gem to open the file automatically."
894
- end
864
+ require 'launchy'
865
+ Launchy.open(path)
866
+ rescue LoadError
867
+ warn "File saved to #{path}.\nPlease install the launchy gem to open the file automatically."
895
868
  end
896
869
 
897
870
  def prepare_path(path, extension)
898
- if config.save_path || config.save_and_open_page_path.nil?
899
- path = File.expand_path(path || default_fn(extension), config.save_path)
900
- else
901
- path = File.expand_path(default_fn(extension), config.save_and_open_page_path) if path.nil?
871
+ File.expand_path(path || default_fn(extension), config.save_path).tap do |p_path|
872
+ FileUtils.mkdir_p(File.dirname(p_path))
902
873
  end
903
- FileUtils.mkdir_p(File.dirname(path))
904
- path
905
874
  end
906
875
 
907
876
  def default_fn(extension)
908
- timestamp = Time.new.strftime("%Y%m%d%H%M%S")
877
+ timestamp = Time.new.strftime('%Y%m%d%H%M%S')
909
878
  "capybara-#{timestamp}#{rand(10**10)}.#{extension}"
910
879
  end
911
880
 
@@ -916,9 +885,9 @@ module Capybara
916
885
  def element_script_result(arg)
917
886
  case arg
918
887
  when Array
919
- arg.map { |e| element_script_result(e) }
888
+ arg.map { |subarg| element_script_result(subarg) }
920
889
  when Hash
921
- arg.each { |k, v| arg[k] = element_script_result(v) }
890
+ arg.transform_values! { |value| element_script_result(value) }
922
891
  when Capybara::Driver::Node
923
892
  Capybara::Node::Element.new(self, arg, nil, nil)
924
893
  else
@@ -926,54 +895,57 @@ module Capybara
926
895
  end
927
896
  end
928
897
 
929
- def _find_frame(*args)
930
- within(document) do # Previous 2.x versions ignored current scope when finding frames - consider changing in 3.0
931
- case args[0]
932
- when Capybara::Node::Element
933
- args[0]
934
- when String, Hash
935
- find(:frame, *args)
936
- when Symbol
937
- find(*args)
938
- when Integer
939
- idx = args[0]
940
- all(:frame, minimum: idx+1)[idx]
941
- else
942
- raise TypeError
943
- end
944
- end
898
+ def adjust_server_port(uri)
899
+ uri.port ||= @server.port if @server && config.always_include_port
945
900
  end
946
901
 
947
- def _switch_to_window(window = nil, options= {})
948
- options, window = window, nil if window.is_a? Hash
902
+ def _find_frame(*args, **kw_args)
903
+ case args[0]
904
+ when Capybara::Node::Element
905
+ args[0]
906
+ when String, nil
907
+ find(:frame, *args, **kw_args)
908
+ when Symbol
909
+ find(*args, **kw_args)
910
+ when Integer
911
+ idx = args[0]
912
+ all(:frame, minimum: idx + 1)[idx]
913
+ else
914
+ raise TypeError
915
+ end
916
+ end
949
917
 
950
- raise Capybara::ScopeError, "Window cannot be switched inside a `within_frame` block" if scopes.include?(:frame)
951
- 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?
952
921
 
953
922
  if window
954
923
  driver.switch_to_window(window.handle)
955
924
  window
956
925
  else
957
- wait_time = Capybara::Queries::BaseQuery.wait(options, config.default_max_wait_time)
958
- document.synchronize(wait_time, errors: [Capybara::WindowError]) do
926
+ synchronize_windows(options) do
959
927
  original_window_handle = driver.current_window_handle
960
928
  begin
961
- driver.window_handles.each do |handle|
962
- driver.switch_to_window handle
963
- if yield
964
- return Window.new(self, handle)
965
- end
966
- end
967
- rescue => e
929
+ _switch_to_window_by_locator(&window_locator)
930
+ rescue StandardError
968
931
  driver.switch_to_window(original_window_handle)
969
- raise e
970
- else
971
- driver.switch_to_window(original_window_handle)
972
- raise Capybara::WindowError, "Could not find a window matching block/lambda"
932
+ raise
973
933
  end
974
934
  end
975
935
  end
976
936
  end
977
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
978
950
  end
979
951
  end