capybara 3.0.0 → 3.40.0

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