capybara 3.32.2

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 (313) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +1 -0
  3. data/History.md +1813 -0
  4. data/License.txt +22 -0
  5. data/README.md +1099 -0
  6. data/lib/capybara.rb +511 -0
  7. data/lib/capybara/config.rb +94 -0
  8. data/lib/capybara/cucumber.rb +27 -0
  9. data/lib/capybara/driver/base.rb +170 -0
  10. data/lib/capybara/driver/node.rb +139 -0
  11. data/lib/capybara/dsl.rb +65 -0
  12. data/lib/capybara/helpers.rb +108 -0
  13. data/lib/capybara/minitest.rb +386 -0
  14. data/lib/capybara/minitest/spec.rb +264 -0
  15. data/lib/capybara/node/actions.rb +420 -0
  16. data/lib/capybara/node/base.rb +143 -0
  17. data/lib/capybara/node/document.rb +48 -0
  18. data/lib/capybara/node/document_matchers.rb +67 -0
  19. data/lib/capybara/node/element.rb +606 -0
  20. data/lib/capybara/node/finders.rb +325 -0
  21. data/lib/capybara/node/matchers.rb +883 -0
  22. data/lib/capybara/node/simple.rb +208 -0
  23. data/lib/capybara/queries/ancestor_query.rb +27 -0
  24. data/lib/capybara/queries/base_query.rb +106 -0
  25. data/lib/capybara/queries/current_path_query.rb +51 -0
  26. data/lib/capybara/queries/match_query.rb +26 -0
  27. data/lib/capybara/queries/selector_query.rb +710 -0
  28. data/lib/capybara/queries/sibling_query.rb +26 -0
  29. data/lib/capybara/queries/style_query.rb +45 -0
  30. data/lib/capybara/queries/text_query.rb +110 -0
  31. data/lib/capybara/queries/title_query.rb +39 -0
  32. data/lib/capybara/rack_test/browser.rb +140 -0
  33. data/lib/capybara/rack_test/css_handlers.rb +13 -0
  34. data/lib/capybara/rack_test/driver.rb +109 -0
  35. data/lib/capybara/rack_test/errors.rb +6 -0
  36. data/lib/capybara/rack_test/form.rb +127 -0
  37. data/lib/capybara/rack_test/node.rb +325 -0
  38. data/lib/capybara/rails.rb +16 -0
  39. data/lib/capybara/registrations/drivers.rb +36 -0
  40. data/lib/capybara/registrations/patches/puma_ssl.rb +27 -0
  41. data/lib/capybara/registrations/servers.rb +44 -0
  42. data/lib/capybara/result.rb +190 -0
  43. data/lib/capybara/rspec.rb +29 -0
  44. data/lib/capybara/rspec/features.rb +23 -0
  45. data/lib/capybara/rspec/matcher_proxies.rb +82 -0
  46. data/lib/capybara/rspec/matchers.rb +201 -0
  47. data/lib/capybara/rspec/matchers/base.rb +111 -0
  48. data/lib/capybara/rspec/matchers/become_closed.rb +33 -0
  49. data/lib/capybara/rspec/matchers/compound.rb +88 -0
  50. data/lib/capybara/rspec/matchers/count_sugar.rb +37 -0
  51. data/lib/capybara/rspec/matchers/have_ancestor.rb +28 -0
  52. data/lib/capybara/rspec/matchers/have_current_path.rb +29 -0
  53. data/lib/capybara/rspec/matchers/have_selector.rb +77 -0
  54. data/lib/capybara/rspec/matchers/have_sibling.rb +27 -0
  55. data/lib/capybara/rspec/matchers/have_text.rb +33 -0
  56. data/lib/capybara/rspec/matchers/have_title.rb +29 -0
  57. data/lib/capybara/rspec/matchers/match_selector.rb +27 -0
  58. data/lib/capybara/rspec/matchers/match_style.rb +38 -0
  59. data/lib/capybara/rspec/matchers/spatial_sugar.rb +39 -0
  60. data/lib/capybara/selector.rb +233 -0
  61. data/lib/capybara/selector/builders/css_builder.rb +84 -0
  62. data/lib/capybara/selector/builders/xpath_builder.rb +69 -0
  63. data/lib/capybara/selector/css.rb +102 -0
  64. data/lib/capybara/selector/definition.rb +276 -0
  65. data/lib/capybara/selector/definition/button.rb +51 -0
  66. data/lib/capybara/selector/definition/checkbox.rb +26 -0
  67. data/lib/capybara/selector/definition/css.rb +10 -0
  68. data/lib/capybara/selector/definition/datalist_input.rb +35 -0
  69. data/lib/capybara/selector/definition/datalist_option.rb +25 -0
  70. data/lib/capybara/selector/definition/element.rb +27 -0
  71. data/lib/capybara/selector/definition/field.rb +40 -0
  72. data/lib/capybara/selector/definition/fieldset.rb +14 -0
  73. data/lib/capybara/selector/definition/file_field.rb +13 -0
  74. data/lib/capybara/selector/definition/fillable_field.rb +33 -0
  75. data/lib/capybara/selector/definition/frame.rb +17 -0
  76. data/lib/capybara/selector/definition/id.rb +6 -0
  77. data/lib/capybara/selector/definition/label.rb +62 -0
  78. data/lib/capybara/selector/definition/link.rb +46 -0
  79. data/lib/capybara/selector/definition/link_or_button.rb +16 -0
  80. data/lib/capybara/selector/definition/option.rb +27 -0
  81. data/lib/capybara/selector/definition/radio_button.rb +27 -0
  82. data/lib/capybara/selector/definition/select.rb +81 -0
  83. data/lib/capybara/selector/definition/table.rb +109 -0
  84. data/lib/capybara/selector/definition/table_row.rb +21 -0
  85. data/lib/capybara/selector/definition/xpath.rb +5 -0
  86. data/lib/capybara/selector/filter.rb +5 -0
  87. data/lib/capybara/selector/filter_set.rb +124 -0
  88. data/lib/capybara/selector/filters/base.rb +77 -0
  89. data/lib/capybara/selector/filters/expression_filter.rb +22 -0
  90. data/lib/capybara/selector/filters/locator_filter.rb +29 -0
  91. data/lib/capybara/selector/filters/node_filter.rb +31 -0
  92. data/lib/capybara/selector/regexp_disassembler.rb +214 -0
  93. data/lib/capybara/selector/selector.rb +147 -0
  94. data/lib/capybara/selector/xpath_extensions.rb +17 -0
  95. data/lib/capybara/selenium/atoms/getAttribute.min.js +1 -0
  96. data/lib/capybara/selenium/atoms/isDisplayed.min.js +1 -0
  97. data/lib/capybara/selenium/atoms/src/getAttribute.js +161 -0
  98. data/lib/capybara/selenium/atoms/src/isDisplayed.js +454 -0
  99. data/lib/capybara/selenium/driver.rb +496 -0
  100. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +119 -0
  101. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +126 -0
  102. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +89 -0
  103. data/lib/capybara/selenium/driver_specializations/internet_explorer_driver.rb +26 -0
  104. data/lib/capybara/selenium/driver_specializations/safari_driver.rb +24 -0
  105. data/lib/capybara/selenium/extensions/file_input_click_emulation.rb +34 -0
  106. data/lib/capybara/selenium/extensions/find.rb +110 -0
  107. data/lib/capybara/selenium/extensions/html5_drag.rb +228 -0
  108. data/lib/capybara/selenium/extensions/modifier_keys_stack.rb +28 -0
  109. data/lib/capybara/selenium/extensions/scroll.rb +78 -0
  110. data/lib/capybara/selenium/logger_suppressor.rb +34 -0
  111. data/lib/capybara/selenium/node.rb +610 -0
  112. data/lib/capybara/selenium/nodes/chrome_node.rb +119 -0
  113. data/lib/capybara/selenium/nodes/edge_node.rb +104 -0
  114. data/lib/capybara/selenium/nodes/firefox_node.rb +131 -0
  115. data/lib/capybara/selenium/nodes/ie_node.rb +22 -0
  116. data/lib/capybara/selenium/nodes/safari_node.rb +118 -0
  117. data/lib/capybara/selenium/patches/action_pauser.rb +26 -0
  118. data/lib/capybara/selenium/patches/atoms.rb +18 -0
  119. data/lib/capybara/selenium/patches/is_displayed.rb +16 -0
  120. data/lib/capybara/selenium/patches/logs.rb +47 -0
  121. data/lib/capybara/selenium/patches/pause_duration_fix.rb +9 -0
  122. data/lib/capybara/selenium/patches/persistent_client.rb +20 -0
  123. data/lib/capybara/server.rb +126 -0
  124. data/lib/capybara/server/animation_disabler.rb +58 -0
  125. data/lib/capybara/server/checker.rb +44 -0
  126. data/lib/capybara/server/middleware.rb +69 -0
  127. data/lib/capybara/session.rb +942 -0
  128. data/lib/capybara/session/config.rb +124 -0
  129. data/lib/capybara/session/matchers.rb +87 -0
  130. data/lib/capybara/spec/fixtures/another_test_file.txt +1 -0
  131. data/lib/capybara/spec/fixtures/capybara.jpg +3 -0
  132. data/lib/capybara/spec/fixtures/no_extension +1 -0
  133. data/lib/capybara/spec/fixtures/test_file.txt +1 -0
  134. data/lib/capybara/spec/public/jquery-ui.js +13 -0
  135. data/lib/capybara/spec/public/jquery.js +5 -0
  136. data/lib/capybara/spec/public/offset.js +6 -0
  137. data/lib/capybara/spec/public/test.js +268 -0
  138. data/lib/capybara/spec/session/accept_alert_spec.rb +81 -0
  139. data/lib/capybara/spec/session/accept_confirm_spec.rb +32 -0
  140. data/lib/capybara/spec/session/accept_prompt_spec.rb +78 -0
  141. data/lib/capybara/spec/session/all_spec.rb +278 -0
  142. data/lib/capybara/spec/session/ancestor_spec.rb +88 -0
  143. data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +140 -0
  144. data/lib/capybara/spec/session/assert_current_path_spec.rb +75 -0
  145. data/lib/capybara/spec/session/assert_selector_spec.rb +143 -0
  146. data/lib/capybara/spec/session/assert_style_spec.rb +26 -0
  147. data/lib/capybara/spec/session/assert_text_spec.rb +258 -0
  148. data/lib/capybara/spec/session/assert_title_spec.rb +93 -0
  149. data/lib/capybara/spec/session/attach_file_spec.rb +216 -0
  150. data/lib/capybara/spec/session/body_spec.rb +23 -0
  151. data/lib/capybara/spec/session/check_spec.rb +235 -0
  152. data/lib/capybara/spec/session/choose_spec.rb +121 -0
  153. data/lib/capybara/spec/session/click_button_spec.rb +506 -0
  154. data/lib/capybara/spec/session/click_link_or_button_spec.rb +129 -0
  155. data/lib/capybara/spec/session/click_link_spec.rb +229 -0
  156. data/lib/capybara/spec/session/current_scope_spec.rb +31 -0
  157. data/lib/capybara/spec/session/current_url_spec.rb +115 -0
  158. data/lib/capybara/spec/session/dismiss_confirm_spec.rb +36 -0
  159. data/lib/capybara/spec/session/dismiss_prompt_spec.rb +21 -0
  160. data/lib/capybara/spec/session/element/assert_match_selector_spec.rb +38 -0
  161. data/lib/capybara/spec/session/element/match_css_spec.rb +31 -0
  162. data/lib/capybara/spec/session/element/match_xpath_spec.rb +25 -0
  163. data/lib/capybara/spec/session/element/matches_selector_spec.rb +120 -0
  164. data/lib/capybara/spec/session/evaluate_async_script_spec.rb +23 -0
  165. data/lib/capybara/spec/session/evaluate_script_spec.rb +49 -0
  166. data/lib/capybara/spec/session/execute_script_spec.rb +28 -0
  167. data/lib/capybara/spec/session/fill_in_spec.rb +286 -0
  168. data/lib/capybara/spec/session/find_button_spec.rb +74 -0
  169. data/lib/capybara/spec/session/find_by_id_spec.rb +33 -0
  170. data/lib/capybara/spec/session/find_field_spec.rb +113 -0
  171. data/lib/capybara/spec/session/find_link_spec.rb +70 -0
  172. data/lib/capybara/spec/session/find_spec.rb +531 -0
  173. data/lib/capybara/spec/session/first_spec.rb +156 -0
  174. data/lib/capybara/spec/session/frame/frame_title_spec.rb +23 -0
  175. data/lib/capybara/spec/session/frame/frame_url_spec.rb +23 -0
  176. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +116 -0
  177. data/lib/capybara/spec/session/frame/within_frame_spec.rb +112 -0
  178. data/lib/capybara/spec/session/go_back_spec.rb +12 -0
  179. data/lib/capybara/spec/session/go_forward_spec.rb +14 -0
  180. data/lib/capybara/spec/session/has_all_selectors_spec.rb +69 -0
  181. data/lib/capybara/spec/session/has_ancestor_spec.rb +46 -0
  182. data/lib/capybara/spec/session/has_any_selectors_spec.rb +25 -0
  183. data/lib/capybara/spec/session/has_button_spec.rb +69 -0
  184. data/lib/capybara/spec/session/has_css_spec.rb +374 -0
  185. data/lib/capybara/spec/session/has_current_path_spec.rb +138 -0
  186. data/lib/capybara/spec/session/has_field_spec.rb +349 -0
  187. data/lib/capybara/spec/session/has_link_spec.rb +39 -0
  188. data/lib/capybara/spec/session/has_none_selectors_spec.rb +78 -0
  189. data/lib/capybara/spec/session/has_select_spec.rb +310 -0
  190. data/lib/capybara/spec/session/has_selector_spec.rb +202 -0
  191. data/lib/capybara/spec/session/has_sibling_spec.rb +50 -0
  192. data/lib/capybara/spec/session/has_table_spec.rb +198 -0
  193. data/lib/capybara/spec/session/has_text_spec.rb +394 -0
  194. data/lib/capybara/spec/session/has_title_spec.rb +71 -0
  195. data/lib/capybara/spec/session/has_xpath_spec.rb +149 -0
  196. data/lib/capybara/spec/session/headers_spec.rb +8 -0
  197. data/lib/capybara/spec/session/html_spec.rb +47 -0
  198. data/lib/capybara/spec/session/matches_style_spec.rb +35 -0
  199. data/lib/capybara/spec/session/node_spec.rb +1292 -0
  200. data/lib/capybara/spec/session/node_wrapper_spec.rb +39 -0
  201. data/lib/capybara/spec/session/refresh_spec.rb +33 -0
  202. data/lib/capybara/spec/session/reset_session_spec.rb +148 -0
  203. data/lib/capybara/spec/session/response_code_spec.rb +8 -0
  204. data/lib/capybara/spec/session/save_and_open_page_spec.rb +21 -0
  205. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +43 -0
  206. data/lib/capybara/spec/session/save_page_spec.rb +110 -0
  207. data/lib/capybara/spec/session/save_screenshot_spec.rb +55 -0
  208. data/lib/capybara/spec/session/screenshot_spec.rb +18 -0
  209. data/lib/capybara/spec/session/scroll_spec.rb +117 -0
  210. data/lib/capybara/spec/session/select_spec.rb +229 -0
  211. data/lib/capybara/spec/session/selectors_spec.rb +98 -0
  212. data/lib/capybara/spec/session/sibling_spec.rb +52 -0
  213. data/lib/capybara/spec/session/source_spec.rb +0 -0
  214. data/lib/capybara/spec/session/text_spec.rb +74 -0
  215. data/lib/capybara/spec/session/title_spec.rb +29 -0
  216. data/lib/capybara/spec/session/uncheck_spec.rb +100 -0
  217. data/lib/capybara/spec/session/unselect_spec.rb +116 -0
  218. data/lib/capybara/spec/session/visit_spec.rb +204 -0
  219. data/lib/capybara/spec/session/window/become_closed_spec.rb +89 -0
  220. data/lib/capybara/spec/session/window/current_window_spec.rb +28 -0
  221. data/lib/capybara/spec/session/window/open_new_window_spec.rb +31 -0
  222. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +132 -0
  223. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +99 -0
  224. data/lib/capybara/spec/session/window/window_spec.rb +203 -0
  225. data/lib/capybara/spec/session/window/windows_spec.rb +34 -0
  226. data/lib/capybara/spec/session/window/within_window_spec.rb +157 -0
  227. data/lib/capybara/spec/session/within_spec.rb +199 -0
  228. data/lib/capybara/spec/spec_helper.rb +134 -0
  229. data/lib/capybara/spec/test_app.rb +226 -0
  230. data/lib/capybara/spec/views/animated.erb +49 -0
  231. data/lib/capybara/spec/views/buttons.erb +5 -0
  232. data/lib/capybara/spec/views/fieldsets.erb +30 -0
  233. data/lib/capybara/spec/views/form.erb +685 -0
  234. data/lib/capybara/spec/views/frame_child.erb +18 -0
  235. data/lib/capybara/spec/views/frame_one.erb +10 -0
  236. data/lib/capybara/spec/views/frame_parent.erb +9 -0
  237. data/lib/capybara/spec/views/frame_two.erb +9 -0
  238. data/lib/capybara/spec/views/header_links.erb +8 -0
  239. data/lib/capybara/spec/views/host_links.erb +13 -0
  240. data/lib/capybara/spec/views/initial_alert.erb +10 -0
  241. data/lib/capybara/spec/views/obscured.erb +47 -0
  242. data/lib/capybara/spec/views/offset.erb +32 -0
  243. data/lib/capybara/spec/views/path.erb +13 -0
  244. data/lib/capybara/spec/views/popup_one.erb +9 -0
  245. data/lib/capybara/spec/views/popup_two.erb +9 -0
  246. data/lib/capybara/spec/views/postback.erb +14 -0
  247. data/lib/capybara/spec/views/react.erb +45 -0
  248. data/lib/capybara/spec/views/scroll.erb +20 -0
  249. data/lib/capybara/spec/views/spatial.erb +31 -0
  250. data/lib/capybara/spec/views/tables.erb +130 -0
  251. data/lib/capybara/spec/views/with_animation.erb +74 -0
  252. data/lib/capybara/spec/views/with_base_tag.erb +11 -0
  253. data/lib/capybara/spec/views/with_count.erb +8 -0
  254. data/lib/capybara/spec/views/with_dragula.erb +22 -0
  255. data/lib/capybara/spec/views/with_fixed_header_footer.erb +17 -0
  256. data/lib/capybara/spec/views/with_hover.erb +24 -0
  257. data/lib/capybara/spec/views/with_hover1.erb +10 -0
  258. data/lib/capybara/spec/views/with_html.erb +208 -0
  259. data/lib/capybara/spec/views/with_html5_svg.erb +20 -0
  260. data/lib/capybara/spec/views/with_html_entities.erb +2 -0
  261. data/lib/capybara/spec/views/with_js.erb +160 -0
  262. data/lib/capybara/spec/views/with_jstree.erb +26 -0
  263. data/lib/capybara/spec/views/with_namespace.erb +20 -0
  264. data/lib/capybara/spec/views/with_scope.erb +42 -0
  265. data/lib/capybara/spec/views/with_scope_other.erb +6 -0
  266. data/lib/capybara/spec/views/with_simple_html.erb +2 -0
  267. data/lib/capybara/spec/views/with_slow_unload.erb +17 -0
  268. data/lib/capybara/spec/views/with_sortable_js.erb +21 -0
  269. data/lib/capybara/spec/views/with_title.erb +5 -0
  270. data/lib/capybara/spec/views/with_unload_alert.erb +14 -0
  271. data/lib/capybara/spec/views/with_windows.erb +54 -0
  272. data/lib/capybara/spec/views/within_frames.erb +15 -0
  273. data/lib/capybara/version.rb +5 -0
  274. data/lib/capybara/window.rb +146 -0
  275. data/spec/basic_node_spec.rb +154 -0
  276. data/spec/capybara_spec.rb +112 -0
  277. data/spec/css_builder_spec.rb +101 -0
  278. data/spec/css_splitter_spec.rb +38 -0
  279. data/spec/dsl_spec.rb +276 -0
  280. data/spec/filter_set_spec.rb +46 -0
  281. data/spec/fixtures/capybara.csv +1 -0
  282. data/spec/fixtures/certificate.pem +25 -0
  283. data/spec/fixtures/key.pem +27 -0
  284. data/spec/fixtures/selenium_driver_rspec_failure.rb +13 -0
  285. data/spec/fixtures/selenium_driver_rspec_success.rb +13 -0
  286. data/spec/minitest_spec.rb +163 -0
  287. data/spec/minitest_spec_spec.rb +162 -0
  288. data/spec/per_session_config_spec.rb +68 -0
  289. data/spec/rack_test_spec.rb +268 -0
  290. data/spec/regexp_dissassembler_spec.rb +250 -0
  291. data/spec/result_spec.rb +196 -0
  292. data/spec/rspec/features_spec.rb +99 -0
  293. data/spec/rspec/scenarios_spec.rb +19 -0
  294. data/spec/rspec/shared_spec_matchers.rb +947 -0
  295. data/spec/rspec/views_spec.rb +14 -0
  296. data/spec/rspec_matchers_spec.rb +62 -0
  297. data/spec/rspec_spec.rb +145 -0
  298. data/spec/sauce_spec_chrome.rb +43 -0
  299. data/spec/selector_spec.rb +513 -0
  300. data/spec/selenium_spec_chrome.rb +188 -0
  301. data/spec/selenium_spec_chrome_remote.rb +96 -0
  302. data/spec/selenium_spec_edge.rb +47 -0
  303. data/spec/selenium_spec_firefox.rb +208 -0
  304. data/spec/selenium_spec_firefox_remote.rb +80 -0
  305. data/spec/selenium_spec_ie.rb +150 -0
  306. data/spec/selenium_spec_safari.rb +148 -0
  307. data/spec/server_spec.rb +292 -0
  308. data/spec/session_spec.rb +91 -0
  309. data/spec/shared_selenium_node.rb +83 -0
  310. data/spec/shared_selenium_session.rb +476 -0
  311. data/spec/spec_helper.rb +100 -0
  312. data/spec/xpath_builder_spec.rb +93 -0
  313. metadata +753 -0
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ Capybara.add_selector(:radio_button, locator_type: [String, Symbol]) do
4
+ label 'radio button'
5
+ xpath do |locator, allow_self: nil, **options|
6
+ xpath = XPath.axis(allow_self ? :"descendant-or-self" : :descendant, :input)[
7
+ XPath.attr(:type) == 'radio'
8
+ ]
9
+ locate_field(xpath, locator, **options)
10
+ end
11
+
12
+ filter_set(:_field, %i[checked unchecked disabled name])
13
+
14
+ node_filter(%i[option with]) do |node, value|
15
+ val = node.value
16
+ (value.is_a?(Regexp) ? value.match?(val) : val == value.to_s).tap do |res|
17
+ add_error("Expected value to be #{value.inspect} but it was #{val.inspect}") unless res
18
+ end
19
+ end
20
+
21
+ describe_node_filters do |option: nil, with: nil, **|
22
+ desc = +''
23
+ desc << " with value #{option.inspect}" if option
24
+ desc << " with value #{with.inspect}" if with
25
+ desc
26
+ end
27
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ Capybara.add_selector(:select, locator_type: [String, Symbol]) do
4
+ label 'select box'
5
+
6
+ xpath do |locator, **options|
7
+ xpath = XPath.descendant(:select)
8
+ locate_field(xpath, locator, **options)
9
+ end
10
+
11
+ filter_set(:_field, %i[disabled multiple name placeholder])
12
+
13
+ node_filter(:options) do |node, options|
14
+ actual = options_text(node)
15
+ (options.sort == actual.sort).tap do |res|
16
+ add_error("Expected options #{options.inspect} found #{actual.inspect}") unless res
17
+ end
18
+ end
19
+
20
+ node_filter(:enabled_options) do |node, options|
21
+ actual = options_text(node) { |o| !o.disabled? }
22
+ (options.sort == actual.sort).tap do |res|
23
+ add_error("Expected enabled options #{options.inspect} found #{actual.inspect}") unless res
24
+ end
25
+ end
26
+
27
+ node_filter(:disabled_options) do |node, options|
28
+ actual = options_text(node, &:disabled?)
29
+ (options.sort == actual.sort).tap do |res|
30
+ add_error("Expected disabled options #{options.inspect} found #{actual.inspect}") unless res
31
+ end
32
+ end
33
+
34
+ expression_filter(:with_options) do |expr, options|
35
+ options.inject(expr) do |xpath, option|
36
+ xpath[expression_for(:option, option)]
37
+ end
38
+ end
39
+
40
+ node_filter(:selected) do |node, selected|
41
+ actual = options_text(node, visible: false, &:selected?)
42
+ (Array(selected).sort == actual.sort).tap do |res|
43
+ add_error("Expected #{selected.inspect} to be selected found #{actual.inspect}") unless res
44
+ end
45
+ end
46
+
47
+ node_filter(:with_selected) do |node, selected|
48
+ actual = options_text(node, visible: false, &:selected?)
49
+ (Array(selected) - actual).empty?.tap do |res|
50
+ add_error("Expected at least #{selected.inspect} to be selected found #{actual.inspect}") unless res
51
+ end
52
+ end
53
+
54
+ describe_expression_filters do |with_options: nil, **|
55
+ desc = +''
56
+ desc << " with at least options #{with_options.inspect}" if with_options
57
+ desc
58
+ end
59
+
60
+ describe_node_filters do |
61
+ options: nil, disabled_options: nil, enabled_options: nil,
62
+ selected: nil, with_selected: nil,
63
+ disabled: nil, **|
64
+ desc = +''
65
+ desc << " with options #{options.inspect}" if options
66
+ desc << " with disabled options #{disabled_options.inspect}}" if disabled_options
67
+ desc << " with enabled options #{enabled_options.inspect}" if enabled_options
68
+ desc << " with #{selected.inspect} selected" if selected
69
+ desc << " with at least #{with_selected.inspect} selected" if with_selected
70
+ desc << ' which is disabled' if disabled
71
+ desc
72
+ end
73
+
74
+ def options_text(node, **opts, &filter_block)
75
+ opts[:wait] = false
76
+ opts[:visible] = false unless node.visible?
77
+ node.all(:xpath, './/option', **opts, &filter_block).map do |o|
78
+ o.text((:all if opts[:visible] == false))
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ Capybara.add_selector(:table, locator_type: [String, Symbol]) do
4
+ xpath do |locator, caption: nil, **|
5
+ xpath = XPath.descendant(:table)
6
+ unless locator.nil?
7
+ locator_matchers = (XPath.attr(:id) == locator.to_s) | XPath.descendant(:caption).is(locator.to_s)
8
+ locator_matchers |= XPath.attr(test_id) == locator if test_id
9
+ xpath = xpath[locator_matchers]
10
+ end
11
+ xpath = xpath[XPath.descendant(:caption) == caption] if caption
12
+ xpath
13
+ end
14
+
15
+ expression_filter(:with_cols, valid_values: [Array]) do |xpath, cols|
16
+ col_conditions = cols.map do |col|
17
+ if col.is_a? Hash
18
+ col.reduce(nil) do |xp, (header, cell_str)|
19
+ header = XPath.descendant(:th)[XPath.string.n.is(header)]
20
+ td = XPath.descendant(:tr)[header].descendant(:td)
21
+ cell_condition = XPath.string.n.is(cell_str)
22
+ if xp
23
+ prev_cell = XPath.ancestor(:table)[1].join(xp)
24
+ cell_condition &= (prev_cell & prev_col_position?(prev_cell))
25
+ end
26
+ td[cell_condition]
27
+ end
28
+ else
29
+ cells_xp = col.reduce(nil) do |prev_cell, cell_str|
30
+ cell_condition = XPath.string.n.is(cell_str)
31
+
32
+ if prev_cell
33
+ prev_cell = XPath.ancestor(:tr)[1].preceding_sibling(:tr).join(prev_cell)
34
+ cell_condition &= (prev_cell & prev_col_position?(prev_cell))
35
+ end
36
+
37
+ XPath.descendant(:td)[cell_condition]
38
+ end
39
+ XPath.descendant(:tr).join(cells_xp)
40
+ end
41
+ end.reduce(:&)
42
+ xpath[col_conditions]
43
+ end
44
+
45
+ expression_filter(:cols, valid_values: [Array]) do |xpath, cols|
46
+ raise ArgumentError, ':cols must be an Array of Arrays' unless cols.all? { |col| col.is_a? Array }
47
+
48
+ rows = cols.transpose
49
+ col_conditions = rows.map { |row| match_row(row, match_size: true) }.reduce(:&)
50
+ xpath[match_row_count(rows.size)][col_conditions]
51
+ end
52
+
53
+ expression_filter(:with_rows, valid_values: [Array]) do |xpath, rows|
54
+ rows_conditions = rows.map { |row| match_row(row) }.reduce(:&)
55
+ xpath[rows_conditions]
56
+ end
57
+
58
+ expression_filter(:rows, valid_values: [Array]) do |xpath, rows|
59
+ rows_conditions = rows.map { |row| match_row(row, match_size: true) }.reduce(:&)
60
+ xpath[match_row_count(rows.size)][rows_conditions]
61
+ end
62
+
63
+ describe_expression_filters do |caption: nil, **|
64
+ " with caption \"#{caption}\"" if caption
65
+ end
66
+
67
+ def prev_col_position?(cell)
68
+ XPath.position.equals(cell_position(cell))
69
+ end
70
+
71
+ def cell_position(cell)
72
+ cell.preceding_sibling(:td).count.plus(1)
73
+ end
74
+
75
+ def match_row(row, match_size: false)
76
+ xp = XPath.descendant(:tr)[
77
+ if row.is_a? Hash
78
+ row_match_cells_to_headers(row)
79
+ else
80
+ XPath.descendant(:td)[row_match_ordered_cells(row)]
81
+ end
82
+ ]
83
+ xp = xp[XPath.descendant(:td).count.equals(row.size)] if match_size
84
+ xp
85
+ end
86
+
87
+ def match_row_count(size)
88
+ XPath.descendant(:tbody).descendant(:tr).count.equals(size) |
89
+ (XPath.descendant(:tr).count.equals(size) & ~XPath.descendant(:tbody))
90
+ end
91
+
92
+ def row_match_cells_to_headers(row)
93
+ row.map do |header, cell|
94
+ header_xp = XPath.ancestor(:table)[1].descendant(:tr)[1].descendant(:th)[XPath.string.n.is(header)]
95
+ XPath.descendant(:td)[
96
+ XPath.string.n.is(cell) & XPath.position.equals(header_xp.preceding_sibling.count.plus(1))
97
+ ]
98
+ end.reduce(:&)
99
+ end
100
+
101
+ def row_match_ordered_cells(row)
102
+ row_conditions = row.map do |cell|
103
+ XPath.self(:td)[XPath.string.n.is(cell)]
104
+ end
105
+ row_conditions.reverse.reduce do |cond, cell|
106
+ cell[XPath.following_sibling[cond]]
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ Capybara.add_selector(:table_row, locator_type: [Array, Hash]) do
4
+ xpath do |locator|
5
+ xpath = XPath.descendant(:tr)
6
+ if locator.is_a? Hash
7
+ locator.reduce(xpath) do |xp, (header, cell)|
8
+ header_xp = XPath.ancestor(:table)[1].descendant(:tr)[1].descendant(:th)[XPath.string.n.is(header)]
9
+ cell_xp = XPath.descendant(:td)[
10
+ XPath.string.n.is(cell) & XPath.position.equals(header_xp.preceding_sibling.count.plus(1))
11
+ ]
12
+ xp[cell_xp]
13
+ end
14
+ else
15
+ initial_td = XPath.descendant(:td)[XPath.string.n.is(locator.shift)]
16
+ tds = locator.reverse.map { |cell| XPath.following_sibling(:td)[XPath.string.n.is(cell)] }
17
+ .reduce { |xp, cell| xp[cell] }
18
+ xpath[initial_td[tds]]
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ Capybara.add_selector(:xpath, locator_type: [:to_xpath, String], raw_locator: true) do
4
+ xpath { |xpath| xpath }
5
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'capybara/selector/filters/node_filter'
4
+ require 'capybara/selector/filters/expression_filter'
5
+ require 'capybara/selector/filters/locator_filter'
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'capybara/selector/filter'
4
+
5
+ module Capybara
6
+ class Selector
7
+ class FilterSet
8
+ attr_reader :node_filters, :expression_filters
9
+
10
+ def initialize(name, &block)
11
+ @name = name
12
+ @node_filters = {}
13
+ @expression_filters = {}
14
+ @descriptions = Hash.new { |hsh, key| hsh[key] = [] }
15
+ instance_eval(&block)
16
+ end
17
+
18
+ def node_filter(names, *types, **options, &block)
19
+ Array(names).each do |name|
20
+ add_filter(name, Filters::NodeFilter, *types, **options, &block)
21
+ end
22
+ end
23
+ alias_method :filter, :node_filter
24
+
25
+ def expression_filter(name, *types, **options, &block)
26
+ add_filter(name, Filters::ExpressionFilter, *types, **options, &block)
27
+ end
28
+
29
+ def describe(what = nil, &block)
30
+ case what
31
+ when nil
32
+ undeclared_descriptions.push block
33
+ when :node_filters
34
+ node_filter_descriptions.push block
35
+ when :expression_filters
36
+ expression_filter_descriptions.push block
37
+ else
38
+ raise ArgumentError, 'Unknown description type'
39
+ end
40
+ end
41
+
42
+ def description(node_filters: true, expression_filters: true, **options)
43
+ opts = options_with_defaults(options)
44
+ description = +''
45
+ description << undeclared_descriptions.map { |desc| desc.call(**opts).to_s }.join
46
+ description << expression_filter_descriptions.map { |desc| desc.call(**opts).to_s }.join if expression_filters
47
+ description << node_filter_descriptions.map { |desc| desc.call(**opts).to_s }.join if node_filters
48
+ description
49
+ end
50
+
51
+ def descriptions
52
+ warn 'DEPRECATED: FilterSet#descriptions is deprecated without replacement'
53
+ [undeclared_descriptions, node_filter_descriptions, expression_filter_descriptions].flatten
54
+ end
55
+
56
+ def import(name, filters = nil)
57
+ filter_selector = filters.nil? ? ->(*) { true } : ->(filter_name, _) { filters.include? filter_name }
58
+
59
+ self.class[name].tap do |f_set|
60
+ expression_filters.merge!(f_set.expression_filters.select(&filter_selector))
61
+ node_filters.merge!(f_set.node_filters.select(&filter_selector))
62
+ f_set.undeclared_descriptions.each { |desc| describe(&desc) }
63
+ f_set.expression_filter_descriptions.each { |desc| describe(:expression_filters, &desc) }
64
+ f_set.node_filter_descriptions.each { |desc| describe(:node_filters, &desc) }
65
+ end
66
+ self
67
+ end
68
+
69
+ class << self
70
+ def all
71
+ @filter_sets ||= {} # rubocop:disable Naming/MemoizedInstanceVariableName
72
+ end
73
+
74
+ def [](name)
75
+ all.fetch(name.to_sym) { |set_name| raise ArgumentError, "Unknown filter set (:#{set_name})" }
76
+ end
77
+
78
+ def add(name, &block)
79
+ all[name.to_sym] = FilterSet.new(name.to_sym, &block)
80
+ end
81
+
82
+ def remove(name)
83
+ all.delete(name.to_sym)
84
+ end
85
+ end
86
+
87
+ protected
88
+
89
+ def undeclared_descriptions
90
+ @descriptions[:undeclared]
91
+ end
92
+
93
+ def node_filter_descriptions
94
+ @descriptions[:node_filters]
95
+ end
96
+
97
+ def expression_filter_descriptions
98
+ @descriptions[:expression_filters]
99
+ end
100
+
101
+ private
102
+
103
+ def options_with_defaults(options)
104
+ options = options.dup
105
+ [expression_filters, node_filters].each do |filters|
106
+ filters.select { |_n, filter| filter.default? }.each do |name, filter|
107
+ options[name] = filter.default unless options.key?(name)
108
+ end
109
+ end
110
+ options
111
+ end
112
+
113
+ def add_filter(name, filter_class, *types, matcher: nil, **options, &block)
114
+ types.each { |type| options[type] = true }
115
+ if matcher && options[:default]
116
+ raise 'ArgumentError', ':default option is not supported for filters with a :matcher option'
117
+ end
118
+
119
+ filter = filter_class.new(name, matcher, block, **options)
120
+ (filter_class <= Filters::ExpressionFilter ? @expression_filters : @node_filters)[name] = filter
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capybara
4
+ class Selector
5
+ module Filters
6
+ class Base
7
+ def initialize(name, matcher, block, **options)
8
+ @name = name
9
+ @matcher = matcher
10
+ @block = block
11
+ @options = options
12
+ @options[:valid_values] = [true, false] if options[:boolean]
13
+ end
14
+
15
+ def default?
16
+ @options.key?(:default)
17
+ end
18
+
19
+ def default
20
+ @options[:default]
21
+ end
22
+
23
+ def skip?(value)
24
+ @options.key?(:skip_if) && value == @options[:skip_if]
25
+ end
26
+
27
+ def format
28
+ @options[:format]
29
+ end
30
+
31
+ def matcher?
32
+ !@matcher.nil?
33
+ end
34
+
35
+ def boolean?
36
+ !!@options[:boolean]
37
+ end
38
+
39
+ def handles_option?(option_name)
40
+ if matcher?
41
+ @matcher.match? option_name
42
+ else
43
+ @name == option_name
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def apply(subject, name, value, skip_value, ctx)
50
+ return skip_value if skip?(value)
51
+
52
+ unless valid_value?(value)
53
+ raise ArgumentError,
54
+ "Invalid value #{value.inspect} passed to #{self.class.name.split('::').last} #{name}" \
55
+ "#{" : #{name}" if @name.is_a?(Regexp)}"
56
+ end
57
+
58
+ if @block.arity == 2
59
+ filter_context(ctx).instance_exec(subject, value, &@block)
60
+ else
61
+ filter_context(ctx).instance_exec(subject, name, value, &@block)
62
+ end
63
+ end
64
+
65
+ def filter_context(context)
66
+ context || @block.binding.receiver
67
+ end
68
+
69
+ def valid_value?(value)
70
+ return true unless @options.key?(:valid_values)
71
+
72
+ Array(@options[:valid_values]).any? { |valid| valid === value } # rubocop:disable Style/CaseEquality
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end