capybara 3.0.0 → 3.36.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 -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