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
@@ -2,19 +2,21 @@
2
2
 
3
3
  module Capybara
4
4
  module Queries
5
- class SiblingQuery < MatchQuery
5
+ class SiblingQuery < SelectorQuery
6
6
  # @api private
7
7
  def resolve_for(node, exact = nil)
8
8
  @sibling_node = node
9
9
  node.synchronize do
10
- match_results = super(node.session.current_scope, exact)
11
- node.all(:xpath, XPath.preceding_sibling + XPath.following_sibling) do |el|
12
- match_results.include?(el)
13
- 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)
14
16
  end
15
17
  end
16
18
 
17
- def description
19
+ def description(applied = false) # rubocop:disable Style/OptionalBooleanParameter
18
20
  desc = super
19
21
  sibling_query = @sibling_node&.instance_variable_get(:@query)
20
22
  desc += " that is a sibling of #{sibling_query.description}" if sibling_query
@@ -5,7 +5,7 @@ module Capybara
5
5
  module Queries
6
6
  class StyleQuery < BaseQuery
7
7
  def initialize(expected_styles, session_options:, **options)
8
- @expected_styles = expected_styles.each_with_object({}) { |(style, value), str_keys| str_keys[style.to_s] = value }
8
+ @expected_styles = stringify_keys(expected_styles)
9
9
  @options = options
10
10
  @actual_styles = {}
11
11
  super(@options)
@@ -19,7 +19,7 @@ module Capybara
19
19
  @actual_styles = node.style(*@expected_styles.keys)
20
20
  @expected_styles.all? do |style, value|
21
21
  if value.is_a? Regexp
22
- @actual_styles[style] =~ value
22
+ value.match? @actual_styles[style]
23
23
  else
24
24
  @actual_styles[style] == value
25
25
  end
@@ -33,6 +33,10 @@ module Capybara
33
33
 
34
34
  private
35
35
 
36
+ def stringify_keys(hsh)
37
+ hsh.transform_keys(&:to_s)
38
+ end
39
+
36
40
  def valid_keys
37
41
  %i[wait]
38
42
  end
@@ -5,17 +5,20 @@ module Capybara
5
5
  module Queries
6
6
  class TextQuery < BaseQuery
7
7
  def initialize(type = nil, expected_text, session_options:, **options) # rubocop:disable Style/OptionalArguments
8
- @type = if type.nil?
9
- Capybara.ignore_hidden_elements || Capybara.visible_text_only ? :visible : :all
10
- else
11
- type
12
- end
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)
13
10
 
14
- @expected_text = expected_text.is_a?(Regexp) ? expected_text : expected_text.to_s
15
11
  @options = options
16
12
  super(@options)
17
13
  self.session_options = session_options
18
14
 
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)}"
18
+ end
19
+
20
+ @expected_text = expected_text.is_a?(Regexp) ? expected_text : expected_text.to_s
21
+
19
22
  @search_regexp = Capybara::Helpers.to_regexp(@expected_text, exact: exact?)
20
23
 
21
24
  assert_valid_keys
@@ -23,7 +26,7 @@ module Capybara
23
26
 
24
27
  def resolve_for(node)
25
28
  @node = node
26
- @actual_text = text(node, @type)
29
+ @actual_text = text
27
30
  @count = @actual_text.scan(@search_regexp).size
28
31
  end
29
32
 
@@ -50,7 +53,7 @@ module Capybara
50
53
  end
51
54
 
52
55
  def build_message(report_on_invisible)
53
- message = +""
56
+ message = +''
54
57
  unless (COUNT_KEYS & @options.keys).empty?
55
58
  message << " but found #{@count} #{Capybara::Helpers.declension('time', 'times', @count)}"
56
59
  end
@@ -69,21 +72,27 @@ module Capybara
69
72
  insensitive_regexp = Capybara::Helpers.to_regexp(@expected_text, options: Regexp::IGNORECASE)
70
73
  insensitive_count = @actual_text.scan(insensitive_regexp).size
71
74
  return if insensitive_count == @count
72
- "it was found #{insensitive_count} #{Capybara::Helpers.declension('time', 'times', insensitive_count)} using a case insensitive search"
75
+
76
+ "it was found #{occurrences insensitive_count} using a case insensitive search"
73
77
  end
74
78
 
75
79
  def invisible_message
76
- invisible_text = text(@node, :all)
80
+ invisible_text = text(query_type: :all)
77
81
  invisible_count = invisible_text.scan(@search_regexp).size
78
82
  return if invisible_count == @count
79
- "it was found #{invisible_count} #{Capybara::Helpers.declension('time', 'times', invisible_count)} including non-visible text"
83
+
84
+ "it was found #{occurrences invisible_count} including non-visible text"
80
85
  rescue StandardError
81
86
  # An error getting the non-visible text (if element goes out of scope) should not affect the response
82
87
  nil
83
88
  end
84
89
 
85
90
  def valid_keys
86
- COUNT_KEYS + %i[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?
@@ -94,8 +103,13 @@ module Capybara
94
103
  !@expected_text.is_a?(Regexp)
95
104
  end
96
105
 
97
- def text(node, query_type)
98
- node.text(query_type)
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
99
113
  end
100
114
  end
101
115
  end
@@ -8,12 +8,12 @@ module Capybara
8
8
  @expected_title = expected_title.is_a?(Regexp) ? expected_title : expected_title.to_s
9
9
  @options = options
10
10
  super(@options)
11
- @search_regexp = Capybara::Helpers.to_regexp(@expected_title, all_whitespace: true, exact: options.fetch(:exact, false))
11
+ @search_regexp = Helpers.to_regexp(@expected_title, all_whitespace: true, exact: options.fetch(:exact, false))
12
12
  assert_valid_keys
13
13
  end
14
14
 
15
15
  def resolves_for?(node)
16
- (@actual_title = node.title).match(@search_regexp)
16
+ (@actual_title = node.title).match?(@search_regexp)
17
17
  end
18
18
 
19
19
  def failure_message
@@ -8,6 +8,7 @@ class Capybara::RackTest::Browser
8
8
 
9
9
  def initialize(driver)
10
10
  @driver = driver
11
+ @current_fragment = nil
11
12
  end
12
13
 
13
14
  def app
@@ -19,6 +20,8 @@ class Capybara::RackTest::Browser
19
20
  end
20
21
 
21
22
  def visit(path, **attributes)
23
+ @new_visit_request = true
24
+ reset_cache!
22
25
  reset_host!
23
26
  process_and_follow_redirects(:get, path, attributes)
24
27
  end
@@ -28,57 +31,78 @@ class Capybara::RackTest::Browser
28
31
  request(last_request.fullpath, last_request.env)
29
32
  end
30
33
 
31
- def submit(method, path, attributes)
34
+ def submit(method, path, attributes, content_type: nil)
32
35
  path = request_path if path.nil? || path.empty?
33
- process_and_follow_redirects(method, path, attributes, 'HTTP_REFERER' => current_url)
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
+ )
34
46
  end
35
47
 
36
48
  def follow(method, path, **attributes)
37
49
  return if fragment_or_script?(path)
38
- process_and_follow_redirects(method, path, attributes, 'HTTP_REFERER' => current_url)
50
+
51
+ process_and_follow_redirects(method, path, attributes, 'HTTP_REFERER' => referer_url)
39
52
  end
40
53
 
41
54
  def process_and_follow_redirects(method, path, attributes = {}, env = {})
55
+ @current_fragment = build_uri(path).fragment
42
56
  process(method, path, attributes, env)
43
-
44
57
  return unless driver.follow_redirects?
45
58
 
46
59
  driver.redirect_limit.times do
47
60
  if last_response.redirect?
48
61
  if [307, 308].include? last_response.status
49
- process(last_request.request_method, last_response["Location"], last_request.params, env)
62
+ process(last_request.request_method, last_response['Location'], last_request.params, env)
50
63
  else
51
- process(:get, last_response["Location"], {}, env)
64
+ process(:get, last_response['Location'], {}, env)
52
65
  end
53
66
  end
54
67
  end
55
- raise Capybara::InfiniteRedirectError, "redirected more than #{driver.redirect_limit} times, check for infinite redirects." if last_response.redirect?
56
- end
57
68
 
58
- def process(method, path, attributes = {}, env = {})
59
- new_uri = URI.parse(path)
60
- method.downcase! unless method.is_a? Symbol
61
- if path.empty?
62
- new_uri.path = request_path
63
- else
64
- new_uri.path = request_path if path.start_with?("?")
65
- new_uri.path = "/" if new_uri.path.empty?
66
- 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."
67
71
  end
68
- new_uri.scheme ||= @current_scheme
69
- new_uri.host ||= @current_host
70
- new_uri.port ||= @current_port unless new_uri.default_port == @current_port
72
+ end
71
73
 
74
+ def process(method, path, attributes = {}, env = {})
75
+ method = method.downcase
76
+ new_uri = build_uri(path)
72
77
  @current_scheme, @current_host, @current_port = new_uri.select(:scheme, :host, :port)
73
-
78
+ @current_fragment = new_uri.fragment || @current_fragment
74
79
  reset_cache!
80
+ @new_visit_request = false
75
81
  send(method, new_uri.to_s, attributes, env.merge(options[:headers] || {}))
76
82
  end
77
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
+
78
100
  def current_url
79
- last_request.url
101
+ uri = build_uri(last_request.url)
102
+ uri.fragment = @current_fragment if @current_fragment
103
+ uri.to_s
80
104
  rescue Rack::Test::Error
81
- ""
105
+ ''
82
106
  end
83
107
 
84
108
  def reset_host!
@@ -105,15 +129,46 @@ class Capybara::RackTest::Browser
105
129
  def html
106
130
  last_response.body
107
131
  rescue Rack::Test::Error
108
- ""
132
+ ''
109
133
  end
110
134
 
111
135
  def title
112
136
  dom.title
113
137
  end
114
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
149
+ end
150
+
115
151
  protected
116
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
+
117
172
  def build_rack_mock_session
118
173
  reset_host! unless current_host
119
174
  Rack::MockSession.new(app, current_host)
@@ -122,7 +177,13 @@ protected
122
177
  def request_path
123
178
  last_request.path
124
179
  rescue Rack::Test::Error
125
- "/"
180
+ '/'
181
+ end
182
+
183
+ def safe_last_request
184
+ last_request
185
+ rescue Rack::Test::Error
186
+ nil
126
187
  end
127
188
 
128
189
  private
@@ -130,4 +191,10 @@ private
130
191
  def fragment_or_script?(path)
131
192
  path.gsub(/^#{Regexp.escape(request_path)}/, '').start_with?('#') || path.downcase.start_with?('javascript:')
132
193
  end
194
+
195
+ def referer_url
196
+ build_uri(last_request.url).to_s
197
+ rescue Rack::Test::Error
198
+ ''
199
+ end
133
200
  end
@@ -15,8 +15,9 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
15
15
  attr_reader :app, :options
16
16
 
17
17
  def initialize(app, **options)
18
- raise ArgumentError, "rack-test requires a rack application, but none was given" unless app
19
- @session = nil
18
+ raise ArgumentError, 'rack-test requires a rack application, but none was given' unless app
19
+
20
+ super()
20
21
  @app = app
21
22
  @options = DEFAULT_OPTIONS.merge(options)
22
23
  end
@@ -42,7 +43,7 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
42
43
  end
43
44
 
44
45
  def visit(path, **attributes)
45
- browser.visit(path, attributes)
46
+ browser.visit(path, **attributes)
46
47
  end
47
48
 
48
49
  def refresh
@@ -75,6 +76,10 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
75
76
 
76
77
  def find_css(selector)
77
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"
78
83
  end
79
84
 
80
85
  def html
@@ -93,9 +98,13 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
93
98
  @browser = nil
94
99
  end
95
100
 
96
- def get(*args, &block); browser.get(*args, &block); end
97
- def post(*args, &block); browser.post(*args, &block); end
98
- def put(*args, &block); browser.put(*args, &block); end
99
- 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
100
105
  def header(key, value); browser.header(key, value); end
106
+
107
+ def invalid_element_errors
108
+ [Capybara::RackTest::Errors::StaleElementReferenceError]
109
+ end
101
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
@@ -6,48 +6,59 @@ class Capybara::RackTest::Form < Capybara::RackTest::Node
6
6
  # That check should be based solely on the form element's 'enctype' attribute value,
7
7
  # which should probably be provided to Rack::Test in its non-GET request methods.
8
8
  class NilUploadedFile < Rack::Test::UploadedFile
9
- def initialize
10
- @empty_file = Tempfile.new("nil_uploaded_file")
9
+ def initialize # rubocop:disable Lint/MissingSuper
10
+ @empty_file = Tempfile.new('nil_uploaded_file')
11
11
  @empty_file.close
12
12
  end
13
13
 
14
- def original_filename; ""; end
15
- def content_type; "application/octet-stream"; end
14
+ def original_filename; ''; end
15
+ def content_type; 'application/octet-stream'; end
16
16
  def path; @empty_file.path; end
17
17
  def size; 0; end
18
- def read; ""; end
18
+ def read; ''; end
19
+ def append_to(_); end
20
+ def set_encoding(_); end # rubocop:disable Naming/AccessorMethodName
19
21
  end
20
22
 
21
23
  def params(button)
22
- params = make_params
23
-
24
- form_element_types = %i[input select textarea]
25
- form_elements_xpath = XPath.generate do |x|
26
- xpath = x.descendant(*form_element_types).where(!x.attr(:form))
27
- xpath += x.anywhere(*form_element_types).where(x.attr(:form) == native[:id]) if native[:id]
28
- xpath.where(!x.attr(:disabled))
24
+ form_element_types = %i[input select textarea button]
25
+ form_elements_xpath = XPath.generate do |xp|
26
+ xpath = xp.descendant(*form_element_types).where(!xp.attr(:form))
27
+ xpath += xp.anywhere(*form_element_types).where(xp.attr(:form) == native[:id]) if native[:id]
28
+ xpath.where(!xp.attr(:disabled))
29
29
  end.to_s
30
30
 
31
- native.xpath(form_elements_xpath).map do |field|
31
+ form_elements = native.xpath(form_elements_xpath).reject { |el| submitter?(el) && (el != button.native) }
32
+
33
+ form_params = form_elements.each_with_object({}.compare_by_identity) do |field, params|
32
34
  case field.name
33
- when 'input' then add_input_param(field, params)
35
+ when 'input', 'button' then add_input_param(field, params)
34
36
  when 'select' then add_select_param(field, params)
35
37
  when 'textarea' then add_textarea_param(field, params)
36
38
  end
37
39
  end
38
- merge_param!(params, button[:name], button[:value] || "") if button[:name]
39
40
 
40
- params.to_params_hash
41
+ form_params.each_with_object(make_params) do |(name, value), params|
42
+ merge_param!(params, name, value)
43
+ end.to_params_hash
44
+
45
+ # form_elements.each_with_object(make_params) do |field, params|
46
+ # case field.name
47
+ # when 'input', 'button' then add_input_param(field, params)
48
+ # when 'select' then add_select_param(field, params)
49
+ # when 'textarea' then add_textarea_param(field, params)
50
+ # end
51
+ # end.to_params_hash
41
52
  end
42
53
 
43
54
  def submit(button)
44
55
  action = button&.[]('formaction') || native['action']
45
56
  method = button&.[]('formmethod') || request_method
46
- driver.submit(method, action.to_s, params(button))
57
+ driver.submit(method, action.to_s, params(button), content_type: native['enctype'])
47
58
  end
48
59
 
49
60
  def multipart?
50
- self[:enctype] == "multipart/form-data"
61
+ self[:enctype] == 'multipart/form-data'
51
62
  end
52
63
 
53
64
  private
@@ -59,7 +70,7 @@ private
59
70
  end
60
71
 
61
72
  def request_method
62
- self[:method] =~ /post/i ? :post : :get
73
+ /post/i.match?(self[:method] || '') ? :post : :get
63
74
  end
64
75
 
65
76
  def merge_param!(params, key, value)
@@ -80,43 +91,59 @@ private
80
91
  end
81
92
 
82
93
  def add_input_param(field, params)
83
- if %w[radio checkbox].include? field['type']
84
- if field['checked']
85
- node = Capybara::RackTest::Node.new(driver, field)
86
- merge_param!(params, field['name'], node.value.to_s)
87
- end
88
- elsif %w[submit image].include? field['type']
89
- # TODO: identify the click button here (in document order, rather
90
- # than leaving until the end of the params)
91
- elsif field['type'] == 'file'
94
+ name, value = field['name'].to_s, field['value'].to_s
95
+ return if name.empty?
96
+
97
+ value = case field['type']
98
+ when 'radio', 'checkbox'
99
+ return unless field['checked']
100
+
101
+ Capybara::RackTest::Node.new(driver, field).value.to_s
102
+ when 'file'
103
+ return if value.empty? && params.keys.include?(name) && Rack::Test::VERSION.to_f >= 2.0 # rubocop:disable Performance/InefficientHashSearch
104
+
92
105
  if multipart?
93
- file = if (value = field['value']).to_s.empty?
94
- NilUploadedFile.new
95
- else
96
- mime_info = MiniMime.lookup_by_filename(value)
97
- Rack::Test::UploadedFile.new(value, mime_info&.content_type&.to_s)
98
- end
99
- merge_param!(params, field['name'], file)
106
+ file_to_upload(value)
100
107
  else
101
- merge_param!(params, field['name'], File.basename(field['value'].to_s))
108
+ File.basename(value)
102
109
  end
103
110
  else
104
- merge_param!(params, field['name'], field['value'].to_s)
111
+ value
112
+ end
113
+ # merge_param!(params, name, value)
114
+ params[name] = value
115
+ end
116
+
117
+ def file_to_upload(filename)
118
+ if filename.empty?
119
+ NilUploadedFile.new
120
+ else
121
+ mime_info = MiniMime.lookup_by_filename(filename)
122
+ Rack::Test::UploadedFile.new(filename, mime_info&.content_type&.to_s)
105
123
  end
106
124
  end
107
125
 
108
126
  def add_select_param(field, params)
127
+ name = field['name']
109
128
  if field.has_attribute?('multiple')
110
- field.xpath(".//option[@selected]").each do |option|
111
- merge_param!(params, field['name'], (option['value'] || option.text).to_s)
129
+ value = field.xpath('.//option[@selected]').map do |option|
130
+ # merge_param!(params, field['name'], (option['value'] || option.text).to_s)
131
+ (option['value'] || option.text).to_s
112
132
  end
133
+ params[name] = value unless value.empty?
113
134
  else
114
135
  option = field.xpath('.//option[@selected]').first || field.xpath('.//option').first
115
- merge_param!(params, field['name'], (option['value'] || option.text).to_s) if option
136
+ # merge_param!(params, field['name'], (option['value'] || option.text).to_s) if option
137
+ params[name] = (option['value'] || option.text).to_s if option
116
138
  end
117
139
  end
118
140
 
119
141
  def add_textarea_param(field, params)
120
- merge_param!(params, field['name'], field['_capybara_raw_value'].to_s.gsub(/\n/, "\r\n"))
142
+ # merge_param!(params, field['name'], field['_capybara_raw_value'].to_s.gsub(/\r?\n/, "\r\n"))
143
+ params[field['name']] = field['_capybara_raw_value'].to_s.gsub(/\r?\n/, "\r\n")
144
+ end
145
+
146
+ def submitter?(el)
147
+ (%w[submit image].include? el['type']) || (el.name == 'button')
121
148
  end
122
149
  end