capybara 3.8.1 → 3.33.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (242) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -0
  3. data/History.md +465 -0
  4. data/License.txt +1 -1
  5. data/README.md +58 -57
  6. data/lib/capybara/config.rb +10 -4
  7. data/lib/capybara/cucumber.rb +1 -1
  8. data/lib/capybara/driver/base.rb +2 -2
  9. data/lib/capybara/driver/node.rb +26 -5
  10. data/lib/capybara/dsl.rb +12 -4
  11. data/lib/capybara/helpers.rb +8 -4
  12. data/lib/capybara/minitest/spec.rb +162 -85
  13. data/lib/capybara/minitest.rb +248 -148
  14. data/lib/capybara/node/actions.rb +149 -96
  15. data/lib/capybara/node/base.rb +27 -10
  16. data/lib/capybara/node/document.rb +12 -0
  17. data/lib/capybara/node/document_matchers.rb +9 -5
  18. data/lib/capybara/node/element.rb +254 -109
  19. data/lib/capybara/node/finders.rb +83 -76
  20. data/lib/capybara/node/matchers.rb +279 -141
  21. data/lib/capybara/node/simple.rb +25 -6
  22. data/lib/capybara/queries/ancestor_query.rb +5 -7
  23. data/lib/capybara/queries/base_query.rb +11 -5
  24. data/lib/capybara/queries/current_path_query.rb +3 -3
  25. data/lib/capybara/queries/match_query.rb +1 -0
  26. data/lib/capybara/queries/selector_query.rb +467 -103
  27. data/lib/capybara/queries/sibling_query.rb +5 -4
  28. data/lib/capybara/queries/style_query.rb +6 -2
  29. data/lib/capybara/queries/text_query.rb +17 -3
  30. data/lib/capybara/queries/title_query.rb +2 -2
  31. data/lib/capybara/rack_test/browser.rb +22 -15
  32. data/lib/capybara/rack_test/driver.rb +10 -1
  33. data/lib/capybara/rack_test/errors.rb +6 -0
  34. data/lib/capybara/rack_test/form.rb +33 -28
  35. data/lib/capybara/rack_test/node.rb +74 -6
  36. data/lib/capybara/registration_container.rb +44 -0
  37. data/lib/capybara/registrations/drivers.rb +36 -0
  38. data/lib/capybara/registrations/patches/puma_ssl.rb +27 -0
  39. data/lib/capybara/registrations/servers.rb +44 -0
  40. data/lib/capybara/result.rb +55 -23
  41. data/lib/capybara/rspec/features.rb +4 -4
  42. data/lib/capybara/rspec/matcher_proxies.rb +36 -15
  43. data/lib/capybara/rspec/matchers/base.rb +111 -0
  44. data/lib/capybara/rspec/matchers/become_closed.rb +33 -0
  45. data/lib/capybara/rspec/matchers/compound.rb +88 -0
  46. data/lib/capybara/rspec/matchers/count_sugar.rb +37 -0
  47. data/lib/capybara/rspec/matchers/have_ancestor.rb +28 -0
  48. data/lib/capybara/rspec/matchers/have_current_path.rb +29 -0
  49. data/lib/capybara/rspec/matchers/have_selector.rb +77 -0
  50. data/lib/capybara/rspec/matchers/have_sibling.rb +27 -0
  51. data/lib/capybara/rspec/matchers/have_text.rb +33 -0
  52. data/lib/capybara/rspec/matchers/have_title.rb +29 -0
  53. data/lib/capybara/rspec/matchers/match_selector.rb +27 -0
  54. data/lib/capybara/rspec/matchers/match_style.rb +38 -0
  55. data/lib/capybara/rspec/matchers/spatial_sugar.rb +39 -0
  56. data/lib/capybara/rspec/matchers.rb +117 -311
  57. data/lib/capybara/selector/builders/css_builder.rb +84 -0
  58. data/lib/capybara/selector/builders/xpath_builder.rb +69 -0
  59. data/lib/capybara/selector/css.rb +17 -15
  60. data/lib/capybara/selector/definition/button.rb +52 -0
  61. data/lib/capybara/selector/definition/checkbox.rb +26 -0
  62. data/lib/capybara/selector/definition/css.rb +10 -0
  63. data/lib/capybara/selector/definition/datalist_input.rb +35 -0
  64. data/lib/capybara/selector/definition/datalist_option.rb +25 -0
  65. data/lib/capybara/selector/definition/element.rb +27 -0
  66. data/lib/capybara/selector/definition/field.rb +40 -0
  67. data/lib/capybara/selector/definition/fieldset.rb +14 -0
  68. data/lib/capybara/selector/definition/file_field.rb +13 -0
  69. data/lib/capybara/selector/definition/fillable_field.rb +33 -0
  70. data/lib/capybara/selector/definition/frame.rb +17 -0
  71. data/lib/capybara/selector/definition/id.rb +6 -0
  72. data/lib/capybara/selector/definition/label.rb +62 -0
  73. data/lib/capybara/selector/definition/link.rb +54 -0
  74. data/lib/capybara/selector/definition/link_or_button.rb +16 -0
  75. data/lib/capybara/selector/definition/option.rb +27 -0
  76. data/lib/capybara/selector/definition/radio_button.rb +27 -0
  77. data/lib/capybara/selector/definition/select.rb +81 -0
  78. data/lib/capybara/selector/definition/table.rb +109 -0
  79. data/lib/capybara/selector/definition/table_row.rb +21 -0
  80. data/lib/capybara/selector/definition/xpath.rb +5 -0
  81. data/lib/capybara/selector/definition.rb +277 -0
  82. data/lib/capybara/selector/filter.rb +1 -0
  83. data/lib/capybara/selector/filter_set.rb +26 -19
  84. data/lib/capybara/selector/filters/base.rb +24 -5
  85. data/lib/capybara/selector/filters/expression_filter.rb +3 -3
  86. data/lib/capybara/selector/filters/locator_filter.rb +29 -0
  87. data/lib/capybara/selector/filters/node_filter.rb +16 -2
  88. data/lib/capybara/selector/regexp_disassembler.rb +214 -0
  89. data/lib/capybara/selector/selector.rb +73 -367
  90. data/lib/capybara/selector/xpath_extensions.rb +17 -0
  91. data/lib/capybara/selector.rb +221 -480
  92. data/lib/capybara/selenium/atoms/getAttribute.min.js +1 -0
  93. data/lib/capybara/selenium/atoms/isDisplayed.min.js +1 -0
  94. data/lib/capybara/selenium/atoms/src/getAttribute.js +161 -0
  95. data/lib/capybara/selenium/atoms/src/isDisplayed.js +454 -0
  96. data/lib/capybara/selenium/driver.rb +203 -86
  97. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +88 -14
  98. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +124 -0
  99. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +89 -0
  100. data/lib/capybara/selenium/driver_specializations/internet_explorer_driver.rb +26 -0
  101. data/lib/capybara/selenium/driver_specializations/safari_driver.rb +24 -0
  102. data/lib/capybara/selenium/extensions/file_input_click_emulation.rb +34 -0
  103. data/lib/capybara/selenium/extensions/find.rb +110 -0
  104. data/lib/capybara/selenium/extensions/html5_drag.rb +191 -22
  105. data/lib/capybara/selenium/extensions/modifier_keys_stack.rb +28 -0
  106. data/lib/capybara/selenium/extensions/scroll.rb +78 -0
  107. data/lib/capybara/selenium/logger_suppressor.rb +34 -0
  108. data/lib/capybara/selenium/node.rb +298 -93
  109. data/lib/capybara/selenium/nodes/chrome_node.rb +100 -8
  110. data/lib/capybara/selenium/nodes/edge_node.rb +104 -0
  111. data/lib/capybara/selenium/nodes/firefox_node.rb +131 -0
  112. data/lib/capybara/selenium/nodes/ie_node.rb +22 -0
  113. data/lib/capybara/selenium/nodes/safari_node.rb +118 -0
  114. data/lib/capybara/selenium/patches/action_pauser.rb +26 -0
  115. data/lib/capybara/selenium/patches/atoms.rb +18 -0
  116. data/lib/capybara/selenium/patches/is_displayed.rb +16 -0
  117. data/lib/capybara/selenium/patches/logs.rb +45 -0
  118. data/lib/capybara/selenium/patches/pause_duration_fix.rb +1 -3
  119. data/lib/capybara/selenium/patches/persistent_client.rb +20 -0
  120. data/lib/capybara/server/animation_disabler.rb +4 -3
  121. data/lib/capybara/server/checker.rb +6 -2
  122. data/lib/capybara/server/middleware.rb +23 -13
  123. data/lib/capybara/server.rb +30 -7
  124. data/lib/capybara/session/config.rb +14 -10
  125. data/lib/capybara/session/matchers.rb +11 -7
  126. data/lib/capybara/session.rb +152 -111
  127. data/lib/capybara/spec/public/offset.js +6 -0
  128. data/lib/capybara/spec/public/test.js +101 -10
  129. data/lib/capybara/spec/session/all_spec.rb +96 -6
  130. data/lib/capybara/spec/session/ancestor_spec.rb +5 -0
  131. data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +29 -0
  132. data/lib/capybara/spec/session/assert_current_path_spec.rb +5 -2
  133. data/lib/capybara/spec/session/assert_selector_spec.rb +0 -10
  134. data/lib/capybara/spec/session/assert_style_spec.rb +4 -4
  135. data/lib/capybara/spec/session/assert_text_spec.rb +9 -5
  136. data/lib/capybara/spec/session/attach_file_spec.rb +63 -36
  137. data/lib/capybara/spec/session/check_spec.rb +10 -4
  138. data/lib/capybara/spec/session/choose_spec.rb +8 -2
  139. data/lib/capybara/spec/session/click_button_spec.rb +117 -61
  140. data/lib/capybara/spec/session/click_link_or_button_spec.rb +16 -0
  141. data/lib/capybara/spec/session/click_link_spec.rb +17 -6
  142. data/lib/capybara/spec/session/element/matches_selector_spec.rb +40 -39
  143. data/lib/capybara/spec/session/evaluate_script_spec.rb +13 -0
  144. data/lib/capybara/spec/session/execute_script_spec.rb +1 -0
  145. data/lib/capybara/spec/session/fill_in_spec.rb +47 -6
  146. data/lib/capybara/spec/session/find_field_spec.rb +1 -1
  147. data/lib/capybara/spec/session/find_spec.rb +74 -4
  148. data/lib/capybara/spec/session/first_spec.rb +1 -1
  149. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +13 -1
  150. data/lib/capybara/spec/session/frame/within_frame_spec.rb +12 -1
  151. data/lib/capybara/spec/session/has_all_selectors_spec.rb +1 -1
  152. data/lib/capybara/spec/session/has_ancestor_spec.rb +46 -0
  153. data/lib/capybara/spec/session/has_any_selectors_spec.rb +25 -0
  154. data/lib/capybara/spec/session/has_button_spec.rb +16 -0
  155. data/lib/capybara/spec/session/has_css_spec.rb +122 -12
  156. data/lib/capybara/spec/session/has_current_path_spec.rb +6 -4
  157. data/lib/capybara/spec/session/has_field_spec.rb +55 -0
  158. data/lib/capybara/spec/session/has_select_spec.rb +34 -6
  159. data/lib/capybara/spec/session/has_selector_spec.rb +11 -4
  160. data/lib/capybara/spec/session/has_sibling_spec.rb +50 -0
  161. data/lib/capybara/spec/session/has_table_spec.rb +166 -0
  162. data/lib/capybara/spec/session/has_text_spec.rb +48 -1
  163. data/lib/capybara/spec/session/has_xpath_spec.rb +17 -0
  164. data/lib/capybara/spec/session/html_spec.rb +7 -0
  165. data/lib/capybara/spec/session/matches_style_spec.rb +35 -0
  166. data/lib/capybara/spec/session/node_spec.rb +643 -18
  167. data/lib/capybara/spec/session/node_wrapper_spec.rb +1 -1
  168. data/lib/capybara/spec/session/refresh_spec.rb +4 -0
  169. data/lib/capybara/spec/session/reset_session_spec.rb +23 -8
  170. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +2 -2
  171. data/lib/capybara/spec/session/save_screenshot_spec.rb +4 -4
  172. data/lib/capybara/spec/session/scroll_spec.rb +117 -0
  173. data/lib/capybara/spec/session/select_spec.rb +10 -10
  174. data/lib/capybara/spec/session/selectors_spec.rb +36 -5
  175. data/lib/capybara/spec/session/uncheck_spec.rb +2 -2
  176. data/lib/capybara/spec/session/unselect_spec.rb +1 -1
  177. data/lib/capybara/spec/session/window/become_closed_spec.rb +20 -17
  178. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +4 -0
  179. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +4 -0
  180. data/lib/capybara/spec/session/window/window_spec.rb +59 -58
  181. data/lib/capybara/spec/session/window/windows_spec.rb +4 -0
  182. data/lib/capybara/spec/session/within_spec.rb +23 -0
  183. data/lib/capybara/spec/spec_helper.rb +16 -6
  184. data/lib/capybara/spec/test_app.rb +28 -23
  185. data/lib/capybara/spec/views/animated.erb +49 -0
  186. data/lib/capybara/spec/views/form.erb +48 -7
  187. data/lib/capybara/spec/views/frame_child.erb +3 -2
  188. data/lib/capybara/spec/views/frame_one.erb +1 -0
  189. data/lib/capybara/spec/views/obscured.erb +47 -0
  190. data/lib/capybara/spec/views/offset.erb +32 -0
  191. data/lib/capybara/spec/views/react.erb +45 -0
  192. data/lib/capybara/spec/views/scroll.erb +20 -0
  193. data/lib/capybara/spec/views/spatial.erb +31 -0
  194. data/lib/capybara/spec/views/tables.erb +67 -0
  195. data/lib/capybara/spec/views/with_animation.erb +29 -1
  196. data/lib/capybara/spec/views/with_dragula.erb +24 -0
  197. data/lib/capybara/spec/views/with_hover.erb +1 -0
  198. data/lib/capybara/spec/views/with_hover1.erb +10 -0
  199. data/lib/capybara/spec/views/with_html.erb +32 -6
  200. data/lib/capybara/spec/views/with_js.erb +3 -1
  201. data/lib/capybara/spec/views/with_jstree.erb +26 -0
  202. data/lib/capybara/spec/views/with_scope_other.erb +6 -0
  203. data/lib/capybara/spec/views/with_sortable_js.erb +21 -0
  204. data/lib/capybara/version.rb +1 -1
  205. data/lib/capybara/window.rb +11 -11
  206. data/lib/capybara.rb +118 -111
  207. data/spec/basic_node_spec.rb +14 -3
  208. data/spec/capybara_spec.rb +29 -29
  209. data/spec/css_builder_spec.rb +101 -0
  210. data/spec/dsl_spec.rb +46 -21
  211. data/spec/filter_set_spec.rb +5 -5
  212. data/spec/fixtures/selenium_driver_rspec_failure.rb +1 -1
  213. data/spec/fixtures/selenium_driver_rspec_success.rb +1 -1
  214. data/spec/minitest_spec.rb +18 -4
  215. data/spec/minitest_spec_spec.rb +59 -44
  216. data/spec/rack_test_spec.rb +117 -89
  217. data/spec/regexp_dissassembler_spec.rb +250 -0
  218. data/spec/result_spec.rb +51 -49
  219. data/spec/rspec/features_spec.rb +3 -0
  220. data/spec/rspec/shared_spec_matchers.rb +112 -97
  221. data/spec/rspec_spec.rb +35 -17
  222. data/spec/sauce_spec_chrome.rb +43 -0
  223. data/spec/selector_spec.rb +244 -28
  224. data/spec/selenium_spec_chrome.rb +125 -54
  225. data/spec/selenium_spec_chrome_remote.rb +26 -12
  226. data/spec/selenium_spec_edge.rb +23 -8
  227. data/spec/selenium_spec_firefox.rb +208 -0
  228. data/spec/selenium_spec_firefox_remote.rb +15 -18
  229. data/spec/selenium_spec_ie.rb +82 -13
  230. data/spec/selenium_spec_safari.rb +148 -0
  231. data/spec/server_spec.rb +118 -77
  232. data/spec/session_spec.rb +19 -3
  233. data/spec/shared_selenium_node.rb +83 -0
  234. data/spec/shared_selenium_session.rb +110 -65
  235. data/spec/spec_helper.rb +57 -9
  236. data/spec/xpath_builder_spec.rb +93 -0
  237. metadata +257 -17
  238. data/lib/capybara/rspec/compound.rb +0 -94
  239. data/lib/capybara/selenium/driver_specializations/marionette_driver.rb +0 -49
  240. data/lib/capybara/selenium/nodes/marionette_node.rb +0 -121
  241. data/lib/capybara/spec/session/has_style_spec.rb +0 -25
  242. data/spec/selenium_spec_marionette.rb +0 -172
@@ -1,407 +1,127 @@
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, Capybara.test_id attribute, 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, Capybara.test_id attribute, 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, Capybara.test_id attribute, 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, Capybara.test_id attribute, 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, Capybara.test_id attribute, 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, Capybara.test_id attribute, 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, Capybara.test_id attribute, 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
167
8
  end
168
9
 
169
- def add(name, &block)
170
- 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)
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
25
 
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
- @format = nil
189
- @expression = nil
190
- @expression_filters = {}
191
- @default_visibility = nil
192
- @config = {
193
- enable_aria_label: false,
194
- test_id: nil
195
- }
196
- instance_eval(&block)
26
+ def for(locator)
27
+ all.values.find { |sel| sel.match?(locator) }
28
+ end
197
29
  end
198
30
 
199
- def custom_filters
200
- warn "Deprecated: Selector#custom_filters is not valid when same named expression and node filter exist - don't use"
201
- node_filters.merge(expression_filters).freeze
202
- end
31
+ attr_reader :errors
203
32
 
204
- def node_filters
205
- @filter_set.node_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 = []
206
40
  end
207
41
 
208
- def expression_filters
209
- @filter_set.expression_filters
210
- end
211
-
212
- ##
213
- #
214
- # Define a selector by an xpath expression
215
- #
216
- # @overload xpath(*expression_filters, &block)
217
- # @param [Array<Symbol>] expression_filters ([]) Names of filters that can be implemented via this expression
218
- # @yield [locator, options] The block to use to generate the XPath expression
219
- # @yieldparam [String] locator The locator string passed to the query
220
- # @yieldparam [Hash] options The options hash passed to the query
221
- # @yieldreturn [#to_xpath, #to_s] An object that can produce an xpath expression
222
- #
223
- # @overload xpath()
224
- # @return [#call] The block that will be called to generate the XPath expression
225
- #
226
- def xpath(*allowed_filters, &block)
227
- if block
228
- @format, @expression = :xpath, block
229
- allowed_filters.flatten.each { |ef| expression_filters[ef] = Filters::IdentityExpressionFilter.new(ef) }
230
- end
231
- format == :xpath ? @expression : nil
42
+ def format
43
+ @format || @definition.default_format
232
44
  end
45
+ alias_method :current_format, :format
233
46
 
234
- ##
235
- #
236
- # Define a selector by a CSS selector
237
- #
238
- # @overload css(*expression_filters, &block)
239
- # @param [Array<Symbol>] expression_filters ([]) Names of filters that can be implemented via this CSS selector
240
- # @yield [locator, options] The block to use to generate the CSS selector
241
- # @yieldparam [String] locator The locator string passed to the query
242
- # @yieldparam [Hash] options The options hash passed to the query
243
- # @yieldreturn [#to_s] An object that can produce a CSS selector
244
- #
245
- # @overload css()
246
- # @return [#call] The block that will be called to generate the CSS selector
247
- #
248
- def css(*allowed_filters, &block)
249
- if block
250
- @format, @expression = :css, block
251
- allowed_filters.flatten.each { |ef| expression_filters[ef] = nil }
252
- end
253
- format == :css ? @expression : nil
47
+ def enable_aria_label
48
+ @config[:enable_aria_label]
254
49
  end
255
50
 
256
- ##
257
- #
258
- # Automatic selector detection
259
- #
260
- # @yield [locator] This block takes the passed in locator string and returns whether or not it matches the selector
261
- # @yieldparam [String], locator The locator string used to determin if it matches the selector
262
- # @yieldreturn [Boolean] Whether this selector matches the locator string
263
- # @return [#call] The block that will be used to detect selector match
264
- #
265
- def match(&block)
266
- @match = block if block
267
- @match
51
+ def enable_aria_role
52
+ @config[:enable_aria_role]
268
53
  end
269
54
 
270
- ##
271
- #
272
- # Set/get a descriptive label for the selector
273
- #
274
- # @overload label(label)
275
- # @param [String] label A descriptive label for this selector - used in error messages
276
- # @overload label()
277
- # @return [String] The currently set label
278
- #
279
- def label(label = nil)
280
- @label = label if label
281
- @label
55
+ def test_id
56
+ @config[:test_id]
282
57
  end
283
58
 
284
- ##
285
- #
286
- # Description of the selector
287
- #
288
- # @!method description(options)
289
- # @param [Hash] options The options of the query used to generate the description
290
- # @return [String] Description of the selector when used with the options passed
291
- def_delegator :@filter_set, :description
292
-
293
- def call(locator, selector_config: {}, **options)
294
- @config.merge! selector_config
59
+ def call(locator, **options)
295
60
  if format
296
- @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])
297
64
  else
298
65
  warn 'Selector has no format'
299
66
  end
67
+ ensure
68
+ unless locator_valid?(locator)
69
+ warn "Locator #{locator.class}:#{locator.inspect} for selector #{name.inspect} must #{locator_description}. This will raise an error in a future version of Capybara."
70
+ end
300
71
  end
301
72
 
302
- ##
303
- #
304
- # Should this selector be used for the passed in locator
305
- #
306
- # This is used by the automatic selector selection mechanism when no selector type is passed to a selector query
307
- #
308
- # @param [String] locator The locator passed to the query
309
- # @return [Boolean] Whether or not to use this selector
310
- #
311
- def match?(locator)
312
- @match&.call(locator)
313
- end
314
-
315
- ##
316
- #
317
- # Define a node filter for use with this selector
318
- #
319
- # @!method node_filter(name, *types, options={}, &block)
320
- # @param [Symbol, Regexp] name The filter name
321
- # @param [Array<Symbol>] types The types of the filter - currently valid types are [:boolean]
322
- # @param [Hash] options ({}) Options of the filter
323
- # @option options [Array<>] :valid_values Valid values for this filter
324
- # @option options :default The default value of the filter (if any)
325
- # @option options :skip_if Value of the filter that will cause it to be skipped
326
- # @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.
327
- #
328
- # If a Symbol is passed for the name the block should accept | node, option_value |, while if a Regexp
329
- # is passed for the name the block should accept | node, option_name, option_value |. In either case
330
- # the block should return `true` if the node passes the filer or `false` if it doesn't
331
-
332
- # @!method filter
333
- # See {Selector#node_filter}
334
-
335
- ##
336
- #
337
- # Define an expression filter for use with this selector
338
- #
339
- # @!method expression_filter(name, *types, matcher: nil, **options, &block)
340
- # @param [Symbol, Regexp] name The filter name
341
- # @param [Regexp] matcher (nil) A Regexp used to check whether a specific option is handled by this filter
342
- # @param [Array<Symbol>] types The types of the filter - currently valid types are [:boolean]
343
- # @param [Hash] options ({}) Options of the filter
344
- # @option options [Array<>] :valid_values Valid values for this filter
345
- # @option options :default The default value of the filter (if any)
346
- # @option options :skip_if Value of the filter that will cause it to be skipped
347
- # @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.
348
- #
349
- # If a Symbol is passed for the name the block should accept | current_expression, option_value |, while if a Regexp
350
- # is passed for the name the block should accept | current_expression, option_name, option_value |. In either case
351
- # the block should return the modified expression
352
-
353
- def_delegators :@filter_set, :node_filter, :expression_filter, :filter
354
-
355
- def filter_set(name, filters_to_use = nil)
356
- @filter_set.import(name, filters_to_use)
357
- end
358
-
359
- def_delegator :@filter_set, :describe
360
-
361
- def describe_expression_filters(&block)
362
- if block_given?
363
- describe(:expression_filters, &block)
364
- else
365
- describe(:expression_filters) do |**options|
366
- describe_all_expression_filters(options)
367
- end
368
- end
73
+ def add_error(error_msg)
74
+ errors << error_msg
369
75
  end
370
76
 
371
- def describe_node_filters(&block)
372
- describe(:node_filters, &block)
77
+ def expression_for(name, locator, config: @config, format: current_format, **options)
78
+ Selector.new(name, config: config, format: format).call(locator, **options)
373
79
  end
374
80
 
375
- ##
376
- #
377
- # Set the default visibility mode that shouble be used if no visibile option is passed when using the selector.
378
- # If not specified will default to the behavior indicated by Capybara.ignore_hidden_elements
379
- #
380
- # @param [Symbol] default_visibility Only find elements with the specified visibility:
381
- # * :all - finds visible and invisible elements.
382
- # * :hidden - only finds invisible elements.
383
- # * :visible - only finds visible elements.
384
- def visible(default_visibility)
385
- @default_visibility = default_visibility
81
+ # @api private
82
+ def with_filter_errors(errors)
83
+ old_errors = @errors
84
+ @errors = errors
85
+ yield
86
+ ensure
87
+ @errors = old_errors
386
88
  end
387
89
 
388
- def default_visibility(fallback = Capybara.ignore_hidden_elements)
389
- return @default_visibility unless @default_visibility.nil?
390
- fallback
90
+ # @api private
91
+ def builder(expr = nil)
92
+ case format
93
+ when :css
94
+ Capybara::Selector::CSSBuilder
95
+ when :xpath
96
+ Capybara::Selector::XPathBuilder
97
+ else
98
+ raise NotImplementedError, "No builder exists for selector of type #{default_format}"
99
+ end.new(expr)
391
100
  end
392
101
 
393
102
  private
394
103
 
395
- def enable_aria_label
396
- @config[:enable_aria_label]
104
+ def locator_description
105
+ locator_types.group_by { |lt| lt.is_a? Symbol }.map do |symbol, types_or_methods|
106
+ if symbol
107
+ "respond to #{types_or_methods.join(' or ')}"
108
+ else
109
+ "be an instance of #{types_or_methods.join(' or ')}"
110
+ end
111
+ end.join(' or ')
397
112
  end
398
113
 
399
- def test_id
400
- @config[:test_id]
114
+ def locator_valid?(locator)
115
+ return true unless locator && locator_types
116
+
117
+ locator_types&.any? do |type_or_method|
118
+ type_or_method.is_a?(Symbol) ? locator.respond_to?(type_or_method) : type_or_method === locator # rubocop:disable Style/CaseEquality
119
+ end
401
120
  end
402
121
 
403
122
  def locate_field(xpath, locator, **_options)
404
123
  return xpath if locator.nil?
124
+
405
125
  locate_xpath = xpath # Need to save original xpath for the label wrap
406
126
  locator = locator.to_s
407
127
  attr_matchers = [XPath.attr(:id) == locator,
@@ -415,18 +135,6 @@ module Capybara
415
135
  locate_xpath + XPath.descendant(:label)[XPath.string.n.is(locator)].descendant(xpath)
416
136
  end
417
137
 
418
- def describe_all_expression_filters(**opts)
419
- expression_filters.map do |ef_name, ef|
420
- if ef.matcher?
421
- opts.keys.map do |key|
422
- " with #{ef_name}[#{key} => #{opts[key]}]" if ef.handles_option?(key) && !::Capybara::Queries::SelectorQuery::VALID_KEYS.include?(key)
423
- end.join
424
- elsif opts.key?(ef_name)
425
- " with #{ef_name} #{opts[ef_name]}"
426
- end
427
- end.join
428
- end
429
-
430
138
  def find_by_attr(attribute, value)
431
139
  finder_name = "find_by_#{attribute}_attr"
432
140
  if respond_to?(finder_name, true)
@@ -441,5 +149,3 @@ module Capybara
441
149
  end
442
150
  end
443
151
  end
444
-
445
- # 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