capybara 3.13.2 → 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 (260) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -0
  3. data/History.md +587 -16
  4. data/README.md +240 -90
  5. data/lib/capybara/config.rb +24 -11
  6. data/lib/capybara/cucumber.rb +1 -1
  7. data/lib/capybara/driver/base.rb +8 -0
  8. data/lib/capybara/driver/node.rb +20 -4
  9. data/lib/capybara/dsl.rb +5 -3
  10. data/lib/capybara/helpers.rb +25 -4
  11. data/lib/capybara/minitest/spec.rb +174 -90
  12. data/lib/capybara/minitest.rb +256 -142
  13. data/lib/capybara/node/actions.rb +123 -77
  14. data/lib/capybara/node/base.rb +20 -12
  15. data/lib/capybara/node/document.rb +2 -2
  16. data/lib/capybara/node/document_matchers.rb +3 -3
  17. data/lib/capybara/node/element.rb +223 -117
  18. data/lib/capybara/node/finders.rb +81 -71
  19. data/lib/capybara/node/matchers.rb +271 -134
  20. data/lib/capybara/node/simple.rb +18 -5
  21. data/lib/capybara/node/whitespace_normalizer.rb +81 -0
  22. data/lib/capybara/queries/active_element_query.rb +18 -0
  23. data/lib/capybara/queries/ancestor_query.rb +8 -9
  24. data/lib/capybara/queries/base_query.rb +3 -2
  25. data/lib/capybara/queries/current_path_query.rb +15 -5
  26. data/lib/capybara/queries/selector_query.rb +364 -54
  27. data/lib/capybara/queries/sibling_query.rb +8 -6
  28. data/lib/capybara/queries/style_query.rb +2 -2
  29. data/lib/capybara/queries/text_query.rb +13 -1
  30. data/lib/capybara/queries/title_query.rb +1 -1
  31. data/lib/capybara/rack_test/browser.rb +76 -11
  32. data/lib/capybara/rack_test/driver.rb +10 -5
  33. data/lib/capybara/rack_test/errors.rb +6 -0
  34. data/lib/capybara/rack_test/form.rb +31 -9
  35. data/lib/capybara/rack_test/node.rb +74 -23
  36. data/lib/capybara/registration_container.rb +41 -0
  37. data/lib/capybara/registrations/drivers.rb +42 -0
  38. data/lib/capybara/registrations/patches/puma_ssl.rb +29 -0
  39. data/lib/capybara/registrations/servers.rb +66 -0
  40. data/lib/capybara/result.rb +44 -20
  41. data/lib/capybara/rspec/matcher_proxies.rb +13 -11
  42. data/lib/capybara/rspec/matchers/base.rb +31 -16
  43. data/lib/capybara/rspec/matchers/compound.rb +1 -1
  44. data/lib/capybara/rspec/matchers/count_sugar.rb +37 -0
  45. data/lib/capybara/rspec/matchers/have_ancestor.rb +28 -0
  46. data/lib/capybara/rspec/matchers/have_current_path.rb +2 -2
  47. data/lib/capybara/rspec/matchers/have_selector.rb +21 -21
  48. data/lib/capybara/rspec/matchers/have_sibling.rb +27 -0
  49. data/lib/capybara/rspec/matchers/have_text.rb +4 -4
  50. data/lib/capybara/rspec/matchers/have_title.rb +2 -2
  51. data/lib/capybara/rspec/matchers/match_selector.rb +3 -3
  52. data/lib/capybara/rspec/matchers/match_style.rb +7 -2
  53. data/lib/capybara/rspec/matchers/spatial_sugar.rb +39 -0
  54. data/lib/capybara/rspec/matchers.rb +111 -68
  55. data/lib/capybara/rspec.rb +2 -0
  56. data/lib/capybara/selector/builders/css_builder.rb +11 -7
  57. data/lib/capybara/selector/builders/xpath_builder.rb +5 -3
  58. data/lib/capybara/selector/css.rb +11 -9
  59. data/lib/capybara/selector/definition/button.rb +68 -0
  60. data/lib/capybara/selector/definition/checkbox.rb +26 -0
  61. data/lib/capybara/selector/definition/css.rb +10 -0
  62. data/lib/capybara/selector/definition/datalist_input.rb +35 -0
  63. data/lib/capybara/selector/definition/datalist_option.rb +25 -0
  64. data/lib/capybara/selector/definition/element.rb +28 -0
  65. data/lib/capybara/selector/definition/field.rb +40 -0
  66. data/lib/capybara/selector/definition/fieldset.rb +14 -0
  67. data/lib/capybara/selector/definition/file_field.rb +13 -0
  68. data/lib/capybara/selector/definition/fillable_field.rb +33 -0
  69. data/lib/capybara/selector/definition/frame.rb +17 -0
  70. data/lib/capybara/selector/definition/id.rb +6 -0
  71. data/lib/capybara/selector/definition/label.rb +62 -0
  72. data/lib/capybara/selector/definition/link.rb +55 -0
  73. data/lib/capybara/selector/definition/link_or_button.rb +16 -0
  74. data/lib/capybara/selector/definition/option.rb +27 -0
  75. data/lib/capybara/selector/definition/radio_button.rb +27 -0
  76. data/lib/capybara/selector/definition/select.rb +81 -0
  77. data/lib/capybara/selector/definition/table.rb +109 -0
  78. data/lib/capybara/selector/definition/table_row.rb +21 -0
  79. data/lib/capybara/selector/definition/xpath.rb +5 -0
  80. data/lib/capybara/selector/definition.rb +280 -0
  81. data/lib/capybara/selector/filter_set.rb +19 -18
  82. data/lib/capybara/selector/filters/base.rb +11 -2
  83. data/lib/capybara/selector/filters/locator_filter.rb +13 -3
  84. data/lib/capybara/selector/regexp_disassembler.rb +11 -7
  85. data/lib/capybara/selector/selector.rb +50 -440
  86. data/lib/capybara/selector/xpath_extensions.rb +17 -0
  87. data/lib/capybara/selector.rb +473 -482
  88. data/lib/capybara/selenium/atoms/getAttribute.min.js +1 -0
  89. data/lib/capybara/selenium/atoms/isDisplayed.min.js +1 -0
  90. data/lib/capybara/selenium/atoms/src/getAttribute.js +161 -0
  91. data/lib/capybara/selenium/atoms/src/isDisplayed.js +454 -0
  92. data/lib/capybara/selenium/driver.rb +174 -62
  93. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +74 -18
  94. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +128 -0
  95. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +37 -3
  96. data/lib/capybara/selenium/driver_specializations/internet_explorer_driver.rb +14 -1
  97. data/lib/capybara/selenium/driver_specializations/safari_driver.rb +24 -0
  98. data/lib/capybara/selenium/extensions/file_input_click_emulation.rb +34 -0
  99. data/lib/capybara/selenium/extensions/find.rb +68 -45
  100. data/lib/capybara/selenium/extensions/html5_drag.rb +192 -22
  101. data/lib/capybara/selenium/extensions/modifier_keys_stack.rb +28 -0
  102. data/lib/capybara/selenium/extensions/scroll.rb +8 -10
  103. data/lib/capybara/selenium/node.rb +268 -72
  104. data/lib/capybara/selenium/nodes/chrome_node.rb +105 -9
  105. data/lib/capybara/selenium/nodes/edge_node.rb +110 -0
  106. data/lib/capybara/selenium/nodes/firefox_node.rb +51 -61
  107. data/lib/capybara/selenium/nodes/ie_node.rb +22 -0
  108. data/lib/capybara/selenium/nodes/safari_node.rb +118 -0
  109. data/lib/capybara/selenium/patches/atoms.rb +18 -0
  110. data/lib/capybara/selenium/patches/is_displayed.rb +16 -0
  111. data/lib/capybara/selenium/patches/logs.rb +45 -0
  112. data/lib/capybara/selenium/patches/pause_duration_fix.rb +1 -1
  113. data/lib/capybara/selenium/patches/persistent_client.rb +20 -0
  114. data/lib/capybara/server/animation_disabler.rb +43 -21
  115. data/lib/capybara/server/checker.rb +6 -2
  116. data/lib/capybara/server/middleware.rb +25 -13
  117. data/lib/capybara/server.rb +20 -4
  118. data/lib/capybara/session/config.rb +15 -11
  119. data/lib/capybara/session/matchers.rb +11 -11
  120. data/lib/capybara/session.rb +162 -131
  121. data/lib/capybara/spec/public/offset.js +6 -0
  122. data/lib/capybara/spec/public/test.js +105 -6
  123. data/lib/capybara/spec/session/accept_alert_spec.rb +1 -1
  124. data/lib/capybara/spec/session/active_element_spec.rb +31 -0
  125. data/lib/capybara/spec/session/all_spec.rb +89 -15
  126. data/lib/capybara/spec/session/ancestor_spec.rb +5 -0
  127. data/lib/capybara/spec/session/assert_current_path_spec.rb +5 -2
  128. data/lib/capybara/spec/session/assert_text_spec.rb +26 -22
  129. data/lib/capybara/spec/session/attach_file_spec.rb +64 -31
  130. data/lib/capybara/spec/session/check_spec.rb +26 -4
  131. data/lib/capybara/spec/session/choose_spec.rb +14 -2
  132. data/lib/capybara/spec/session/click_button_spec.rb +109 -61
  133. data/lib/capybara/spec/session/click_link_or_button_spec.rb +9 -0
  134. data/lib/capybara/spec/session/click_link_spec.rb +23 -1
  135. data/lib/capybara/spec/session/current_scope_spec.rb +1 -1
  136. data/lib/capybara/spec/session/current_url_spec.rb +11 -1
  137. data/lib/capybara/spec/session/element/matches_selector_spec.rb +40 -39
  138. data/lib/capybara/spec/session/evaluate_script_spec.rb +12 -0
  139. data/lib/capybara/spec/session/fill_in_spec.rb +46 -5
  140. data/lib/capybara/spec/session/find_link_spec.rb +10 -0
  141. data/lib/capybara/spec/session/find_spec.rb +80 -7
  142. data/lib/capybara/spec/session/first_spec.rb +2 -2
  143. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +14 -1
  144. data/lib/capybara/spec/session/frame/within_frame_spec.rb +14 -1
  145. data/lib/capybara/spec/session/has_all_selectors_spec.rb +5 -5
  146. data/lib/capybara/spec/session/has_ancestor_spec.rb +46 -0
  147. data/lib/capybara/spec/session/has_any_selectors_spec.rb +6 -2
  148. data/lib/capybara/spec/session/has_button_spec.rb +81 -0
  149. data/lib/capybara/spec/session/has_css_spec.rb +45 -8
  150. data/lib/capybara/spec/session/has_current_path_spec.rb +22 -7
  151. data/lib/capybara/spec/session/has_element_spec.rb +47 -0
  152. data/lib/capybara/spec/session/has_field_spec.rb +59 -1
  153. data/lib/capybara/spec/session/has_link_spec.rb +40 -0
  154. data/lib/capybara/spec/session/has_none_selectors_spec.rb +7 -7
  155. data/lib/capybara/spec/session/has_select_spec.rb +42 -8
  156. data/lib/capybara/spec/session/has_selector_spec.rb +19 -4
  157. data/lib/capybara/spec/session/has_sibling_spec.rb +50 -0
  158. data/lib/capybara/spec/session/has_table_spec.rb +177 -0
  159. data/lib/capybara/spec/session/has_text_spec.rb +31 -3
  160. data/lib/capybara/spec/session/html_spec.rb +1 -1
  161. data/lib/capybara/spec/session/matches_style_spec.rb +6 -4
  162. data/lib/capybara/spec/session/node_spec.rb +697 -23
  163. data/lib/capybara/spec/session/node_wrapper_spec.rb +1 -1
  164. data/lib/capybara/spec/session/refresh_spec.rb +2 -1
  165. data/lib/capybara/spec/session/reset_session_spec.rb +21 -7
  166. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +2 -2
  167. data/lib/capybara/spec/session/save_page_spec.rb +4 -4
  168. data/lib/capybara/spec/session/save_screenshot_spec.rb +4 -4
  169. data/lib/capybara/spec/session/scroll_spec.rb +9 -7
  170. data/lib/capybara/spec/session/select_spec.rb +5 -10
  171. data/lib/capybara/spec/session/selectors_spec.rb +24 -3
  172. data/lib/capybara/spec/session/uncheck_spec.rb +3 -3
  173. data/lib/capybara/spec/session/unselect_spec.rb +1 -1
  174. data/lib/capybara/spec/session/visit_spec.rb +20 -0
  175. data/lib/capybara/spec/session/window/become_closed_spec.rb +20 -17
  176. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +1 -1
  177. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +1 -1
  178. data/lib/capybara/spec/session/window/window_spec.rb +54 -57
  179. data/lib/capybara/spec/session/window/windows_spec.rb +2 -2
  180. data/lib/capybara/spec/session/within_spec.rb +36 -0
  181. data/lib/capybara/spec/spec_helper.rb +30 -19
  182. data/lib/capybara/spec/test_app.rb +122 -34
  183. data/lib/capybara/spec/views/animated.erb +49 -0
  184. data/lib/capybara/spec/views/form.erb +86 -8
  185. data/lib/capybara/spec/views/frame_child.erb +3 -2
  186. data/lib/capybara/spec/views/frame_one.erb +2 -1
  187. data/lib/capybara/spec/views/frame_parent.erb +1 -1
  188. data/lib/capybara/spec/views/frame_two.erb +1 -1
  189. data/lib/capybara/spec/views/initial_alert.erb +2 -1
  190. data/lib/capybara/spec/views/layout.erb +10 -0
  191. data/lib/capybara/spec/views/obscured.erb +10 -10
  192. data/lib/capybara/spec/views/offset.erb +33 -0
  193. data/lib/capybara/spec/views/path.erb +2 -2
  194. data/lib/capybara/spec/views/popup_one.erb +1 -1
  195. data/lib/capybara/spec/views/popup_two.erb +1 -1
  196. data/lib/capybara/spec/views/react.erb +45 -0
  197. data/lib/capybara/spec/views/scroll.erb +2 -1
  198. data/lib/capybara/spec/views/spatial.erb +31 -0
  199. data/lib/capybara/spec/views/tables.erb +67 -0
  200. data/lib/capybara/spec/views/with_animation.erb +39 -4
  201. data/lib/capybara/spec/views/with_base_tag.erb +2 -2
  202. data/lib/capybara/spec/views/with_dragula.erb +24 -0
  203. data/lib/capybara/spec/views/with_fixed_header_footer.erb +2 -1
  204. data/lib/capybara/spec/views/with_hover.erb +3 -2
  205. data/lib/capybara/spec/views/with_hover1.erb +10 -0
  206. data/lib/capybara/spec/views/with_html.erb +34 -6
  207. data/lib/capybara/spec/views/with_jquery_animation.erb +24 -0
  208. data/lib/capybara/spec/views/with_js.erb +7 -4
  209. data/lib/capybara/spec/views/with_jstree.erb +26 -0
  210. data/lib/capybara/spec/views/with_namespace.erb +1 -0
  211. data/lib/capybara/spec/views/with_scope.erb +2 -2
  212. data/lib/capybara/spec/views/with_scope_other.erb +6 -0
  213. data/lib/capybara/spec/views/with_shadow.erb +31 -0
  214. data/lib/capybara/spec/views/with_slow_unload.erb +2 -1
  215. data/lib/capybara/spec/views/with_sortable_js.erb +21 -0
  216. data/lib/capybara/spec/views/with_unload_alert.erb +1 -0
  217. data/lib/capybara/spec/views/with_windows.erb +1 -1
  218. data/lib/capybara/spec/views/within_frames.erb +1 -1
  219. data/lib/capybara/version.rb +1 -1
  220. data/lib/capybara/window.rb +14 -18
  221. data/lib/capybara.rb +91 -126
  222. data/spec/basic_node_spec.rb +30 -16
  223. data/spec/capybara_spec.rb +40 -28
  224. data/spec/counter_spec.rb +35 -0
  225. data/spec/css_builder_spec.rb +3 -1
  226. data/spec/css_splitter_spec.rb +1 -1
  227. data/spec/dsl_spec.rb +33 -22
  228. data/spec/filter_set_spec.rb +5 -5
  229. data/spec/fixtures/selenium_driver_rspec_failure.rb +3 -3
  230. data/spec/fixtures/selenium_driver_rspec_success.rb +3 -3
  231. data/spec/minitest_spec.rb +24 -2
  232. data/spec/minitest_spec_spec.rb +60 -45
  233. data/spec/per_session_config_spec.rb +1 -1
  234. data/spec/rack_test_spec.rb +131 -98
  235. data/spec/regexp_dissassembler_spec.rb +53 -39
  236. data/spec/result_spec.rb +68 -66
  237. data/spec/rspec/features_spec.rb +9 -4
  238. data/spec/rspec/scenarios_spec.rb +6 -2
  239. data/spec/rspec/shared_spec_matchers.rb +137 -98
  240. data/spec/rspec_matchers_spec.rb +25 -0
  241. data/spec/rspec_spec.rb +23 -21
  242. data/spec/sauce_spec_chrome.rb +43 -0
  243. data/spec/selector_spec.rb +77 -21
  244. data/spec/selenium_spec_chrome.rb +141 -39
  245. data/spec/selenium_spec_chrome_remote.rb +32 -17
  246. data/spec/selenium_spec_edge.rb +36 -8
  247. data/spec/selenium_spec_firefox.rb +110 -68
  248. data/spec/selenium_spec_firefox_remote.rb +22 -15
  249. data/spec/selenium_spec_ie.rb +29 -22
  250. data/spec/selenium_spec_safari.rb +162 -0
  251. data/spec/server_spec.rb +153 -81
  252. data/spec/session_spec.rb +11 -4
  253. data/spec/shared_selenium_node.rb +79 -0
  254. data/spec/shared_selenium_session.rb +179 -74
  255. data/spec/spec_helper.rb +80 -5
  256. data/spec/whitespace_normalizer_spec.rb +54 -0
  257. data/spec/xpath_builder_spec.rb +3 -1
  258. metadata +218 -30
  259. data/lib/capybara/spec/session/source_spec.rb +0 -0
  260. data/lib/capybara/spec/views/with_title.erb +0 -5
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capybara
4
+ module RSpecMatchers
5
+ module SpatialSugar
6
+ def above(el)
7
+ options[:above] = el
8
+ self
9
+ end
10
+
11
+ def below(el)
12
+ options[:below] = el
13
+ self
14
+ end
15
+
16
+ def left_of(el)
17
+ options[:left_of] = el
18
+ self
19
+ end
20
+
21
+ def right_of(el)
22
+ options[:right_of] = el
23
+ self
24
+ end
25
+
26
+ def near(el)
27
+ options[:near] = el
28
+ self
29
+ end
30
+
31
+ private
32
+
33
+ def options
34
+ # (@args.last.is_a?(Hash) ? @args : @args.push({})).last
35
+ @kw_args
36
+ end
37
+ end
38
+ end
39
+ end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'capybara/rspec/matchers/have_selector'
4
+ require 'capybara/rspec/matchers/have_ancestor'
5
+ require 'capybara/rspec/matchers/have_sibling'
4
6
  require 'capybara/rspec/matchers/match_selector'
5
7
  require 'capybara/rspec/matchers/have_current_path'
6
8
  require 'capybara/rspec/matchers/match_style'
@@ -10,153 +12,194 @@ require 'capybara/rspec/matchers/become_closed'
10
12
 
11
13
  module Capybara
12
14
  module RSpecMatchers
13
- # RSpec matcher for whether the element(s) matching a given selector exist
14
- # See {Capybara::Node::Matcher#assert_selector}
15
- def have_selector(*args, &optional_filter_block)
16
- Matchers::HaveSelector.new(*args, &optional_filter_block)
15
+ # RSpec matcher for whether the element(s) matching a given selector exist.
16
+ #
17
+ # @see Capybara::Node::Matchers#assert_selector
18
+ def have_selector(...)
19
+ Matchers::HaveSelector.new(...)
17
20
  end
18
21
 
19
- # RSpec matcher for whether the element(s) matching a group of selectors exist
20
- # See {Capybara::Node::Matcher#assert_all_of_selectors}
21
- def have_all_of_selectors(*args, &optional_filter_block)
22
- Matchers::HaveAllSelectors.new(*args, &optional_filter_block)
22
+ # RSpec matcher for whether the element(s) matching a group of selectors exist.
23
+ #
24
+ # @see Capybara::Node::Matchers#assert_all_of_selectors
25
+ def have_all_of_selectors(...)
26
+ Matchers::HaveAllSelectors.new(...)
23
27
  end
24
28
 
25
- # RSpec matcher for whether no element(s) matching a group of selectors exist
26
- # See {Capybara::Node::Matcher#assert_none_of_selectors}
27
- def have_none_of_selectors(*args, &optional_filter_block)
28
- Matchers::HaveNoSelectors.new(*args, &optional_filter_block)
29
+ # RSpec matcher for whether no element(s) matching a group of selectors exist.
30
+ #
31
+ # @see Capybara::Node::Matchers#assert_none_of_selectors
32
+ def have_none_of_selectors(...)
33
+ Matchers::HaveNoSelectors.new(...)
29
34
  end
30
35
 
31
- # RSpec matcher for whether the element(s) matching any of a group of selectors exist
32
- # See {Capybara::Node::Matcher#assert_any_of_selectors}
33
- def have_any_of_selectors(*args, &optional_filter_block)
34
- Matchers::HaveAnySelectors.new(*args, &optional_filter_block)
36
+ # RSpec matcher for whether the element(s) matching any of a group of selectors exist.
37
+ #
38
+ # @see Capybara::Node::Matchers#assert_any_of_selectors
39
+ def have_any_of_selectors(...)
40
+ Matchers::HaveAnySelectors.new(...)
35
41
  end
36
42
 
37
- # RSpec matcher for whether the current element matches a given selector
38
- # See {Capybara::Node::Matchers#assert_matches_selector}
39
- def match_selector(*args, &optional_filter_block)
40
- Matchers::MatchSelector.new(*args, &optional_filter_block)
43
+ # RSpec matcher for whether the current element matches a given selector.
44
+ #
45
+ # @see Capybara::Node::Matchers#assert_matches_selector
46
+ def match_selector(...)
47
+ Matchers::MatchSelector.new(...)
41
48
  end
42
49
 
43
50
  %i[css xpath].each do |selector|
44
51
  define_method "have_#{selector}" do |expr, **options, &optional_filter_block|
45
- Matchers::HaveSelector.new(selector, expr, options, &optional_filter_block)
52
+ Matchers::HaveSelector.new(selector, expr, **options, &optional_filter_block)
46
53
  end
47
54
 
48
55
  define_method "match_#{selector}" do |expr, **options, &optional_filter_block|
49
- Matchers::MatchSelector.new(selector, expr, options, &optional_filter_block)
56
+ Matchers::MatchSelector.new(selector, expr, **options, &optional_filter_block)
50
57
  end
51
58
  end
52
59
 
53
60
  # @!method have_xpath(xpath, **options, &optional_filter_block)
54
- # RSpec matcher for whether elements(s) matching a given xpath selector exist
55
- # See {Capybara::Node::Matchers#has_xpath?}
61
+ # RSpec matcher for whether elements(s) matching a given xpath selector exist.
62
+ #
63
+ # @see Capybara::Node::Matchers#has_xpath?
56
64
 
57
65
  # @!method have_css(css, **options, &optional_filter_block)
58
66
  # RSpec matcher for whether elements(s) matching a given css selector exist
59
- # See {Capybara::Node::Matchers#has_css?}
67
+ #
68
+ # @see Capybara::Node::Matchers#has_css?
60
69
 
61
70
  # @!method match_xpath(xpath, **options, &optional_filter_block)
62
- # RSpec matcher for whether the current element matches a given xpath selector
63
- # See {Capybara::Node::Matchers#matches_xpath?}
71
+ # RSpec matcher for whether the current element matches a given xpath selector.
72
+ #
73
+ # @see Capybara::Node::Matchers#matches_xpath?
64
74
 
65
75
  # @!method match_css(css, **options, &optional_filter_block)
66
- # RSpec matcher for whether the current element matches a given css selector
67
- # See {Capybara::Node::Matchers#matches_css?}
76
+ # RSpec matcher for whether the current element matches a given css selector.
77
+ #
78
+ # @see Capybara::Node::Matchers#matches_css?
68
79
 
69
- %i[link button field select table].each do |selector|
80
+ %i[link button field select table element].each do |selector|
70
81
  define_method "have_#{selector}" do |locator = nil, **options, &optional_filter_block|
71
- Matchers::HaveSelector.new(selector, locator, options, &optional_filter_block)
82
+ Matchers::HaveSelector.new(selector, locator, **options, &optional_filter_block)
72
83
  end
73
84
  end
74
85
 
86
+ # @!method have_element(locator = nil, **options, &optional_filter_block)
87
+ # RSpec matcher for elements.
88
+ #
89
+ # @see Capybara::Node::Matchers#has_element?
90
+
75
91
  # @!method have_link(locator = nil, **options, &optional_filter_block)
76
- # RSpec matcher for links
77
- # See {Capybara::Node::Matchers#has_link?}
92
+ # RSpec matcher for links.
93
+ #
94
+ # @see Capybara::Node::Matchers#has_link?
78
95
 
79
96
  # @!method have_button(locator = nil, **options, &optional_filter_block)
80
- # RSpec matcher for buttons
81
- # See {Capybara::Node::Matchers#has_button?}
97
+ # RSpec matcher for buttons.
98
+ #
99
+ # @see Capybara::Node::Matchers#has_button?
82
100
 
83
101
  # @!method have_field(locator = nil, **options, &optional_filter_block)
84
- # RSpec matcher for links
85
- # See {Capybara::Node::Matchers#has_field?}
102
+ # RSpec matcher for form fields.
103
+ #
104
+ # @see Capybara::Node::Matchers#has_field?
86
105
 
87
106
  # @!method have_select(locator = nil, **options, &optional_filter_block)
88
- # RSpec matcher for select elements
89
- # See {Capybara::Node::Matchers#has_select?}
107
+ # RSpec matcher for select elements.
108
+ #
109
+ # @see Capybara::Node::Matchers#has_select?
90
110
 
91
111
  # @!method have_table(locator = nil, **options, &optional_filter_block)
92
- # RSpec matcher for table elements
93
- # See {Capybara::Node::Matchers#has_table?}
112
+ # RSpec matcher for table elements.
113
+ #
114
+ # @see Capybara::Node::Matchers#has_table?
94
115
 
95
116
  %i[checked unchecked].each do |state|
96
117
  define_method "have_#{state}_field" do |locator = nil, **options, &optional_filter_block|
97
- Matchers::HaveSelector.new(:field, locator, options.merge(state => true), &optional_filter_block)
118
+ Matchers::HaveSelector.new(:field, locator, **options.merge(state => true), &optional_filter_block)
98
119
  end
99
120
  end
100
121
 
101
122
  # @!method have_checked_field(locator = nil, **options, &optional_filter_block)
102
- # RSpec matcher for checked fields
103
- # See {Capybara::Node::Matchers#has_checked_field?}
123
+ # RSpec matcher for checked fields.
124
+ #
125
+ # @see Capybara::Node::Matchers#has_checked_field?
104
126
 
105
127
  # @!method have_unchecked_field(locator = nil, **options, &optional_filter_block)
106
- # RSpec matcher for unchecked fields
107
- # See {Capybara::Node::Matchers#has_unchecked_field?}
128
+ # RSpec matcher for unchecked fields.
129
+ #
130
+ # @see Capybara::Node::Matchers#has_unchecked_field?
108
131
 
109
- # RSpec matcher for text content
110
- # See {Capybara::SessionMatchers#assert_text}
111
- def have_text(*args)
112
- Matchers::HaveText.new(*args)
132
+ # RSpec matcher for text content.
133
+ #
134
+ # @see Capybara::Node::Matchers#assert_text
135
+ def have_text(text_or_type, *args, **options)
136
+ Matchers::HaveText.new(text_or_type, *args, **options)
113
137
  end
114
138
  alias_method :have_content, :have_text
115
139
 
116
140
  def have_title(title, **options)
117
- Matchers::HaveTitle.new(title, options)
141
+ Matchers::HaveTitle.new(title, **options)
118
142
  end
119
143
 
120
- # RSpec matcher for the current path
121
- # See {Capybara::SessionMatchers#assert_current_path}
122
- def have_current_path(path, **options)
123
- Matchers::HaveCurrentPath.new(path, options)
144
+ # RSpec matcher for the current path.
145
+ #
146
+ # @see Capybara::SessionMatchers#assert_current_path
147
+ def have_current_path(path, **options, &optional_filter_block)
148
+ Matchers::HaveCurrentPath.new(path, **options, &optional_filter_block)
124
149
  end
125
150
 
126
- # RSpec matcher for element style
127
- # See {Capybara::Node::Matchers#matches_style?}
128
- def match_style(styles, **options)
129
- Matchers::MatchStyle.new(styles, options)
151
+ # RSpec matcher for element style.
152
+ #
153
+ # @see Capybara::Node::Matchers#matches_style?
154
+ def match_style(styles = nil, **options)
155
+ styles, options = options, {} if styles.nil?
156
+ Matchers::MatchStyle.new(styles, **options)
130
157
  end
131
158
 
132
159
  ##
133
160
  # @deprecated
134
161
  #
135
- def have_style(styles, **options)
136
- warn 'DEPRECATED: have_style is deprecated, please use match_style'
162
+ def have_style(styles = nil, **options)
163
+ Capybara::Helpers.warn "DEPRECATED: have_style is deprecated, please use match_style : #{Capybara::Helpers.filter_backtrace(caller)}"
137
164
  match_style(styles, **options)
138
165
  end
139
166
 
140
167
  %w[selector css xpath text title current_path link button
141
- field checked_field unchecked_field select table].each do |matcher_type|
142
- define_method "have_no_#{matcher_type}" do |*args, &optional_filter_block|
143
- Matchers::NegatedMatcher.new(send("have_#{matcher_type}", *args, &optional_filter_block))
168
+ field checked_field unchecked_field select table
169
+ sibling ancestor element].each do |matcher_type|
170
+ define_method "have_no_#{matcher_type}" do |*args, **kw_args, &optional_filter_block|
171
+ Matchers::NegatedMatcher.new(send("have_#{matcher_type}", *args, **kw_args, &optional_filter_block))
144
172
  end
145
173
  end
146
174
  alias_method :have_no_content, :have_no_text
147
175
 
148
176
  %w[selector css xpath].each do |matcher_type|
149
- define_method "not_match_#{matcher_type}" do |*args, &optional_filter_block|
150
- Matchers::NegatedMatcher.new(send("match_#{matcher_type}", *args, &optional_filter_block))
177
+ define_method "not_match_#{matcher_type}" do |*args, **kw_args, &optional_filter_block|
178
+ Matchers::NegatedMatcher.new(send("match_#{matcher_type}", *args, **kw_args, &optional_filter_block))
151
179
  end
152
180
  end
153
181
 
182
+ # RSpec matcher for whether sibling element(s) matching a given selector exist.
183
+ #
184
+ # @see Capybara::Node::Matchers#assert_sibling
185
+ def have_sibling(...)
186
+ Matchers::HaveSibling.new(...)
187
+ end
188
+
189
+ # RSpec matcher for whether ancestor element(s) matching a given selector exist.
190
+ #
191
+ # @see Capybara::Node::Matchers#assert_ancestor
192
+ def have_ancestor(...)
193
+ Matchers::HaveAncestor.new(...)
194
+ end
195
+
154
196
  ##
155
197
  # Wait for window to become closed.
198
+ #
156
199
  # @example
157
200
  # expect(window).to become_closed(wait: 0.8)
158
- # @param options [Hash] optional param
159
- # @option options [Numeric] :wait (Capybara.default_max_wait_time) Maximum wait time
201
+ #
202
+ # @option options [Numeric] :wait Maximum wait time. Defaults to {Capybara.configure default_max_wait_time}
160
203
  def become_closed(**options)
161
204
  Matchers::BecomeClosed.new(options)
162
205
  end
@@ -9,6 +9,8 @@ require 'capybara/rspec/matcher_proxies'
9
9
  RSpec.configure do |config|
10
10
  config.include Capybara::DSL, type: :feature
11
11
  config.include Capybara::RSpecMatchers, type: :feature
12
+ config.include Capybara::DSL, type: :system
13
+ config.include Capybara::RSpecMatchers, type: :system
12
14
  config.include Capybara::RSpecMatchers, type: :view
13
15
 
14
16
  # The before and after blocks must run instantaneously, because Capybara
@@ -17,11 +17,7 @@ module Capybara
17
17
  conditions = if name == :class
18
18
  class_conditions(value)
19
19
  elsif value.is_a? Regexp
20
- Selector::RegexpDisassembler.new(value).alternated_substrings.map do |strs|
21
- strs.map do |str|
22
- "[#{name}*='#{str}'#{' i' if value.casefold?}]"
23
- end.join
24
- end
20
+ regexp_conditions(name, value)
25
21
  else
26
22
  [attribute_conditions(name => value)]
27
23
  end
@@ -36,6 +32,14 @@ module Capybara
36
32
 
37
33
  private
38
34
 
35
+ def regexp_conditions(name, value)
36
+ Selector::RegexpDisassembler.new(value).alternated_substrings.map do |strs|
37
+ strs.map do |str|
38
+ "[#{name}*='#{str}'#{' i' if value.casefold?}]"
39
+ end.join
40
+ end
41
+ end
42
+
39
43
  def attribute_conditions(attributes)
40
44
  attributes.map do |attribute, value|
41
45
  case value
@@ -70,9 +74,9 @@ module Capybara
70
74
  end.join
71
75
  end
72
76
  else
73
- cls = Array(classes).group_by { |cl| cl.start_with?('!') && !cl.start_with?('!!!') }
77
+ cls = Array(classes).reject { |c| c.is_a? Regexp }.group_by { |cl| cl.match?(/^!(?!!!)/) }
74
78
  [(cls[false].to_a.map { |cl| ".#{Capybara::Selector::CSS.escape(cl.sub(/^!!/, ''))}" } +
75
- cls[true].to_a.map { |cl| ":not(.#{Capybara::Selector::CSS.escape(cl.slice(1..-1))})" }).join]
79
+ cls[true].to_a.map { |cl| ":not(.#{Capybara::Selector::CSS.escape(cl.slice(1..))})" }).join]
76
80
  end
77
81
  end
78
82
  end
@@ -15,6 +15,8 @@ module Capybara
15
15
  def add_attribute_conditions(**conditions)
16
16
  @expression = conditions.inject(expression) do |xp, (name, value)|
17
17
  conditions = name == :class ? class_conditions(value) : attribute_conditions(name => value)
18
+ return xp if conditions.nil?
19
+
18
20
  if xp.is_a? XPath::Expression
19
21
  xp[conditions]
20
22
  else
@@ -47,9 +49,9 @@ module Capybara
47
49
  when XPath::Expression, Regexp
48
50
  attribute_conditions(class: classes)
49
51
  else
50
- Array(classes).map do |klass|
51
- if klass.start_with?('!') && !klass.start_with?('!!!')
52
- !XPath.attr(:class).contains_word(klass.slice(1..-1))
52
+ Array(classes).reject { |c| c.is_a? Regexp }.map do |klass|
53
+ if klass.match?(/^!(?!!!)/)
54
+ !XPath.attr(:class).contains_word(klass.slice(1..))
53
55
  else
54
56
  XPath.attr(:class).contains_word(klass.sub(/^!!/, ''))
55
57
  end
@@ -1,19 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'capybara/selector/selector'
4
+
3
5
  module Capybara
4
6
  class Selector
5
7
  class CSS
6
8
  def self.escape(str)
7
9
  value = str.dup
8
10
  out = +''
9
- out << value.slice!(0...1) if value =~ /^[-_]/
10
- out << (value[0] =~ NMSTART ? value.slice!(0...1) : escape_char(value.slice!(0...1)))
11
+ out << value.slice!(0...1) if value.match?(/^[-_]/)
12
+ out << (value[0].match?(NMSTART) ? value.slice!(0...1) : escape_char(value.slice!(0...1)))
11
13
  out << value.gsub(/[^a-zA-Z0-9_-]/) { |char| escape_char char }
12
14
  out
13
15
  end
14
16
 
15
17
  def self.escape_char(char)
16
- char =~ %r{[ -/:-~]} ? "\\#{char}" : format('\\%06x', char.ord)
18
+ char.match?(%r{[ -/:-~]}) ? "\\#{char}" : format('\\%06<hex>x', hex: char.ord)
17
19
  end
18
20
 
19
21
  def self.split(css)
@@ -21,11 +23,11 @@ module Capybara
21
23
  end
22
24
 
23
25
  S = '\u{80}-\u{D7FF}\u{E000}-\u{FFFD}\u{10000}-\u{10FFFF}'
24
- H = /[0-9a-fA-F]/.freeze
25
- UNICODE = /\\#{H}{1,6}[ \t\r\n\f]?/.freeze
26
- NONASCII = /[#{S}]/.freeze
27
- ESCAPE = /#{UNICODE}|\\[ -~#{S}]/.freeze
28
- NMSTART = /[_a-zA-Z]|#{NONASCII}|#{ESCAPE}/.freeze
26
+ H = /[0-9a-fA-F]/
27
+ UNICODE = /\\#{H}{1,6}[ \t\r\n\f]?/
28
+ NONASCII = /[#{S}]/
29
+ ESCAPE = /#{UNICODE}|\\[ -~#{S}]/
30
+ NMSTART = /[_a-zA-Z]|#{NONASCII}|#{ESCAPE}/
29
31
 
30
32
  class Splitter
31
33
  def split(css)
@@ -41,7 +43,7 @@ module Capybara
41
43
  when '"', "'"
42
44
  selector << parse_string(char, str)
43
45
  when '\\'
44
- selector << char + str.getc
46
+ selector << (char + str.getc)
45
47
  when ','
46
48
  selectors << selector.strip
47
49
  selector.clear
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ Capybara.add_selector(:button, locator_type: [String, Symbol]) do
4
+ xpath(:value, :title, :type, :name) do |locator, **options|
5
+ input_btn_xpath = XPath.descendant(:input)[XPath.attr(:type).one_of('submit', 'reset', 'image', 'button')]
6
+ btn_xpath = XPath.descendant(:button)
7
+ aria_btn_xpath = XPath.descendant[XPath.attr(:role).equals('button')]
8
+ image_btn_xpath = XPath.descendant(:input)[XPath.attr(:type) == 'image']
9
+
10
+ unless locator.nil?
11
+ locator = locator.to_s
12
+ locator_matchers = combine_locators(locator, config: self)
13
+ btn_matchers = locator_matchers |
14
+ XPath.string.n.is(locator) |
15
+ XPath.descendant(:img)[XPath.attr(:alt).is(locator)]
16
+
17
+ label_contains_xpath = locate_label(locator).descendant[labellable_elements]
18
+ input_btn_xpath = input_btn_xpath[locator_matchers]
19
+ btn_xpath = btn_xpath[btn_matchers]
20
+ aria_btn_xpath = aria_btn_xpath[btn_matchers]
21
+
22
+ alt_matches = XPath.attr(:alt).is(locator)
23
+ alt_matches |= XPath.attr(:'aria-label').is(locator) if enable_aria_label
24
+ image_btn_xpath = image_btn_xpath[alt_matches]
25
+ end
26
+
27
+ btn_xpaths = [input_btn_xpath, btn_xpath, image_btn_xpath, label_contains_xpath].compact
28
+ btn_xpaths << aria_btn_xpath if enable_aria_role
29
+
30
+ %i[value title type].inject(btn_xpaths.inject(&:union)) do |memo, ef|
31
+ memo.where(find_by_attr(ef, options[ef]))
32
+ end
33
+ end
34
+
35
+ node_filter(:disabled, :boolean, default: false, skip_if: :all) { |node, value| !(value ^ node.disabled?) }
36
+ expression_filter(:disabled) { |xpath, val| val ? xpath : xpath[~XPath.attr(:disabled)] }
37
+
38
+ node_filter(:name) { |node, value| !value.is_a?(Regexp) || value.match?(node[:name]) }
39
+ expression_filter(:name) do |xpath, val|
40
+ builder(xpath).add_attribute_conditions(name: val)
41
+ end
42
+
43
+ describe_expression_filters do |disabled: nil, **options|
44
+ desc = +''
45
+ desc << ' that is not disabled' if disabled == false
46
+ desc << describe_all_expression_filters(**options)
47
+ end
48
+
49
+ describe_node_filters do |disabled: nil, **|
50
+ ' that is disabled' if disabled == true
51
+ end
52
+
53
+ def combine_locators(locator, config:)
54
+ [
55
+ XPath.attr(:id).equals(locator),
56
+ XPath.attr(:name).equals(locator),
57
+ XPath.attr(:value).is(locator),
58
+ XPath.attr(:title).is(locator),
59
+ (XPath.attr(:id) == XPath.anywhere(:label)[XPath.string.n.is(locator)].attr(:for)),
60
+ (XPath.attr(:'aria-label').is(locator) if config.enable_aria_label),
61
+ (XPath.attr(config.test_id) == locator if config.test_id)
62
+ ].compact.inject(&:|)
63
+ end
64
+
65
+ def labellable_elements
66
+ (XPath.self(:input) & XPath.attr(:type).one_of('submit', 'reset', 'image', 'button')) | XPath.self(:button)
67
+ end
68
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ Capybara.add_selector(:checkbox, locator_type: [String, Symbol]) do
4
+ xpath do |locator, allow_self: nil, **options|
5
+ xpath = XPath.axis(allow_self ? :'descendant-or-self' : :descendant, :input)[
6
+ XPath.attr(:type) == 'checkbox'
7
+ ]
8
+ locate_field(xpath, locator, **options)
9
+ end
10
+
11
+ filter_set(:_field, %i[checked unchecked disabled name])
12
+
13
+ node_filter(%i[option with]) do |node, value|
14
+ val = node.value
15
+ (value.is_a?(Regexp) ? value.match?(val) : val == value.to_s).tap do |res|
16
+ add_error("Expected value to be #{value.inspect} but it was #{val.inspect}") unless res
17
+ end
18
+ end
19
+
20
+ describe_node_filters do |option: nil, with: nil, **|
21
+ desc = +''
22
+ desc << " with value #{option.inspect}" if option
23
+ desc << " with value #{with.inspect}" if with
24
+ desc
25
+ end
26
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ Capybara.add_selector(:css, locator_type: [String, Symbol], raw_locator: true) do
4
+ css do |css|
5
+ if css.is_a? Symbol
6
+ Capybara::Helpers.warn "DEPRECATED: Passing a symbol (#{css.inspect}) as the CSS locator is deprecated - please pass a string instead : #{Capybara::Helpers.filter_backtrace(caller)}"
7
+ end
8
+ css
9
+ end
10
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ Capybara.add_selector(:datalist_input, locator_type: [String, Symbol]) do
4
+ label 'input box with datalist completion'
5
+
6
+ xpath do |locator, **options|
7
+ xpath = XPath.descendant(:input)[XPath.attr(:list)]
8
+ locate_field(xpath, locator, **options)
9
+ end
10
+
11
+ filter_set(:_field, %i[disabled name placeholder])
12
+
13
+ node_filter(:options) do |node, options|
14
+ actual = node.find("//datalist[@id=#{node[:list]}]", visible: :all).all(:datalist_option, wait: false).map(&:value)
15
+ (options.sort == actual.sort).tap do |res|
16
+ add_error("Expected #{options.inspect} options found #{actual.inspect}") unless res
17
+ end
18
+ end
19
+
20
+ expression_filter(:with_options) do |expr, options|
21
+ options.inject(expr) do |xpath, option|
22
+ xpath.where(XPath.attr(:list) == XPath.anywhere(:datalist)[expression_for(:datalist_option, option)].attr(:id))
23
+ end
24
+ end
25
+
26
+ describe_expression_filters do |with_options: nil, **|
27
+ desc = +''
28
+ desc << " with at least options #{with_options.inspect}" if with_options
29
+ desc
30
+ end
31
+
32
+ describe_node_filters do |options: nil, **|
33
+ " with options #{options.inspect}" if options
34
+ end
35
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ Capybara.add_selector(:datalist_option, locator_type: [String, Symbol]) do
4
+ label 'datalist option'
5
+ visible(:all)
6
+
7
+ xpath do |locator|
8
+ xpath = XPath.descendant(:option)
9
+ xpath = xpath[XPath.string.n.is(locator.to_s) | (XPath.attr(:value) == locator.to_s)] unless locator.nil?
10
+ xpath
11
+ end
12
+
13
+ node_filter(:disabled, :boolean) { |node, value| !(value ^ node.disabled?) }
14
+ expression_filter(:disabled) { |xpath, val| val ? xpath : xpath[~XPath.attr(:disabled)] }
15
+
16
+ describe_expression_filters do |disabled: nil, **options|
17
+ desc = +''
18
+ desc << ' that is not disabled' if disabled == false
19
+ desc << describe_all_expression_filters(**options)
20
+ end
21
+
22
+ describe_node_filters do |**options|
23
+ ' that is disabled' if options[:disabled]
24
+ end
25
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ Capybara.add_selector(:element, locator_type: [String, Symbol]) do
4
+ xpath do |locator, **|
5
+ XPath.descendant.where(locator ? XPath.local_name == locator.to_s : nil)
6
+ end
7
+
8
+ expression_filter(:attributes, matcher: /.+/) do |xpath, name, val|
9
+ builder(xpath).add_attribute_conditions(name => val)
10
+ end
11
+
12
+ node_filter(:attributes, matcher: /.+/) do |node, name, val|
13
+ next true unless val.is_a?(Regexp)
14
+
15
+ (val.match? node[name]).tap do |res|
16
+ add_error("Expected #{name} to match #{val.inspect} but it was #{node[name]}") unless res
17
+ end
18
+ end
19
+
20
+ describe_expression_filters do |**options|
21
+ boolean_values = [true, false]
22
+ booleans, values = options.partition { |_k, v| boolean_values.include? v }.map(&:to_h)
23
+ desc = describe_all_expression_filters(**values)
24
+ desc + booleans.map do |k, v|
25
+ v ? " with #{k} attribute" : "without #{k} attribute"
26
+ end.join
27
+ end
28
+ end