capybara 3.3.0 → 3.40.0

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