capybara 2.18.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 (316) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -1
  3. data/History.md +945 -12
  4. data/License.txt +1 -1
  5. data/README.md +264 -90
  6. data/lib/capybara/config.rb +29 -57
  7. data/lib/capybara/cucumber.rb +2 -3
  8. data/lib/capybara/driver/base.rb +35 -18
  9. data/lib/capybara/driver/node.rb +40 -10
  10. data/lib/capybara/dsl.rb +10 -7
  11. data/lib/capybara/helpers.rb +70 -31
  12. data/lib/capybara/minitest/spec.rb +185 -83
  13. data/lib/capybara/minitest.rb +232 -112
  14. data/lib/capybara/node/actions.rb +274 -171
  15. data/lib/capybara/node/base.rb +42 -34
  16. data/lib/capybara/node/document.rb +15 -3
  17. data/lib/capybara/node/document_matchers.rb +19 -21
  18. data/lib/capybara/node/element.rb +362 -135
  19. data/lib/capybara/node/finders.rb +151 -137
  20. data/lib/capybara/node/matchers.rb +394 -209
  21. data/lib/capybara/node/simple.rb +59 -26
  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 +12 -9
  25. data/lib/capybara/queries/base_query.rb +39 -28
  26. data/lib/capybara/queries/current_path_query.rb +21 -27
  27. data/lib/capybara/queries/match_query.rb +14 -7
  28. data/lib/capybara/queries/selector_query.rb +659 -149
  29. data/lib/capybara/queries/sibling_query.rb +11 -9
  30. data/lib/capybara/queries/style_query.rb +45 -0
  31. data/lib/capybara/queries/text_query.rb +56 -38
  32. data/lib/capybara/queries/title_query.rb +8 -11
  33. data/lib/capybara/rack_test/browser.rb +114 -42
  34. data/lib/capybara/rack_test/css_handlers.rb +6 -4
  35. data/lib/capybara/rack_test/driver.rb +22 -17
  36. data/lib/capybara/rack_test/errors.rb +6 -0
  37. data/lib/capybara/rack_test/form.rb +93 -58
  38. data/lib/capybara/rack_test/node.rb +184 -81
  39. data/lib/capybara/rails.rb +3 -7
  40. data/lib/capybara/registration_container.rb +41 -0
  41. data/lib/capybara/registrations/drivers.rb +42 -0
  42. data/lib/capybara/registrations/patches/puma_ssl.rb +29 -0
  43. data/lib/capybara/registrations/servers.rb +66 -0
  44. data/lib/capybara/result.rb +97 -63
  45. data/lib/capybara/rspec/features.rb +17 -50
  46. data/lib/capybara/rspec/matcher_proxies.rb +52 -15
  47. data/lib/capybara/rspec/matchers/base.rb +113 -0
  48. data/lib/capybara/rspec/matchers/become_closed.rb +33 -0
  49. data/lib/capybara/rspec/matchers/compound.rb +88 -0
  50. data/lib/capybara/rspec/matchers/count_sugar.rb +37 -0
  51. data/lib/capybara/rspec/matchers/have_ancestor.rb +28 -0
  52. data/lib/capybara/rspec/matchers/have_current_path.rb +29 -0
  53. data/lib/capybara/rspec/matchers/have_selector.rb +69 -0
  54. data/lib/capybara/rspec/matchers/have_sibling.rb +27 -0
  55. data/lib/capybara/rspec/matchers/have_text.rb +33 -0
  56. data/lib/capybara/rspec/matchers/have_title.rb +29 -0
  57. data/lib/capybara/rspec/matchers/match_selector.rb +27 -0
  58. data/lib/capybara/rspec/matchers/match_style.rb +43 -0
  59. data/lib/capybara/rspec/matchers/spatial_sugar.rb +39 -0
  60. data/lib/capybara/rspec/matchers.rb +146 -310
  61. data/lib/capybara/rspec.rb +7 -11
  62. data/lib/capybara/selector/builders/css_builder.rb +84 -0
  63. data/lib/capybara/selector/builders/xpath_builder.rb +71 -0
  64. data/lib/capybara/selector/css.rb +85 -13
  65. data/lib/capybara/selector/definition/button.rb +68 -0
  66. data/lib/capybara/selector/definition/checkbox.rb +26 -0
  67. data/lib/capybara/selector/definition/css.rb +10 -0
  68. data/lib/capybara/selector/definition/datalist_input.rb +35 -0
  69. data/lib/capybara/selector/definition/datalist_option.rb +25 -0
  70. data/lib/capybara/selector/definition/element.rb +28 -0
  71. data/lib/capybara/selector/definition/field.rb +40 -0
  72. data/lib/capybara/selector/definition/fieldset.rb +14 -0
  73. data/lib/capybara/selector/definition/file_field.rb +13 -0
  74. data/lib/capybara/selector/definition/fillable_field.rb +33 -0
  75. data/lib/capybara/selector/definition/frame.rb +17 -0
  76. data/lib/capybara/selector/definition/id.rb +6 -0
  77. data/lib/capybara/selector/definition/label.rb +62 -0
  78. data/lib/capybara/selector/definition/link.rb +55 -0
  79. data/lib/capybara/selector/definition/link_or_button.rb +16 -0
  80. data/lib/capybara/selector/definition/option.rb +27 -0
  81. data/lib/capybara/selector/definition/radio_button.rb +27 -0
  82. data/lib/capybara/selector/definition/select.rb +81 -0
  83. data/lib/capybara/selector/definition/table.rb +109 -0
  84. data/lib/capybara/selector/definition/table_row.rb +21 -0
  85. data/lib/capybara/selector/definition/xpath.rb +5 -0
  86. data/lib/capybara/selector/definition.rb +280 -0
  87. data/lib/capybara/selector/filter.rb +2 -17
  88. data/lib/capybara/selector/filter_set.rb +80 -33
  89. data/lib/capybara/selector/filters/base.rb +50 -6
  90. data/lib/capybara/selector/filters/expression_filter.rb +8 -26
  91. data/lib/capybara/selector/filters/locator_filter.rb +29 -0
  92. data/lib/capybara/selector/filters/node_filter.rb +16 -12
  93. data/lib/capybara/selector/regexp_disassembler.rb +211 -0
  94. data/lib/capybara/selector/selector.rb +93 -210
  95. data/lib/capybara/selector/xpath_extensions.rb +17 -0
  96. data/lib/capybara/selector.rb +475 -523
  97. data/lib/capybara/selenium/atoms/getAttribute.min.js +1 -0
  98. data/lib/capybara/selenium/atoms/isDisplayed.min.js +1 -0
  99. data/lib/capybara/selenium/atoms/src/getAttribute.js +161 -0
  100. data/lib/capybara/selenium/atoms/src/isDisplayed.js +454 -0
  101. data/lib/capybara/selenium/driver.rb +298 -267
  102. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +117 -0
  103. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +128 -0
  104. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +84 -0
  105. data/lib/capybara/selenium/driver_specializations/internet_explorer_driver.rb +26 -0
  106. data/lib/capybara/selenium/driver_specializations/safari_driver.rb +24 -0
  107. data/lib/capybara/selenium/extensions/file_input_click_emulation.rb +34 -0
  108. data/lib/capybara/selenium/extensions/find.rb +110 -0
  109. data/lib/capybara/selenium/extensions/html5_drag.rb +229 -0
  110. data/lib/capybara/selenium/extensions/modifier_keys_stack.rb +28 -0
  111. data/lib/capybara/selenium/extensions/scroll.rb +76 -0
  112. data/lib/capybara/selenium/node.rb +517 -145
  113. data/lib/capybara/selenium/nodes/chrome_node.rb +125 -0
  114. data/lib/capybara/selenium/nodes/edge_node.rb +110 -0
  115. data/lib/capybara/selenium/nodes/firefox_node.rb +136 -0
  116. data/lib/capybara/selenium/nodes/ie_node.rb +22 -0
  117. data/lib/capybara/selenium/nodes/safari_node.rb +118 -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 +80 -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 +59 -67
  127. data/lib/capybara/session/config.rb +81 -67
  128. data/lib/capybara/session/matchers.rb +28 -20
  129. data/lib/capybara/session.rb +337 -365
  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 +151 -12
  133. data/lib/capybara/spec/session/accept_alert_spec.rb +12 -11
  134. data/lib/capybara/spec/session/accept_confirm_spec.rb +6 -5
  135. data/lib/capybara/spec/session/accept_prompt_spec.rb +10 -10
  136. data/lib/capybara/spec/session/active_element_spec.rb +31 -0
  137. data/lib/capybara/spec/session/all_spec.rb +161 -57
  138. data/lib/capybara/spec/session/ancestor_spec.rb +27 -24
  139. data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +68 -38
  140. data/lib/capybara/spec/session/assert_current_path_spec.rb +75 -0
  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_spec.rb +258 -0
  144. data/lib/capybara/spec/session/{assert_title.rb → assert_title_spec.rb} +22 -12
  145. data/lib/capybara/spec/session/attach_file_spec.rb +144 -69
  146. data/lib/capybara/spec/session/body_spec.rb +12 -13
  147. data/lib/capybara/spec/session/check_spec.rb +117 -55
  148. data/lib/capybara/spec/session/choose_spec.rb +64 -31
  149. data/lib/capybara/spec/session/click_button_spec.rb +231 -173
  150. data/lib/capybara/spec/session/click_link_or_button_spec.rb +55 -35
  151. data/lib/capybara/spec/session/click_link_spec.rb +93 -58
  152. data/lib/capybara/spec/session/current_scope_spec.rb +12 -11
  153. data/lib/capybara/spec/session/current_url_spec.rb +57 -39
  154. data/lib/capybara/spec/session/dismiss_confirm_spec.rb +4 -4
  155. data/lib/capybara/spec/session/dismiss_prompt_spec.rb +3 -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 +9 -7
  159. data/lib/capybara/spec/session/element/matches_selector_spec.rb +71 -57
  160. data/lib/capybara/spec/session/evaluate_async_script_spec.rb +8 -7
  161. data/lib/capybara/spec/session/evaluate_script_spec.rb +29 -8
  162. data/lib/capybara/spec/session/execute_script_spec.rb +10 -8
  163. data/lib/capybara/spec/session/fill_in_spec.rb +134 -43
  164. data/lib/capybara/spec/session/find_button_spec.rb +25 -24
  165. data/lib/capybara/spec/session/find_by_id_spec.rb +10 -9
  166. data/lib/capybara/spec/session/find_field_spec.rb +37 -41
  167. data/lib/capybara/spec/session/find_link_spec.rb +46 -17
  168. data/lib/capybara/spec/session/find_spec.rb +260 -145
  169. data/lib/capybara/spec/session/first_spec.rb +80 -52
  170. data/lib/capybara/spec/session/frame/frame_title_spec.rb +23 -0
  171. data/lib/capybara/spec/session/frame/frame_url_spec.rb +23 -0
  172. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +33 -20
  173. data/lib/capybara/spec/session/frame/within_frame_spec.rb +52 -32
  174. data/lib/capybara/spec/session/go_back_spec.rb +2 -1
  175. data/lib/capybara/spec/session/go_forward_spec.rb +2 -1
  176. data/lib/capybara/spec/session/has_all_selectors_spec.rb +31 -31
  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 +100 -13
  180. data/lib/capybara/spec/session/has_css_spec.rb +272 -137
  181. data/lib/capybara/spec/session/has_current_path_spec.rb +60 -61
  182. data/lib/capybara/spec/session/has_element_spec.rb +47 -0
  183. data/lib/capybara/spec/session/has_field_spec.rb +139 -59
  184. data/lib/capybara/spec/session/has_link_spec.rb +47 -6
  185. data/lib/capybara/spec/session/has_none_selectors_spec.rb +42 -40
  186. data/lib/capybara/spec/session/has_select_spec.rb +107 -72
  187. data/lib/capybara/spec/session/has_selector_spec.rb +120 -71
  188. data/lib/capybara/spec/session/has_sibling_spec.rb +50 -0
  189. data/lib/capybara/spec/session/has_table_spec.rb +183 -5
  190. data/lib/capybara/spec/session/has_text_spec.rb +106 -62
  191. data/lib/capybara/spec/session/has_title_spec.rb +20 -14
  192. data/lib/capybara/spec/session/has_xpath_spec.rb +57 -38
  193. data/lib/capybara/spec/session/{headers.rb → headers_spec.rb} +3 -2
  194. data/lib/capybara/spec/session/html_spec.rb +14 -6
  195. data/lib/capybara/spec/session/matches_style_spec.rb +37 -0
  196. data/lib/capybara/spec/session/node_spec.rb +1024 -153
  197. data/lib/capybara/spec/session/node_wrapper_spec.rb +39 -0
  198. data/lib/capybara/spec/session/refresh_spec.rb +12 -6
  199. data/lib/capybara/spec/session/reset_session_spec.rb +82 -35
  200. data/lib/capybara/spec/session/{response_code.rb → response_code_spec.rb} +2 -1
  201. data/lib/capybara/spec/session/save_and_open_page_spec.rb +3 -2
  202. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +8 -12
  203. data/lib/capybara/spec/session/save_page_spec.rb +42 -55
  204. data/lib/capybara/spec/session/save_screenshot_spec.rb +16 -14
  205. data/lib/capybara/spec/session/screenshot_spec.rb +2 -2
  206. data/lib/capybara/spec/session/scroll_spec.rb +119 -0
  207. data/lib/capybara/spec/session/select_spec.rb +107 -81
  208. data/lib/capybara/spec/session/selectors_spec.rb +52 -19
  209. data/lib/capybara/spec/session/sibling_spec.rb +10 -10
  210. data/lib/capybara/spec/session/text_spec.rb +37 -21
  211. data/lib/capybara/spec/session/title_spec.rb +17 -5
  212. data/lib/capybara/spec/session/uncheck_spec.rb +43 -23
  213. data/lib/capybara/spec/session/unselect_spec.rb +39 -38
  214. data/lib/capybara/spec/session/visit_spec.rb +85 -53
  215. data/lib/capybara/spec/session/window/become_closed_spec.rb +24 -20
  216. data/lib/capybara/spec/session/window/current_window_spec.rb +5 -3
  217. data/lib/capybara/spec/session/window/open_new_window_spec.rb +5 -3
  218. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +27 -22
  219. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +12 -6
  220. data/lib/capybara/spec/session/window/window_spec.rb +97 -63
  221. data/lib/capybara/spec/session/window/windows_spec.rb +12 -10
  222. data/lib/capybara/spec/session/window/within_window_spec.rb +31 -86
  223. data/lib/capybara/spec/session/within_spec.rb +83 -44
  224. data/lib/capybara/spec/spec_helper.rb +54 -44
  225. data/lib/capybara/spec/test_app.rb +158 -43
  226. data/lib/capybara/spec/views/animated.erb +49 -0
  227. data/lib/capybara/spec/views/form.erb +163 -42
  228. data/lib/capybara/spec/views/frame_child.erb +4 -3
  229. data/lib/capybara/spec/views/frame_one.erb +2 -1
  230. data/lib/capybara/spec/views/frame_parent.erb +1 -1
  231. data/lib/capybara/spec/views/frame_two.erb +1 -1
  232. data/lib/capybara/spec/views/initial_alert.erb +2 -1
  233. data/lib/capybara/spec/views/layout.erb +10 -0
  234. data/lib/capybara/spec/views/obscured.erb +47 -0
  235. data/lib/capybara/spec/views/offset.erb +33 -0
  236. data/lib/capybara/spec/views/path.erb +2 -2
  237. data/lib/capybara/spec/views/popup_one.erb +1 -1
  238. data/lib/capybara/spec/views/popup_two.erb +1 -1
  239. data/lib/capybara/spec/views/react.erb +45 -0
  240. data/lib/capybara/spec/views/scroll.erb +21 -0
  241. data/lib/capybara/spec/views/spatial.erb +31 -0
  242. data/lib/capybara/spec/views/tables.erb +68 -1
  243. data/lib/capybara/spec/views/with_animation.erb +81 -0
  244. data/lib/capybara/spec/views/with_base_tag.erb +2 -2
  245. data/lib/capybara/spec/views/with_dragula.erb +24 -0
  246. data/lib/capybara/spec/views/with_fixed_header_footer.erb +2 -1
  247. data/lib/capybara/spec/views/with_hover.erb +3 -2
  248. data/lib/capybara/spec/views/with_hover1.erb +10 -0
  249. data/lib/capybara/spec/views/with_html.erb +69 -12
  250. data/lib/capybara/spec/views/with_html5_svg.erb +20 -0
  251. data/lib/capybara/spec/views/with_jquery_animation.erb +24 -0
  252. data/lib/capybara/spec/views/with_js.erb +30 -5
  253. data/lib/capybara/spec/views/with_jstree.erb +26 -0
  254. data/lib/capybara/spec/views/with_namespace.erb +21 -0
  255. data/lib/capybara/spec/views/with_scope.erb +2 -2
  256. data/lib/capybara/spec/views/with_scope_other.erb +6 -0
  257. data/lib/capybara/spec/views/with_shadow.erb +31 -0
  258. data/lib/capybara/spec/views/with_slow_unload.erb +2 -1
  259. data/lib/capybara/spec/views/with_sortable_js.erb +21 -0
  260. data/lib/capybara/spec/views/with_unload_alert.erb +1 -0
  261. data/lib/capybara/spec/views/with_windows.erb +1 -1
  262. data/lib/capybara/spec/views/within_frames.erb +5 -2
  263. data/lib/capybara/version.rb +2 -1
  264. data/lib/capybara/window.rb +36 -34
  265. data/lib/capybara.rb +134 -107
  266. data/spec/basic_node_spec.rb +60 -34
  267. data/spec/capybara_spec.rb +63 -88
  268. data/spec/counter_spec.rb +35 -0
  269. data/spec/css_builder_spec.rb +101 -0
  270. data/spec/css_splitter_spec.rb +38 -0
  271. data/spec/dsl_spec.rb +85 -64
  272. data/spec/filter_set_spec.rb +27 -9
  273. data/spec/fixtures/certificate.pem +25 -0
  274. data/spec/fixtures/key.pem +27 -0
  275. data/spec/fixtures/selenium_driver_rspec_failure.rb +6 -5
  276. data/spec/fixtures/selenium_driver_rspec_success.rb +6 -5
  277. data/spec/minitest_spec.rb +52 -7
  278. data/spec/minitest_spec_spec.rb +94 -63
  279. data/spec/per_session_config_spec.rb +14 -13
  280. data/spec/rack_test_spec.rb +194 -125
  281. data/spec/regexp_dissassembler_spec.rb +250 -0
  282. data/spec/result_spec.rb +111 -50
  283. data/spec/rspec/features_spec.rb +37 -31
  284. data/spec/rspec/scenarios_spec.rb +10 -8
  285. data/spec/rspec/shared_spec_matchers.rb +473 -422
  286. data/spec/rspec/views_spec.rb +5 -3
  287. data/spec/rspec_matchers_spec.rb +52 -11
  288. data/spec/rspec_spec.rb +109 -89
  289. data/spec/sauce_spec_chrome.rb +43 -0
  290. data/spec/selector_spec.rb +397 -68
  291. data/spec/selenium_spec_chrome.rb +187 -40
  292. data/spec/selenium_spec_chrome_remote.rb +96 -0
  293. data/spec/selenium_spec_edge.rb +60 -0
  294. data/spec/selenium_spec_firefox.rb +201 -41
  295. data/spec/selenium_spec_firefox_remote.rb +94 -0
  296. data/spec/selenium_spec_ie.rb +149 -0
  297. data/spec/selenium_spec_safari.rb +162 -0
  298. data/spec/server_spec.rb +213 -102
  299. data/spec/session_spec.rb +53 -16
  300. data/spec/shared_selenium_node.rb +79 -0
  301. data/spec/shared_selenium_session.rb +473 -122
  302. data/spec/spec_helper.rb +126 -7
  303. data/spec/whitespace_normalizer_spec.rb +54 -0
  304. data/spec/xpath_builder_spec.rb +93 -0
  305. metadata +355 -73
  306. data/.yard/templates_custom/default/class/html/selectors.erb +0 -38
  307. data/.yard/templates_custom/default/class/html/setup.rb +0 -17
  308. data/.yard/yard_extensions.rb +0 -78
  309. data/lib/capybara/query.rb +0 -7
  310. data/lib/capybara/rspec/compound.rb +0 -95
  311. data/lib/capybara/spec/session/assert_current_path.rb +0 -72
  312. data/lib/capybara/spec/session/assert_selector.rb +0 -148
  313. data/lib/capybara/spec/session/assert_text.rb +0 -234
  314. data/lib/capybara/spec/session/source_spec.rb +0 -0
  315. data/lib/capybara/spec/views/with_title.erb +0 -5
  316. data/spec/selenium_spec_marionette.rb +0 -127
@@ -1,23 +1,25 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Capybara
3
4
  module Queries
4
- class SiblingQuery < MatchQuery
5
+ class SiblingQuery < SelectorQuery
5
6
  # @api private
6
7
  def resolve_for(node, exact = nil)
7
8
  @sibling_node = node
8
9
  node.synchronize do
9
- match_results = super(node.session.current_scope, exact)
10
- node.all(:xpath, XPath.preceding_sibling.union(XPath.following_sibling)) do |el|
11
- match_results.include?(el)
12
- end
10
+ scope = node.respond_to?(:session) ? node.session.current_scope : node.find(:xpath, '/*')
11
+ match_results = super(scope, exact)
12
+ siblings = node.find_xpath((XPath.preceding_sibling + XPath.following_sibling).to_s)
13
+ .map(&method(:to_element))
14
+ .select { |el| match_results.include?(el) }
15
+ Capybara::Result.new(ordered_results(siblings), self)
13
16
  end
14
17
  end
15
18
 
16
- def description
19
+ def description(applied = false) # rubocop:disable Style/OptionalBooleanParameter
17
20
  desc = super
18
- if @sibling_node && (sibling_query = @sibling_node.instance_variable_get(:@query))
19
- desc += " that is a sibling of #{sibling_query.description}"
20
- end
21
+ sibling_query = @sibling_node&.instance_variable_get(:@query)
22
+ desc += " that is a sibling of #{sibling_query.description}" if sibling_query
21
23
  desc
22
24
  end
23
25
  end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capybara
4
+ # @api private
5
+ module Queries
6
+ class StyleQuery < BaseQuery
7
+ def initialize(expected_styles, session_options:, **options)
8
+ @expected_styles = stringify_keys(expected_styles)
9
+ @options = options
10
+ @actual_styles = {}
11
+ super(@options)
12
+ self.session_options = session_options
13
+
14
+ assert_valid_keys
15
+ end
16
+
17
+ def resolves_for?(node)
18
+ @node = node
19
+ @actual_styles = node.style(*@expected_styles.keys)
20
+ @expected_styles.all? do |style, value|
21
+ if value.is_a? Regexp
22
+ value.match? @actual_styles[style]
23
+ else
24
+ @actual_styles[style] == value
25
+ end
26
+ end
27
+ end
28
+
29
+ def failure_message
30
+ +"Expected node to have styles #{@expected_styles.inspect}. " \
31
+ "Actual styles were #{@actual_styles.inspect}"
32
+ end
33
+
34
+ private
35
+
36
+ def stringify_keys(hsh)
37
+ hsh.transform_keys(&:to_s)
38
+ end
39
+
40
+ def valid_keys
41
+ %i[wait]
42
+ end
43
+ end
44
+ end
45
+ end
@@ -1,28 +1,32 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Capybara
3
4
  # @api private
4
5
  module Queries
5
6
  class TextQuery < BaseQuery
6
- def initialize(*args)
7
- @type = (args.first.is_a?(Symbol) || args.first.nil?) ? args.shift : nil
8
- # @type = (Capybara.ignore_hidden_elements or Capybara.visible_text_only) ? :visible : :all if @type.nil?
9
- @options = if args.last.is_a?(Hash) then args.pop.dup else {} end
10
- super(@options)
7
+ def initialize(type = nil, expected_text, session_options:, **options) # rubocop:disable Style/OptionalArguments
8
+ @type = type.nil? ? default_type : type
9
+ raise ArgumentError, "#{@type} is not a valid type for a text query" unless valid_types.include?(@type)
11
10
 
12
- @type = (session_options.ignore_hidden_elements or session_options.visible_text_only) ? :visible : :all if @type.nil?
11
+ @options = options
12
+ super(@options)
13
+ self.session_options = session_options
13
14
 
14
- @expected_text = args.shift
15
- unless @expected_text.is_a?(Regexp)
16
- @expected_text = Capybara::Helpers.normalize_whitespace(@expected_text)
15
+ if expected_text.nil? && !exact?
16
+ warn 'Checking for expected text of nil is confusing and/or pointless since it will always match. ' \
17
+ "Please specify a string or regexp instead. #{Capybara::Helpers.filter_backtrace(caller)}"
17
18
  end
18
- @search_regexp = Capybara::Helpers.to_regexp(@expected_text, nil, exact?)
19
- warn "Unused parameters passed to #{self.class.name} : #{args}" unless args.empty?
19
+
20
+ @expected_text = expected_text.is_a?(Regexp) ? expected_text : expected_text.to_s
21
+
22
+ @search_regexp = Capybara::Helpers.to_regexp(@expected_text, exact: exact?)
23
+
20
24
  assert_valid_keys
21
25
  end
22
26
 
23
27
  def resolve_for(node)
24
28
  @node = node
25
- @actual_text = text(node, @type)
29
+ @actual_text = text
26
30
  @count = @actual_text.scan(@search_regexp).size
27
31
  end
28
32
 
@@ -38,60 +42,74 @@ module Capybara
38
42
  if @expected_text.is_a?(Regexp)
39
43
  "text matching #{@expected_text.inspect}"
40
44
  else
41
- "#{"exact " if exact?}text #{@expected_text.inspect}"
45
+ "#{'exact ' if exact?}text #{@expected_text.inspect}"
42
46
  end
43
47
  end
44
48
 
45
- private
49
+ private
46
50
 
47
51
  def exact?
48
52
  options.fetch(:exact, session_options.exact_text)
49
53
  end
50
54
 
51
55
  def build_message(report_on_invisible)
52
- message = String.new()
56
+ message = +''
53
57
  unless (COUNT_KEYS & @options.keys).empty?
54
58
  message << " but found #{@count} #{Capybara::Helpers.declension('time', 'times', @count)}"
55
59
  end
56
60
  message << " in #{@actual_text.inspect}"
57
61
 
58
62
  details_message = []
63
+ details_message << case_insensitive_message if @node && check_case_insensitive?
64
+ details_message << invisible_message if @node && check_visible_text? && report_on_invisible
65
+ details_message.compact!
59
66
 
60
- if @node and !@expected_text.is_a? Regexp
61
- insensitive_regexp = Capybara::Helpers.to_regexp(@expected_text, Regexp::IGNORECASE)
62
- insensitive_count = @actual_text.scan(insensitive_regexp).size
63
- if insensitive_count != @count
64
- details_message << "it was found #{insensitive_count} #{Capybara::Helpers.declension("time", "times", insensitive_count)} using a case insensitive search"
65
- end
66
- end
67
+ message << ". (However, #{details_message.join(' and ')}.)" unless details_message.empty?
68
+ message
69
+ end
67
70
 
68
- if @node and check_visible_text? and report_on_invisible
69
- begin
70
- invisible_text = text(@node, :all)
71
- invisible_count = invisible_text.scan(@search_regexp).size
72
- if invisible_count != @count
73
- details_message << "it was found #{invisible_count} #{Capybara::Helpers.declension("time", "times", invisible_count)} including non-visible text"
74
- end
75
- rescue
76
- # An error getting the non-visible text (if element goes out of scope) should not affect the response
77
- end
78
- end
71
+ def case_insensitive_message
72
+ insensitive_regexp = Capybara::Helpers.to_regexp(@expected_text, options: Regexp::IGNORECASE)
73
+ insensitive_count = @actual_text.scan(insensitive_regexp).size
74
+ return if insensitive_count == @count
79
75
 
80
- message << ". (However, #{details_message.join(' and ')}.)" unless details_message.empty?
76
+ "it was found #{occurrences insensitive_count} using a case insensitive search"
77
+ end
81
78
 
82
- message
79
+ def invisible_message
80
+ invisible_text = text(query_type: :all)
81
+ invisible_count = invisible_text.scan(@search_regexp).size
82
+ return if invisible_count == @count
83
+
84
+ "it was found #{occurrences invisible_count} including non-visible text"
85
+ rescue StandardError
86
+ # An error getting the non-visible text (if element goes out of scope) should not affect the response
87
+ nil
83
88
  end
84
89
 
85
90
  def valid_keys
86
- COUNT_KEYS + [:wait, :exact]
91
+ COUNT_KEYS + %i[wait exact normalize_ws]
92
+ end
93
+
94
+ def valid_types
95
+ %i[all visible]
87
96
  end
88
97
 
89
98
  def check_visible_text?
90
99
  @type == :visible
91
100
  end
92
101
 
93
- def text(node, query_type)
94
- Capybara::Helpers.normalize_whitespace(node.text(query_type))
102
+ def check_case_insensitive?
103
+ !@expected_text.is_a?(Regexp)
104
+ end
105
+
106
+ def text(node: @node, query_type: @type)
107
+ normalize_ws = options.fetch(:normalize_ws, session_options.default_normalize_ws)
108
+ node.text(query_type, normalize_ws: normalize_ws)
109
+ end
110
+
111
+ def default_type
112
+ Capybara.ignore_hidden_elements || Capybara.visible_text_only ? :visible : :all
95
113
  end
96
114
  end
97
115
  end
@@ -1,22 +1,19 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Capybara
3
4
  # @api private
4
5
  module Queries
5
6
  class TitleQuery < BaseQuery
6
- def initialize(expected_title, options = {})
7
- @expected_title = expected_title
7
+ def initialize(expected_title, **options)
8
+ @expected_title = expected_title.is_a?(Regexp) ? expected_title : expected_title.to_s
8
9
  @options = options
9
10
  super(@options)
10
- unless @expected_title.is_a?(Regexp)
11
- @expected_title = Capybara::Helpers.normalize_whitespace(@expected_title)
12
- end
13
- @search_regexp = Capybara::Helpers.to_regexp(@expected_title, nil, options.fetch(:exact, false))
11
+ @search_regexp = Helpers.to_regexp(@expected_title, all_whitespace: true, exact: options.fetch(:exact, false))
14
12
  assert_valid_keys
15
13
  end
16
14
 
17
15
  def resolves_for?(node)
18
- @actual_title = node.title
19
- @actual_title.match(@search_regexp)
16
+ (@actual_title = node.title).match?(@search_regexp)
20
17
  end
21
18
 
22
19
  def failure_message
@@ -27,15 +24,15 @@ module Capybara
27
24
  failure_message_helper(' not')
28
25
  end
29
26
 
30
- private
27
+ private
31
28
 
32
29
  def failure_message_helper(negated = '')
33
- verb = (@expected_title.is_a?(Regexp))? 'match' : 'include'
30
+ verb = @expected_title.is_a?(Regexp) ? 'match' : 'include'
34
31
  "expected #{@actual_title.inspect}#{negated} to #{verb} #{@expected_title.inspect}"
35
32
  end
36
33
 
37
34
  def valid_keys
38
- [:wait, :exact]
35
+ %i[wait exact]
39
36
  end
40
37
  end
41
38
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  class Capybara::RackTest::Browser
3
4
  include ::Rack::Test::Methods
4
5
 
@@ -7,6 +8,7 @@ class Capybara::RackTest::Browser
7
8
 
8
9
  def initialize(driver)
9
10
  @driver = driver
11
+ @current_fragment = nil
10
12
  end
11
13
 
12
14
  def app
@@ -17,7 +19,9 @@ class Capybara::RackTest::Browser
17
19
  driver.options
18
20
  end
19
21
 
20
- def visit(path, attributes = {})
22
+ def visit(path, **attributes)
23
+ @new_visit_request = true
24
+ reset_cache!
21
25
  reset_host!
22
26
  process_and_follow_redirects(:get, path, attributes)
23
27
  end
@@ -27,59 +31,83 @@ class Capybara::RackTest::Browser
27
31
  request(last_request.fullpath, last_request.env)
28
32
  end
29
33
 
30
- def submit(method, path, attributes)
31
- path = request_path if not path or path.empty?
32
- process_and_follow_redirects(method, path, attributes, {'HTTP_REFERER' => current_url})
34
+ def submit(method, path, attributes, content_type: nil)
35
+ path = request_path if path.nil? || path.empty?
36
+ uri = build_uri(path)
37
+ uri.query = '' if method.to_s.casecmp('get').zero?
38
+ env = { 'HTTP_REFERER' => referer_url }
39
+ env['CONTENT_TYPE'] = content_type if content_type
40
+ process_and_follow_redirects(
41
+ method,
42
+ uri.to_s,
43
+ attributes,
44
+ env
45
+ )
33
46
  end
34
47
 
35
- def follow(method, path, attributes = {})
36
- return if path.gsub(/^#{Regexp.escape(request_path)}/, '').start_with?('#') || path.downcase.start_with?('javascript:')
37
- process_and_follow_redirects(method, path, attributes, {'HTTP_REFERER' => current_url})
48
+ def follow(method, path, **attributes)
49
+ return if fragment_or_script?(path)
50
+
51
+ process_and_follow_redirects(method, path, attributes, 'HTTP_REFERER' => referer_url)
38
52
  end
39
53
 
40
54
  def process_and_follow_redirects(method, path, attributes = {}, env = {})
55
+ @current_fragment = build_uri(path).fragment
41
56
  process(method, path, attributes, env)
42
- if driver.follow_redirects?
43
- driver.redirect_limit.times do
44
- process(:get, last_response["Location"], {}, env) if last_response.redirect?
57
+ return unless driver.follow_redirects?
58
+
59
+ driver.redirect_limit.times do
60
+ if last_response.redirect?
61
+ if [307, 308].include? last_response.status
62
+ process(last_request.request_method, last_response['Location'], last_request.params, env)
63
+ else
64
+ process(:get, last_response['Location'], {}, env)
65
+ end
45
66
  end
46
- raise Capybara::InfiniteRedirectError, "redirected more than #{driver.redirect_limit} times, check for infinite redirects." if last_response.redirect?
47
67
  end
48
- end
49
68
 
50
- def process(method, path, attributes = {}, env = {})
51
- new_uri = URI.parse(path)
52
- method.downcase! unless method.is_a? Symbol
53
- if path.empty?
54
- new_uri.path = request_path
55
- else
56
- new_uri.path = request_path if path.start_with?("?")
57
- new_uri.path = "/" if new_uri.path.empty?
58
- new_uri.path = request_path.sub(%r(/[^/]*$), '/') + new_uri.path unless new_uri.path.start_with?('/')
69
+ if last_response.redirect? # rubocop:disable Style/GuardClause
70
+ raise Capybara::InfiniteRedirectError, "redirected more than #{driver.redirect_limit} times, check for infinite redirects."
59
71
  end
60
- new_uri.scheme ||= @current_scheme
61
- new_uri.host ||= @current_host
62
- new_uri.port ||= @current_port unless new_uri.default_port == @current_port
63
-
64
- @current_scheme = new_uri.scheme
65
- @current_host = new_uri.host
66
- @current_port = new_uri.port
72
+ end
67
73
 
74
+ def process(method, path, attributes = {}, env = {})
75
+ method = method.downcase
76
+ new_uri = build_uri(path)
77
+ @current_scheme, @current_host, @current_port = new_uri.select(:scheme, :host, :port)
78
+ @current_fragment = new_uri.fragment || @current_fragment
68
79
  reset_cache!
80
+ @new_visit_request = false
69
81
  send(method, new_uri.to_s, attributes, env.merge(options[:headers] || {}))
70
82
  end
71
83
 
84
+ def build_uri(path)
85
+ uri = URI.parse(path)
86
+ base_uri = base_relative_uri_for(uri)
87
+
88
+ uri.path = base_uri.path + uri.path unless uri.absolute? || uri.path.start_with?('/')
89
+
90
+ if base_uri.absolute?
91
+ base_uri.merge(uri)
92
+ else
93
+ uri.scheme ||= @current_scheme
94
+ uri.host ||= @current_host
95
+ uri.port ||= @current_port unless uri.default_port == @current_port
96
+ uri
97
+ end
98
+ end
99
+
72
100
  def current_url
73
- last_request.url
101
+ uri = build_uri(last_request.url)
102
+ uri.fragment = @current_fragment if @current_fragment
103
+ uri.to_s
74
104
  rescue Rack::Test::Error
75
- ""
105
+ ''
76
106
  end
77
107
 
78
108
  def reset_host!
79
109
  uri = URI.parse(driver.session_options.app_host || driver.session_options.default_host)
80
- @current_scheme = uri.scheme
81
- @current_host = uri.host
82
- @current_port = uri.port
110
+ @current_scheme, @current_host, @current_port = uri.select(:scheme, :host, :port)
83
111
  end
84
112
 
85
113
  def reset_cache!
@@ -91,7 +119,7 @@ class Capybara::RackTest::Browser
91
119
  end
92
120
 
93
121
  def find(format, selector)
94
- if format==:css
122
+ if format == :css
95
123
  dom.css(selector, Capybara::RackTest::CSSHandlers.new)
96
124
  else
97
125
  dom.xpath(selector)
@@ -101,20 +129,46 @@ class Capybara::RackTest::Browser
101
129
  def html
102
130
  last_response.body
103
131
  rescue Rack::Test::Error
104
- ""
132
+ ''
105
133
  end
106
134
 
107
135
  def title
108
- if dom.respond_to? :title
109
- dom.title
110
- else
111
- #old versions of nokogiri don't have #title - remove in 3.0
112
- dom.xpath('/html/head/title | /html/title').first.text
113
- end
136
+ dom.title
137
+ end
138
+
139
+ def last_request
140
+ raise Rack::Test::Error if @new_visit_request
141
+
142
+ super
143
+ end
144
+
145
+ def last_response
146
+ raise Rack::Test::Error if @new_visit_request
147
+
148
+ super
114
149
  end
115
150
 
116
151
  protected
117
152
 
153
+ def base_href
154
+ find(:css, 'head > base').first&.[](:href).to_s
155
+ end
156
+
157
+ def base_relative_uri_for(uri)
158
+ base_uri = URI.parse(base_href)
159
+ current_uri = URI.parse(safe_last_request&.url.to_s).tap do |c|
160
+ c.path.sub!(%r{/[^/]*$}, '/') unless uri.path.empty?
161
+ c.path = '/' if c.path.empty?
162
+ end
163
+
164
+ if [current_uri, base_uri].any?(&:absolute?)
165
+ current_uri.merge(base_uri)
166
+ else
167
+ base_uri.path = current_uri.path if base_uri.path.empty?
168
+ base_uri
169
+ end
170
+ end
171
+
118
172
  def build_rack_mock_session
119
173
  reset_host! unless current_host
120
174
  Rack::MockSession.new(app, current_host)
@@ -123,6 +177,24 @@ protected
123
177
  def request_path
124
178
  last_request.path
125
179
  rescue Rack::Test::Error
126
- "/"
180
+ '/'
181
+ end
182
+
183
+ def safe_last_request
184
+ last_request
185
+ rescue Rack::Test::Error
186
+ nil
187
+ end
188
+
189
+ private
190
+
191
+ def fragment_or_script?(path)
192
+ path.gsub(/^#{Regexp.escape(request_path)}/, '').start_with?('#') || path.downcase.start_with?('javascript:')
193
+ end
194
+
195
+ def referer_url
196
+ build_uri(last_request.url).to_s
197
+ rescue Rack::Test::Error
198
+ ''
127
199
  end
128
200
  end
@@ -1,11 +1,13 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  class Capybara::RackTest::CSSHandlers < BasicObject
3
4
  include ::Kernel
4
-
5
- def disabled list
5
+
6
+ def disabled(list)
6
7
  list.find_all { |node| node.has_attribute? 'disabled' }
7
- end
8
- def enabled list
8
+ end
9
+
10
+ def enabled(list)
9
11
  list.find_all { |node| !node.has_attribute? 'disabled' }
10
12
  end
11
13
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'rack/test'
3
4
  require 'rack/utils'
4
5
  require 'mini_mime'
@@ -10,12 +11,13 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
10
11
  respect_data_method: false,
11
12
  follow_redirects: true,
12
13
  redirect_limit: 5
13
- }
14
+ }.freeze
14
15
  attr_reader :app, :options
15
16
 
16
- def initialize(app, options={})
17
- raise ArgumentError, "rack-test requires a rack application, but none was given" unless app
18
- @session = nil
17
+ def initialize(app, **options)
18
+ raise ArgumentError, 'rack-test requires a rack application, but none was given' unless app
19
+
20
+ super()
19
21
  @app = app
20
22
  @options = DEFAULT_OPTIONS.merge(options)
21
23
  end
@@ -40,8 +42,8 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
40
42
  browser.last_request
41
43
  end
42
44
 
43
- def visit(path, attributes = {})
44
- browser.visit(path, attributes)
45
+ def visit(path, **attributes)
46
+ browser.visit(path, **attributes)
45
47
  end
46
48
 
47
49
  def refresh
@@ -52,7 +54,7 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
52
54
  browser.submit(method, path, attributes)
53
55
  end
54
56
 
55
- def follow(method, path, attributes = {})
57
+ def follow(method, path, **attributes)
56
58
  browser.follow(method, path, attributes)
57
59
  end
58
60
 
@@ -73,7 +75,11 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
73
75
  end
74
76
 
75
77
  def find_css(selector)
76
- browser.find(:css,selector)
78
+ browser.find(:css, selector)
79
+ rescue Nokogiri::CSS::SyntaxError
80
+ raise unless selector.include?(' i]')
81
+
82
+ raise ArgumentError, "This driver doesn't support case insensitive attribute matching when using CSS base selectors"
77
83
  end
78
84
 
79
85
  def html
@@ -92,14 +98,13 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
92
98
  @browser = nil
93
99
  end
94
100
 
95
- # @deprecated This method is being removed
96
- def browser_initialized?
97
- super && !@browser.nil?
98
- end
99
-
100
- def get(*args, &block); browser.get(*args, &block); end
101
- def post(*args, &block); browser.post(*args, &block); end
102
- def put(*args, &block); browser.put(*args, &block); end
103
- def delete(*args, &block); browser.delete(*args, &block); end
101
+ def get(...); browser.get(...); end
102
+ def post(...); browser.post(...); end
103
+ def put(...); browser.put(...); end
104
+ def delete(...); browser.delete(...); end
104
105
  def header(key, value); browser.header(key, value); end
106
+
107
+ def invalid_element_errors
108
+ [Capybara::RackTest::Errors::StaleElementReferenceError]
109
+ end
105
110
  end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capybara::RackTest::Errors
4
+ class StaleElementReferenceError < StandardError
5
+ end
6
+ end