capybara 3.0.0 → 3.36.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 -1
  3. data/History.md +777 -10
  4. data/License.txt +1 -1
  5. data/README.md +75 -58
  6. data/lib/capybara/config.rb +29 -10
  7. data/lib/capybara/cucumber.rb +1 -1
  8. data/lib/capybara/driver/base.rb +22 -4
  9. data/lib/capybara/driver/node.rb +34 -9
  10. data/lib/capybara/dsl.rb +14 -6
  11. data/lib/capybara/helpers.rb +52 -7
  12. data/lib/capybara/minitest/spec.rb +173 -84
  13. data/lib/capybara/minitest.rb +250 -144
  14. data/lib/capybara/node/actions.rb +248 -124
  15. data/lib/capybara/node/base.rb +34 -20
  16. data/lib/capybara/node/document.rb +14 -2
  17. data/lib/capybara/node/document_matchers.rb +13 -15
  18. data/lib/capybara/node/element.rb +339 -113
  19. data/lib/capybara/node/finders.rb +95 -82
  20. data/lib/capybara/node/matchers.rb +338 -157
  21. data/lib/capybara/node/simple.rb +54 -15
  22. data/lib/capybara/queries/active_element_query.rb +18 -0
  23. data/lib/capybara/queries/ancestor_query.rb +9 -10
  24. data/lib/capybara/queries/base_query.rb +25 -18
  25. data/lib/capybara/queries/current_path_query.rb +16 -6
  26. data/lib/capybara/queries/match_query.rb +11 -0
  27. data/lib/capybara/queries/selector_query.rb +600 -103
  28. data/lib/capybara/queries/sibling_query.rb +9 -7
  29. data/lib/capybara/queries/style_query.rb +45 -0
  30. data/lib/capybara/queries/text_query.rb +40 -22
  31. data/lib/capybara/queries/title_query.rb +2 -2
  32. data/lib/capybara/rack_test/browser.rb +47 -28
  33. data/lib/capybara/rack_test/driver.rb +12 -3
  34. data/lib/capybara/rack_test/errors.rb +6 -0
  35. data/lib/capybara/rack_test/form.rb +53 -50
  36. data/lib/capybara/rack_test/node.rb +114 -37
  37. data/lib/capybara/rails.rb +1 -1
  38. data/lib/capybara/registration_container.rb +44 -0
  39. data/lib/capybara/registrations/drivers.rb +42 -0
  40. data/lib/capybara/registrations/patches/puma_ssl.rb +29 -0
  41. data/lib/capybara/registrations/servers.rb +45 -0
  42. data/lib/capybara/result.rb +86 -52
  43. data/lib/capybara/rspec/features.rb +8 -10
  44. data/lib/capybara/rspec/matcher_proxies.rb +39 -18
  45. data/lib/capybara/rspec/matchers/base.rb +111 -0
  46. data/lib/capybara/rspec/matchers/become_closed.rb +33 -0
  47. data/lib/capybara/rspec/matchers/compound.rb +88 -0
  48. data/lib/capybara/rspec/matchers/count_sugar.rb +37 -0
  49. data/lib/capybara/rspec/matchers/have_ancestor.rb +28 -0
  50. data/lib/capybara/rspec/matchers/have_current_path.rb +29 -0
  51. data/lib/capybara/rspec/matchers/have_selector.rb +77 -0
  52. data/lib/capybara/rspec/matchers/have_sibling.rb +27 -0
  53. data/lib/capybara/rspec/matchers/have_text.rb +33 -0
  54. data/lib/capybara/rspec/matchers/have_title.rb +29 -0
  55. data/lib/capybara/rspec/matchers/match_selector.rb +27 -0
  56. data/lib/capybara/rspec/matchers/match_style.rb +43 -0
  57. data/lib/capybara/rspec/matchers/spatial_sugar.rb +39 -0
  58. data/lib/capybara/rspec/matchers.rb +138 -316
  59. data/lib/capybara/rspec.rb +3 -2
  60. data/lib/capybara/selector/builders/css_builder.rb +84 -0
  61. data/lib/capybara/selector/builders/xpath_builder.rb +71 -0
  62. data/lib/capybara/selector/css.rb +89 -12
  63. data/lib/capybara/selector/definition/button.rb +68 -0
  64. data/lib/capybara/selector/definition/checkbox.rb +26 -0
  65. data/lib/capybara/selector/definition/css.rb +10 -0
  66. data/lib/capybara/selector/definition/datalist_input.rb +35 -0
  67. data/lib/capybara/selector/definition/datalist_option.rb +25 -0
  68. data/lib/capybara/selector/definition/element.rb +28 -0
  69. data/lib/capybara/selector/definition/field.rb +40 -0
  70. data/lib/capybara/selector/definition/fieldset.rb +14 -0
  71. data/lib/capybara/selector/definition/file_field.rb +13 -0
  72. data/lib/capybara/selector/definition/fillable_field.rb +33 -0
  73. data/lib/capybara/selector/definition/frame.rb +17 -0
  74. data/lib/capybara/selector/definition/id.rb +6 -0
  75. data/lib/capybara/selector/definition/label.rb +62 -0
  76. data/lib/capybara/selector/definition/link.rb +54 -0
  77. data/lib/capybara/selector/definition/link_or_button.rb +16 -0
  78. data/lib/capybara/selector/definition/option.rb +27 -0
  79. data/lib/capybara/selector/definition/radio_button.rb +27 -0
  80. data/lib/capybara/selector/definition/select.rb +81 -0
  81. data/lib/capybara/selector/definition/table.rb +109 -0
  82. data/lib/capybara/selector/definition/table_row.rb +21 -0
  83. data/lib/capybara/selector/definition/xpath.rb +5 -0
  84. data/lib/capybara/selector/definition.rb +279 -0
  85. data/lib/capybara/selector/filter.rb +1 -0
  86. data/lib/capybara/selector/filter_set.rb +72 -27
  87. data/lib/capybara/selector/filters/base.rb +45 -2
  88. data/lib/capybara/selector/filters/expression_filter.rb +5 -6
  89. data/lib/capybara/selector/filters/locator_filter.rb +29 -0
  90. data/lib/capybara/selector/filters/node_filter.rb +18 -4
  91. data/lib/capybara/selector/regexp_disassembler.rb +214 -0
  92. data/lib/capybara/selector/selector.rb +85 -200
  93. data/lib/capybara/selector/xpath_extensions.rb +17 -0
  94. data/lib/capybara/selector.rb +226 -537
  95. data/lib/capybara/selenium/atoms/getAttribute.min.js +1 -0
  96. data/lib/capybara/selenium/atoms/isDisplayed.min.js +1 -0
  97. data/lib/capybara/selenium/atoms/src/getAttribute.js +161 -0
  98. data/lib/capybara/selenium/atoms/src/isDisplayed.js +454 -0
  99. data/lib/capybara/selenium/driver.rb +296 -238
  100. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +117 -0
  101. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +124 -0
  102. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +89 -0
  103. data/lib/capybara/selenium/driver_specializations/internet_explorer_driver.rb +26 -0
  104. data/lib/capybara/selenium/driver_specializations/safari_driver.rb +24 -0
  105. data/lib/capybara/selenium/extensions/file_input_click_emulation.rb +34 -0
  106. data/lib/capybara/selenium/extensions/find.rb +110 -0
  107. data/lib/capybara/selenium/extensions/html5_drag.rb +228 -0
  108. data/lib/capybara/selenium/extensions/modifier_keys_stack.rb +28 -0
  109. data/lib/capybara/selenium/extensions/scroll.rb +76 -0
  110. data/lib/capybara/selenium/logger_suppressor.rb +40 -0
  111. data/lib/capybara/selenium/node.rb +436 -166
  112. data/lib/capybara/selenium/nodes/chrome_node.rb +137 -0
  113. data/lib/capybara/selenium/nodes/edge_node.rb +104 -0
  114. data/lib/capybara/selenium/nodes/firefox_node.rb +136 -0
  115. data/lib/capybara/selenium/nodes/ie_node.rb +22 -0
  116. data/lib/capybara/selenium/nodes/safari_node.rb +118 -0
  117. data/lib/capybara/selenium/patches/action_pauser.rb +26 -0
  118. data/lib/capybara/selenium/patches/atoms.rb +18 -0
  119. data/lib/capybara/selenium/patches/is_displayed.rb +16 -0
  120. data/lib/capybara/selenium/patches/logs.rb +45 -0
  121. data/lib/capybara/selenium/patches/pause_duration_fix.rb +9 -0
  122. data/lib/capybara/selenium/patches/persistent_client.rb +20 -0
  123. data/lib/capybara/server/animation_disabler.rb +69 -0
  124. data/lib/capybara/server/checker.rb +44 -0
  125. data/lib/capybara/server/middleware.rb +71 -0
  126. data/lib/capybara/server.rb +58 -67
  127. data/lib/capybara/session/config.rb +38 -6
  128. data/lib/capybara/session/matchers.rb +26 -19
  129. data/lib/capybara/session.rb +258 -190
  130. data/lib/capybara/spec/public/jquery.js +5 -5
  131. data/lib/capybara/spec/public/offset.js +6 -0
  132. data/lib/capybara/spec/public/test.js +122 -8
  133. data/lib/capybara/spec/session/accept_alert_spec.rb +11 -11
  134. data/lib/capybara/spec/session/accept_confirm_spec.rb +3 -3
  135. data/lib/capybara/spec/session/accept_prompt_spec.rb +9 -10
  136. data/lib/capybara/spec/session/active_element_spec.rb +31 -0
  137. data/lib/capybara/spec/session/all_spec.rb +135 -42
  138. data/lib/capybara/spec/session/ancestor_spec.rb +24 -19
  139. data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +67 -38
  140. data/lib/capybara/spec/session/{assert_current_path.rb → assert_current_path_spec.rb} +20 -18
  141. data/lib/capybara/spec/session/assert_selector_spec.rb +143 -0
  142. data/lib/capybara/spec/session/assert_style_spec.rb +26 -0
  143. data/lib/capybara/spec/session/{assert_text.rb → assert_text_spec.rb} +62 -38
  144. data/lib/capybara/spec/session/{assert_title.rb → assert_title_spec.rb} +12 -12
  145. data/lib/capybara/spec/session/attach_file_spec.rb +120 -72
  146. data/lib/capybara/spec/session/body_spec.rb +11 -13
  147. data/lib/capybara/spec/session/check_spec.rb +111 -51
  148. data/lib/capybara/spec/session/choose_spec.rb +62 -30
  149. data/lib/capybara/spec/session/click_button_spec.rb +227 -161
  150. data/lib/capybara/spec/session/click_link_or_button_spec.rb +49 -30
  151. data/lib/capybara/spec/session/click_link_spec.rb +78 -55
  152. data/lib/capybara/spec/session/current_scope_spec.rb +7 -7
  153. data/lib/capybara/spec/session/current_url_spec.rb +44 -37
  154. data/lib/capybara/spec/session/dismiss_confirm_spec.rb +3 -3
  155. data/lib/capybara/spec/session/dismiss_prompt_spec.rb +2 -2
  156. data/lib/capybara/spec/session/element/{assert_match_selector.rb → assert_match_selector_spec.rb} +11 -9
  157. data/lib/capybara/spec/session/element/match_css_spec.rb +18 -10
  158. data/lib/capybara/spec/session/element/match_xpath_spec.rb +8 -6
  159. data/lib/capybara/spec/session/element/matches_selector_spec.rb +70 -56
  160. data/lib/capybara/spec/session/evaluate_async_script_spec.rb +7 -7
  161. data/lib/capybara/spec/session/evaluate_script_spec.rb +28 -8
  162. data/lib/capybara/spec/session/execute_script_spec.rb +8 -7
  163. data/lib/capybara/spec/session/fill_in_spec.rb +104 -44
  164. data/lib/capybara/spec/session/find_button_spec.rb +23 -23
  165. data/lib/capybara/spec/session/find_by_id_spec.rb +8 -8
  166. data/lib/capybara/spec/session/find_field_spec.rb +33 -31
  167. data/lib/capybara/spec/session/find_link_spec.rb +32 -14
  168. data/lib/capybara/spec/session/find_spec.rb +236 -141
  169. data/lib/capybara/spec/session/first_spec.rb +44 -43
  170. data/lib/capybara/spec/session/frame/frame_title_spec.rb +6 -6
  171. data/lib/capybara/spec/session/frame/frame_url_spec.rb +6 -6
  172. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +32 -20
  173. data/lib/capybara/spec/session/frame/within_frame_spec.rb +44 -19
  174. data/lib/capybara/spec/session/go_back_spec.rb +1 -1
  175. data/lib/capybara/spec/session/go_forward_spec.rb +1 -1
  176. data/lib/capybara/spec/session/has_all_selectors_spec.rb +18 -18
  177. data/lib/capybara/spec/session/has_ancestor_spec.rb +46 -0
  178. data/lib/capybara/spec/session/has_any_selectors_spec.rb +29 -0
  179. data/lib/capybara/spec/session/has_button_spec.rb +92 -12
  180. data/lib/capybara/spec/session/has_css_spec.rb +271 -137
  181. data/lib/capybara/spec/session/has_current_path_spec.rb +48 -33
  182. data/lib/capybara/spec/session/has_field_spec.rb +137 -58
  183. data/lib/capybara/spec/session/has_link_spec.rb +30 -6
  184. data/lib/capybara/spec/session/has_none_selectors_spec.rb +26 -24
  185. data/lib/capybara/spec/session/has_select_spec.rb +75 -47
  186. data/lib/capybara/spec/session/has_selector_spec.rb +102 -69
  187. data/lib/capybara/spec/session/has_sibling_spec.rb +50 -0
  188. data/lib/capybara/spec/session/has_table_spec.rb +170 -4
  189. data/lib/capybara/spec/session/has_text_spec.rb +108 -52
  190. data/lib/capybara/spec/session/has_title_spec.rb +19 -14
  191. data/lib/capybara/spec/session/has_xpath_spec.rb +56 -38
  192. data/lib/capybara/spec/session/{headers.rb → headers_spec.rb} +1 -1
  193. data/lib/capybara/spec/session/html_spec.rb +13 -6
  194. data/lib/capybara/spec/session/matches_style_spec.rb +35 -0
  195. data/lib/capybara/spec/session/node_spec.rb +871 -122
  196. data/lib/capybara/spec/session/node_wrapper_spec.rb +15 -12
  197. data/lib/capybara/spec/session/refresh_spec.rb +9 -7
  198. data/lib/capybara/spec/session/reset_session_spec.rb +52 -37
  199. data/lib/capybara/spec/session/{response_code.rb → response_code_spec.rb} +1 -1
  200. data/lib/capybara/spec/session/save_and_open_page_spec.rb +2 -2
  201. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +5 -4
  202. data/lib/capybara/spec/session/save_page_spec.rb +41 -38
  203. data/lib/capybara/spec/session/save_screenshot_spec.rb +13 -11
  204. data/lib/capybara/spec/session/screenshot_spec.rb +2 -2
  205. data/lib/capybara/spec/session/scroll_spec.rb +117 -0
  206. data/lib/capybara/spec/session/select_spec.rb +102 -76
  207. data/lib/capybara/spec/session/selectors_spec.rb +51 -18
  208. data/lib/capybara/spec/session/sibling_spec.rb +9 -9
  209. data/lib/capybara/spec/session/text_spec.rb +26 -24
  210. data/lib/capybara/spec/session/title_spec.rb +8 -6
  211. data/lib/capybara/spec/session/uncheck_spec.rb +40 -21
  212. data/lib/capybara/spec/session/unselect_spec.rb +37 -37
  213. data/lib/capybara/spec/session/visit_spec.rb +59 -53
  214. data/lib/capybara/spec/session/window/become_closed_spec.rb +22 -19
  215. data/lib/capybara/spec/session/window/current_window_spec.rb +4 -3
  216. data/lib/capybara/spec/session/window/open_new_window_spec.rb +4 -3
  217. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +25 -21
  218. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +10 -5
  219. data/lib/capybara/spec/session/window/window_spec.rb +88 -54
  220. data/lib/capybara/spec/session/window/windows_spec.rb +10 -7
  221. data/lib/capybara/spec/session/window/within_window_spec.rb +17 -16
  222. data/lib/capybara/spec/session/within_spec.rb +69 -44
  223. data/lib/capybara/spec/spec_helper.rb +41 -53
  224. data/lib/capybara/spec/test_app.rb +79 -40
  225. data/lib/capybara/spec/views/animated.erb +49 -0
  226. data/lib/capybara/spec/views/form.erb +134 -42
  227. data/lib/capybara/spec/views/frame_child.erb +4 -3
  228. data/lib/capybara/spec/views/frame_one.erb +2 -1
  229. data/lib/capybara/spec/views/frame_parent.erb +1 -1
  230. data/lib/capybara/spec/views/frame_two.erb +1 -1
  231. data/lib/capybara/spec/views/initial_alert.erb +2 -1
  232. data/lib/capybara/spec/views/layout.erb +10 -0
  233. data/lib/capybara/spec/views/obscured.erb +47 -0
  234. data/lib/capybara/spec/views/offset.erb +33 -0
  235. data/lib/capybara/spec/views/path.erb +2 -2
  236. data/lib/capybara/spec/views/popup_one.erb +1 -1
  237. data/lib/capybara/spec/views/popup_two.erb +1 -1
  238. data/lib/capybara/spec/views/react.erb +45 -0
  239. data/lib/capybara/spec/views/scroll.erb +21 -0
  240. data/lib/capybara/spec/views/spatial.erb +31 -0
  241. data/lib/capybara/spec/views/tables.erb +68 -1
  242. data/lib/capybara/spec/views/with_animation.erb +81 -0
  243. data/lib/capybara/spec/views/with_base_tag.erb +2 -2
  244. data/lib/capybara/spec/views/with_dragula.erb +24 -0
  245. data/lib/capybara/spec/views/with_fixed_header_footer.erb +2 -1
  246. data/lib/capybara/spec/views/with_hover.erb +3 -2
  247. data/lib/capybara/spec/views/with_hover1.erb +10 -0
  248. data/lib/capybara/spec/views/with_html.erb +43 -10
  249. data/lib/capybara/spec/views/with_html5_svg.erb +20 -0
  250. data/lib/capybara/spec/views/with_jquery_animation.erb +24 -0
  251. data/lib/capybara/spec/views/with_js.erb +30 -5
  252. data/lib/capybara/spec/views/with_jstree.erb +26 -0
  253. data/lib/capybara/spec/views/with_namespace.erb +21 -0
  254. data/lib/capybara/spec/views/with_scope_other.erb +6 -0
  255. data/lib/capybara/spec/views/with_slow_unload.erb +2 -1
  256. data/lib/capybara/spec/views/with_sortable_js.erb +21 -0
  257. data/lib/capybara/spec/views/with_unload_alert.erb +1 -0
  258. data/lib/capybara/spec/views/with_windows.erb +1 -1
  259. data/lib/capybara/spec/views/within_frames.erb +1 -1
  260. data/lib/capybara/version.rb +1 -1
  261. data/lib/capybara/window.rb +31 -25
  262. data/lib/capybara.rb +120 -100
  263. data/spec/basic_node_spec.rb +59 -34
  264. data/spec/capybara_spec.rb +54 -52
  265. data/spec/css_builder_spec.rb +101 -0
  266. data/spec/css_splitter_spec.rb +38 -0
  267. data/spec/dsl_spec.rb +80 -53
  268. data/spec/filter_set_spec.rb +24 -7
  269. data/spec/fixtures/certificate.pem +25 -0
  270. data/spec/fixtures/key.pem +27 -0
  271. data/spec/fixtures/selenium_driver_rspec_failure.rb +4 -4
  272. data/spec/fixtures/selenium_driver_rspec_success.rb +4 -4
  273. data/spec/minitest_spec.rb +38 -5
  274. data/spec/minitest_spec_spec.rb +88 -62
  275. data/spec/per_session_config_spec.rb +5 -5
  276. data/spec/rack_test_spec.rb +169 -115
  277. data/spec/regexp_dissassembler_spec.rb +250 -0
  278. data/spec/result_spec.rb +86 -33
  279. data/spec/rspec/features_spec.rb +26 -23
  280. data/spec/rspec/scenarios_spec.rb +8 -4
  281. data/spec/rspec/shared_spec_matchers.rb +393 -363
  282. data/spec/rspec/views_spec.rb +4 -3
  283. data/spec/rspec_matchers_spec.rb +10 -10
  284. data/spec/rspec_spec.rb +109 -85
  285. data/spec/sauce_spec_chrome.rb +43 -0
  286. data/spec/selector_spec.rb +391 -61
  287. data/spec/selenium_spec_chrome.rb +180 -41
  288. data/spec/selenium_spec_chrome_remote.rb +101 -0
  289. data/spec/selenium_spec_edge.rb +28 -8
  290. data/spec/selenium_spec_firefox.rb +211 -0
  291. data/spec/selenium_spec_firefox_remote.rb +80 -0
  292. data/spec/selenium_spec_ie.rb +127 -11
  293. data/spec/selenium_spec_safari.rb +156 -0
  294. data/spec/server_spec.rb +177 -78
  295. data/spec/session_spec.rb +52 -16
  296. data/spec/shared_selenium_node.rb +79 -0
  297. data/spec/shared_selenium_session.rb +455 -123
  298. data/spec/spec_helper.rb +91 -2
  299. data/spec/xpath_builder_spec.rb +93 -0
  300. metadata +343 -42
  301. data/.yard/templates_custom/default/class/html/selectors.erb +0 -38
  302. data/.yard/templates_custom/default/class/html/setup.rb +0 -17
  303. data/.yard/yard_extensions.rb +0 -78
  304. data/lib/capybara/rspec/compound.rb +0 -90
  305. data/lib/capybara/spec/session/assert_selector.rb +0 -149
  306. data/lib/capybara/spec/session/source_spec.rb +0 -0
  307. data/lib/capybara/spec/views/with_title.erb +0 -5
  308. data/spec/selenium_spec_marionette.rb +0 -143
@@ -2,21 +2,23 @@
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
- sibling_query = @sibling_node && @sibling_node.instance_variable_get(:@query)
21
+ sibling_query = @sibling_node&.instance_variable_get(:@query)
20
22
  desc += " that is a sibling of #{sibling_query.description}" if sibling_query
21
23
  desc
22
24
  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
@@ -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
 
@@ -39,7 +42,7 @@ module Capybara
39
42
  if @expected_text.is_a?(Regexp)
40
43
  "text matching #{@expected_text.inspect}"
41
44
  else
42
- "#{"exact " if exact?}text #{@expected_text.inspect}"
45
+ "#{'exact ' if exact?}text #{@expected_text.inspect}"
43
46
  end
44
47
  end
45
48
 
@@ -50,15 +53,15 @@ module Capybara
50
53
  end
51
54
 
52
55
  def build_message(report_on_invisible)
53
- message = "".dup
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
57
60
  message << " in #{@actual_text.inspect}"
58
61
 
59
62
  details_message = []
60
- details_message << case_insensitive_message if @node and !@expected_text.is_a? Regexp
61
- details_message << invisible_message if @node and check_visible_text? and report_on_invisible
63
+ details_message << case_insensitive_message if @node && check_case_insensitive?
64
+ details_message << invisible_message if @node && check_visible_text? && report_on_invisible
62
65
  details_message.compact!
63
66
 
64
67
  message << ". (However, #{details_message.join(' and ')}.)" unless details_message.empty?
@@ -68,30 +71,45 @@ module Capybara
68
71
  def case_insensitive_message
69
72
  insensitive_regexp = Capybara::Helpers.to_regexp(@expected_text, options: Regexp::IGNORECASE)
70
73
  insensitive_count = @actual_text.scan(insensitive_regexp).size
71
- if insensitive_count != @count
72
- "it was found #{insensitive_count} #{Capybara::Helpers.declension("time", "times", insensitive_count)} using a case insensitive search"
73
- end
74
+ return if insensitive_count == @count
75
+
76
+ "it was found #{occurrences insensitive_count} using a case insensitive search"
74
77
  end
75
78
 
76
79
  def invisible_message
77
- invisible_text = text(@node, :all)
80
+ invisible_text = text(query_type: :all)
78
81
  invisible_count = invisible_text.scan(@search_regexp).size
79
- if invisible_count != @count
80
- "it was found #{invisible_count} #{Capybara::Helpers.declension("time", "times", invisible_count)} including non-visible text"
81
- end
82
- rescue # An error getting the non-visible text (if element goes out of scope) should not affect the response
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 + %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?
90
99
  @type == :visible
91
100
  end
92
101
 
93
- def text(node, query_type)
94
- 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
@@ -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
@@ -30,58 +31,70 @@ class Capybara::RackTest::Browser
30
31
 
31
32
  def submit(method, path, attributes)
32
33
  path = request_path if path.nil? || path.empty?
33
- process_and_follow_redirects(method, path, attributes, 'HTTP_REFERER' => current_url)
34
+ uri = build_uri(path)
35
+ uri.query = '' if method.to_s.casecmp('get').zero?
36
+ process_and_follow_redirects(method, uri.to_s, attributes, 'HTTP_REFERER' => current_url)
34
37
  end
35
38
 
36
39
  def follow(method, path, **attributes)
37
- return if path.gsub(/^#{Regexp.escape(request_path)}/, '').start_with?('#') || path.downcase.start_with?('javascript:')
40
+ return if fragment_or_script?(path)
41
+
38
42
  process_and_follow_redirects(method, path, attributes, 'HTTP_REFERER' => current_url)
39
43
  end
40
44
 
41
45
  def process_and_follow_redirects(method, path, attributes = {}, env = {})
46
+ @current_fragment = build_uri(path).fragment
42
47
  process(method, path, attributes, env)
43
48
 
44
49
  return unless driver.follow_redirects?
45
50
 
46
51
  driver.redirect_limit.times do
47
- process(:get, last_response["Location"], {}, env) if last_response.redirect?
52
+ if last_response.redirect?
53
+ if [307, 308].include? last_response.status
54
+ process(last_request.request_method, last_response['Location'], last_request.params, env)
55
+ else
56
+ process(:get, last_response['Location'], {}, env)
57
+ end
58
+ end
48
59
  end
49
- raise Capybara::InfiniteRedirectError, "redirected more than #{driver.redirect_limit} times, check for infinite redirects." if last_response.redirect?
50
- end
51
60
 
52
- def process(method, path, attributes = {}, env = {})
53
- new_uri = URI.parse(path)
54
- method.downcase! unless method.is_a? Symbol
55
- if path.empty?
56
- new_uri.path = request_path
57
- else
58
- new_uri.path = request_path if path.start_with?("?")
59
- new_uri.path = "/" if new_uri.path.empty?
60
- new_uri.path = request_path.sub(%r{/[^/]*$}, '/') + new_uri.path unless new_uri.path.start_with?('/')
61
+ if last_response.redirect? # rubocop:disable Style/GuardClause
62
+ raise Capybara::InfiniteRedirectError, "redirected more than #{driver.redirect_limit} times, check for infinite redirects."
61
63
  end
62
- new_uri.scheme ||= @current_scheme
63
- new_uri.host ||= @current_host
64
- new_uri.port ||= @current_port unless new_uri.default_port == @current_port
65
-
66
- @current_scheme = new_uri.scheme
67
- @current_host = new_uri.host
68
- @current_port = new_uri.port
64
+ end
69
65
 
66
+ def process(method, path, attributes = {}, env = {})
67
+ method = method.downcase
68
+ new_uri = build_uri(path)
69
+ @current_scheme, @current_host, @current_port = new_uri.select(:scheme, :host, :port)
70
+ @current_fragment = new_uri.fragment || @current_fragment
70
71
  reset_cache!
71
72
  send(method, new_uri.to_s, attributes, env.merge(options[:headers] || {}))
72
73
  end
73
74
 
75
+ def build_uri(path)
76
+ URI.parse(path).tap do |uri|
77
+ uri.path = request_path if path.empty? || path.start_with?('?')
78
+ uri.path = '/' if uri.path.empty?
79
+ uri.path = request_path.sub(%r{/[^/]*$}, '/') + uri.path unless uri.path.start_with?('/')
80
+
81
+ uri.scheme ||= @current_scheme
82
+ uri.host ||= @current_host
83
+ uri.port ||= @current_port unless uri.default_port == @current_port
84
+ end
85
+ end
86
+
74
87
  def current_url
75
- last_request.url
88
+ uri = build_uri(last_request.url)
89
+ uri.fragment = @current_fragment if @current_fragment
90
+ uri.to_s
76
91
  rescue Rack::Test::Error
77
- ""
92
+ ''
78
93
  end
79
94
 
80
95
  def reset_host!
81
96
  uri = URI.parse(driver.session_options.app_host || driver.session_options.default_host)
82
- @current_scheme = uri.scheme
83
- @current_host = uri.host
84
- @current_port = uri.port
97
+ @current_scheme, @current_host, @current_port = uri.select(:scheme, :host, :port)
85
98
  end
86
99
 
87
100
  def reset_cache!
@@ -103,7 +116,7 @@ class Capybara::RackTest::Browser
103
116
  def html
104
117
  last_response.body
105
118
  rescue Rack::Test::Error
106
- ""
119
+ ''
107
120
  end
108
121
 
109
122
  def title
@@ -120,6 +133,12 @@ protected
120
133
  def request_path
121
134
  last_request.path
122
135
  rescue Rack::Test::Error
123
- "/"
136
+ '/'
137
+ end
138
+
139
+ private
140
+
141
+ def fragment_or_script?(path)
142
+ path.gsub(/^#{Regexp.escape(request_path)}/, '').start_with?('#') || path.downcase.start_with?('javascript:')
124
143
  end
125
144
  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
@@ -98,4 +103,8 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
98
103
  def put(*args, &block); browser.put(*args, &block); end
99
104
  def delete(*args, &block); browser.delete(*args, &block); 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,51 +6,45 @@ 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
19
  end
20
20
 
21
21
  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))
22
+ form_element_types = %i[input select textarea button]
23
+ form_elements_xpath = XPath.generate do |xp|
24
+ xpath = xp.descendant(*form_element_types).where(!xp.attr(:form))
25
+ xpath += xp.anywhere(*form_element_types).where(xp.attr(:form) == native[:id]) if native[:id]
26
+ xpath.where(!xp.attr(:disabled))
29
27
  end.to_s
30
28
 
31
- native.xpath(form_elements_xpath).map do |field|
29
+ form_elements = native.xpath(form_elements_xpath).reject { |el| submitter?(el) && (el != button.native) }
30
+
31
+ form_elements.each_with_object(make_params) do |field, params|
32
32
  case field.name
33
- when 'input'
34
- add_input_param(field, params)
35
- when 'select'
36
- add_select_param(field, params)
37
- when 'textarea'
38
- add_textarea_param(field, params)
33
+ when 'input', 'button' then add_input_param(field, params)
34
+ when 'select' then add_select_param(field, params)
35
+ when 'textarea' then add_textarea_param(field, params)
39
36
  end
40
- end
41
- merge_param!(params, button[:name], button[:value] || "") if button[:name]
42
-
43
- params.to_params_hash
37
+ end.to_params_hash
44
38
  end
45
39
 
46
40
  def submit(button)
47
- action = (button && button['formaction']) || native['action']
48
- method = (button && button['formmethod']) || request_method
41
+ action = button&.[]('formaction') || native['action']
42
+ method = button&.[]('formmethod') || request_method
49
43
  driver.submit(method, action.to_s, params(button))
50
44
  end
51
45
 
52
46
  def multipart?
53
- self[:enctype] == "multipart/form-data"
47
+ self[:enctype] == 'multipart/form-data'
54
48
  end
55
49
 
56
50
  private
@@ -62,10 +56,11 @@ private
62
56
  end
63
57
 
64
58
  def request_method
65
- self[:method] =~ /post/i ? :post : :get
59
+ /post/i.match?(self[:method] || '') ? :post : :get
66
60
  end
67
61
 
68
62
  def merge_param!(params, key, value)
63
+ key = key.to_s
69
64
  if Rack::Utils.respond_to?(:default_query_parser)
70
65
  Rack::Utils.default_query_parser.normalize_params(params, key, value, Rack::Utils.param_depth_limit)
71
66
  else
@@ -82,43 +77,51 @@ private
82
77
  end
83
78
 
84
79
  def add_input_param(field, params)
85
- if %w[radio checkbox].include? field['type']
86
- if field['checked']
87
- node = Capybara::RackTest::Node.new(driver, field)
88
- merge_param!(params, field['name'].to_s, node.value.to_s)
89
- end
90
- elsif %w[submit image].include? field['type']
91
- # TODO: identify the click button here (in document order, rather
92
- # than leaving until the end of the params)
93
- elsif field['type'] == 'file'
80
+ name, value = field['name'].to_s, field['value'].to_s
81
+ return if name.empty?
82
+
83
+ value = case field['type']
84
+ when 'radio', 'checkbox'
85
+ return unless field['checked']
86
+
87
+ Capybara::RackTest::Node.new(driver, field).value.to_s
88
+ when 'file'
94
89
  if multipart?
95
- file = if (value = field['value']).to_s.empty?
96
- NilUploadedFile.new
97
- else
98
- mime_info = MiniMime.lookup_by_filename(value)
99
- Rack::Test::UploadedFile.new(value, (mime_info && mime_info.content_type).to_s)
100
- end
101
- merge_param!(params, field['name'].to_s, file)
90
+ file_to_upload(value)
102
91
  else
103
- merge_param!(params, field['name'].to_s, File.basename(field['value'].to_s))
92
+ File.basename(value)
104
93
  end
105
94
  else
106
- merge_param!(params, field['name'].to_s, field['value'].to_s)
95
+ value
96
+ end
97
+ merge_param!(params, name, value)
98
+ end
99
+
100
+ def file_to_upload(filename)
101
+ if filename.empty?
102
+ NilUploadedFile.new
103
+ else
104
+ mime_info = MiniMime.lookup_by_filename(filename)
105
+ Rack::Test::UploadedFile.new(filename, mime_info&.content_type&.to_s)
107
106
  end
108
107
  end
109
108
 
110
109
  def add_select_param(field, params)
111
- if field['multiple'] == 'multiple'
112
- field.xpath(".//option[@selected]").each do |option|
113
- merge_param!(params, field['name'].to_s, (option['value'] || option.text).to_s)
110
+ if field.has_attribute?('multiple')
111
+ field.xpath('.//option[@selected]').each do |option|
112
+ merge_param!(params, field['name'], (option['value'] || option.text).to_s)
114
113
  end
115
114
  else
116
115
  option = field.xpath('.//option[@selected]').first || field.xpath('.//option').first
117
- merge_param!(params, field['name'].to_s, (option['value'] || option.text).to_s) if option
116
+ merge_param!(params, field['name'], (option['value'] || option.text).to_s) if option
118
117
  end
119
118
  end
120
119
 
121
120
  def add_textarea_param(field, params)
122
- merge_param!(params, field['name'].to_s, field['_capybara_raw_value'].to_s.gsub(/\n/, "\r\n"))
121
+ merge_param!(params, field['name'], field['_capybara_raw_value'].to_s.gsub(/\n/, "\r\n"))
122
+ end
123
+
124
+ def submitter?(el)
125
+ (%w[submit image].include? el['type']) || (el.name == 'button')
123
126
  end
124
127
  end