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
@@ -1,257 +1,142 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'capybara/selector/filter_set'
4
- require 'capybara/selector/css'
5
- require 'xpath'
6
-
7
- # Patch XPath to allow a nil condition in where
8
- module XPath
9
- class Renderer
10
- undef :where if method_defined?(:where)
11
- def where(on, condition)
12
- condition = condition.to_s
13
- if !condition.empty?
14
- "#{on}[#{condition}]"
15
- else
16
- on.to_s
17
- end
18
- end
19
- end
20
- end
21
-
22
3
  module Capybara
23
- class Selector
24
- attr_reader :name, :format
25
-
4
+ class Selector < SimpleDelegator
26
5
  class << self
27
6
  def all
28
- @selectors ||= {} # rubocop:disable Naming/MemoizedInstanceVariableName
7
+ @definitions ||= {} # rubocop:disable Naming/MemoizedInstanceVariableName
29
8
  end
30
9
 
31
- def add(name, &block)
32
- all[name.to_sym] = Capybara::Selector.new(name.to_sym, &block)
10
+ def [](name)
11
+ all.fetch(name.to_sym) { |sel_type| raise ArgumentError, "Unknown selector type (:#{sel_type})" }
12
+ end
13
+
14
+ def add(name, **options, &block)
15
+ all[name.to_sym] = Definition.new(name.to_sym, **options, &block)
33
16
  end
34
17
 
35
18
  def update(name, &block)
36
- all[name.to_sym].instance_eval(&block)
19
+ self[name].instance_eval(&block)
37
20
  end
38
21
 
39
22
  def remove(name)
40
23
  all.delete(name.to_sym)
41
24
  end
42
- end
43
-
44
- def initialize(name, &block)
45
- @name = name
46
- @filter_set = FilterSet.add(name) {}
47
- @match = nil
48
- @label = nil
49
- @failure_message = nil
50
- @description = nil
51
- @format = nil
52
- @expression = nil
53
- @expression_filters = {}
54
- @default_visibility = nil
55
- instance_eval(&block)
56
- end
57
25
 
58
- def custom_filters
59
- @filter_set.filters
26
+ def for(locator)
27
+ all.values.find { |sel| sel.match?(locator) }
28
+ end
60
29
  end
61
30
 
62
- def node_filters
63
- @filter_set.node_filters
64
- end
31
+ attr_reader :errors
65
32
 
66
- def expression_filters
67
- @filter_set.expression_filters
33
+ def initialize(definition, config:, format:)
34
+ definition = self.class[definition] unless definition.is_a? Definition
35
+ super(definition)
36
+ @definition = definition
37
+ @config = config
38
+ @format = format
39
+ @errors = []
68
40
  end
69
41
 
70
- ##
71
- #
72
- # Define a selector by an xpath expression
73
- #
74
- # @overload xpath(*expression_filters, &block)
75
- # @param [Array<Symbol>] expression_filters ([]) Names of filters that can be implemented via this expression
76
- # @yield [locator, options] The block to use to generate the XPath expression
77
- # @yieldparam [String] locator The locator string passed to the query
78
- # @yieldparam [Hash] options The options hash passed to the query
79
- # @yieldreturn [#to_xpath, #to_s] An object that can produce an xpath expression
80
- #
81
- # @overload xpath()
82
- # @return [#call] The block that will be called to generate the XPath expression
83
- #
84
- def xpath(*expression_filters, &block)
85
- if block
86
- @format, @expression = :xpath, block
87
- expression_filters.flatten.each { |ef| custom_filters[ef] = Filters::IdentityExpressionFilter.new }
88
- end
89
- format == :xpath ? @expression : nil
42
+ def format
43
+ @format || @definition.default_format
90
44
  end
45
+ alias_method :current_format, :format
91
46
 
92
- ##
93
- #
94
- # Define a selector by a CSS selector
95
- #
96
- # @overload css(*expression_filters, &block)
97
- # @param [Array<Symbol>] expression_filters ([]) Names of filters that can be implemented via this CSS selector
98
- # @yield [locator, options] The block to use to generate the CSS selector
99
- # @yieldparam [String] locator The locator string passed to the query
100
- # @yieldparam [Hash] options The options hash passed to the query
101
- # @yieldreturn [#to_s] An object that can produce a CSS selector
102
- #
103
- # @overload css()
104
- # @return [#call] The block that will be called to generate the CSS selector
105
- #
106
- def css(*expression_filters, &block)
107
- if block
108
- @format, @expression = :css, block
109
- expression_filters.flatten.each { |ef| custom_filters[ef] = nil }
110
- end
111
- format == :css ? @expression : nil
47
+ def enable_aria_label
48
+ @config[:enable_aria_label]
112
49
  end
113
50
 
114
- ##
115
- #
116
- # Automatic selector detection
117
- #
118
- # @yield [locator] This block takes the passed in locator string and returns whether or not it matches the selector
119
- # @yieldparam [String], locator The locator string used to determin if it matches the selector
120
- # @yieldreturn [Boolean] Whether this selector matches the locator string
121
- # @return [#call] The block that will be used to detect selector match
122
- #
123
- def match(&block)
124
- @match = block if block
125
- @match
51
+ def enable_aria_role
52
+ @config[:enable_aria_role]
126
53
  end
127
54
 
128
- ##
129
- #
130
- # Set/get a descriptive label for the selector
131
- #
132
- # @overload label(label)
133
- # @param [String] label A descriptive label for this selector - used in error messages
134
- # @overload label()
135
- # @return [String] The currently set label
136
- #
137
- def label(label = nil)
138
- @label = label if label
139
- @label
140
- end
141
-
142
- ##
143
- #
144
- # Description of the selector
145
- #
146
- # @param [Hash] options The options of the query used to generate the description
147
- # @return [String] Description of the selector when used with the options passed
148
- #
149
- def description(**options)
150
- @filter_set.description(options)
55
+ def test_id
56
+ @config[:test_id]
151
57
  end
152
58
 
153
59
  def call(locator, **options)
154
60
  if format
155
- @expression.call(locator, options)
61
+ raise ArgumentError, "Selector #{@name} does not support #{format}" unless expressions.key?(format)
62
+
63
+ instance_exec(locator, **options, &expressions[format])
156
64
  else
157
- warn "Selector has no format"
65
+ warn 'Selector has no format'
66
+ end
67
+ ensure
68
+ unless locator_valid?(locator)
69
+ warn "Locator #{locator.class}:#{locator.inspect} for selector #{name.inspect} must #{locator_description}. This will raise an error in a future version of Capybara."
158
70
  end
159
71
  end
160
72
 
161
- ##
162
- #
163
- # Should this selector be used for the passed in locator
164
- #
165
- # This is used by the automatic selector selection mechanism when no selector type is passed to a selector query
166
- #
167
- # @param [String] locator The locator passed to the query
168
- # @return [Boolean] Whether or not to use this selector
169
- #
170
- def match?(locator)
171
- @match and @match.call(locator)
73
+ def add_error(error_msg)
74
+ errors << error_msg
172
75
  end
173
76
 
174
- ##
175
- #
176
- # Define a non-expression filter for use with this selector
177
- #
178
- # @overload filter(name, *types, options={}, &block)
179
- # @param [Symbol] name The filter name
180
- # @param [Array<Symbol>] types The types of the filter - currently valid types are [:boolean]
181
- # @param [Hash] options ({}) Options of the filter
182
- # @option options [Array<>] :valid_values Valid values for this filter
183
- # @option options :default The default value of the filter (if any)
184
- # @option options :skip_if Value of the filter that will cause it to be skipped
185
- #
186
- def filter(name, *types_and_options, &block)
187
- add_filter(name, Filters::NodeFilter, *types_and_options, &block)
77
+ def expression_for(name, locator, config: @config, format: current_format, **options)
78
+ Selector.new(name, config: config, format: format).call(locator, **options)
188
79
  end
189
80
 
190
- def expression_filter(name, *types_and_options, &block)
191
- add_filter(name, Filters::ExpressionFilter, *types_and_options, &block)
81
+ # @api private
82
+ def with_filter_errors(errors)
83
+ old_errors = @errors
84
+ @errors = errors
85
+ yield
86
+ ensure
87
+ @errors = old_errors
192
88
  end
193
89
 
194
- def filter_set(name, filters_to_use = nil)
195
- f_set = FilterSet.all[name]
196
- f_set.filters.each do |n, filter|
197
- custom_filters[n] = filter if filters_to_use.nil? || filters_to_use.include?(n)
198
- end
199
-
200
- f_set.descriptions.each { |desc| @filter_set.describe(&desc) }
90
+ # @api private
91
+ def builder(expr = nil)
92
+ case format
93
+ when :css
94
+ Capybara::Selector::CSSBuilder
95
+ when :xpath
96
+ Capybara::Selector::XPathBuilder
97
+ else
98
+ raise NotImplementedError, "No builder exists for selector of type #{default_format}"
99
+ end.new(expr)
201
100
  end
202
101
 
203
- def describe(&block)
204
- @filter_set.describe(&block)
205
- end
102
+ private
206
103
 
207
- ##
208
- #
209
- # Set the default visibility mode that shouble be used if no visibile option is passed when using the selector.
210
- # If not specified will default to the behavior indicated by Capybara.ignore_hidden_elements
211
- #
212
- # @param [Symbol] default_visibility Only find elements with the specified visibility:
213
- # * :all - finds visible and invisible elements.
214
- # * :hidden - only finds invisible elements.
215
- # * :visible - only finds visible elements.
216
- def visible(default_visibility)
217
- @default_visibility = default_visibility
104
+ def locator_description
105
+ locator_types.group_by { |lt| lt.is_a? Symbol }.map do |symbol, types_or_methods|
106
+ if symbol
107
+ "respond to #{types_or_methods.join(' or ')}"
108
+ else
109
+ "be an instance of #{types_or_methods.join(' or ')}"
110
+ end
111
+ end.join(' or ')
218
112
  end
219
113
 
220
- def default_visibility(fallback = Capybara.ignore_hidden_elements)
221
- if @default_visibility.nil?
222
- fallback
223
- else
224
- @default_visibility
114
+ def locator_valid?(locator)
115
+ return true unless locator && locator_types
116
+
117
+ locator_types&.any? do |type_or_method|
118
+ type_or_method.is_a?(Symbol) ? locator.respond_to?(type_or_method) : type_or_method === locator # rubocop:disable Style/CaseEquality
225
119
  end
226
120
  end
227
121
 
228
- private
229
-
230
- def add_filter(name, filter_class, *types, **options, &block)
231
- types.each { |k| options[k] = true }
232
- custom_filters[name] = filter_class.new(name, block, options)
233
- end
122
+ def locate_field(xpath, locator, **_options)
123
+ return xpath if locator.nil?
234
124
 
235
- def locate_field(xpath, locator, enable_aria_label: false, **_options)
236
125
  locate_xpath = xpath # Need to save original xpath for the label wrap
237
- if locator
238
- locator = locator.to_s
239
- attr_matchers = [XPath.attr(:id) == locator,
240
- XPath.attr(:name) == locator,
241
- XPath.attr(:placeholder) == locator,
242
- XPath.attr(:id) == XPath.anywhere(:label)[XPath.string.n.is(locator)].attr(:for)].reduce(:|)
243
- attr_matchers |= XPath.attr(:'aria-label').is(locator) if enable_aria_label
244
-
245
- locate_xpath = locate_xpath[attr_matchers]
246
- locate_xpath = locate_xpath.union(XPath.descendant(:label)[XPath.string.n.is(locator)].descendant(xpath))
247
- end
126
+ locator = locator.to_s
127
+ attr_matchers = [XPath.attr(:id) == locator,
128
+ XPath.attr(:name) == locator,
129
+ XPath.attr(:placeholder) == locator,
130
+ XPath.attr(:id) == XPath.anywhere(:label)[XPath.string.n.is(locator)].attr(:for)].reduce(:|)
131
+ attr_matchers |= XPath.attr(:'aria-label').is(locator) if enable_aria_label
132
+ attr_matchers |= XPath.attr(test_id) == locator if test_id
248
133
 
249
- # locate_xpath = [:name, :placeholder].inject(locate_xpath) { |memo, ef| memo[find_by_attr(ef, options[ef])] }
250
- locate_xpath
134
+ locate_xpath = locate_xpath[attr_matchers]
135
+ locate_xpath + locate_label(locator).descendant(xpath)
251
136
  end
252
137
 
253
- def describe_all_expression_filters(**opts)
254
- expression_filters.map { |ef| " with #{ef} #{opts[ef]}" if opts.key?(ef) }.join
138
+ def locate_label(locator)
139
+ XPath.descendant(:label)[XPath.string.n.is(locator)]
255
140
  end
256
141
 
257
142
  def find_by_attr(attribute, value)
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module XPath
4
+ class Renderer
5
+ def join(*expressions)
6
+ expressions.join('/')
7
+ end
8
+ end
9
+ end
10
+
11
+ module XPath
12
+ module DSL
13
+ def join(*expressions)
14
+ XPath::Expression.new(:join, *[self, expressions].flatten)
15
+ end
16
+ end
17
+ end