capybara 3.0.0 → 3.36.0

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