capybara 3.0.0 → 3.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (312) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -1
  3. data/History.md +891 -12
  4. data/License.txt +1 -1
  5. data/README.md +257 -84
  6. data/lib/capybara/config.rb +29 -10
  7. data/lib/capybara/cucumber.rb +1 -1
  8. data/lib/capybara/driver/base.rb +22 -4
  9. data/lib/capybara/driver/node.rb +38 -9
  10. data/lib/capybara/dsl.rb +9 -7
  11. data/lib/capybara/helpers.rb +57 -8
  12. data/lib/capybara/minitest/spec.rb +185 -84
  13. data/lib/capybara/minitest.rb +264 -145
  14. data/lib/capybara/node/actions.rb +248 -124
  15. data/lib/capybara/node/base.rb +35 -20
  16. data/lib/capybara/node/document.rb +14 -2
  17. data/lib/capybara/node/document_matchers.rb +13 -15
  18. data/lib/capybara/node/element.rb +350 -113
  19. data/lib/capybara/node/finders.rb +104 -82
  20. data/lib/capybara/node/matchers.rb +363 -157
  21. data/lib/capybara/node/simple.rb +54 -15
  22. data/lib/capybara/node/whitespace_normalizer.rb +81 -0
  23. data/lib/capybara/queries/active_element_query.rb +18 -0
  24. data/lib/capybara/queries/ancestor_query.rb +9 -10
  25. data/lib/capybara/queries/base_query.rb +25 -18
  26. data/lib/capybara/queries/current_path_query.rb +16 -6
  27. data/lib/capybara/queries/match_query.rb +11 -0
  28. data/lib/capybara/queries/selector_query.rb +617 -104
  29. data/lib/capybara/queries/sibling_query.rb +9 -7
  30. data/lib/capybara/queries/style_query.rb +45 -0
  31. data/lib/capybara/queries/text_query.rb +40 -22
  32. data/lib/capybara/queries/title_query.rb +2 -2
  33. data/lib/capybara/rack_test/browser.rb +106 -31
  34. data/lib/capybara/rack_test/driver.rb +16 -7
  35. data/lib/capybara/rack_test/errors.rb +6 -0
  36. data/lib/capybara/rack_test/form.rb +74 -49
  37. data/lib/capybara/rack_test/node.rb +120 -47
  38. data/lib/capybara/rails.rb +1 -1
  39. data/lib/capybara/registration_container.rb +41 -0
  40. data/lib/capybara/registrations/drivers.rb +42 -0
  41. data/lib/capybara/registrations/patches/puma_ssl.rb +29 -0
  42. data/lib/capybara/registrations/servers.rb +66 -0
  43. data/lib/capybara/result.rb +87 -53
  44. data/lib/capybara/rspec/features.rb +8 -10
  45. data/lib/capybara/rspec/matcher_proxies.rb +39 -18
  46. data/lib/capybara/rspec/matchers/base.rb +113 -0
  47. data/lib/capybara/rspec/matchers/become_closed.rb +33 -0
  48. data/lib/capybara/rspec/matchers/compound.rb +88 -0
  49. data/lib/capybara/rspec/matchers/count_sugar.rb +37 -0
  50. data/lib/capybara/rspec/matchers/have_ancestor.rb +28 -0
  51. data/lib/capybara/rspec/matchers/have_current_path.rb +29 -0
  52. data/lib/capybara/rspec/matchers/have_selector.rb +69 -0
  53. data/lib/capybara/rspec/matchers/have_sibling.rb +27 -0
  54. data/lib/capybara/rspec/matchers/have_text.rb +33 -0
  55. data/lib/capybara/rspec/matchers/have_title.rb +29 -0
  56. data/lib/capybara/rspec/matchers/match_selector.rb +27 -0
  57. data/lib/capybara/rspec/matchers/match_style.rb +43 -0
  58. data/lib/capybara/rspec/matchers/spatial_sugar.rb +39 -0
  59. data/lib/capybara/rspec/matchers.rb +142 -315
  60. data/lib/capybara/rspec.rb +3 -2
  61. data/lib/capybara/selector/builders/css_builder.rb +84 -0
  62. data/lib/capybara/selector/builders/xpath_builder.rb +71 -0
  63. data/lib/capybara/selector/css.rb +85 -8
  64. data/lib/capybara/selector/definition/button.rb +68 -0
  65. data/lib/capybara/selector/definition/checkbox.rb +26 -0
  66. data/lib/capybara/selector/definition/css.rb +10 -0
  67. data/lib/capybara/selector/definition/datalist_input.rb +35 -0
  68. data/lib/capybara/selector/definition/datalist_option.rb +25 -0
  69. data/lib/capybara/selector/definition/element.rb +28 -0
  70. data/lib/capybara/selector/definition/field.rb +40 -0
  71. data/lib/capybara/selector/definition/fieldset.rb +14 -0
  72. data/lib/capybara/selector/definition/file_field.rb +13 -0
  73. data/lib/capybara/selector/definition/fillable_field.rb +33 -0
  74. data/lib/capybara/selector/definition/frame.rb +17 -0
  75. data/lib/capybara/selector/definition/id.rb +6 -0
  76. data/lib/capybara/selector/definition/label.rb +62 -0
  77. data/lib/capybara/selector/definition/link.rb +55 -0
  78. data/lib/capybara/selector/definition/link_or_button.rb +16 -0
  79. data/lib/capybara/selector/definition/option.rb +27 -0
  80. data/lib/capybara/selector/definition/radio_button.rb +27 -0
  81. data/lib/capybara/selector/definition/select.rb +81 -0
  82. data/lib/capybara/selector/definition/table.rb +109 -0
  83. data/lib/capybara/selector/definition/table_row.rb +21 -0
  84. data/lib/capybara/selector/definition/xpath.rb +5 -0
  85. data/lib/capybara/selector/definition.rb +280 -0
  86. data/lib/capybara/selector/filter.rb +1 -0
  87. data/lib/capybara/selector/filter_set.rb +72 -28
  88. data/lib/capybara/selector/filters/base.rb +45 -2
  89. data/lib/capybara/selector/filters/expression_filter.rb +5 -6
  90. data/lib/capybara/selector/filters/locator_filter.rb +29 -0
  91. data/lib/capybara/selector/filters/node_filter.rb +18 -4
  92. data/lib/capybara/selector/regexp_disassembler.rb +211 -0
  93. data/lib/capybara/selector/selector.rb +89 -200
  94. data/lib/capybara/selector/xpath_extensions.rb +17 -0
  95. data/lib/capybara/selector.rb +474 -534
  96. data/lib/capybara/selenium/atoms/getAttribute.min.js +1 -0
  97. data/lib/capybara/selenium/atoms/isDisplayed.min.js +1 -0
  98. data/lib/capybara/selenium/atoms/src/getAttribute.js +161 -0
  99. data/lib/capybara/selenium/atoms/src/isDisplayed.js +454 -0
  100. data/lib/capybara/selenium/driver.rb +270 -245
  101. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +117 -0
  102. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +128 -0
  103. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +84 -0
  104. data/lib/capybara/selenium/driver_specializations/internet_explorer_driver.rb +26 -0
  105. data/lib/capybara/selenium/driver_specializations/safari_driver.rb +24 -0
  106. data/lib/capybara/selenium/extensions/file_input_click_emulation.rb +34 -0
  107. data/lib/capybara/selenium/extensions/find.rb +110 -0
  108. data/lib/capybara/selenium/extensions/html5_drag.rb +229 -0
  109. data/lib/capybara/selenium/extensions/modifier_keys_stack.rb +28 -0
  110. data/lib/capybara/selenium/extensions/scroll.rb +76 -0
  111. data/lib/capybara/selenium/node.rb +460 -170
  112. data/lib/capybara/selenium/nodes/chrome_node.rb +125 -0
  113. data/lib/capybara/selenium/nodes/edge_node.rb +110 -0
  114. data/lib/capybara/selenium/nodes/firefox_node.rb +136 -0
  115. data/lib/capybara/selenium/nodes/ie_node.rb +22 -0
  116. data/lib/capybara/selenium/nodes/safari_node.rb +118 -0
  117. data/lib/capybara/selenium/patches/atoms.rb +18 -0
  118. data/lib/capybara/selenium/patches/is_displayed.rb +16 -0
  119. data/lib/capybara/selenium/patches/logs.rb +45 -0
  120. data/lib/capybara/selenium/patches/pause_duration_fix.rb +9 -0
  121. data/lib/capybara/selenium/patches/persistent_client.rb +20 -0
  122. data/lib/capybara/server/animation_disabler.rb +80 -0
  123. data/lib/capybara/server/checker.rb +44 -0
  124. data/lib/capybara/server/middleware.rb +71 -0
  125. data/lib/capybara/server.rb +58 -67
  126. data/lib/capybara/session/config.rb +40 -6
  127. data/lib/capybara/session/matchers.rb +26 -19
  128. data/lib/capybara/session.rb +252 -194
  129. data/lib/capybara/spec/public/jquery.js +5 -5
  130. data/lib/capybara/spec/public/offset.js +6 -0
  131. data/lib/capybara/spec/public/test.js +126 -8
  132. data/lib/capybara/spec/session/accept_alert_spec.rb +11 -11
  133. data/lib/capybara/spec/session/accept_confirm_spec.rb +3 -3
  134. data/lib/capybara/spec/session/accept_prompt_spec.rb +9 -10
  135. data/lib/capybara/spec/session/active_element_spec.rb +31 -0
  136. data/lib/capybara/spec/session/all_spec.rb +135 -44
  137. data/lib/capybara/spec/session/ancestor_spec.rb +24 -19
  138. data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +67 -38
  139. data/lib/capybara/spec/session/{assert_current_path.rb → assert_current_path_spec.rb} +20 -18
  140. data/lib/capybara/spec/session/assert_selector_spec.rb +143 -0
  141. data/lib/capybara/spec/session/assert_style_spec.rb +26 -0
  142. data/lib/capybara/spec/session/{assert_text.rb → assert_text_spec.rb} +76 -52
  143. data/lib/capybara/spec/session/{assert_title.rb → assert_title_spec.rb} +12 -12
  144. data/lib/capybara/spec/session/attach_file_spec.rb +126 -72
  145. data/lib/capybara/spec/session/body_spec.rb +11 -13
  146. data/lib/capybara/spec/session/check_spec.rb +112 -51
  147. data/lib/capybara/spec/session/choose_spec.rb +62 -30
  148. data/lib/capybara/spec/session/click_button_spec.rb +227 -161
  149. data/lib/capybara/spec/session/click_link_or_button_spec.rb +49 -30
  150. data/lib/capybara/spec/session/click_link_spec.rb +89 -55
  151. data/lib/capybara/spec/session/current_scope_spec.rb +8 -8
  152. data/lib/capybara/spec/session/current_url_spec.rb +44 -37
  153. data/lib/capybara/spec/session/dismiss_confirm_spec.rb +3 -3
  154. data/lib/capybara/spec/session/dismiss_prompt_spec.rb +2 -2
  155. data/lib/capybara/spec/session/element/{assert_match_selector.rb → assert_match_selector_spec.rb} +11 -9
  156. data/lib/capybara/spec/session/element/match_css_spec.rb +18 -10
  157. data/lib/capybara/spec/session/element/match_xpath_spec.rb +8 -6
  158. data/lib/capybara/spec/session/element/matches_selector_spec.rb +70 -56
  159. data/lib/capybara/spec/session/evaluate_async_script_spec.rb +7 -7
  160. data/lib/capybara/spec/session/evaluate_script_spec.rb +28 -8
  161. data/lib/capybara/spec/session/execute_script_spec.rb +8 -7
  162. data/lib/capybara/spec/session/fill_in_spec.rb +110 -44
  163. data/lib/capybara/spec/session/find_button_spec.rb +23 -23
  164. data/lib/capybara/spec/session/find_by_id_spec.rb +8 -8
  165. data/lib/capybara/spec/session/find_field_spec.rb +33 -31
  166. data/lib/capybara/spec/session/find_link_spec.rb +42 -14
  167. data/lib/capybara/spec/session/find_spec.rb +251 -142
  168. data/lib/capybara/spec/session/first_spec.rb +45 -44
  169. data/lib/capybara/spec/session/frame/frame_title_spec.rb +6 -6
  170. data/lib/capybara/spec/session/frame/frame_url_spec.rb +6 -6
  171. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +32 -20
  172. data/lib/capybara/spec/session/frame/within_frame_spec.rb +46 -19
  173. data/lib/capybara/spec/session/go_back_spec.rb +1 -1
  174. data/lib/capybara/spec/session/go_forward_spec.rb +1 -1
  175. data/lib/capybara/spec/session/has_all_selectors_spec.rb +23 -23
  176. data/lib/capybara/spec/session/has_ancestor_spec.rb +46 -0
  177. data/lib/capybara/spec/session/has_any_selectors_spec.rb +29 -0
  178. data/lib/capybara/spec/session/has_button_spec.rb +98 -12
  179. data/lib/capybara/spec/session/has_css_spec.rb +271 -137
  180. data/lib/capybara/spec/session/has_current_path_spec.rb +50 -35
  181. data/lib/capybara/spec/session/has_element_spec.rb +47 -0
  182. data/lib/capybara/spec/session/has_field_spec.rb +137 -58
  183. data/lib/capybara/spec/session/has_link_spec.rb +46 -6
  184. data/lib/capybara/spec/session/has_none_selectors_spec.rb +33 -31
  185. data/lib/capybara/spec/session/has_select_spec.rb +84 -50
  186. data/lib/capybara/spec/session/has_selector_spec.rb +117 -69
  187. data/lib/capybara/spec/session/has_sibling_spec.rb +50 -0
  188. data/lib/capybara/spec/session/has_table_spec.rb +181 -4
  189. data/lib/capybara/spec/session/has_text_spec.rb +101 -53
  190. data/lib/capybara/spec/session/has_title_spec.rb +19 -14
  191. data/lib/capybara/spec/session/has_xpath_spec.rb +56 -38
  192. data/lib/capybara/spec/session/{headers.rb → headers_spec.rb} +1 -1
  193. data/lib/capybara/spec/session/html_spec.rb +13 -6
  194. data/lib/capybara/spec/session/matches_style_spec.rb +37 -0
  195. data/lib/capybara/spec/session/node_spec.rb +958 -122
  196. data/lib/capybara/spec/session/node_wrapper_spec.rb +15 -12
  197. data/lib/capybara/spec/session/refresh_spec.rb +9 -7
  198. data/lib/capybara/spec/session/reset_session_spec.rb +65 -37
  199. data/lib/capybara/spec/session/{response_code.rb → response_code_spec.rb} +1 -1
  200. data/lib/capybara/spec/session/save_and_open_page_spec.rb +2 -2
  201. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +5 -4
  202. data/lib/capybara/spec/session/save_page_spec.rb +41 -38
  203. data/lib/capybara/spec/session/save_screenshot_spec.rb +13 -11
  204. data/lib/capybara/spec/session/screenshot_spec.rb +2 -2
  205. data/lib/capybara/spec/session/scroll_spec.rb +119 -0
  206. data/lib/capybara/spec/session/select_spec.rb +102 -76
  207. data/lib/capybara/spec/session/selectors_spec.rb +51 -18
  208. data/lib/capybara/spec/session/sibling_spec.rb +9 -9
  209. data/lib/capybara/spec/session/text_spec.rb +26 -24
  210. data/lib/capybara/spec/session/title_spec.rb +8 -6
  211. data/lib/capybara/spec/session/uncheck_spec.rb +41 -22
  212. data/lib/capybara/spec/session/unselect_spec.rb +37 -37
  213. data/lib/capybara/spec/session/visit_spec.rb +79 -53
  214. data/lib/capybara/spec/session/window/become_closed_spec.rb +22 -19
  215. data/lib/capybara/spec/session/window/current_window_spec.rb +4 -3
  216. data/lib/capybara/spec/session/window/open_new_window_spec.rb +4 -3
  217. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +25 -21
  218. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +10 -5
  219. data/lib/capybara/spec/session/window/window_spec.rb +88 -54
  220. data/lib/capybara/spec/session/window/windows_spec.rb +11 -8
  221. data/lib/capybara/spec/session/window/within_window_spec.rb +17 -16
  222. data/lib/capybara/spec/session/within_spec.rb +82 -44
  223. data/lib/capybara/spec/spec_helper.rb +46 -52
  224. data/lib/capybara/spec/test_app.rb +148 -41
  225. data/lib/capybara/spec/views/animated.erb +49 -0
  226. data/lib/capybara/spec/views/form.erb +156 -42
  227. data/lib/capybara/spec/views/frame_child.erb +4 -3
  228. data/lib/capybara/spec/views/frame_one.erb +2 -1
  229. data/lib/capybara/spec/views/frame_parent.erb +1 -1
  230. data/lib/capybara/spec/views/frame_two.erb +1 -1
  231. data/lib/capybara/spec/views/initial_alert.erb +2 -1
  232. data/lib/capybara/spec/views/layout.erb +10 -0
  233. data/lib/capybara/spec/views/obscured.erb +47 -0
  234. data/lib/capybara/spec/views/offset.erb +33 -0
  235. data/lib/capybara/spec/views/path.erb +2 -2
  236. data/lib/capybara/spec/views/popup_one.erb +1 -1
  237. data/lib/capybara/spec/views/popup_two.erb +1 -1
  238. data/lib/capybara/spec/views/react.erb +45 -0
  239. data/lib/capybara/spec/views/scroll.erb +21 -0
  240. data/lib/capybara/spec/views/spatial.erb +31 -0
  241. data/lib/capybara/spec/views/tables.erb +68 -1
  242. data/lib/capybara/spec/views/with_animation.erb +81 -0
  243. data/lib/capybara/spec/views/with_base_tag.erb +2 -2
  244. data/lib/capybara/spec/views/with_dragula.erb +24 -0
  245. data/lib/capybara/spec/views/with_fixed_header_footer.erb +2 -1
  246. data/lib/capybara/spec/views/with_hover.erb +3 -2
  247. data/lib/capybara/spec/views/with_hover1.erb +10 -0
  248. data/lib/capybara/spec/views/with_html.erb +46 -11
  249. data/lib/capybara/spec/views/with_html5_svg.erb +20 -0
  250. data/lib/capybara/spec/views/with_jquery_animation.erb +24 -0
  251. data/lib/capybara/spec/views/with_js.erb +30 -5
  252. data/lib/capybara/spec/views/with_jstree.erb +26 -0
  253. data/lib/capybara/spec/views/with_namespace.erb +21 -0
  254. data/lib/capybara/spec/views/with_scope.erb +2 -2
  255. data/lib/capybara/spec/views/with_scope_other.erb +6 -0
  256. data/lib/capybara/spec/views/with_shadow.erb +31 -0
  257. data/lib/capybara/spec/views/with_slow_unload.erb +2 -1
  258. data/lib/capybara/spec/views/with_sortable_js.erb +21 -0
  259. data/lib/capybara/spec/views/with_unload_alert.erb +1 -0
  260. data/lib/capybara/spec/views/with_windows.erb +1 -1
  261. data/lib/capybara/spec/views/within_frames.erb +1 -1
  262. data/lib/capybara/version.rb +1 -1
  263. data/lib/capybara/window.rb +32 -26
  264. data/lib/capybara.rb +128 -104
  265. data/spec/basic_node_spec.rb +59 -34
  266. data/spec/capybara_spec.rb +65 -51
  267. data/spec/counter_spec.rb +35 -0
  268. data/spec/css_builder_spec.rb +101 -0
  269. data/spec/css_splitter_spec.rb +38 -0
  270. data/spec/dsl_spec.rb +84 -55
  271. data/spec/filter_set_spec.rb +24 -7
  272. data/spec/fixtures/certificate.pem +25 -0
  273. data/spec/fixtures/key.pem +27 -0
  274. data/spec/fixtures/selenium_driver_rspec_failure.rb +5 -5
  275. data/spec/fixtures/selenium_driver_rspec_success.rb +5 -5
  276. data/spec/minitest_spec.rb +49 -5
  277. data/spec/minitest_spec_spec.rb +92 -62
  278. data/spec/per_session_config_spec.rb +6 -6
  279. data/spec/rack_test_spec.rb +183 -115
  280. data/spec/regexp_dissassembler_spec.rb +250 -0
  281. data/spec/result_spec.rb +99 -39
  282. data/spec/rspec/features_spec.rb +28 -25
  283. data/spec/rspec/scenarios_spec.rb +10 -6
  284. data/spec/rspec/shared_spec_matchers.rb +418 -364
  285. data/spec/rspec/views_spec.rb +4 -3
  286. data/spec/rspec_matchers_spec.rb +35 -10
  287. data/spec/rspec_spec.rb +109 -85
  288. data/spec/sauce_spec_chrome.rb +43 -0
  289. data/spec/selector_spec.rb +392 -62
  290. data/spec/selenium_spec_chrome.rb +183 -41
  291. data/spec/selenium_spec_chrome_remote.rb +96 -0
  292. data/spec/selenium_spec_edge.rb +41 -8
  293. data/spec/selenium_spec_firefox.rb +228 -0
  294. data/spec/selenium_spec_firefox_remote.rb +94 -0
  295. data/spec/selenium_spec_ie.rb +129 -11
  296. data/spec/selenium_spec_safari.rb +162 -0
  297. data/spec/server_spec.rb +192 -81
  298. data/spec/session_spec.rb +52 -16
  299. data/spec/shared_selenium_node.rb +79 -0
  300. data/spec/shared_selenium_session.rb +460 -123
  301. data/spec/spec_helper.rb +124 -2
  302. data/spec/whitespace_normalizer_spec.rb +54 -0
  303. data/spec/xpath_builder_spec.rb +93 -0
  304. metadata +344 -45
  305. data/.yard/templates_custom/default/class/html/selectors.erb +0 -38
  306. data/.yard/templates_custom/default/class/html/setup.rb +0 -17
  307. data/.yard/yard_extensions.rb +0 -78
  308. data/lib/capybara/rspec/compound.rb +0 -90
  309. data/lib/capybara/spec/session/assert_selector.rb +0 -149
  310. data/lib/capybara/spec/session/source_spec.rb +0 -0
  311. data/lib/capybara/spec/views/with_title.erb +0 -5
  312. data/spec/selenium_spec_marionette.rb +0 -143
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capybara
4
+ class Server
5
+ class AnimationDisabler
6
+ def self.selector_for(css_or_bool)
7
+ case css_or_bool
8
+ when String
9
+ css_or_bool
10
+ when true
11
+ '*'
12
+ else
13
+ raise CapybaraError, 'Capybara.disable_animation supports either a String (the css selector to disable) or a boolean'
14
+ end
15
+ end
16
+
17
+ def initialize(app)
18
+ @app = app
19
+ @disable_css_markup = format(DISABLE_CSS_MARKUP_TEMPLATE,
20
+ selector: self.class.selector_for(Capybara.disable_animation))
21
+ @disable_js_markup = +DISABLE_JS_MARKUP_TEMPLATE
22
+ end
23
+
24
+ def call(env)
25
+ status, headers, body = @app.call(env)
26
+ return [status, headers, body] unless html_content?(headers)
27
+
28
+ nonces = directive_nonces(headers).transform_values { |nonce| "nonce=\"#{nonce}\"" if nonce && !nonce.empty? }
29
+ response = Rack::Response.new([], status, headers)
30
+
31
+ body.each { |html| response.write insert_disable(html, nonces) }
32
+ body.close if body.respond_to?(:close)
33
+
34
+ response.finish
35
+ end
36
+
37
+ private
38
+
39
+ attr_reader :disable_css_markup, :disable_js_markup
40
+
41
+ def html_content?(headers)
42
+ /html/.match?(headers['Content-Type']) # rubocop:todo Performance/StringInclude
43
+ end
44
+
45
+ def insert_disable(html, nonces)
46
+ html.sub(%r{(</head>)}, "<style #{nonces['style-src']}>#{disable_css_markup}</style>\\1")
47
+ .sub(%r{(</body>)}, "<script #{nonces['script-src']}>#{disable_js_markup}</script>\\1")
48
+ end
49
+
50
+ def directive_nonces(headers)
51
+ headers.fetch('Content-Security-Policy', '')
52
+ .split(';')
53
+ .map(&:split) # rubocop:disable Style/MapToHash
54
+ .to_h do |s|
55
+ [
56
+ s[0], s[1..].filter_map do |value|
57
+ /^'nonce-(?<nonce>.+)'/ =~ value
58
+ nonce
59
+ end[0]
60
+ ]
61
+ end
62
+ end
63
+
64
+ DISABLE_CSS_MARKUP_TEMPLATE = <<~CSS
65
+ %<selector>s, %<selector>s::before, %<selector>s::after {
66
+ transition: none !important;
67
+ animation-duration: 0s !important;
68
+ animation-delay: 0s !important;
69
+ scroll-behavior: auto !important;
70
+ }
71
+ CSS
72
+
73
+ DISABLE_JS_MARKUP_TEMPLATE = <<~SCRIPT
74
+ //<![CDATA[
75
+ (typeof jQuery !== 'undefined') && (jQuery.fx.off = true);
76
+ //]]>
77
+ SCRIPT
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capybara
4
+ class Server
5
+ class Checker
6
+ TRY_HTTPS_ERRORS = [EOFError, Net::ReadTimeout, Errno::ECONNRESET].freeze
7
+
8
+ def initialize(host, port)
9
+ @host, @port = host, port
10
+ @ssl = false
11
+ end
12
+
13
+ def request(&block)
14
+ ssl? ? https_request(&block) : http_request(&block)
15
+ rescue *TRY_HTTPS_ERRORS
16
+ res = https_request(&block)
17
+ @ssl = true
18
+ res
19
+ end
20
+
21
+ def ssl?
22
+ @ssl
23
+ end
24
+
25
+ private
26
+
27
+ def http_request(&block)
28
+ make_request(read_timeout: 2, &block)
29
+ end
30
+
31
+ def https_request(&block)
32
+ make_request(**ssl_options, &block)
33
+ end
34
+
35
+ def make_request(**options, &block)
36
+ Net::HTTP.start(@host, @port, options.merge(max_retries: 0), &block)
37
+ end
38
+
39
+ def ssl_options
40
+ { use_ssl: true, verify_mode: OpenSSL::SSL::VERIFY_NONE }
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capybara
4
+ class Server
5
+ class Middleware
6
+ class Counter
7
+ def initialize
8
+ @value = []
9
+ @mutex = Mutex.new
10
+ end
11
+
12
+ def increment(uri)
13
+ @mutex.synchronize { @value.push(uri) }
14
+ end
15
+
16
+ def decrement(uri)
17
+ @mutex.synchronize { @value.delete_at(@value.index(uri) || - 1) }
18
+ end
19
+
20
+ def positive?
21
+ @mutex.synchronize { @value.length.positive? }
22
+ end
23
+
24
+ def value
25
+ @mutex.synchronize { @value.dup }
26
+ end
27
+ end
28
+
29
+ attr_reader :error
30
+
31
+ def initialize(app, server_errors, extra_middleware = [])
32
+ @app = app
33
+ @extended_app = extra_middleware.inject(@app) do |ex_app, klass|
34
+ klass.new(ex_app)
35
+ end
36
+ @counter = Counter.new
37
+ @server_errors = server_errors
38
+ end
39
+
40
+ def pending_requests
41
+ @counter.value
42
+ end
43
+
44
+ def pending_requests?
45
+ @counter.positive?
46
+ end
47
+
48
+ def clear_error
49
+ @error = nil
50
+ end
51
+
52
+ def call(env)
53
+ if env['PATH_INFO'] == '/__identify__'
54
+ [200, {}, [@app.object_id.to_s]]
55
+ else
56
+ request_uri = env['REQUEST_URI']
57
+ @counter.increment(request_uri)
58
+
59
+ begin
60
+ @extended_app.call(env)
61
+ rescue *@server_errors => e
62
+ @error ||= e
63
+ raise e
64
+ ensure
65
+ @counter.decrement(request_uri)
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -3,56 +3,13 @@
3
3
  require 'uri'
4
4
  require 'net/http'
5
5
  require 'rack'
6
+ require 'capybara/server/middleware'
7
+ require 'capybara/server/animation_disabler'
8
+ require 'capybara/server/checker'
6
9
 
7
10
  module Capybara
11
+ # @api private
8
12
  class Server
9
- class Middleware
10
- class Counter
11
- attr_reader :value
12
-
13
- def initialize
14
- @value = 0
15
- @mutex = Mutex.new
16
- end
17
-
18
- def increment
19
- @mutex.synchronize { @value += 1 }
20
- end
21
-
22
- def decrement
23
- @mutex.synchronize { @value -= 1 }
24
- end
25
- end
26
-
27
- attr_accessor :error
28
-
29
- def initialize(app, server_errors)
30
- @app = app
31
- @counter = Counter.new
32
- @server_errors = server_errors
33
- end
34
-
35
- def pending_requests?
36
- @counter.value > 0
37
- end
38
-
39
- def call(env)
40
- if env["PATH_INFO"] == "/__identify__"
41
- [200, {}, [@app.object_id.to_s]]
42
- else
43
- @counter.increment
44
- begin
45
- @app.call(env)
46
- rescue *@server_errors => e
47
- @error ||= e
48
- raise e
49
- ensure
50
- @counter.decrement
51
- end
52
- end
53
- end
54
- end
55
-
56
13
  class << self
57
14
  def ports
58
15
  @ports ||= {}
@@ -61,38 +18,55 @@ module Capybara
61
18
 
62
19
  attr_reader :app, :port, :host
63
20
 
64
- def initialize(app, port = Capybara.server_port, host = Capybara.server_host, server_errors = Capybara.server_errors)
21
+ def initialize(app,
22
+ *deprecated_options,
23
+ port: Capybara.server_port,
24
+ host: Capybara.server_host,
25
+ reportable_errors: Capybara.server_errors,
26
+ extra_middleware: [])
27
+ unless deprecated_options.empty?
28
+ warn 'Positional arguments, other than the application, to Server#new are deprecated, please use keyword arguments'
29
+ end
65
30
  @app = app
31
+ @extra_middleware = extra_middleware
66
32
  @server_thread = nil # suppress warnings
67
- @host, @port, @server_errors = host, port, server_errors
33
+ @host = deprecated_options[1] || host
34
+ @reportable_errors = deprecated_options[2] || reportable_errors
35
+ @port = deprecated_options[0] || port
68
36
  @port ||= Capybara::Server.ports[port_key]
69
37
  @port ||= find_available_port(host)
38
+ @checker = Checker.new(@host, @port)
70
39
  end
71
40
 
72
41
  def reset_error!
73
- middleware.error = nil
42
+ middleware.clear_error
74
43
  end
75
44
 
76
45
  def error
77
46
  middleware.error
78
47
  end
79
48
 
49
+ def using_ssl?
50
+ @checker.ssl?
51
+ end
52
+
80
53
  def responsive?
81
- return false if @server_thread && @server_thread.join(0)
54
+ return false if @server_thread&.join(0)
82
55
 
83
- res = Net::HTTP.start(host, port) { |http| http.get('/__identify__') }
56
+ res = @checker.request { |http| http.get('/__identify__') }
84
57
 
85
- if res.is_a?(Net::HTTPSuccess) or res.is_a?(Net::HTTPRedirection)
86
- return res.body == app.object_id.to_s
87
- end
88
- rescue SystemCallError
89
- return false
58
+ res.body == app.object_id.to_s if res.is_a?(Net::HTTPSuccess) || res.is_a?(Net::HTTPRedirection)
59
+ rescue SystemCallError, Net::ReadTimeout, OpenSSL::SSL::SSLError
60
+ false
90
61
  end
91
62
 
92
63
  def wait_for_pending_requests
93
- Timeout.timeout(60) { sleep(0.01) while pending_requests? }
94
- rescue Timeout::Error
95
- raise "Requests did not finish in 60 seconds"
64
+ timer = Capybara::Helpers.timer(expire_in: 60)
65
+ while pending_requests?
66
+ raise "Requests did not finish in 60 seconds: #{middleware.pending_requests}" if timer.expired?
67
+
68
+ sleep 0.01
69
+ end
96
70
  end
97
71
 
98
72
  def boot
@@ -103,18 +77,25 @@ module Capybara
103
77
  Capybara.server.call(middleware, port, host)
104
78
  end
105
79
 
106
- Timeout.timeout(60) { @server_thread.join(0.1) until responsive? }
80
+ timer = Capybara::Helpers.timer(expire_in: 60)
81
+ until responsive?
82
+ raise 'Rack application timed out during boot' if timer.expired?
83
+
84
+ @server_thread.join(0.1)
85
+ end
107
86
  end
108
- rescue Timeout::Error
109
- raise "Rack application timed out during boot"
110
- else
87
+
111
88
  self
112
89
  end
113
90
 
91
+ def base_url
92
+ "http#{'s' if using_ssl?}://#{host}:#{port}"
93
+ end
94
+
114
95
  private
115
96
 
116
97
  def middleware
117
- @middleware ||= Middleware.new(app, @server_errors)
98
+ @middleware ||= Middleware.new(app, @reportable_errors, @extra_middleware)
118
99
  end
119
100
 
120
101
  def port_key
@@ -127,9 +108,19 @@ module Capybara
127
108
 
128
109
  def find_available_port(host)
129
110
  server = TCPServer.new(host, 0)
130
- server.addr[1]
111
+ port = server.addr[1]
112
+ server.close
113
+
114
+ # Workaround issue where some platforms (mac, ???) when passed a host
115
+ # of '0.0.0.0' will return a port that is only available on one of the
116
+ # ip addresses that resolves to, but the next binding to that port requires
117
+ # that port to be available on all ips
118
+ server = TCPServer.new(host, port)
119
+ port
120
+ rescue Errno::EADDRINUSE
121
+ retry
131
122
  ensure
132
- server.close if server
123
+ server&.close
133
124
  end
134
125
  end
135
126
  end
@@ -7,7 +7,8 @@ module Capybara
7
7
  OPTIONS = %i[always_include_port run_server default_selector default_max_wait_time ignore_hidden_elements
8
8
  automatic_reload match exact exact_text raise_server_errors visible_text_only
9
9
  automatic_label_click enable_aria_label save_path asset_host default_host app_host
10
- server_host server_port server_errors].freeze
10
+ server_host server_port server_errors default_set_options disable_animation test_id
11
+ predicates_wait default_normalize_ws w3c_click_offset enable_aria_role default_retry_interval].freeze
11
12
 
12
13
  attr_accessor(*OPTIONS)
13
14
 
@@ -20,6 +21,8 @@ module Capybara
20
21
  # See {Capybara.configure}
21
22
  # @!method default_max_wait_time
22
23
  # See {Capybara.configure}
24
+ # @!method default_retry_interval
25
+ # See {Capybara.configure}
23
26
  # @!method ignore_hidden_elements
24
27
  # See {Capybara.configure}
25
28
  # @!method automatic_reload
@@ -36,6 +39,8 @@ module Capybara
36
39
  # See {Capybara.configure}
37
40
  # @!method enable_aria_label
38
41
  # See {Capybara.configure}
42
+ # @!method enable_aria_role
43
+ # See {Capybara.configure}
39
44
  # @!method save_path
40
45
  # See {Capybara.configure}
41
46
  # @!method asset_host
@@ -50,6 +55,16 @@ module Capybara
50
55
  # See {Capybara.configure}
51
56
  # @!method server_errors
52
57
  # See {Capybara.configure}
58
+ # @!method default_set_options
59
+ # See {Capybara.configure}
60
+ # @!method disable_animation
61
+ # See {Capybara.configure}
62
+ # @!method test_id
63
+ # See {Capybara.configure}
64
+ # @!method default_normalize_ws
65
+ # See {Capybara.configure}
66
+ # @!method w3c_click_offset
67
+ # See {Capybara.configure}
53
68
 
54
69
  remove_method :server_host
55
70
 
@@ -68,16 +83,35 @@ module Capybara
68
83
 
69
84
  remove_method :app_host=
70
85
  def app_host=(url)
71
- raise ArgumentError, "Capybara.app_host should be set to a url (http://www.example.com). Attempted to set #{url.inspect}." if url && url !~ URI::DEFAULT_PARSER.make_regexp
86
+ unless url.nil? || url.match?(URI::DEFAULT_PARSER.make_regexp)
87
+ raise ArgumentError, "Capybara.app_host should be set to a url (http://www.example.com). Attempted to set #{url.inspect}."
88
+ end
89
+
72
90
  @app_host = url
73
91
  end
74
92
 
75
93
  remove_method :default_host=
76
94
  def default_host=(url)
77
- raise ArgumentError, "Capybara.default_host should be set to a url (http://www.example.com). Attempted to set #{url.inspect}." if url && url !~ URI::DEFAULT_PARSER.make_regexp
95
+ unless url.nil? || url.match?(URI::DEFAULT_PARSER.make_regexp)
96
+ raise ArgumentError, "Capybara.default_host should be set to a url (http://www.example.com). Attempted to set #{url.inspect}."
97
+ end
98
+
78
99
  @default_host = url
79
100
  end
80
101
 
102
+ remove_method :test_id=
103
+ ##
104
+ #
105
+ # Set an attribute to be optionally matched against the locator for builtin selector types.
106
+ # This attribute will be checked by builtin selector types whenever id would normally be checked.
107
+ # If `nil` then it will be ignored.
108
+ #
109
+ # @param [String, Symbol, nil] id Name of the attribute to use as the test id
110
+ #
111
+ def test_id=(id)
112
+ @test_id = id&.to_sym
113
+ end
114
+
81
115
  def initialize_copy(other)
82
116
  super
83
117
  @server_errors = @server_errors.dup
@@ -85,9 +119,9 @@ module Capybara
85
119
  end
86
120
 
87
121
  class ReadOnlySessionConfig < SimpleDelegator
88
- SessionConfig::OPTIONS.each do |m|
89
- define_method "#{m}=" do |_|
90
- raise "Per session settings are only supported when Capybara.threadsafe == true"
122
+ SessionConfig::OPTIONS.each do |option|
123
+ define_method "#{option}=" do |_|
124
+ raise 'Per session settings are only supported when Capybara.threadsafe == true'
91
125
  end
92
126
  end
93
127
  end
@@ -6,21 +6,23 @@ module Capybara
6
6
  # Asserts that the page has the given path.
7
7
  # By default, if passed a full url this will compare against the full url,
8
8
  # if passed a path only the path+query portion will be compared, if passed a regexp
9
- # the comparison will depend on the :url option
9
+ # the comparison will depend on the :url option (path+query by default)
10
10
  #
11
11
  # @!macro current_path_query_params
12
- # @overload $0(string, options = {})
12
+ # @overload $0(string, **options)
13
13
  # @param string [String] The string that the current 'path' should equal
14
- # @overload $0(regexp, options = {})
14
+ # @overload $0(regexp, **options)
15
15
  # @param regexp [Regexp] The regexp that the current 'path' should match to
16
- # @option options [Boolean] :url (true if `string` ia a full url, otherwise false) Whether the compare should be done against the full current url or just the path
16
+ # @option options [Boolean] :url (true if `string` is a full url, otherwise false) Whether the comparison should be done against the full current url or just the path
17
17
  # @option options [Boolean] :ignore_query (false) Whether the query portion of the current url/path should be ignored
18
18
  # @option options [Numeric] :wait (Capybara.default_max_wait_time) Maximum time that Capybara will wait for the current url/path to eq/match given string/regexp argument
19
19
  # @raise [Capybara::ExpectationNotMet] if the assertion hasn't succeeded during wait time
20
20
  # @return [true]
21
21
  #
22
- def assert_current_path(path, **options)
23
- _verify_current_path(path, options) { |query| raise Capybara::ExpectationNotMet, query.failure_message unless query.resolves_for?(self) }
22
+ def assert_current_path(path, **options, &optional_filter_block)
23
+ _verify_current_path(path, optional_filter_block, **options) do |query|
24
+ raise Capybara::ExpectationNotMet, query.failure_message unless query.resolves_for?(self)
25
+ end
24
26
  end
25
27
 
26
28
  ##
@@ -33,8 +35,10 @@ module Capybara
33
35
  # @raise [Capybara::ExpectationNotMet] if the assertion hasn't succeeded during wait time
34
36
  # @return [true]
35
37
  #
36
- def assert_no_current_path(path, **options)
37
- _verify_current_path(path, options) { |query| raise Capybara::ExpectationNotMet, query.negative_failure_message if query.resolves_for?(self) }
38
+ def assert_no_current_path(path, **options, &optional_filter_block)
39
+ _verify_current_path(path, optional_filter_block, **options) do |query|
40
+ raise Capybara::ExpectationNotMet, query.negative_failure_message if query.resolves_for?(self)
41
+ end
38
42
  end
39
43
 
40
44
  ##
@@ -46,10 +50,8 @@ module Capybara
46
50
  # @macro current_path_query_params
47
51
  # @return [Boolean]
48
52
  #
49
- def has_current_path?(path, **options)
50
- assert_current_path(path, options)
51
- rescue Capybara::ExpectationNotMet
52
- return false
53
+ def has_current_path?(path, **options, &optional_filter_block)
54
+ make_predicate(options) { assert_current_path(path, **options, &optional_filter_block) }
53
55
  end
54
56
 
55
57
  ##
@@ -61,20 +63,25 @@ module Capybara
61
63
  # @macro current_path_query_params
62
64
  # @return [Boolean]
63
65
  #
64
- def has_no_current_path?(path, **options)
65
- assert_no_current_path(path, options)
66
- rescue Capybara::ExpectationNotMet
67
- return false
66
+ def has_no_current_path?(path, **options, &optional_filter_block)
67
+ make_predicate(options) { assert_no_current_path(path, **options, &optional_filter_block) }
68
68
  end
69
69
 
70
70
  private
71
71
 
72
- def _verify_current_path(path, options)
73
- query = Capybara::Queries::CurrentPathQuery.new(path, options)
72
+ def _verify_current_path(path, filter_block, **options)
73
+ query = Capybara::Queries::CurrentPathQuery.new(path, **options, &filter_block)
74
74
  document.synchronize(query.wait) do
75
75
  yield(query)
76
76
  end
77
- return true
77
+ true
78
+ end
79
+
80
+ def make_predicate(options)
81
+ options[:wait] = 0 unless options.key?(:wait) || config.predicates_wait
82
+ yield
83
+ rescue Capybara::ExpectationNotMet
84
+ false
78
85
  end
79
86
  end
80
87
  end