capybara 3.0.0 → 3.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (312) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -1
  3. data/History.md +891 -12
  4. data/License.txt +1 -1
  5. data/README.md +257 -84
  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 +38 -9
  10. data/lib/capybara/dsl.rb +9 -7
  11. data/lib/capybara/helpers.rb +57 -8
  12. data/lib/capybara/minitest/spec.rb +185 -84
  13. data/lib/capybara/minitest.rb +264 -145
  14. data/lib/capybara/node/actions.rb +248 -124
  15. data/lib/capybara/node/base.rb +35 -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 +350 -113
  19. data/lib/capybara/node/finders.rb +104 -82
  20. data/lib/capybara/node/matchers.rb +363 -157
  21. data/lib/capybara/node/simple.rb +54 -15
  22. data/lib/capybara/node/whitespace_normalizer.rb +81 -0
  23. data/lib/capybara/queries/active_element_query.rb +18 -0
  24. data/lib/capybara/queries/ancestor_query.rb +9 -10
  25. data/lib/capybara/queries/base_query.rb +25 -18
  26. data/lib/capybara/queries/current_path_query.rb +16 -6
  27. data/lib/capybara/queries/match_query.rb +11 -0
  28. data/lib/capybara/queries/selector_query.rb +617 -104
  29. data/lib/capybara/queries/sibling_query.rb +9 -7
  30. data/lib/capybara/queries/style_query.rb +45 -0
  31. data/lib/capybara/queries/text_query.rb +40 -22
  32. data/lib/capybara/queries/title_query.rb +2 -2
  33. data/lib/capybara/rack_test/browser.rb +106 -31
  34. data/lib/capybara/rack_test/driver.rb +16 -7
  35. data/lib/capybara/rack_test/errors.rb +6 -0
  36. data/lib/capybara/rack_test/form.rb +74 -49
  37. data/lib/capybara/rack_test/node.rb +120 -47
  38. data/lib/capybara/rails.rb +1 -1
  39. data/lib/capybara/registration_container.rb +41 -0
  40. data/lib/capybara/registrations/drivers.rb +42 -0
  41. data/lib/capybara/registrations/patches/puma_ssl.rb +29 -0
  42. data/lib/capybara/registrations/servers.rb +66 -0
  43. data/lib/capybara/result.rb +87 -53
  44. data/lib/capybara/rspec/features.rb +8 -10
  45. data/lib/capybara/rspec/matcher_proxies.rb +39 -18
  46. data/lib/capybara/rspec/matchers/base.rb +113 -0
  47. data/lib/capybara/rspec/matchers/become_closed.rb +33 -0
  48. data/lib/capybara/rspec/matchers/compound.rb +88 -0
  49. data/lib/capybara/rspec/matchers/count_sugar.rb +37 -0
  50. data/lib/capybara/rspec/matchers/have_ancestor.rb +28 -0
  51. data/lib/capybara/rspec/matchers/have_current_path.rb +29 -0
  52. data/lib/capybara/rspec/matchers/have_selector.rb +69 -0
  53. data/lib/capybara/rspec/matchers/have_sibling.rb +27 -0
  54. data/lib/capybara/rspec/matchers/have_text.rb +33 -0
  55. data/lib/capybara/rspec/matchers/have_title.rb +29 -0
  56. data/lib/capybara/rspec/matchers/match_selector.rb +27 -0
  57. data/lib/capybara/rspec/matchers/match_style.rb +43 -0
  58. data/lib/capybara/rspec/matchers/spatial_sugar.rb +39 -0
  59. data/lib/capybara/rspec/matchers.rb +142 -315
  60. data/lib/capybara/rspec.rb +3 -2
  61. data/lib/capybara/selector/builders/css_builder.rb +84 -0
  62. data/lib/capybara/selector/builders/xpath_builder.rb +71 -0
  63. data/lib/capybara/selector/css.rb +85 -8
  64. data/lib/capybara/selector/definition/button.rb +68 -0
  65. data/lib/capybara/selector/definition/checkbox.rb +26 -0
  66. data/lib/capybara/selector/definition/css.rb +10 -0
  67. data/lib/capybara/selector/definition/datalist_input.rb +35 -0
  68. data/lib/capybara/selector/definition/datalist_option.rb +25 -0
  69. data/lib/capybara/selector/definition/element.rb +28 -0
  70. data/lib/capybara/selector/definition/field.rb +40 -0
  71. data/lib/capybara/selector/definition/fieldset.rb +14 -0
  72. data/lib/capybara/selector/definition/file_field.rb +13 -0
  73. data/lib/capybara/selector/definition/fillable_field.rb +33 -0
  74. data/lib/capybara/selector/definition/frame.rb +17 -0
  75. data/lib/capybara/selector/definition/id.rb +6 -0
  76. data/lib/capybara/selector/definition/label.rb +62 -0
  77. data/lib/capybara/selector/definition/link.rb +55 -0
  78. data/lib/capybara/selector/definition/link_or_button.rb +16 -0
  79. data/lib/capybara/selector/definition/option.rb +27 -0
  80. data/lib/capybara/selector/definition/radio_button.rb +27 -0
  81. data/lib/capybara/selector/definition/select.rb +81 -0
  82. data/lib/capybara/selector/definition/table.rb +109 -0
  83. data/lib/capybara/selector/definition/table_row.rb +21 -0
  84. data/lib/capybara/selector/definition/xpath.rb +5 -0
  85. data/lib/capybara/selector/definition.rb +280 -0
  86. data/lib/capybara/selector/filter.rb +1 -0
  87. data/lib/capybara/selector/filter_set.rb +72 -28
  88. data/lib/capybara/selector/filters/base.rb +45 -2
  89. data/lib/capybara/selector/filters/expression_filter.rb +5 -6
  90. data/lib/capybara/selector/filters/locator_filter.rb +29 -0
  91. data/lib/capybara/selector/filters/node_filter.rb +18 -4
  92. data/lib/capybara/selector/regexp_disassembler.rb +211 -0
  93. data/lib/capybara/selector/selector.rb +89 -200
  94. data/lib/capybara/selector/xpath_extensions.rb +17 -0
  95. data/lib/capybara/selector.rb +474 -534
  96. data/lib/capybara/selenium/atoms/getAttribute.min.js +1 -0
  97. data/lib/capybara/selenium/atoms/isDisplayed.min.js +1 -0
  98. data/lib/capybara/selenium/atoms/src/getAttribute.js +161 -0
  99. data/lib/capybara/selenium/atoms/src/isDisplayed.js +454 -0
  100. data/lib/capybara/selenium/driver.rb +270 -245
  101. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +117 -0
  102. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +128 -0
  103. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +84 -0
  104. data/lib/capybara/selenium/driver_specializations/internet_explorer_driver.rb +26 -0
  105. data/lib/capybara/selenium/driver_specializations/safari_driver.rb +24 -0
  106. data/lib/capybara/selenium/extensions/file_input_click_emulation.rb +34 -0
  107. data/lib/capybara/selenium/extensions/find.rb +110 -0
  108. data/lib/capybara/selenium/extensions/html5_drag.rb +229 -0
  109. data/lib/capybara/selenium/extensions/modifier_keys_stack.rb +28 -0
  110. data/lib/capybara/selenium/extensions/scroll.rb +76 -0
  111. data/lib/capybara/selenium/node.rb +460 -170
  112. data/lib/capybara/selenium/nodes/chrome_node.rb +125 -0
  113. data/lib/capybara/selenium/nodes/edge_node.rb +110 -0
  114. data/lib/capybara/selenium/nodes/firefox_node.rb +136 -0
  115. data/lib/capybara/selenium/nodes/ie_node.rb +22 -0
  116. data/lib/capybara/selenium/nodes/safari_node.rb +118 -0
  117. data/lib/capybara/selenium/patches/atoms.rb +18 -0
  118. data/lib/capybara/selenium/patches/is_displayed.rb +16 -0
  119. data/lib/capybara/selenium/patches/logs.rb +45 -0
  120. data/lib/capybara/selenium/patches/pause_duration_fix.rb +9 -0
  121. data/lib/capybara/selenium/patches/persistent_client.rb +20 -0
  122. data/lib/capybara/server/animation_disabler.rb +80 -0
  123. data/lib/capybara/server/checker.rb +44 -0
  124. data/lib/capybara/server/middleware.rb +71 -0
  125. data/lib/capybara/server.rb +58 -67
  126. data/lib/capybara/session/config.rb +40 -6
  127. data/lib/capybara/session/matchers.rb +26 -19
  128. data/lib/capybara/session.rb +252 -194
  129. data/lib/capybara/spec/public/jquery.js +5 -5
  130. data/lib/capybara/spec/public/offset.js +6 -0
  131. data/lib/capybara/spec/public/test.js +126 -8
  132. data/lib/capybara/spec/session/accept_alert_spec.rb +11 -11
  133. data/lib/capybara/spec/session/accept_confirm_spec.rb +3 -3
  134. data/lib/capybara/spec/session/accept_prompt_spec.rb +9 -10
  135. data/lib/capybara/spec/session/active_element_spec.rb +31 -0
  136. data/lib/capybara/spec/session/all_spec.rb +135 -44
  137. data/lib/capybara/spec/session/ancestor_spec.rb +24 -19
  138. data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +67 -38
  139. data/lib/capybara/spec/session/{assert_current_path.rb → assert_current_path_spec.rb} +20 -18
  140. data/lib/capybara/spec/session/assert_selector_spec.rb +143 -0
  141. data/lib/capybara/spec/session/assert_style_spec.rb +26 -0
  142. data/lib/capybara/spec/session/{assert_text.rb → assert_text_spec.rb} +76 -52
  143. data/lib/capybara/spec/session/{assert_title.rb → assert_title_spec.rb} +12 -12
  144. data/lib/capybara/spec/session/attach_file_spec.rb +126 -72
  145. data/lib/capybara/spec/session/body_spec.rb +11 -13
  146. data/lib/capybara/spec/session/check_spec.rb +112 -51
  147. data/lib/capybara/spec/session/choose_spec.rb +62 -30
  148. data/lib/capybara/spec/session/click_button_spec.rb +227 -161
  149. data/lib/capybara/spec/session/click_link_or_button_spec.rb +49 -30
  150. data/lib/capybara/spec/session/click_link_spec.rb +89 -55
  151. data/lib/capybara/spec/session/current_scope_spec.rb +8 -8
  152. data/lib/capybara/spec/session/current_url_spec.rb +44 -37
  153. data/lib/capybara/spec/session/dismiss_confirm_spec.rb +3 -3
  154. data/lib/capybara/spec/session/dismiss_prompt_spec.rb +2 -2
  155. data/lib/capybara/spec/session/element/{assert_match_selector.rb → assert_match_selector_spec.rb} +11 -9
  156. data/lib/capybara/spec/session/element/match_css_spec.rb +18 -10
  157. data/lib/capybara/spec/session/element/match_xpath_spec.rb +8 -6
  158. data/lib/capybara/spec/session/element/matches_selector_spec.rb +70 -56
  159. data/lib/capybara/spec/session/evaluate_async_script_spec.rb +7 -7
  160. data/lib/capybara/spec/session/evaluate_script_spec.rb +28 -8
  161. data/lib/capybara/spec/session/execute_script_spec.rb +8 -7
  162. data/lib/capybara/spec/session/fill_in_spec.rb +110 -44
  163. data/lib/capybara/spec/session/find_button_spec.rb +23 -23
  164. data/lib/capybara/spec/session/find_by_id_spec.rb +8 -8
  165. data/lib/capybara/spec/session/find_field_spec.rb +33 -31
  166. data/lib/capybara/spec/session/find_link_spec.rb +42 -14
  167. data/lib/capybara/spec/session/find_spec.rb +251 -142
  168. data/lib/capybara/spec/session/first_spec.rb +45 -44
  169. data/lib/capybara/spec/session/frame/frame_title_spec.rb +6 -6
  170. data/lib/capybara/spec/session/frame/frame_url_spec.rb +6 -6
  171. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +32 -20
  172. data/lib/capybara/spec/session/frame/within_frame_spec.rb +46 -19
  173. data/lib/capybara/spec/session/go_back_spec.rb +1 -1
  174. data/lib/capybara/spec/session/go_forward_spec.rb +1 -1
  175. data/lib/capybara/spec/session/has_all_selectors_spec.rb +23 -23
  176. data/lib/capybara/spec/session/has_ancestor_spec.rb +46 -0
  177. data/lib/capybara/spec/session/has_any_selectors_spec.rb +29 -0
  178. data/lib/capybara/spec/session/has_button_spec.rb +98 -12
  179. data/lib/capybara/spec/session/has_css_spec.rb +271 -137
  180. data/lib/capybara/spec/session/has_current_path_spec.rb +50 -35
  181. data/lib/capybara/spec/session/has_element_spec.rb +47 -0
  182. data/lib/capybara/spec/session/has_field_spec.rb +137 -58
  183. data/lib/capybara/spec/session/has_link_spec.rb +46 -6
  184. data/lib/capybara/spec/session/has_none_selectors_spec.rb +33 -31
  185. data/lib/capybara/spec/session/has_select_spec.rb +84 -50
  186. data/lib/capybara/spec/session/has_selector_spec.rb +117 -69
  187. data/lib/capybara/spec/session/has_sibling_spec.rb +50 -0
  188. data/lib/capybara/spec/session/has_table_spec.rb +181 -4
  189. data/lib/capybara/spec/session/has_text_spec.rb +101 -53
  190. data/lib/capybara/spec/session/has_title_spec.rb +19 -14
  191. data/lib/capybara/spec/session/has_xpath_spec.rb +56 -38
  192. data/lib/capybara/spec/session/{headers.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 +37 -0
  195. data/lib/capybara/spec/session/node_spec.rb +958 -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 +65 -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 +119 -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 +41 -22
  212. data/lib/capybara/spec/session/unselect_spec.rb +37 -37
  213. data/lib/capybara/spec/session/visit_spec.rb +79 -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 +11 -8
  221. data/lib/capybara/spec/session/window/within_window_spec.rb +17 -16
  222. data/lib/capybara/spec/session/within_spec.rb +82 -44
  223. data/lib/capybara/spec/spec_helper.rb +46 -52
  224. data/lib/capybara/spec/test_app.rb +148 -41
  225. data/lib/capybara/spec/views/animated.erb +49 -0
  226. data/lib/capybara/spec/views/form.erb +156 -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 +46 -11
  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.erb +2 -2
  255. data/lib/capybara/spec/views/with_scope_other.erb +6 -0
  256. data/lib/capybara/spec/views/with_shadow.erb +31 -0
  257. data/lib/capybara/spec/views/with_slow_unload.erb +2 -1
  258. data/lib/capybara/spec/views/with_sortable_js.erb +21 -0
  259. data/lib/capybara/spec/views/with_unload_alert.erb +1 -0
  260. data/lib/capybara/spec/views/with_windows.erb +1 -1
  261. data/lib/capybara/spec/views/within_frames.erb +1 -1
  262. data/lib/capybara/version.rb +1 -1
  263. data/lib/capybara/window.rb +32 -26
  264. data/lib/capybara.rb +128 -104
  265. data/spec/basic_node_spec.rb +59 -34
  266. data/spec/capybara_spec.rb +65 -51
  267. data/spec/counter_spec.rb +35 -0
  268. data/spec/css_builder_spec.rb +101 -0
  269. data/spec/css_splitter_spec.rb +38 -0
  270. data/spec/dsl_spec.rb +84 -55
  271. data/spec/filter_set_spec.rb +24 -7
  272. data/spec/fixtures/certificate.pem +25 -0
  273. data/spec/fixtures/key.pem +27 -0
  274. data/spec/fixtures/selenium_driver_rspec_failure.rb +5 -5
  275. data/spec/fixtures/selenium_driver_rspec_success.rb +5 -5
  276. data/spec/minitest_spec.rb +49 -5
  277. data/spec/minitest_spec_spec.rb +92 -62
  278. data/spec/per_session_config_spec.rb +6 -6
  279. data/spec/rack_test_spec.rb +183 -115
  280. data/spec/regexp_dissassembler_spec.rb +250 -0
  281. data/spec/result_spec.rb +99 -39
  282. data/spec/rspec/features_spec.rb +28 -25
  283. data/spec/rspec/scenarios_spec.rb +10 -6
  284. data/spec/rspec/shared_spec_matchers.rb +418 -364
  285. data/spec/rspec/views_spec.rb +4 -3
  286. data/spec/rspec_matchers_spec.rb +35 -10
  287. data/spec/rspec_spec.rb +109 -85
  288. data/spec/sauce_spec_chrome.rb +43 -0
  289. data/spec/selector_spec.rb +392 -62
  290. data/spec/selenium_spec_chrome.rb +183 -41
  291. data/spec/selenium_spec_chrome_remote.rb +96 -0
  292. data/spec/selenium_spec_edge.rb +41 -8
  293. data/spec/selenium_spec_firefox.rb +228 -0
  294. data/spec/selenium_spec_firefox_remote.rb +94 -0
  295. data/spec/selenium_spec_ie.rb +129 -11
  296. data/spec/selenium_spec_safari.rb +162 -0
  297. data/spec/server_spec.rb +192 -81
  298. data/spec/session_spec.rb +52 -16
  299. data/spec/shared_selenium_node.rb +79 -0
  300. data/spec/shared_selenium_session.rb +460 -123
  301. data/spec/spec_helper.rb +124 -2
  302. data/spec/whitespace_normalizer_spec.rb +54 -0
  303. data/spec/xpath_builder_spec.rb +93 -0
  304. metadata +344 -45
  305. data/.yard/templates_custom/default/class/html/selectors.erb +0 -38
  306. data/.yard/templates_custom/default/class/html/setup.rb +0 -17
  307. data/.yard/yard_extensions.rb +0 -78
  308. data/lib/capybara/rspec/compound.rb +0 -90
  309. data/lib/capybara/spec/session/assert_selector.rb +0 -149
  310. data/lib/capybara/spec/session/source_spec.rb +0 -0
  311. data/lib/capybara/spec/views/with_title.erb +0 -5
  312. data/spec/selenium_spec_marionette.rb +0 -143
@@ -1,257 +1,146 @@
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
+ Capybara::Helpers.warn(
70
+ "Locator #{locator.class}:#{locator.inspect} for selector #{name.inspect} must #{locator_description}. " \
71
+ 'This will raise an error in a future version of Capybara. ' \
72
+ "Called from: #{Capybara::Helpers.filter_backtrace(caller)}"
73
+ )
158
74
  end
159
75
  end
160
76
 
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)
77
+ def add_error(error_msg)
78
+ errors << error_msg
172
79
  end
173
80
 
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)
81
+ def expression_for(name, locator, config: @config, format: current_format, **options)
82
+ Selector.new(name, config: config, format: format).call(locator, **options)
188
83
  end
189
84
 
190
- def expression_filter(name, *types_and_options, &block)
191
- add_filter(name, Filters::ExpressionFilter, *types_and_options, &block)
85
+ # @api private
86
+ def with_filter_errors(errors)
87
+ old_errors = @errors
88
+ @errors = errors
89
+ yield
90
+ ensure
91
+ @errors = old_errors
192
92
  end
193
93
 
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) }
94
+ # @api private
95
+ def builder(expr = nil)
96
+ case format
97
+ when :css
98
+ Capybara::Selector::CSSBuilder
99
+ when :xpath
100
+ Capybara::Selector::XPathBuilder
101
+ else
102
+ raise NotImplementedError, "No builder exists for selector of type #{default_format}"
103
+ end.new(expr)
201
104
  end
202
105
 
203
- def describe(&block)
204
- @filter_set.describe(&block)
205
- end
106
+ private
206
107
 
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
108
+ def locator_description
109
+ locator_types.group_by { |lt| lt.is_a? Symbol }.map do |symbol, types_or_methods|
110
+ if symbol
111
+ "respond to #{types_or_methods.join(' or ')}"
112
+ else
113
+ "be an instance of #{types_or_methods.join(' or ')}"
114
+ end
115
+ end.join(' or ')
218
116
  end
219
117
 
220
- def default_visibility(fallback = Capybara.ignore_hidden_elements)
221
- if @default_visibility.nil?
222
- fallback
223
- else
224
- @default_visibility
118
+ def locator_valid?(locator)
119
+ return true unless locator && locator_types
120
+
121
+ locator_types&.any? do |type_or_method|
122
+ type_or_method.is_a?(Symbol) ? locator.respond_to?(type_or_method) : type_or_method === locator # rubocop:disable Style/CaseEquality
225
123
  end
226
124
  end
227
125
 
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
126
+ def locate_field(xpath, locator, **_options)
127
+ return xpath if locator.nil?
234
128
 
235
- def locate_field(xpath, locator, enable_aria_label: false, **_options)
236
129
  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
130
+ locator = locator.to_s
131
+ attr_matchers = [XPath.attr(:id) == locator,
132
+ XPath.attr(:name) == locator,
133
+ XPath.attr(:placeholder) == locator,
134
+ XPath.attr(:id) == XPath.anywhere(:label)[XPath.string.n.is(locator)].attr(:for)].reduce(:|)
135
+ attr_matchers |= XPath.attr(:'aria-label').is(locator) if enable_aria_label
136
+ attr_matchers |= XPath.attr(test_id) == locator if test_id
248
137
 
249
- # locate_xpath = [:name, :placeholder].inject(locate_xpath) { |memo, ef| memo[find_by_attr(ef, options[ef])] }
250
- locate_xpath
138
+ locate_xpath = locate_xpath[attr_matchers]
139
+ locate_xpath + locate_label(locator).descendant(xpath)
251
140
  end
252
141
 
253
- def describe_all_expression_filters(**opts)
254
- expression_filters.map { |ef| " with #{ef} #{opts[ef]}" if opts.key?(ef) }.join
142
+ def locate_label(locator)
143
+ XPath.descendant(:label)[XPath.string.n.is(locator)]
255
144
  end
256
145
 
257
146
  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