capybara 3.3.0 → 3.40.0

Sign up to get free protection for your applications and to get access to all the features.
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