capybara 3.3.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 (308) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -0
  3. data/History.md +803 -13
  4. data/License.txt +1 -1
  5. data/README.md +257 -84
  6. data/lib/capybara/config.rb +25 -9
  7. data/lib/capybara/cucumber.rb +1 -1
  8. data/lib/capybara/driver/base.rb +17 -3
  9. data/lib/capybara/driver/node.rb +31 -6
  10. data/lib/capybara/dsl.rb +9 -7
  11. data/lib/capybara/helpers.rb +31 -7
  12. data/lib/capybara/minitest/spec.rb +180 -88
  13. data/lib/capybara/minitest.rb +262 -149
  14. data/lib/capybara/node/actions.rb +202 -116
  15. data/lib/capybara/node/base.rb +34 -19
  16. data/lib/capybara/node/document.rb +14 -2
  17. data/lib/capybara/node/document_matchers.rb +10 -12
  18. data/lib/capybara/node/element.rb +269 -115
  19. data/lib/capybara/node/finders.rb +99 -77
  20. data/lib/capybara/node/matchers.rb +327 -151
  21. data/lib/capybara/node/simple.rb +48 -13
  22. data/lib/capybara/node/whitespace_normalizer.rb +81 -0
  23. data/lib/capybara/queries/active_element_query.rb +18 -0
  24. data/lib/capybara/queries/ancestor_query.rb +8 -9
  25. data/lib/capybara/queries/base_query.rb +23 -16
  26. data/lib/capybara/queries/current_path_query.rb +16 -6
  27. data/lib/capybara/queries/match_query.rb +1 -0
  28. data/lib/capybara/queries/selector_query.rb +587 -130
  29. data/lib/capybara/queries/sibling_query.rb +8 -6
  30. data/lib/capybara/queries/style_query.rb +6 -2
  31. data/lib/capybara/queries/text_query.rb +28 -14
  32. data/lib/capybara/queries/title_query.rb +2 -2
  33. data/lib/capybara/rack_test/browser.rb +92 -25
  34. data/lib/capybara/rack_test/driver.rb +16 -7
  35. data/lib/capybara/rack_test/errors.rb +6 -0
  36. data/lib/capybara/rack_test/form.rb +68 -41
  37. data/lib/capybara/rack_test/node.rb +106 -39
  38. data/lib/capybara/rails.rb +1 -1
  39. data/lib/capybara/registration_container.rb +41 -0
  40. data/lib/capybara/registrations/drivers.rb +42 -0
  41. data/lib/capybara/registrations/patches/puma_ssl.rb +29 -0
  42. data/lib/capybara/registrations/servers.rb +66 -0
  43. data/lib/capybara/result.rb +75 -52
  44. data/lib/capybara/rspec/features.rb +7 -7
  45. data/lib/capybara/rspec/matcher_proxies.rb +39 -18
  46. data/lib/capybara/rspec/matchers/base.rb +113 -0
  47. data/lib/capybara/rspec/matchers/become_closed.rb +33 -0
  48. data/lib/capybara/rspec/matchers/compound.rb +88 -0
  49. data/lib/capybara/rspec/matchers/count_sugar.rb +37 -0
  50. data/lib/capybara/rspec/matchers/have_ancestor.rb +28 -0
  51. data/lib/capybara/rspec/matchers/have_current_path.rb +29 -0
  52. data/lib/capybara/rspec/matchers/have_selector.rb +69 -0
  53. data/lib/capybara/rspec/matchers/have_sibling.rb +27 -0
  54. data/lib/capybara/rspec/matchers/have_text.rb +33 -0
  55. data/lib/capybara/rspec/matchers/have_title.rb +29 -0
  56. data/lib/capybara/rspec/matchers/match_selector.rb +27 -0
  57. data/lib/capybara/rspec/matchers/match_style.rb +43 -0
  58. data/lib/capybara/rspec/matchers/spatial_sugar.rb +39 -0
  59. data/lib/capybara/rspec/matchers.rb +141 -339
  60. data/lib/capybara/rspec.rb +2 -0
  61. data/lib/capybara/selector/builders/css_builder.rb +84 -0
  62. data/lib/capybara/selector/builders/xpath_builder.rb +71 -0
  63. data/lib/capybara/selector/css.rb +27 -25
  64. data/lib/capybara/selector/definition/button.rb +68 -0
  65. data/lib/capybara/selector/definition/checkbox.rb +26 -0
  66. data/lib/capybara/selector/definition/css.rb +10 -0
  67. data/lib/capybara/selector/definition/datalist_input.rb +35 -0
  68. data/lib/capybara/selector/definition/datalist_option.rb +25 -0
  69. data/lib/capybara/selector/definition/element.rb +28 -0
  70. data/lib/capybara/selector/definition/field.rb +40 -0
  71. data/lib/capybara/selector/definition/fieldset.rb +14 -0
  72. data/lib/capybara/selector/definition/file_field.rb +13 -0
  73. data/lib/capybara/selector/definition/fillable_field.rb +33 -0
  74. data/lib/capybara/selector/definition/frame.rb +17 -0
  75. data/lib/capybara/selector/definition/id.rb +6 -0
  76. data/lib/capybara/selector/definition/label.rb +62 -0
  77. data/lib/capybara/selector/definition/link.rb +55 -0
  78. data/lib/capybara/selector/definition/link_or_button.rb +16 -0
  79. data/lib/capybara/selector/definition/option.rb +27 -0
  80. data/lib/capybara/selector/definition/radio_button.rb +27 -0
  81. data/lib/capybara/selector/definition/select.rb +81 -0
  82. data/lib/capybara/selector/definition/table.rb +109 -0
  83. data/lib/capybara/selector/definition/table_row.rb +21 -0
  84. data/lib/capybara/selector/definition/xpath.rb +5 -0
  85. data/lib/capybara/selector/definition.rb +280 -0
  86. data/lib/capybara/selector/filter.rb +1 -0
  87. data/lib/capybara/selector/filter_set.rb +73 -25
  88. data/lib/capybara/selector/filters/base.rb +24 -5
  89. data/lib/capybara/selector/filters/expression_filter.rb +3 -3
  90. data/lib/capybara/selector/filters/locator_filter.rb +29 -0
  91. data/lib/capybara/selector/filters/node_filter.rb +16 -2
  92. data/lib/capybara/selector/regexp_disassembler.rb +211 -0
  93. data/lib/capybara/selector/selector.rb +85 -348
  94. data/lib/capybara/selector/xpath_extensions.rb +17 -0
  95. data/lib/capybara/selector.rb +474 -447
  96. data/lib/capybara/selenium/atoms/getAttribute.min.js +1 -0
  97. data/lib/capybara/selenium/atoms/isDisplayed.min.js +1 -0
  98. data/lib/capybara/selenium/atoms/src/getAttribute.js +161 -0
  99. data/lib/capybara/selenium/atoms/src/isDisplayed.js +454 -0
  100. data/lib/capybara/selenium/driver.rb +255 -143
  101. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +93 -11
  102. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +128 -0
  103. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +84 -0
  104. data/lib/capybara/selenium/driver_specializations/internet_explorer_driver.rb +26 -0
  105. data/lib/capybara/selenium/driver_specializations/safari_driver.rb +24 -0
  106. data/lib/capybara/selenium/extensions/file_input_click_emulation.rb +34 -0
  107. data/lib/capybara/selenium/extensions/find.rb +110 -0
  108. data/lib/capybara/selenium/extensions/html5_drag.rb +229 -0
  109. data/lib/capybara/selenium/extensions/modifier_keys_stack.rb +28 -0
  110. data/lib/capybara/selenium/extensions/scroll.rb +76 -0
  111. data/lib/capybara/selenium/node.rb +436 -134
  112. data/lib/capybara/selenium/nodes/chrome_node.rb +125 -0
  113. data/lib/capybara/selenium/nodes/edge_node.rb +110 -0
  114. data/lib/capybara/selenium/nodes/firefox_node.rb +136 -0
  115. data/lib/capybara/selenium/nodes/ie_node.rb +22 -0
  116. data/lib/capybara/selenium/nodes/safari_node.rb +118 -0
  117. data/lib/capybara/selenium/patches/atoms.rb +18 -0
  118. data/lib/capybara/selenium/patches/is_displayed.rb +16 -0
  119. data/lib/capybara/selenium/patches/logs.rb +45 -0
  120. data/lib/capybara/selenium/patches/pause_duration_fix.rb +9 -0
  121. data/lib/capybara/selenium/patches/persistent_client.rb +20 -0
  122. data/lib/capybara/server/animation_disabler.rb +56 -19
  123. data/lib/capybara/server/checker.rb +9 -3
  124. data/lib/capybara/server/middleware.rb +28 -12
  125. data/lib/capybara/server.rb +33 -10
  126. data/lib/capybara/session/config.rb +34 -10
  127. data/lib/capybara/session/matchers.rb +23 -16
  128. data/lib/capybara/session.rb +230 -170
  129. data/lib/capybara/spec/public/jquery.js +5 -5
  130. data/lib/capybara/spec/public/offset.js +6 -0
  131. data/lib/capybara/spec/public/test.js +121 -8
  132. data/lib/capybara/spec/session/accept_alert_spec.rb +11 -11
  133. data/lib/capybara/spec/session/accept_confirm_spec.rb +3 -3
  134. data/lib/capybara/spec/session/accept_prompt_spec.rb +9 -10
  135. data/lib/capybara/spec/session/active_element_spec.rb +31 -0
  136. data/lib/capybara/spec/session/all_spec.rb +127 -40
  137. data/lib/capybara/spec/session/ancestor_spec.rb +24 -19
  138. data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +67 -38
  139. data/lib/capybara/spec/session/assert_current_path_spec.rb +21 -18
  140. data/lib/capybara/spec/session/assert_selector_spec.rb +52 -58
  141. data/lib/capybara/spec/session/assert_style_spec.rb +7 -7
  142. data/lib/capybara/spec/session/assert_text_spec.rb +74 -50
  143. data/lib/capybara/spec/session/assert_title_spec.rb +12 -12
  144. data/lib/capybara/spec/session/attach_file_spec.rb +126 -72
  145. data/lib/capybara/spec/session/body_spec.rb +6 -6
  146. data/lib/capybara/spec/session/check_spec.rb +102 -47
  147. data/lib/capybara/spec/session/choose_spec.rb +58 -32
  148. data/lib/capybara/spec/session/click_button_spec.rb +219 -163
  149. data/lib/capybara/spec/session/click_link_or_button_spec.rb +49 -23
  150. data/lib/capybara/spec/session/click_link_spec.rb +77 -54
  151. data/lib/capybara/spec/session/current_scope_spec.rb +8 -8
  152. data/lib/capybara/spec/session/current_url_spec.rb +38 -29
  153. data/lib/capybara/spec/session/dismiss_confirm_spec.rb +3 -3
  154. data/lib/capybara/spec/session/dismiss_prompt_spec.rb +2 -2
  155. data/lib/capybara/spec/session/element/assert_match_selector_spec.rb +8 -8
  156. data/lib/capybara/spec/session/element/match_css_spec.rb +16 -10
  157. data/lib/capybara/spec/session/element/match_xpath_spec.rb +6 -6
  158. data/lib/capybara/spec/session/element/matches_selector_spec.rb +68 -56
  159. data/lib/capybara/spec/session/evaluate_async_script_spec.rb +7 -7
  160. data/lib/capybara/spec/session/evaluate_script_spec.rb +28 -8
  161. data/lib/capybara/spec/session/execute_script_spec.rb +8 -7
  162. data/lib/capybara/spec/session/fill_in_spec.rb +101 -46
  163. data/lib/capybara/spec/session/find_button_spec.rb +23 -23
  164. data/lib/capybara/spec/session/find_by_id_spec.rb +7 -7
  165. data/lib/capybara/spec/session/find_field_spec.rb +32 -30
  166. data/lib/capybara/spec/session/find_link_spec.rb +31 -21
  167. data/lib/capybara/spec/session/find_spec.rb +244 -141
  168. data/lib/capybara/spec/session/first_spec.rb +43 -43
  169. data/lib/capybara/spec/session/frame/frame_title_spec.rb +5 -5
  170. data/lib/capybara/spec/session/frame/frame_url_spec.rb +5 -5
  171. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +30 -18
  172. data/lib/capybara/spec/session/frame/within_frame_spec.rb +45 -18
  173. data/lib/capybara/spec/session/go_back_spec.rb +1 -1
  174. data/lib/capybara/spec/session/go_forward_spec.rb +1 -1
  175. data/lib/capybara/spec/session/has_all_selectors_spec.rb +23 -23
  176. data/lib/capybara/spec/session/has_ancestor_spec.rb +46 -0
  177. data/lib/capybara/spec/session/has_any_selectors_spec.rb +29 -0
  178. data/lib/capybara/spec/session/has_button_spec.rb +94 -13
  179. data/lib/capybara/spec/session/has_css_spec.rb +272 -132
  180. data/lib/capybara/spec/session/has_current_path_spec.rb +50 -35
  181. data/lib/capybara/spec/session/has_element_spec.rb +47 -0
  182. data/lib/capybara/spec/session/has_field_spec.rb +137 -58
  183. data/lib/capybara/spec/session/has_link_spec.rb +44 -4
  184. data/lib/capybara/spec/session/has_none_selectors_spec.rb +31 -31
  185. data/lib/capybara/spec/session/has_select_spec.rb +84 -50
  186. data/lib/capybara/spec/session/has_selector_spec.rb +111 -71
  187. data/lib/capybara/spec/session/has_sibling_spec.rb +50 -0
  188. data/lib/capybara/spec/session/has_table_spec.rb +181 -4
  189. data/lib/capybara/spec/session/has_text_spec.rb +101 -53
  190. data/lib/capybara/spec/session/has_title_spec.rb +19 -14
  191. data/lib/capybara/spec/session/has_xpath_spec.rb +56 -38
  192. data/lib/capybara/spec/session/headers_spec.rb +1 -1
  193. data/lib/capybara/spec/session/html_spec.rb +13 -6
  194. data/lib/capybara/spec/session/matches_style_spec.rb +37 -0
  195. data/lib/capybara/spec/session/node_spec.rb +894 -142
  196. data/lib/capybara/spec/session/node_wrapper_spec.rb +10 -7
  197. data/lib/capybara/spec/session/refresh_spec.rb +9 -7
  198. data/lib/capybara/spec/session/reset_session_spec.rb +63 -35
  199. data/lib/capybara/spec/session/response_code_spec.rb +1 -1
  200. data/lib/capybara/spec/session/save_and_open_page_spec.rb +2 -2
  201. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +2 -2
  202. data/lib/capybara/spec/session/save_page_spec.rb +37 -37
  203. data/lib/capybara/spec/session/save_screenshot_spec.rb +10 -10
  204. data/lib/capybara/spec/session/screenshot_spec.rb +2 -2
  205. data/lib/capybara/spec/session/scroll_spec.rb +119 -0
  206. data/lib/capybara/spec/session/select_spec.rb +85 -85
  207. data/lib/capybara/spec/session/selectors_spec.rb +49 -18
  208. data/lib/capybara/spec/session/sibling_spec.rb +9 -9
  209. data/lib/capybara/spec/session/text_spec.rb +25 -24
  210. data/lib/capybara/spec/session/title_spec.rb +7 -6
  211. data/lib/capybara/spec/session/uncheck_spec.rb +33 -21
  212. data/lib/capybara/spec/session/unselect_spec.rb +37 -37
  213. data/lib/capybara/spec/session/visit_spec.rb +68 -49
  214. data/lib/capybara/spec/session/window/become_closed_spec.rb +20 -17
  215. data/lib/capybara/spec/session/window/current_window_spec.rb +1 -1
  216. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +20 -16
  217. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +6 -2
  218. data/lib/capybara/spec/session/window/window_spec.rb +62 -63
  219. data/lib/capybara/spec/session/window/windows_spec.rb +5 -1
  220. data/lib/capybara/spec/session/window/within_window_spec.rb +14 -14
  221. data/lib/capybara/spec/session/within_spec.rb +79 -42
  222. data/lib/capybara/spec/spec_helper.rb +41 -53
  223. data/lib/capybara/spec/test_app.rb +132 -43
  224. data/lib/capybara/spec/views/animated.erb +49 -0
  225. data/lib/capybara/spec/views/form.erb +139 -42
  226. data/lib/capybara/spec/views/frame_child.erb +4 -3
  227. data/lib/capybara/spec/views/frame_one.erb +2 -1
  228. data/lib/capybara/spec/views/frame_parent.erb +1 -1
  229. data/lib/capybara/spec/views/frame_two.erb +1 -1
  230. data/lib/capybara/spec/views/initial_alert.erb +2 -1
  231. data/lib/capybara/spec/views/layout.erb +10 -0
  232. data/lib/capybara/spec/views/obscured.erb +47 -0
  233. data/lib/capybara/spec/views/offset.erb +33 -0
  234. data/lib/capybara/spec/views/path.erb +2 -2
  235. data/lib/capybara/spec/views/popup_one.erb +1 -1
  236. data/lib/capybara/spec/views/popup_two.erb +1 -1
  237. data/lib/capybara/spec/views/react.erb +45 -0
  238. data/lib/capybara/spec/views/scroll.erb +21 -0
  239. data/lib/capybara/spec/views/spatial.erb +31 -0
  240. data/lib/capybara/spec/views/tables.erb +67 -0
  241. data/lib/capybara/spec/views/with_animation.erb +39 -4
  242. data/lib/capybara/spec/views/with_base_tag.erb +2 -2
  243. data/lib/capybara/spec/views/with_dragula.erb +24 -0
  244. data/lib/capybara/spec/views/with_fixed_header_footer.erb +2 -1
  245. data/lib/capybara/spec/views/with_hover.erb +3 -2
  246. data/lib/capybara/spec/views/with_hover1.erb +10 -0
  247. data/lib/capybara/spec/views/with_html.erb +37 -9
  248. data/lib/capybara/spec/views/with_html5_svg.erb +20 -0
  249. data/lib/capybara/spec/views/with_jquery_animation.erb +24 -0
  250. data/lib/capybara/spec/views/with_js.erb +26 -5
  251. data/lib/capybara/spec/views/with_jstree.erb +26 -0
  252. data/lib/capybara/spec/views/with_namespace.erb +1 -0
  253. data/lib/capybara/spec/views/with_scope.erb +2 -2
  254. data/lib/capybara/spec/views/with_scope_other.erb +6 -0
  255. data/lib/capybara/spec/views/with_shadow.erb +31 -0
  256. data/lib/capybara/spec/views/with_slow_unload.erb +2 -1
  257. data/lib/capybara/spec/views/with_sortable_js.erb +21 -0
  258. data/lib/capybara/spec/views/with_unload_alert.erb +1 -0
  259. data/lib/capybara/spec/views/with_windows.erb +1 -1
  260. data/lib/capybara/spec/views/within_frames.erb +1 -1
  261. data/lib/capybara/version.rb +1 -1
  262. data/lib/capybara/window.rb +19 -25
  263. data/lib/capybara.rb +126 -111
  264. data/spec/basic_node_spec.rb +59 -34
  265. data/spec/capybara_spec.rb +56 -44
  266. data/spec/counter_spec.rb +35 -0
  267. data/spec/css_builder_spec.rb +101 -0
  268. data/spec/css_splitter_spec.rb +8 -8
  269. data/spec/dsl_spec.rb +79 -52
  270. data/spec/filter_set_spec.rb +9 -9
  271. data/spec/fixtures/selenium_driver_rspec_failure.rb +4 -4
  272. data/spec/fixtures/selenium_driver_rspec_success.rb +4 -4
  273. data/spec/minitest_spec.rb +45 -7
  274. data/spec/minitest_spec_spec.rb +87 -64
  275. data/spec/per_session_config_spec.rb +6 -6
  276. data/spec/rack_test_spec.rb +172 -116
  277. data/spec/regexp_dissassembler_spec.rb +250 -0
  278. data/spec/result_spec.rb +80 -72
  279. data/spec/rspec/features_spec.rb +21 -16
  280. data/spec/rspec/scenarios_spec.rb +10 -6
  281. data/spec/rspec/shared_spec_matchers.rb +407 -365
  282. data/spec/rspec/views_spec.rb +3 -3
  283. data/spec/rspec_matchers_spec.rb +35 -10
  284. data/spec/rspec_spec.rb +63 -41
  285. data/spec/sauce_spec_chrome.rb +43 -0
  286. data/spec/selector_spec.rb +334 -89
  287. data/spec/selenium_spec_chrome.rb +176 -62
  288. data/spec/selenium_spec_chrome_remote.rb +54 -14
  289. data/spec/selenium_spec_edge.rb +41 -8
  290. data/spec/selenium_spec_firefox.rb +228 -0
  291. data/spec/selenium_spec_firefox_remote.rb +94 -0
  292. data/spec/selenium_spec_ie.rb +129 -11
  293. data/spec/selenium_spec_safari.rb +162 -0
  294. data/spec/server_spec.rb +171 -97
  295. data/spec/session_spec.rb +34 -18
  296. data/spec/shared_selenium_node.rb +79 -0
  297. data/spec/shared_selenium_session.rb +344 -80
  298. data/spec/spec_helper.rb +124 -2
  299. data/spec/whitespace_normalizer_spec.rb +54 -0
  300. data/spec/xpath_builder_spec.rb +93 -0
  301. metadata +326 -28
  302. data/lib/capybara/rspec/compound.rb +0 -94
  303. data/lib/capybara/selenium/driver_specializations/marionette_driver.rb +0 -31
  304. data/lib/capybara/selenium/nodes/marionette_node.rb +0 -31
  305. data/lib/capybara/spec/session/has_style_spec.rb +0 -25
  306. data/lib/capybara/spec/session/source_spec.rb +0 -0
  307. data/lib/capybara/spec/views/with_title.erb +0 -5
  308. data/spec/selenium_spec_marionette.rb +0 -167
@@ -3,41 +3,78 @@
3
3
  module Capybara
4
4
  class Server
5
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
+
6
17
  def initialize(app)
7
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
8
22
  end
9
23
 
10
24
  def call(env)
11
- @status, @headers, @body = @app.call(env)
12
- return [@status, @headers, @body] unless html_content?
13
- response = Rack::Response.new([], @status, @headers)
25
+ status, headers, body = @app.call(env)
26
+ return [status, headers, body] unless html_content?(headers)
14
27
 
15
- @body.each { |html| response.write insert_disable(html) }
16
- @body.close if @body.respond_to?(:close)
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)
17
33
 
18
34
  response.finish
19
35
  end
20
36
 
21
37
  private
22
38
 
23
- def html_content?
24
- !!(@headers["Content-Type"] =~ /html/)
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
25
43
  end
26
44
 
27
- def insert_disable(html)
28
- html.sub(%r{(</head>)}, DISABLE_MARKUP + '\\1')
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")
29
48
  end
30
49
 
31
- DISABLE_MARKUP = <<~HTML
32
- <script defer>(typeof jQuery !== 'undefined') && (jQuery.fx.off = true);</script>
33
- <style>
34
- * {
35
- transition: none !important;
36
- animation-duration: 0s !important;
37
- animation-delay: 0s !important;
38
- }
39
- </style>
40
- HTML
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
41
78
  end
42
79
  end
43
80
  end
@@ -3,6 +3,8 @@
3
3
  module Capybara
4
4
  class Server
5
5
  class Checker
6
+ TRY_HTTPS_ERRORS = [EOFError, Net::ReadTimeout, Errno::ECONNRESET].freeze
7
+
6
8
  def initialize(host, port)
7
9
  @host, @port = host, port
8
10
  @ssl = false
@@ -10,7 +12,7 @@ module Capybara
10
12
 
11
13
  def request(&block)
12
14
  ssl? ? https_request(&block) : http_request(&block)
13
- rescue EOFError, Net::ReadTimeout
15
+ rescue *TRY_HTTPS_ERRORS
14
16
  res = https_request(&block)
15
17
  @ssl = true
16
18
  res
@@ -23,11 +25,15 @@ module Capybara
23
25
  private
24
26
 
25
27
  def http_request(&block)
26
- Net::HTTP.start(@host, @port, read_timeout: 2, &block)
28
+ make_request(read_timeout: 2, &block)
27
29
  end
28
30
 
29
31
  def https_request(&block)
30
- Net::HTTP.start(@host, @port, ssl_options, &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)
31
37
  end
32
38
 
33
39
  def ssl_options
@@ -4,23 +4,29 @@ module Capybara
4
4
  class Server
5
5
  class Middleware
6
6
  class Counter
7
- attr_reader :value
8
-
9
7
  def initialize
10
- @value = 0
8
+ @value = []
11
9
  @mutex = Mutex.new
12
10
  end
13
11
 
14
- def increment
15
- @mutex.synchronize { @value += 1 }
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? }
16
22
  end
17
23
 
18
- def decrement
19
- @mutex.synchronize { @value -= 1 }
24
+ def value
25
+ @mutex.synchronize { @value.dup }
20
26
  end
21
27
  end
22
28
 
23
- attr_accessor :error
29
+ attr_reader :error
24
30
 
25
31
  def initialize(app, server_errors, extra_middleware = [])
26
32
  @app = app
@@ -31,22 +37,32 @@ module Capybara
31
37
  @server_errors = server_errors
32
38
  end
33
39
 
40
+ def pending_requests
41
+ @counter.value
42
+ end
43
+
34
44
  def pending_requests?
35
- @counter.value.positive?
45
+ @counter.positive?
46
+ end
47
+
48
+ def clear_error
49
+ @error = nil
36
50
  end
37
51
 
38
52
  def call(env)
39
- if env["PATH_INFO"] == "/__identify__"
53
+ if env['PATH_INFO'] == '/__identify__'
40
54
  [200, {}, [@app.object_id.to_s]]
41
55
  else
42
- @counter.increment
56
+ request_uri = env['REQUEST_URI']
57
+ @counter.increment(request_uri)
58
+
43
59
  begin
44
60
  @extended_app.call(env)
45
61
  rescue *@server_errors => e
46
62
  @error ||= e
47
63
  raise e
48
64
  ensure
49
- @counter.decrement
65
+ @counter.decrement(request_uri)
50
66
  end
51
67
  end
52
68
  end
@@ -8,6 +8,7 @@ require 'capybara/server/animation_disabler'
8
8
  require 'capybara/server/checker'
9
9
 
10
10
  module Capybara
11
+ # @api private
11
12
  class Server
12
13
  class << self
13
14
  def ports
@@ -17,8 +18,15 @@ module Capybara
17
18
 
18
19
  attr_reader :app, :port, :host
19
20
 
20
- def initialize(app, *deprecated_options, port: Capybara.server_port, host: Capybara.server_host, reportable_errors: Capybara.server_errors, extra_middleware: [])
21
- warn "Positional arguments, other than the application, to Server#new are deprecated, please use keyword arguments" unless deprecated_options.empty?
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
22
30
  @app = app
23
31
  @extra_middleware = extra_middleware
24
32
  @server_thread = nil # suppress warnings
@@ -31,7 +39,7 @@ module Capybara
31
39
  end
32
40
 
33
41
  def reset_error!
34
- middleware.error = nil
42
+ middleware.clear_error
35
43
  end
36
44
 
37
45
  def error
@@ -44,19 +52,19 @@ module Capybara
44
52
 
45
53
  def responsive?
46
54
  return false if @server_thread&.join(0)
55
+
47
56
  res = @checker.request { |http| http.get('/__identify__') }
48
57
 
49
- if res.is_a?(Net::HTTPSuccess) || res.is_a?(Net::HTTPRedirection)
50
- return res.body == app.object_id.to_s
51
- end
52
- rescue SystemCallError
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
53
60
  false
54
61
  end
55
62
 
56
63
  def wait_for_pending_requests
57
64
  timer = Capybara::Helpers.timer(expire_in: 60)
58
65
  while pending_requests?
59
- raise "Requests did not finish in 60 seconds" if timer.expired?
66
+ raise "Requests did not finish in 60 seconds: #{middleware.pending_requests}" if timer.expired?
67
+
60
68
  sleep 0.01
61
69
  end
62
70
  end
@@ -71,7 +79,8 @@ module Capybara
71
79
 
72
80
  timer = Capybara::Helpers.timer(expire_in: 60)
73
81
  until responsive?
74
- raise "Rack application timed out during boot" if timer.expired?
82
+ raise 'Rack application timed out during boot' if timer.expired?
83
+
75
84
  @server_thread.join(0.1)
76
85
  end
77
86
  end
@@ -79,6 +88,10 @@ module Capybara
79
88
  self
80
89
  end
81
90
 
91
+ def base_url
92
+ "http#{'s' if using_ssl?}://#{host}:#{port}"
93
+ end
94
+
82
95
  private
83
96
 
84
97
  def middleware
@@ -95,7 +108,17 @@ module Capybara
95
108
 
96
109
  def find_available_port(host)
97
110
  server = TCPServer.new(host, 0)
98
- 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
99
122
  ensure
100
123
  server&.close
101
124
  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 default_set_options disable_animation].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
@@ -54,6 +59,12 @@ module Capybara
54
59
  # See {Capybara.configure}
55
60
  # @!method disable_animation
56
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}
57
68
 
58
69
  remove_method :server_host
59
70
 
@@ -72,20 +83,33 @@ module Capybara
72
83
 
73
84
  remove_method :app_host=
74
85
  def app_host=(url)
75
- 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
+
76
90
  @app_host = url
77
91
  end
78
92
 
79
93
  remove_method :default_host=
80
94
  def default_host=(url)
81
- 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
+
82
99
  @default_host = url
83
100
  end
84
101
 
85
- remove_method :disable_animation=
86
- def disable_animation=(bool)
87
- warn "Capybara.disable_animation is a beta feature - it may change/disappear in a future point version" if bool
88
- @disable_animation = bool
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
89
113
  end
90
114
 
91
115
  def initialize_copy(other)
@@ -95,9 +119,9 @@ module Capybara
95
119
  end
96
120
 
97
121
  class ReadOnlySessionConfig < SimpleDelegator
98
- SessionConfig::OPTIONS.each do |m|
99
- define_method "#{m}=" do |_|
100
- 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'
101
125
  end
102
126
  end
103
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
12
  # @overload $0(string, **options)
13
13
  # @param string [String] The string that the current 'path' should equal
14
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
- 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
- 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
77
  true
78
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
85
+ end
79
86
  end
80
87
  end