capybara 3.3.0 → 3.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (308) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -0
  3. data/History.md +803 -13
  4. data/License.txt +1 -1
  5. data/README.md +257 -84
  6. data/lib/capybara/config.rb +25 -9
  7. data/lib/capybara/cucumber.rb +1 -1
  8. data/lib/capybara/driver/base.rb +17 -3
  9. data/lib/capybara/driver/node.rb +31 -6
  10. data/lib/capybara/dsl.rb +9 -7
  11. data/lib/capybara/helpers.rb +31 -7
  12. data/lib/capybara/minitest/spec.rb +180 -88
  13. data/lib/capybara/minitest.rb +262 -149
  14. data/lib/capybara/node/actions.rb +202 -116
  15. data/lib/capybara/node/base.rb +34 -19
  16. data/lib/capybara/node/document.rb +14 -2
  17. data/lib/capybara/node/document_matchers.rb +10 -12
  18. data/lib/capybara/node/element.rb +269 -115
  19. data/lib/capybara/node/finders.rb +99 -77
  20. data/lib/capybara/node/matchers.rb +327 -151
  21. data/lib/capybara/node/simple.rb +48 -13
  22. data/lib/capybara/node/whitespace_normalizer.rb +81 -0
  23. data/lib/capybara/queries/active_element_query.rb +18 -0
  24. data/lib/capybara/queries/ancestor_query.rb +8 -9
  25. data/lib/capybara/queries/base_query.rb +23 -16
  26. data/lib/capybara/queries/current_path_query.rb +16 -6
  27. data/lib/capybara/queries/match_query.rb +1 -0
  28. data/lib/capybara/queries/selector_query.rb +587 -130
  29. data/lib/capybara/queries/sibling_query.rb +8 -6
  30. data/lib/capybara/queries/style_query.rb +6 -2
  31. data/lib/capybara/queries/text_query.rb +28 -14
  32. data/lib/capybara/queries/title_query.rb +2 -2
  33. data/lib/capybara/rack_test/browser.rb +92 -25
  34. data/lib/capybara/rack_test/driver.rb +16 -7
  35. data/lib/capybara/rack_test/errors.rb +6 -0
  36. data/lib/capybara/rack_test/form.rb +68 -41
  37. data/lib/capybara/rack_test/node.rb +106 -39
  38. data/lib/capybara/rails.rb +1 -1
  39. data/lib/capybara/registration_container.rb +41 -0
  40. data/lib/capybara/registrations/drivers.rb +42 -0
  41. data/lib/capybara/registrations/patches/puma_ssl.rb +29 -0
  42. data/lib/capybara/registrations/servers.rb +66 -0
  43. data/lib/capybara/result.rb +75 -52
  44. data/lib/capybara/rspec/features.rb +7 -7
  45. data/lib/capybara/rspec/matcher_proxies.rb +39 -18
  46. data/lib/capybara/rspec/matchers/base.rb +113 -0
  47. data/lib/capybara/rspec/matchers/become_closed.rb +33 -0
  48. data/lib/capybara/rspec/matchers/compound.rb +88 -0
  49. data/lib/capybara/rspec/matchers/count_sugar.rb +37 -0
  50. data/lib/capybara/rspec/matchers/have_ancestor.rb +28 -0
  51. data/lib/capybara/rspec/matchers/have_current_path.rb +29 -0
  52. data/lib/capybara/rspec/matchers/have_selector.rb +69 -0
  53. data/lib/capybara/rspec/matchers/have_sibling.rb +27 -0
  54. data/lib/capybara/rspec/matchers/have_text.rb +33 -0
  55. data/lib/capybara/rspec/matchers/have_title.rb +29 -0
  56. data/lib/capybara/rspec/matchers/match_selector.rb +27 -0
  57. data/lib/capybara/rspec/matchers/match_style.rb +43 -0
  58. data/lib/capybara/rspec/matchers/spatial_sugar.rb +39 -0
  59. data/lib/capybara/rspec/matchers.rb +141 -339
  60. data/lib/capybara/rspec.rb +2 -0
  61. data/lib/capybara/selector/builders/css_builder.rb +84 -0
  62. data/lib/capybara/selector/builders/xpath_builder.rb +71 -0
  63. data/lib/capybara/selector/css.rb +27 -25
  64. data/lib/capybara/selector/definition/button.rb +68 -0
  65. data/lib/capybara/selector/definition/checkbox.rb +26 -0
  66. data/lib/capybara/selector/definition/css.rb +10 -0
  67. data/lib/capybara/selector/definition/datalist_input.rb +35 -0
  68. data/lib/capybara/selector/definition/datalist_option.rb +25 -0
  69. data/lib/capybara/selector/definition/element.rb +28 -0
  70. data/lib/capybara/selector/definition/field.rb +40 -0
  71. data/lib/capybara/selector/definition/fieldset.rb +14 -0
  72. data/lib/capybara/selector/definition/file_field.rb +13 -0
  73. data/lib/capybara/selector/definition/fillable_field.rb +33 -0
  74. data/lib/capybara/selector/definition/frame.rb +17 -0
  75. data/lib/capybara/selector/definition/id.rb +6 -0
  76. data/lib/capybara/selector/definition/label.rb +62 -0
  77. data/lib/capybara/selector/definition/link.rb +55 -0
  78. data/lib/capybara/selector/definition/link_or_button.rb +16 -0
  79. data/lib/capybara/selector/definition/option.rb +27 -0
  80. data/lib/capybara/selector/definition/radio_button.rb +27 -0
  81. data/lib/capybara/selector/definition/select.rb +81 -0
  82. data/lib/capybara/selector/definition/table.rb +109 -0
  83. data/lib/capybara/selector/definition/table_row.rb +21 -0
  84. data/lib/capybara/selector/definition/xpath.rb +5 -0
  85. data/lib/capybara/selector/definition.rb +280 -0
  86. data/lib/capybara/selector/filter.rb +1 -0
  87. data/lib/capybara/selector/filter_set.rb +73 -25
  88. data/lib/capybara/selector/filters/base.rb +24 -5
  89. data/lib/capybara/selector/filters/expression_filter.rb +3 -3
  90. data/lib/capybara/selector/filters/locator_filter.rb +29 -0
  91. data/lib/capybara/selector/filters/node_filter.rb +16 -2
  92. data/lib/capybara/selector/regexp_disassembler.rb +211 -0
  93. data/lib/capybara/selector/selector.rb +85 -348
  94. data/lib/capybara/selector/xpath_extensions.rb +17 -0
  95. data/lib/capybara/selector.rb +474 -447
  96. data/lib/capybara/selenium/atoms/getAttribute.min.js +1 -0
  97. data/lib/capybara/selenium/atoms/isDisplayed.min.js +1 -0
  98. data/lib/capybara/selenium/atoms/src/getAttribute.js +161 -0
  99. data/lib/capybara/selenium/atoms/src/isDisplayed.js +454 -0
  100. data/lib/capybara/selenium/driver.rb +255 -143
  101. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +93 -11
  102. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +128 -0
  103. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +84 -0
  104. data/lib/capybara/selenium/driver_specializations/internet_explorer_driver.rb +26 -0
  105. data/lib/capybara/selenium/driver_specializations/safari_driver.rb +24 -0
  106. data/lib/capybara/selenium/extensions/file_input_click_emulation.rb +34 -0
  107. data/lib/capybara/selenium/extensions/find.rb +110 -0
  108. data/lib/capybara/selenium/extensions/html5_drag.rb +229 -0
  109. data/lib/capybara/selenium/extensions/modifier_keys_stack.rb +28 -0
  110. data/lib/capybara/selenium/extensions/scroll.rb +76 -0
  111. data/lib/capybara/selenium/node.rb +436 -134
  112. data/lib/capybara/selenium/nodes/chrome_node.rb +125 -0
  113. data/lib/capybara/selenium/nodes/edge_node.rb +110 -0
  114. data/lib/capybara/selenium/nodes/firefox_node.rb +136 -0
  115. data/lib/capybara/selenium/nodes/ie_node.rb +22 -0
  116. data/lib/capybara/selenium/nodes/safari_node.rb +118 -0
  117. data/lib/capybara/selenium/patches/atoms.rb +18 -0
  118. data/lib/capybara/selenium/patches/is_displayed.rb +16 -0
  119. data/lib/capybara/selenium/patches/logs.rb +45 -0
  120. data/lib/capybara/selenium/patches/pause_duration_fix.rb +9 -0
  121. data/lib/capybara/selenium/patches/persistent_client.rb +20 -0
  122. data/lib/capybara/server/animation_disabler.rb +56 -19
  123. data/lib/capybara/server/checker.rb +9 -3
  124. data/lib/capybara/server/middleware.rb +28 -12
  125. data/lib/capybara/server.rb +33 -10
  126. data/lib/capybara/session/config.rb +34 -10
  127. data/lib/capybara/session/matchers.rb +23 -16
  128. data/lib/capybara/session.rb +230 -170
  129. data/lib/capybara/spec/public/jquery.js +5 -5
  130. data/lib/capybara/spec/public/offset.js +6 -0
  131. data/lib/capybara/spec/public/test.js +121 -8
  132. data/lib/capybara/spec/session/accept_alert_spec.rb +11 -11
  133. data/lib/capybara/spec/session/accept_confirm_spec.rb +3 -3
  134. data/lib/capybara/spec/session/accept_prompt_spec.rb +9 -10
  135. data/lib/capybara/spec/session/active_element_spec.rb +31 -0
  136. data/lib/capybara/spec/session/all_spec.rb +127 -40
  137. data/lib/capybara/spec/session/ancestor_spec.rb +24 -19
  138. data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +67 -38
  139. data/lib/capybara/spec/session/assert_current_path_spec.rb +21 -18
  140. data/lib/capybara/spec/session/assert_selector_spec.rb +52 -58
  141. data/lib/capybara/spec/session/assert_style_spec.rb +7 -7
  142. data/lib/capybara/spec/session/assert_text_spec.rb +74 -50
  143. data/lib/capybara/spec/session/assert_title_spec.rb +12 -12
  144. data/lib/capybara/spec/session/attach_file_spec.rb +126 -72
  145. data/lib/capybara/spec/session/body_spec.rb +6 -6
  146. data/lib/capybara/spec/session/check_spec.rb +102 -47
  147. data/lib/capybara/spec/session/choose_spec.rb +58 -32
  148. data/lib/capybara/spec/session/click_button_spec.rb +219 -163
  149. data/lib/capybara/spec/session/click_link_or_button_spec.rb +49 -23
  150. data/lib/capybara/spec/session/click_link_spec.rb +77 -54
  151. data/lib/capybara/spec/session/current_scope_spec.rb +8 -8
  152. data/lib/capybara/spec/session/current_url_spec.rb +38 -29
  153. data/lib/capybara/spec/session/dismiss_confirm_spec.rb +3 -3
  154. data/lib/capybara/spec/session/dismiss_prompt_spec.rb +2 -2
  155. data/lib/capybara/spec/session/element/assert_match_selector_spec.rb +8 -8
  156. data/lib/capybara/spec/session/element/match_css_spec.rb +16 -10
  157. data/lib/capybara/spec/session/element/match_xpath_spec.rb +6 -6
  158. data/lib/capybara/spec/session/element/matches_selector_spec.rb +68 -56
  159. data/lib/capybara/spec/session/evaluate_async_script_spec.rb +7 -7
  160. data/lib/capybara/spec/session/evaluate_script_spec.rb +28 -8
  161. data/lib/capybara/spec/session/execute_script_spec.rb +8 -7
  162. data/lib/capybara/spec/session/fill_in_spec.rb +101 -46
  163. data/lib/capybara/spec/session/find_button_spec.rb +23 -23
  164. data/lib/capybara/spec/session/find_by_id_spec.rb +7 -7
  165. data/lib/capybara/spec/session/find_field_spec.rb +32 -30
  166. data/lib/capybara/spec/session/find_link_spec.rb +31 -21
  167. data/lib/capybara/spec/session/find_spec.rb +244 -141
  168. data/lib/capybara/spec/session/first_spec.rb +43 -43
  169. data/lib/capybara/spec/session/frame/frame_title_spec.rb +5 -5
  170. data/lib/capybara/spec/session/frame/frame_url_spec.rb +5 -5
  171. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +30 -18
  172. data/lib/capybara/spec/session/frame/within_frame_spec.rb +45 -18
  173. data/lib/capybara/spec/session/go_back_spec.rb +1 -1
  174. data/lib/capybara/spec/session/go_forward_spec.rb +1 -1
  175. data/lib/capybara/spec/session/has_all_selectors_spec.rb +23 -23
  176. data/lib/capybara/spec/session/has_ancestor_spec.rb +46 -0
  177. data/lib/capybara/spec/session/has_any_selectors_spec.rb +29 -0
  178. data/lib/capybara/spec/session/has_button_spec.rb +94 -13
  179. data/lib/capybara/spec/session/has_css_spec.rb +272 -132
  180. data/lib/capybara/spec/session/has_current_path_spec.rb +50 -35
  181. data/lib/capybara/spec/session/has_element_spec.rb +47 -0
  182. data/lib/capybara/spec/session/has_field_spec.rb +137 -58
  183. data/lib/capybara/spec/session/has_link_spec.rb +44 -4
  184. data/lib/capybara/spec/session/has_none_selectors_spec.rb +31 -31
  185. data/lib/capybara/spec/session/has_select_spec.rb +84 -50
  186. data/lib/capybara/spec/session/has_selector_spec.rb +111 -71
  187. data/lib/capybara/spec/session/has_sibling_spec.rb +50 -0
  188. data/lib/capybara/spec/session/has_table_spec.rb +181 -4
  189. data/lib/capybara/spec/session/has_text_spec.rb +101 -53
  190. data/lib/capybara/spec/session/has_title_spec.rb +19 -14
  191. data/lib/capybara/spec/session/has_xpath_spec.rb +56 -38
  192. data/lib/capybara/spec/session/headers_spec.rb +1 -1
  193. data/lib/capybara/spec/session/html_spec.rb +13 -6
  194. data/lib/capybara/spec/session/matches_style_spec.rb +37 -0
  195. data/lib/capybara/spec/session/node_spec.rb +894 -142
  196. data/lib/capybara/spec/session/node_wrapper_spec.rb +10 -7
  197. data/lib/capybara/spec/session/refresh_spec.rb +9 -7
  198. data/lib/capybara/spec/session/reset_session_spec.rb +63 -35
  199. data/lib/capybara/spec/session/response_code_spec.rb +1 -1
  200. data/lib/capybara/spec/session/save_and_open_page_spec.rb +2 -2
  201. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +2 -2
  202. data/lib/capybara/spec/session/save_page_spec.rb +37 -37
  203. data/lib/capybara/spec/session/save_screenshot_spec.rb +10 -10
  204. data/lib/capybara/spec/session/screenshot_spec.rb +2 -2
  205. data/lib/capybara/spec/session/scroll_spec.rb +119 -0
  206. data/lib/capybara/spec/session/select_spec.rb +85 -85
  207. data/lib/capybara/spec/session/selectors_spec.rb +49 -18
  208. data/lib/capybara/spec/session/sibling_spec.rb +9 -9
  209. data/lib/capybara/spec/session/text_spec.rb +25 -24
  210. data/lib/capybara/spec/session/title_spec.rb +7 -6
  211. data/lib/capybara/spec/session/uncheck_spec.rb +33 -21
  212. data/lib/capybara/spec/session/unselect_spec.rb +37 -37
  213. data/lib/capybara/spec/session/visit_spec.rb +68 -49
  214. data/lib/capybara/spec/session/window/become_closed_spec.rb +20 -17
  215. data/lib/capybara/spec/session/window/current_window_spec.rb +1 -1
  216. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +20 -16
  217. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +6 -2
  218. data/lib/capybara/spec/session/window/window_spec.rb +62 -63
  219. data/lib/capybara/spec/session/window/windows_spec.rb +5 -1
  220. data/lib/capybara/spec/session/window/within_window_spec.rb +14 -14
  221. data/lib/capybara/spec/session/within_spec.rb +79 -42
  222. data/lib/capybara/spec/spec_helper.rb +41 -53
  223. data/lib/capybara/spec/test_app.rb +132 -43
  224. data/lib/capybara/spec/views/animated.erb +49 -0
  225. data/lib/capybara/spec/views/form.erb +139 -42
  226. data/lib/capybara/spec/views/frame_child.erb +4 -3
  227. data/lib/capybara/spec/views/frame_one.erb +2 -1
  228. data/lib/capybara/spec/views/frame_parent.erb +1 -1
  229. data/lib/capybara/spec/views/frame_two.erb +1 -1
  230. data/lib/capybara/spec/views/initial_alert.erb +2 -1
  231. data/lib/capybara/spec/views/layout.erb +10 -0
  232. data/lib/capybara/spec/views/obscured.erb +47 -0
  233. data/lib/capybara/spec/views/offset.erb +33 -0
  234. data/lib/capybara/spec/views/path.erb +2 -2
  235. data/lib/capybara/spec/views/popup_one.erb +1 -1
  236. data/lib/capybara/spec/views/popup_two.erb +1 -1
  237. data/lib/capybara/spec/views/react.erb +45 -0
  238. data/lib/capybara/spec/views/scroll.erb +21 -0
  239. data/lib/capybara/spec/views/spatial.erb +31 -0
  240. data/lib/capybara/spec/views/tables.erb +67 -0
  241. data/lib/capybara/spec/views/with_animation.erb +39 -4
  242. data/lib/capybara/spec/views/with_base_tag.erb +2 -2
  243. data/lib/capybara/spec/views/with_dragula.erb +24 -0
  244. data/lib/capybara/spec/views/with_fixed_header_footer.erb +2 -1
  245. data/lib/capybara/spec/views/with_hover.erb +3 -2
  246. data/lib/capybara/spec/views/with_hover1.erb +10 -0
  247. data/lib/capybara/spec/views/with_html.erb +37 -9
  248. data/lib/capybara/spec/views/with_html5_svg.erb +20 -0
  249. data/lib/capybara/spec/views/with_jquery_animation.erb +24 -0
  250. data/lib/capybara/spec/views/with_js.erb +26 -5
  251. data/lib/capybara/spec/views/with_jstree.erb +26 -0
  252. data/lib/capybara/spec/views/with_namespace.erb +1 -0
  253. data/lib/capybara/spec/views/with_scope.erb +2 -2
  254. data/lib/capybara/spec/views/with_scope_other.erb +6 -0
  255. data/lib/capybara/spec/views/with_shadow.erb +31 -0
  256. data/lib/capybara/spec/views/with_slow_unload.erb +2 -1
  257. data/lib/capybara/spec/views/with_sortable_js.erb +21 -0
  258. data/lib/capybara/spec/views/with_unload_alert.erb +1 -0
  259. data/lib/capybara/spec/views/with_windows.erb +1 -1
  260. data/lib/capybara/spec/views/within_frames.erb +1 -1
  261. data/lib/capybara/version.rb +1 -1
  262. data/lib/capybara/window.rb +19 -25
  263. data/lib/capybara.rb +126 -111
  264. data/spec/basic_node_spec.rb +59 -34
  265. data/spec/capybara_spec.rb +56 -44
  266. data/spec/counter_spec.rb +35 -0
  267. data/spec/css_builder_spec.rb +101 -0
  268. data/spec/css_splitter_spec.rb +8 -8
  269. data/spec/dsl_spec.rb +79 -52
  270. data/spec/filter_set_spec.rb +9 -9
  271. data/spec/fixtures/selenium_driver_rspec_failure.rb +4 -4
  272. data/spec/fixtures/selenium_driver_rspec_success.rb +4 -4
  273. data/spec/minitest_spec.rb +45 -7
  274. data/spec/minitest_spec_spec.rb +87 -64
  275. data/spec/per_session_config_spec.rb +6 -6
  276. data/spec/rack_test_spec.rb +172 -116
  277. data/spec/regexp_dissassembler_spec.rb +250 -0
  278. data/spec/result_spec.rb +80 -72
  279. data/spec/rspec/features_spec.rb +21 -16
  280. data/spec/rspec/scenarios_spec.rb +10 -6
  281. data/spec/rspec/shared_spec_matchers.rb +407 -365
  282. data/spec/rspec/views_spec.rb +3 -3
  283. data/spec/rspec_matchers_spec.rb +35 -10
  284. data/spec/rspec_spec.rb +63 -41
  285. data/spec/sauce_spec_chrome.rb +43 -0
  286. data/spec/selector_spec.rb +334 -89
  287. data/spec/selenium_spec_chrome.rb +176 -62
  288. data/spec/selenium_spec_chrome_remote.rb +54 -14
  289. data/spec/selenium_spec_edge.rb +41 -8
  290. data/spec/selenium_spec_firefox.rb +228 -0
  291. data/spec/selenium_spec_firefox_remote.rb +94 -0
  292. data/spec/selenium_spec_ie.rb +129 -11
  293. data/spec/selenium_spec_safari.rb +162 -0
  294. data/spec/server_spec.rb +171 -97
  295. data/spec/session_spec.rb +34 -18
  296. data/spec/shared_selenium_node.rb +79 -0
  297. data/spec/shared_selenium_session.rb +344 -80
  298. data/spec/spec_helper.rb +124 -2
  299. data/spec/whitespace_normalizer_spec.rb +54 -0
  300. data/spec/xpath_builder_spec.rb +93 -0
  301. metadata +326 -28
  302. data/lib/capybara/rspec/compound.rb +0 -94
  303. data/lib/capybara/selenium/driver_specializations/marionette_driver.rb +0 -31
  304. data/lib/capybara/selenium/nodes/marionette_node.rb +0 -31
  305. data/lib/capybara/spec/session/has_style_spec.rb +0 -25
  306. data/lib/capybara/spec/session/source_spec.rb +0 -0
  307. data/lib/capybara/spec/views/with_title.erb +0 -5
  308. data/spec/selenium_spec_marionette.rb +0 -167
@@ -1,385 +1,131 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # rubocop:disable Style/AsciiComments
4
-
5
- require 'capybara/selector/filter_set'
6
- require 'capybara/selector/css'
7
-
8
3
  module Capybara
9
- #
10
- # ## Built-in Selectors
11
- #
12
- # * **:xpath** - Select elements by XPath expression
13
- # * Locator: An XPath expression
14
- #
15
- # * **:css** - Select elements by CSS selector
16
- # * Locator: A CSS selector
17
- #
18
- # * **:id** - Select element by id
19
- # * Locator: The id of the element to match
20
- #
21
- # * **:field** - Select field elements (input [not of type submit, image, or hidden], textarea, select)
22
- # * Locator: Matches against the id, name, or placeholder
23
- # * Filters:
24
- # * :id (String) — Matches the id attribute
25
- # * :name (String) — Matches the name attribute
26
- # * :placeholder (String) — Matches the placeholder attribute
27
- # * :type (String) — Matches the type attribute of the field or element type for 'textarea' and 'select'
28
- # * :readonly (Boolean)
29
- # * :with (String) — Matches the current value of the field
30
- # * :class (String, Array<String>) — Matches the class(es) provided
31
- # * :checked (Boolean) — Match checked fields?
32
- # * :unchecked (Boolean) — Match unchecked fields?
33
- # * :disabled (Boolean) — Match disabled field?
34
- # * :multiple (Boolean) — Match fields that accept multiple values
35
- #
36
- # * **:fieldset** - Select fieldset elements
37
- # * Locator: Matches id or contents of wrapped legend
38
- # * Filters:
39
- # * :id (String) — Matches id attribute
40
- # * :legend (String) — Matches contents of wrapped legend
41
- # * :class (String, Array<String>) — Matches the class(es) provided
42
- #
43
- # * **:link** - Find links ( <a> elements with an href attribute )
44
- # * Locator: Matches the id or title attributes, or the string content of the link, or the alt attribute of a contained img element
45
- # * Filters:
46
- # * :id (String) — Matches the id attribute
47
- # * :title (String) — Matches the title attribute
48
- # * :alt (String) — Matches the alt attribute of a contained img element
49
- # * :class (String) — Matches the class(es) provided
50
- # * :href (String, Regexp, nil) — Matches the normalized href of the link, if nil will find <a> elements with no href attribute
51
- #
52
- # * **:button** - Find buttons ( input [of type submit, reset, image, button] or button elements )
53
- # * Locator: Matches the id, value, or title attributes, string content of a button, or the alt attribute of an image type button or of a descendant image of a button
54
- # * Filters:
55
- # * :id (String) — Matches the id attribute
56
- # * :title (String) — Matches the title attribute
57
- # * :class (String) — Matches the class(es) provided
58
- # * :value (String) — Matches the value of an input button
59
- # * :type
60
- #
61
- # * **:link_or_button** - Find links or buttons
62
- # * Locator: See :link and :button selectors
63
- #
64
- # * **:fillable_field** - Find text fillable fields ( textarea, input [not of type submit, image, radio, checkbox, hidden, file] )
65
- # * Locator: Matches against the id, name, or placeholder
66
- # * Filters:
67
- # * :id (String) — Matches the id attribute
68
- # * :name (String) — Matches the name attribute
69
- # * :placeholder (String) — Matches the placeholder attribute
70
- # * :with (String) — Matches the current value of the field
71
- # * :type (String) — Matches the type attribute of the field or element type for 'textarea'
72
- # * :class (String, Array<String>) — Matches the class(es) provided
73
- # * :disabled (Boolean) — Match disabled field?
74
- # * :multiple (Boolean) — Match fields that accept multiple values
75
- #
76
- # * **:radio_button** - Find radio buttons
77
- # * Locator: Match id, name, or associated label text
78
- # * Filters:
79
- # * :id (String) — Matches the id attribute
80
- # * :name (String) — Matches the name attribute
81
- # * :class (String, Array<String>) — Matches the class(es) provided
82
- # * :checked (Boolean) — Match checked fields?
83
- # * :unchecked (Boolean) — Match unchecked fields?
84
- # * :disabled (Boolean) — Match disabled field?
85
- # * :option (String) — Match the value
86
- #
87
- # * **:checkbox** - Find checkboxes
88
- # * Locator: Match id, name, or associated label text
89
- # * Filters:
90
- # * *:id (String) — Matches the id attribute
91
- # * *:name (String) — Matches the name attribute
92
- # * *:class (String, Array<String>) — Matches the class(es) provided
93
- # * *:checked (Boolean) — Match checked fields?
94
- # * *:unchecked (Boolean) — Match unchecked fields?
95
- # * *:disabled (Boolean) — Match disabled field?
96
- # * *:option (String) — Match the value
97
- #
98
- # * **:select** - Find select elements
99
- # * Locator: Match id, name, placeholder, or associated label text
100
- # * Filters:
101
- # * :id (String) — Matches the id attribute
102
- # * :name (String) — Matches the name attribute
103
- # * :placeholder (String) — Matches the placeholder attribute
104
- # * :class (String, Array<String>) — Matches the class(es) provided
105
- # * :disabled (Boolean) — Match disabled field?
106
- # * :multiple (Boolean) — Match fields that accept multiple values
107
- # * :options (Array<String>) — Exact match options
108
- # * :with_options (Array<String>) — Partial match options
109
- # * :selected (String, Array<String>) — Match the selection(s)
110
- # * :with_selected (String, Array<String>) — Partial match the selection(s)
111
- #
112
- # * **:option** - Find option elements
113
- # * Locator: Match text of option
114
- # * Filters:
115
- # * :disabled (Boolean) — Match disabled option
116
- # * :selected (Boolean) — Match selected option
117
- #
118
- # * **:datalist_input**
119
- # * Locator:
120
- # * Filters:
121
- # * :disabled
122
- # * :name
123
- # * :placeholder
124
- #
125
- # * **:datalist_option**
126
- # * Locator:
127
- #
128
- # * **:file_field** - Find file input elements
129
- # * Locator: Match id, name, or associated label text
130
- # * Filters:
131
- # * :id (String) — Matches the id attribute
132
- # * :name (String) — Matches the name attribute
133
- # * :class (String, Array<String>) — Matches the class(es) provided
134
- # * :disabled (Boolean) — Match disabled field?
135
- # * :multiple (Boolean) — Match field that accepts multiple values
136
- #
137
- # * **:label** - Find label elements
138
- # * Locator: Match id or text contents
139
- # * Filters:
140
- # * :for (Element, String) — The element or id of the element associated with the label
141
- #
142
- # * **:table** - Find table elements
143
- # * Locator: id or caption text of table
144
- # * Filters:
145
- # * :id (String) — Match id attribute of table
146
- # * :caption (String) — Match text of associated caption
147
- # * :class (String, Array<String>) — Matches the class(es) provided
148
- #
149
- # * **:frame** - Find frame/iframe elements
150
- # * Locator: Match id or name
151
- # * Filters:
152
- # * :id (String) — Match id attribute
153
- # * :name (String) — Match name attribute
154
- # * :class (String, Array<String>) — Matches the class(es) provided
155
- #
156
- # * **:element**
157
- # * Locator: Type of element ('div', 'a', etc) - if not specified defaults to '*'
158
- # * Filters: Matches on any element attribute
159
- #
160
- class Selector
161
- attr_reader :name, :format
162
- extend Forwardable
163
-
4
+ class Selector < SimpleDelegator
164
5
  class << self
165
6
  def all
166
- @selectors ||= {} # rubocop:disable Naming/MemoizedInstanceVariableName
7
+ @definitions ||= {} # rubocop:disable Naming/MemoizedInstanceVariableName
8
+ end
9
+
10
+ def [](name)
11
+ all.fetch(name.to_sym) { |sel_type| raise ArgumentError, "Unknown selector type (:#{sel_type})" }
167
12
  end
168
13
 
169
- def add(name, &block)
170
- all[name.to_sym] = Capybara::Selector.new(name.to_sym, &block)
14
+ def add(name, **options, &block)
15
+ all[name.to_sym] = Definition.new(name.to_sym, **options, &block)
171
16
  end
172
17
 
173
18
  def update(name, &block)
174
- all[name.to_sym].instance_eval(&block)
19
+ self[name].instance_eval(&block)
175
20
  end
176
21
 
177
22
  def remove(name)
178
23
  all.delete(name.to_sym)
179
24
  end
180
- end
181
-
182
- def initialize(name, &block)
183
- @name = name
184
- @filter_set = FilterSet.add(name) {}
185
- @match = nil
186
- @label = nil
187
- @failure_message = nil
188
- @description = nil
189
- @format = nil
190
- @expression = nil
191
- @expression_filters = {}
192
- @default_visibility = nil
193
- instance_eval(&block)
194
- end
195
25
 
196
- def custom_filters
197
- warn "Deprecated: Selector#custom_filters is not valid when same named expression and node filter exist - don't use"
198
- node_filters.merge(expression_filters).freeze
26
+ def for(locator)
27
+ all.values.find { |sel| sel.match?(locator) }
28
+ end
199
29
  end
200
30
 
201
- def node_filters
202
- @filter_set.node_filters
203
- end
31
+ attr_reader :errors
204
32
 
205
- def expression_filters
206
- @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 = []
207
40
  end
208
41
 
209
- ##
210
- #
211
- # Define a selector by an xpath expression
212
- #
213
- # @overload xpath(*expression_filters, &block)
214
- # @param [Array<Symbol>] expression_filters ([]) Names of filters that can be implemented via this expression
215
- # @yield [locator, options] The block to use to generate the XPath expression
216
- # @yieldparam [String] locator The locator string passed to the query
217
- # @yieldparam [Hash] options The options hash passed to the query
218
- # @yieldreturn [#to_xpath, #to_s] An object that can produce an xpath expression
219
- #
220
- # @overload xpath()
221
- # @return [#call] The block that will be called to generate the XPath expression
222
- #
223
- def xpath(*allowed_filters, &block)
224
- if block
225
- @format, @expression = :xpath, block
226
- allowed_filters.flatten.each { |ef| expression_filters[ef] = Filters::IdentityExpressionFilter.new(ef) }
227
- end
228
- format == :xpath ? @expression : nil
42
+ def format
43
+ @format || @definition.default_format
229
44
  end
45
+ alias_method :current_format, :format
230
46
 
231
- ##
232
- #
233
- # Define a selector by a CSS selector
234
- #
235
- # @overload css(*expression_filters, &block)
236
- # @param [Array<Symbol>] expression_filters ([]) Names of filters that can be implemented via this CSS selector
237
- # @yield [locator, options] The block to use to generate the CSS selector
238
- # @yieldparam [String] locator The locator string passed to the query
239
- # @yieldparam [Hash] options The options hash passed to the query
240
- # @yieldreturn [#to_s] An object that can produce a CSS selector
241
- #
242
- # @overload css()
243
- # @return [#call] The block that will be called to generate the CSS selector
244
- #
245
- def css(*allowed_filters, &block)
246
- if block
247
- @format, @expression = :css, block
248
- allowed_filters.flatten.each { |ef| expression_filters[ef] = nil }
249
- end
250
- format == :css ? @expression : nil
47
+ def enable_aria_label
48
+ @config[:enable_aria_label]
251
49
  end
252
50
 
253
- ##
254
- #
255
- # Automatic selector detection
256
- #
257
- # @yield [locator] This block takes the passed in locator string and returns whether or not it matches the selector
258
- # @yieldparam [String], locator The locator string used to determin if it matches the selector
259
- # @yieldreturn [Boolean] Whether this selector matches the locator string
260
- # @return [#call] The block that will be used to detect selector match
261
- #
262
- def match(&block)
263
- @match = block if block
264
- @match
51
+ def enable_aria_role
52
+ @config[:enable_aria_role]
265
53
  end
266
54
 
267
- ##
268
- #
269
- # Set/get a descriptive label for the selector
270
- #
271
- # @overload label(label)
272
- # @param [String] label A descriptive label for this selector - used in error messages
273
- # @overload label()
274
- # @return [String] The currently set label
275
- #
276
- def label(label = nil)
277
- @label = label if label
278
- @label
55
+ def test_id
56
+ @config[:test_id]
279
57
  end
280
58
 
281
- ##
282
- #
283
- # Description of the selector
284
- #
285
- # @!method description(options)
286
- # @param [Hash] options The options of the query used to generate the description
287
- # @return [String] Description of the selector when used with the options passed
288
- def_delegator :@filter_set, :description
289
-
290
59
  def call(locator, **options)
291
60
  if format
292
- @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])
293
64
  else
294
- 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
+ )
295
74
  end
296
75
  end
297
76
 
298
- ##
299
- #
300
- # Should this selector be used for the passed in locator
301
- #
302
- # This is used by the automatic selector selection mechanism when no selector type is passed to a selector query
303
- #
304
- # @param [String] locator The locator passed to the query
305
- # @return [Boolean] Whether or not to use this selector
306
- #
307
- def match?(locator)
308
- @match&.call(locator)
77
+ def add_error(error_msg)
78
+ errors << error_msg
309
79
  end
310
80
 
311
- ##
312
- #
313
- # Define a node filter for use with this selector
314
- #
315
- # @!method node_filter(name, *types, options={}, &block)
316
- # @param [Symbol, Regexp] name The filter name
317
- # @param [Array<Symbol>] types The types of the filter - currently valid types are [:boolean]
318
- # @param [Hash] options ({}) Options of the filter
319
- # @option options [Array<>] :valid_values Valid values for this filter
320
- # @option options :default The default value of the filter (if any)
321
- # @option options :skip_if Value of the filter that will cause it to be skipped
322
- # @option options [Regexp] :matcher (nil) A Regexp used to check whether a specific option is handled by this filter. If not provided the filter will be used for options matching the filter name.
323
- #
324
- # If a Symbol is passed for the name the block should accept | node, option_value |, while if a Regexp
325
- # is passed for the name the block should accept | node, option_name, option_value |. In either case
326
- # the block should return `true` if the node passes the filer or `false` if it doesn't
327
-
328
- # @!method filter
329
- # See {Selector#node_filter}
330
-
331
- ##
332
- #
333
- # Define an expression filter for use with this selector
334
- #
335
- # @!method expression_filter(name, *types, options={}, &block)
336
- # @param [Symbol, Regexp] name The filter name
337
- # @param [Regexp] matcher (nil) A Regexp used to check whether a specific option is handled by this filter
338
- # @param [Array<Symbol>] types The types of the filter - currently valid types are [:boolean]
339
- # @param [Hash] options ({}) Options of the filter
340
- # @option options [Array<>] :valid_values Valid values for this filter
341
- # @option options :default The default value of the filter (if any)
342
- # @option options :skip_if Value of the filter that will cause it to be skipped
343
- # @option options [Regexp] :matcher (nil) A Regexp used to check whether a specific option is handled by this filter. If not provided the filter will be used for options matching the filter name.
344
- #
345
- # If a Symbol is passed for the name the block should accept | current_expression, option_value |, while if a Regexp
346
- # is passed for the name the block should accept | current_expression, option_name, option_value |. In either case
347
- # the block should return the modified expression
81
+ def expression_for(name, locator, config: @config, format: current_format, **options)
82
+ Selector.new(name, config: config, format: format).call(locator, **options)
83
+ end
348
84
 
349
- def_delegators :@filter_set, :node_filter, :expression_filter, :filter
85
+ # @api private
86
+ def with_filter_errors(errors)
87
+ old_errors = @errors
88
+ @errors = errors
89
+ yield
90
+ ensure
91
+ @errors = old_errors
92
+ end
350
93
 
351
- def filter_set(name, filters_to_use = nil)
352
- f_set = FilterSet.all[name]
353
- filter_selector = filters_to_use.nil? ? ->(*) { true } : ->(n, _) { filters_to_use.include? n }
354
- @filter_set.expression_filters.merge!(f_set.expression_filters.select(&filter_selector))
355
- @filter_set.node_filters.merge!(f_set.node_filters.select(&filter_selector))
356
- 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)
357
104
  end
358
105
 
359
- def_delegator :@filter_set, :describe
106
+ private
360
107
 
361
- ##
362
- #
363
- # Set the default visibility mode that shouble be used if no visibile option is passed when using the selector.
364
- # If not specified will default to the behavior indicated by Capybara.ignore_hidden_elements
365
- #
366
- # @param [Symbol] default_visibility Only find elements with the specified visibility:
367
- # * :all - finds visible and invisible elements.
368
- # * :hidden - only finds invisible elements.
369
- # * :visible - only finds visible elements.
370
- def visible(default_visibility)
371
- @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 ')
372
116
  end
373
117
 
374
- def default_visibility(fallback = Capybara.ignore_hidden_elements)
375
- return @default_visibility unless @default_visibility.nil?
376
- fallback
377
- end
118
+ def locator_valid?(locator)
119
+ return true unless locator && locator_types
378
120
 
379
- private
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
123
+ end
124
+ end
380
125
 
381
- def locate_field(xpath, locator, enable_aria_label: false, **_options)
126
+ def locate_field(xpath, locator, **_options)
382
127
  return xpath if locator.nil?
128
+
383
129
  locate_xpath = xpath # Need to save original xpath for the label wrap
384
130
  locator = locator.to_s
385
131
  attr_matchers = [XPath.attr(:id) == locator,
@@ -387,21 +133,14 @@ module Capybara
387
133
  XPath.attr(:placeholder) == locator,
388
134
  XPath.attr(:id) == XPath.anywhere(:label)[XPath.string.n.is(locator)].attr(:for)].reduce(:|)
389
135
  attr_matchers |= XPath.attr(:'aria-label').is(locator) if enable_aria_label
136
+ attr_matchers |= XPath.attr(test_id) == locator if test_id
390
137
 
391
138
  locate_xpath = locate_xpath[attr_matchers]
392
- locate_xpath + XPath.descendant(:label)[XPath.string.n.is(locator)].descendant(xpath)
139
+ locate_xpath + locate_label(locator).descendant(xpath)
393
140
  end
394
141
 
395
- def describe_all_expression_filters(**opts)
396
- expression_filters.map do |ef_name, ef|
397
- if ef.matcher?
398
- opts.keys.map do |k|
399
- " with #{ef_name}[#{k} => #{opts[k]}]" if ef.handles_option?(k) && !::Capybara::Queries::SelectorQuery::VALID_KEYS.include?(k)
400
- end.join
401
- elsif opts.key?(ef_name)
402
- " with #{ef_name} #{opts[ef_name]}"
403
- end
404
- end.join
142
+ def locate_label(locator)
143
+ XPath.descendant(:label)[XPath.string.n.is(locator)]
405
144
  end
406
145
 
407
146
  def find_by_attr(attribute, value)
@@ -418,5 +157,3 @@ module Capybara
418
157
  end
419
158
  end
420
159
  end
421
-
422
- # rubocop:enable Style/AsciiComments
@@ -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