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,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capybara::RackTest::Errors
4
+ class StaleElementReferenceError < StandardError
5
+ end
6
+ end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Capybara::RackTest::Form < Capybara::RackTest::Node
4
+ # This only needs to inherit from Rack::Test::UploadedFile because Rack::Test checks for
5
+ # the class specifically when determining whether to construct the request as multipart.
6
+ # That check should be based solely on the form element's 'enctype' attribute value,
7
+ # which should probably be provided to Rack::Test in its non-GET request methods.
8
+ class NilUploadedFile < Rack::Test::UploadedFile
9
+ def initialize
10
+ @empty_file = Tempfile.new('nil_uploaded_file')
11
+ @empty_file.close
12
+ end
13
+
14
+ def original_filename; ''; end
15
+ def content_type; 'application/octet-stream'; end
16
+ def path; @empty_file.path; end
17
+ def size; 0; end
18
+ def read; ''; end
19
+ end
20
+
21
+ def params(button)
22
+ form_element_types = %i[input select textarea button]
23
+ form_elements_xpath = XPath.generate do |xp|
24
+ xpath = xp.descendant(*form_element_types).where(!xp.attr(:form))
25
+ xpath += xp.anywhere(*form_element_types).where(xp.attr(:form) == native[:id]) if native[:id]
26
+ xpath.where(!xp.attr(:disabled))
27
+ end.to_s
28
+
29
+ form_elements = native.xpath(form_elements_xpath).reject { |el| submitter?(el) && (el != button.native) }
30
+
31
+ form_elements.each_with_object(make_params) do |field, params|
32
+ case field.name
33
+ when 'input', 'button' then add_input_param(field, params)
34
+ when 'select' then add_select_param(field, params)
35
+ when 'textarea' then add_textarea_param(field, params)
36
+ end
37
+ end.to_params_hash
38
+ end
39
+
40
+ def submit(button)
41
+ action = button&.[]('formaction') || native['action']
42
+ method = button&.[]('formmethod') || request_method
43
+ driver.submit(method, action.to_s, params(button))
44
+ end
45
+
46
+ def multipart?
47
+ self[:enctype] == 'multipart/form-data'
48
+ end
49
+
50
+ private
51
+
52
+ class ParamsHash < Hash
53
+ def to_params_hash
54
+ self
55
+ end
56
+ end
57
+
58
+ def request_method
59
+ /post/i.match?(self[:method] || '') ? :post : :get
60
+ end
61
+
62
+ def merge_param!(params, key, value)
63
+ key = key.to_s
64
+ if Rack::Utils.respond_to?(:default_query_parser)
65
+ Rack::Utils.default_query_parser.normalize_params(params, key, value, Rack::Utils.param_depth_limit)
66
+ else
67
+ Rack::Utils.normalize_params(params, key, value)
68
+ end
69
+ end
70
+
71
+ def make_params
72
+ if Rack::Utils.respond_to?(:default_query_parser)
73
+ Rack::Utils.default_query_parser.make_params
74
+ else
75
+ ParamsHash.new
76
+ end
77
+ end
78
+
79
+ def add_input_param(field, params)
80
+ name, value = field['name'].to_s, field['value'].to_s
81
+ return if name.empty?
82
+
83
+ value = case field['type']
84
+ when 'radio', 'checkbox'
85
+ return unless field['checked']
86
+
87
+ Capybara::RackTest::Node.new(driver, field).value.to_s
88
+ when 'file'
89
+ if multipart?
90
+ file_to_upload(value)
91
+ else
92
+ File.basename(value)
93
+ end
94
+ else
95
+ value
96
+ end
97
+ merge_param!(params, name, value)
98
+ end
99
+
100
+ def file_to_upload(filename)
101
+ if filename.empty?
102
+ NilUploadedFile.new
103
+ else
104
+ mime_info = MiniMime.lookup_by_filename(filename)
105
+ Rack::Test::UploadedFile.new(filename, mime_info&.content_type&.to_s)
106
+ end
107
+ end
108
+
109
+ def add_select_param(field, params)
110
+ if field.has_attribute?('multiple')
111
+ field.xpath('.//option[@selected]').each do |option|
112
+ merge_param!(params, field['name'], (option['value'] || option.text).to_s)
113
+ end
114
+ else
115
+ option = field.xpath('.//option[@selected]').first || field.xpath('.//option').first
116
+ merge_param!(params, field['name'], (option['value'] || option.text).to_s) if option
117
+ end
118
+ end
119
+
120
+ def add_textarea_param(field, params)
121
+ merge_param!(params, field['name'], field['_capybara_raw_value'].to_s.gsub(/\n/, "\r\n"))
122
+ end
123
+
124
+ def submitter?(el)
125
+ (%w[submit image].include? el['type']) || (el.name == 'button')
126
+ end
127
+ end
@@ -0,0 +1,325 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'capybara/rack_test/errors'
4
+
5
+ class Capybara::RackTest::Node < Capybara::Driver::Node
6
+ BLOCK_ELEMENTS = %w[p h1 h2 h3 h4 h5 h6 ol ul pre address blockquote dl div fieldset form hr noscript table].freeze
7
+
8
+ def all_text
9
+ native.text
10
+ .gsub(/[\u200b\u200e\u200f]/, '')
11
+ .gsub(/[\ \n\f\t\v\u2028\u2029]+/, ' ')
12
+ .gsub(/\A[[:space:]&&[^\u00a0]]+/, '')
13
+ .gsub(/[[:space:]&&[^\u00a0]]+\z/, '')
14
+ .tr("\u00a0", ' ')
15
+ end
16
+
17
+ def visible_text
18
+ displayed_text.gsub(/\ +/, ' ')
19
+ .gsub(/[\ \n]*\n[\ \n]*/, "\n")
20
+ .gsub(/\A[[:space:]&&[^\u00a0]]+/, '')
21
+ .gsub(/[[:space:]&&[^\u00a0]]+\z/, '')
22
+ .tr("\u00a0", ' ')
23
+ end
24
+
25
+ def [](name)
26
+ string_node[name]
27
+ end
28
+
29
+ def style(_styles)
30
+ raise NotImplementedError, 'The rack_test driver does not process CSS'
31
+ end
32
+
33
+ def value
34
+ string_node.value
35
+ end
36
+
37
+ def set(value, **options)
38
+ return if disabled? || readonly?
39
+
40
+ warn "Options passed to Node#set but the RackTest driver doesn't support any - ignoring" unless options.empty?
41
+
42
+ if value.is_a?(Array) && !multiple?
43
+ raise TypeError, "Value cannot be an Array when 'multiple' attribute is not present. Not a #{value.class}"
44
+ end
45
+
46
+ if radio? then set_radio(value)
47
+ elsif checkbox? then set_checkbox(value)
48
+ elsif range? then set_range(value)
49
+ elsif input_field? then set_input(value)
50
+ elsif textarea? then native['_capybara_raw_value'] = value.to_s
51
+ end
52
+ end
53
+
54
+ def select_option
55
+ return if disabled?
56
+
57
+ deselect_options unless select_node.multiple?
58
+ native['selected'] = 'selected'
59
+ end
60
+
61
+ def unselect_option
62
+ raise Capybara::UnselectNotAllowed, 'Cannot unselect option from single select box.' unless select_node.multiple?
63
+
64
+ native.remove_attribute('selected')
65
+ end
66
+
67
+ def click(keys = [], **options)
68
+ options.delete(:offset)
69
+ raise ArgumentError, 'The RackTest driver does not support click options' unless keys.empty? && options.empty?
70
+
71
+ if link?
72
+ follow_link
73
+ elsif submits?
74
+ associated_form = form
75
+ Capybara::RackTest::Form.new(driver, associated_form).submit(self) if associated_form
76
+ elsif checkable?
77
+ set(!checked?)
78
+ elsif tag_name == 'label'
79
+ click_label
80
+ elsif (details = native.xpath('.//ancestor-or-self::details').last)
81
+ toggle_details(details)
82
+ end
83
+ end
84
+
85
+ def tag_name
86
+ native.node_name
87
+ end
88
+
89
+ def visible?
90
+ string_node.visible?
91
+ end
92
+
93
+ def checked?
94
+ string_node.checked?
95
+ end
96
+
97
+ def selected?
98
+ string_node.selected?
99
+ end
100
+
101
+ def disabled?
102
+ return true if string_node.disabled?
103
+
104
+ if %w[option optgroup].include? tag_name
105
+ find_xpath(OPTION_OWNER_XPATH)[0].disabled?
106
+ else
107
+ !find_xpath(DISABLED_BY_FIELDSET_XPATH).empty?
108
+ end
109
+ end
110
+
111
+ def path
112
+ native.path
113
+ end
114
+
115
+ def find_xpath(locator, **_hints)
116
+ native.xpath(locator).map { |el| self.class.new(driver, el) }
117
+ end
118
+
119
+ def find_css(locator, **_hints)
120
+ native.css(locator, Capybara::RackTest::CSSHandlers.new).map { |el| self.class.new(driver, el) }
121
+ end
122
+
123
+ public_instance_methods(false).each do |meth_name|
124
+ alias_method "unchecked_#{meth_name}", meth_name
125
+ private "unchecked_#{meth_name}" # rubocop:disable Style/AccessModifierDeclarations
126
+
127
+ if RUBY_VERSION >= '2.7'
128
+ class_eval <<~METHOD, __FILE__, __LINE__ + 1
129
+ def #{meth_name}(...)
130
+ stale_check
131
+ method(:"unchecked_#{meth_name}").call(...)
132
+ end
133
+ METHOD
134
+ else
135
+ define_method meth_name do |*args|
136
+ stale_check
137
+ send("unchecked_#{meth_name}", *args)
138
+ end
139
+ end
140
+ end
141
+
142
+ def ==(other)
143
+ native == other.native
144
+ end
145
+
146
+ protected
147
+
148
+ # @api private
149
+ def displayed_text(check_ancestor: true)
150
+ if !string_node.visible?(check_ancestor)
151
+ ''
152
+ elsif native.text?
153
+ native.text
154
+ .gsub(/[\u200b\u200e\u200f]/, '')
155
+ .gsub(/[\ \n\f\t\v\u2028\u2029]+/, ' ')
156
+ elsif native.element?
157
+ text = native.children.map do |child|
158
+ Capybara::RackTest::Node.new(driver, child).displayed_text(check_ancestor: false)
159
+ end.join || ''
160
+ text = "\n#{text}\n" if BLOCK_ELEMENTS.include?(tag_name)
161
+ text
162
+ else
163
+ ''
164
+ end
165
+ end
166
+
167
+ private
168
+
169
+ def stale_check
170
+ raise Capybara::RackTest::Errors::StaleElementReferenceError unless native.document == driver.dom
171
+ end
172
+
173
+ def deselect_options
174
+ select_node.find_xpath('.//option[@selected]').each { |node| node.native.remove_attribute('selected') }
175
+ end
176
+
177
+ def string_node
178
+ @string_node ||= Capybara::Node::Simple.new(native)
179
+ end
180
+
181
+ # a reference to the select node if this is an option node
182
+ def select_node
183
+ find_xpath('./ancestor::select[1]').first
184
+ end
185
+
186
+ def type
187
+ native[:type]
188
+ end
189
+
190
+ def form
191
+ if native[:form]
192
+ native.xpath("//form[@id='#{native[:form]}']")
193
+ else
194
+ native.ancestors('form')
195
+ end.first
196
+ end
197
+
198
+ def set_radio(_value) # rubocop:disable Naming/AccessorMethodName
199
+ other_radios_xpath = XPath.generate { |xp| xp.anywhere(:input)[xp.attr(:name) == self[:name]] }.to_s
200
+ driver.dom.xpath(other_radios_xpath).each { |node| node.remove_attribute('checked') }
201
+ native['checked'] = 'checked'
202
+ end
203
+
204
+ def set_checkbox(value) # rubocop:disable Naming/AccessorMethodName
205
+ if value && !native['checked']
206
+ native['checked'] = 'checked'
207
+ elsif !value && native['checked']
208
+ native.remove_attribute('checked')
209
+ end
210
+ end
211
+
212
+ def set_range(value) # rubocop:disable Naming/AccessorMethodName
213
+ min, max, step = (native['min'] || 0).to_f, (native['max'] || 100).to_f, (native['step'] || 1).to_f
214
+ value = value.to_f
215
+ value = value.clamp(min, max)
216
+ value = ((value - min) / step).round * step + min
217
+ native['value'] = value.clamp(min, max)
218
+ end
219
+
220
+ def set_input(value) # rubocop:disable Naming/AccessorMethodName
221
+ if text_or_password? && attribute_is_not_blank?(:maxlength)
222
+ # Browser behavior for maxlength="0" is inconsistent, so we stick with
223
+ # Firefox, allowing no input
224
+ value = value.to_s[0...self[:maxlength].to_i]
225
+ end
226
+ if value.is_a?(Array) # Assert multiple attribute is present
227
+ value.each do |val|
228
+ new_native = native.clone
229
+ new_native.remove_attribute('value')
230
+ native.add_next_sibling(new_native)
231
+ new_native['value'] = val.to_s
232
+ end
233
+ native.remove
234
+ else
235
+ native['value'] = value.to_s
236
+ end
237
+ end
238
+
239
+ def attribute_is_not_blank?(attribute)
240
+ self[attribute] && !self[attribute].empty?
241
+ end
242
+
243
+ def follow_link
244
+ method = self['data-method'] if driver.options[:respect_data_method]
245
+ method ||= :get
246
+ driver.follow(method, self[:href].to_s)
247
+ end
248
+
249
+ def click_label
250
+ labelled_control = if native[:for]
251
+ find_xpath("//input[@id='#{native[:for]}']")
252
+ else
253
+ find_xpath('.//input')
254
+ end.first
255
+
256
+ labelled_control.set(!labelled_control.checked?) if checkbox_or_radio?(labelled_control)
257
+ end
258
+
259
+ def toggle_details(details = nil)
260
+ details ||= native.xpath('.//ancestor-or-self::details').last
261
+ return unless details
262
+
263
+ if details.has_attribute?('open')
264
+ details.remove_attribute('open')
265
+ else
266
+ details.set_attribute('open', 'open')
267
+ end
268
+ end
269
+
270
+ def link?
271
+ tag_name == 'a' && !self[:href].nil?
272
+ end
273
+
274
+ def submits?
275
+ (tag_name == 'input' && %w[submit image].include?(type)) || (tag_name == 'button' && [nil, 'submit'].include?(type))
276
+ end
277
+
278
+ def checkable?
279
+ tag_name == 'input' && %w[checkbox radio].include?(type)
280
+ end
281
+
282
+ protected
283
+
284
+ def checkbox_or_radio?(field = self)
285
+ field&.checkbox? || field&.radio?
286
+ end
287
+
288
+ def checkbox?
289
+ input_field? && type == 'checkbox'
290
+ end
291
+
292
+ def radio?
293
+ input_field? && type == 'radio'
294
+ end
295
+
296
+ def text_or_password?
297
+ input_field? && (type == 'text' || type == 'password')
298
+ end
299
+
300
+ def input_field?
301
+ tag_name == 'input'
302
+ end
303
+
304
+ def textarea?
305
+ tag_name == 'textarea'
306
+ end
307
+
308
+ def range?
309
+ input_field? && type == 'range'
310
+ end
311
+
312
+ OPTION_OWNER_XPATH = XPath.parent(:optgroup, :select, :datalist).to_s.freeze
313
+ DISABLED_BY_FIELDSET_XPATH = XPath.generate do |x|
314
+ x.parent(:fieldset)[
315
+ XPath.attr(:disabled)
316
+ ] + x.ancestor[
317
+ ~x.self(:legend) |
318
+ x.preceding_sibling(:legend)
319
+ ][
320
+ x.parent(:fieldset)[
321
+ x.attr(:disabled)
322
+ ]
323
+ ]
324
+ end.to_s.freeze
325
+ end