capybara 2.18.0 → 3.38.0

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