capybara 2.7.0 → 3.35.3

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 (318) hide show
  1. checksums.yaml +5 -5
  2. data/.yardopts +1 -0
  3. data/History.md +1147 -11
  4. data/License.txt +1 -1
  5. data/README.md +252 -131
  6. data/lib/capybara/config.rb +92 -0
  7. data/lib/capybara/cucumber.rb +3 -3
  8. data/lib/capybara/driver/base.rb +52 -21
  9. data/lib/capybara/driver/node.rb +48 -14
  10. data/lib/capybara/dsl.rb +16 -9
  11. data/lib/capybara/helpers.rb +72 -81
  12. data/lib/capybara/minitest/spec.rb +267 -0
  13. data/lib/capybara/minitest.rb +385 -0
  14. data/lib/capybara/node/actions.rb +337 -89
  15. data/lib/capybara/node/base.rb +50 -32
  16. data/lib/capybara/node/document.rb +19 -3
  17. data/lib/capybara/node/document_matchers.rb +22 -24
  18. data/lib/capybara/node/element.rb +388 -125
  19. data/lib/capybara/node/finders.rb +231 -121
  20. data/lib/capybara/node/matchers.rb +503 -217
  21. data/lib/capybara/node/simple.rb +64 -27
  22. data/lib/capybara/queries/ancestor_query.rb +27 -0
  23. data/lib/capybara/queries/base_query.rb +87 -11
  24. data/lib/capybara/queries/current_path_query.rb +24 -24
  25. data/lib/capybara/queries/match_query.rb +15 -10
  26. data/lib/capybara/queries/selector_query.rb +675 -81
  27. data/lib/capybara/queries/sibling_query.rb +26 -0
  28. data/lib/capybara/queries/style_query.rb +45 -0
  29. data/lib/capybara/queries/text_query.rb +88 -20
  30. data/lib/capybara/queries/title_query.rb +9 -11
  31. data/lib/capybara/rack_test/browser.rb +63 -39
  32. data/lib/capybara/rack_test/css_handlers.rb +6 -4
  33. data/lib/capybara/rack_test/driver.rb +26 -16
  34. data/lib/capybara/rack_test/errors.rb +6 -0
  35. data/lib/capybara/rack_test/form.rb +73 -58
  36. data/lib/capybara/rack_test/node.rb +187 -67
  37. data/lib/capybara/rails.rb +4 -8
  38. data/lib/capybara/registration_container.rb +44 -0
  39. data/lib/capybara/registrations/drivers.rb +42 -0
  40. data/lib/capybara/registrations/patches/puma_ssl.rb +29 -0
  41. data/lib/capybara/registrations/servers.rb +45 -0
  42. data/lib/capybara/result.rb +142 -14
  43. data/lib/capybara/rspec/features.rb +17 -42
  44. data/lib/capybara/rspec/matcher_proxies.rb +82 -0
  45. data/lib/capybara/rspec/matchers/base.rb +111 -0
  46. data/lib/capybara/rspec/matchers/become_closed.rb +33 -0
  47. data/lib/capybara/rspec/matchers/compound.rb +88 -0
  48. data/lib/capybara/rspec/matchers/count_sugar.rb +37 -0
  49. data/lib/capybara/rspec/matchers/have_ancestor.rb +28 -0
  50. data/lib/capybara/rspec/matchers/have_current_path.rb +29 -0
  51. data/lib/capybara/rspec/matchers/have_selector.rb +77 -0
  52. data/lib/capybara/rspec/matchers/have_sibling.rb +27 -0
  53. data/lib/capybara/rspec/matchers/have_text.rb +33 -0
  54. data/lib/capybara/rspec/matchers/have_title.rb +29 -0
  55. data/lib/capybara/rspec/matchers/match_selector.rb +27 -0
  56. data/lib/capybara/rspec/matchers/match_style.rb +43 -0
  57. data/lib/capybara/rspec/matchers/spatial_sugar.rb +39 -0
  58. data/lib/capybara/rspec/matchers.rb +143 -244
  59. data/lib/capybara/rspec.rb +10 -12
  60. data/lib/capybara/selector/builders/css_builder.rb +84 -0
  61. data/lib/capybara/selector/builders/xpath_builder.rb +71 -0
  62. data/lib/capybara/selector/css.rb +102 -0
  63. data/lib/capybara/selector/definition/button.rb +63 -0
  64. data/lib/capybara/selector/definition/checkbox.rb +26 -0
  65. data/lib/capybara/selector/definition/css.rb +10 -0
  66. data/lib/capybara/selector/definition/datalist_input.rb +35 -0
  67. data/lib/capybara/selector/definition/datalist_option.rb +25 -0
  68. data/lib/capybara/selector/definition/element.rb +28 -0
  69. data/lib/capybara/selector/definition/field.rb +40 -0
  70. data/lib/capybara/selector/definition/fieldset.rb +14 -0
  71. data/lib/capybara/selector/definition/file_field.rb +13 -0
  72. data/lib/capybara/selector/definition/fillable_field.rb +33 -0
  73. data/lib/capybara/selector/definition/frame.rb +17 -0
  74. data/lib/capybara/selector/definition/id.rb +6 -0
  75. data/lib/capybara/selector/definition/label.rb +62 -0
  76. data/lib/capybara/selector/definition/link.rb +54 -0
  77. data/lib/capybara/selector/definition/link_or_button.rb +16 -0
  78. data/lib/capybara/selector/definition/option.rb +27 -0
  79. data/lib/capybara/selector/definition/radio_button.rb +27 -0
  80. data/lib/capybara/selector/definition/select.rb +81 -0
  81. data/lib/capybara/selector/definition/table.rb +109 -0
  82. data/lib/capybara/selector/definition/table_row.rb +21 -0
  83. data/lib/capybara/selector/definition/xpath.rb +5 -0
  84. data/lib/capybara/selector/definition.rb +278 -0
  85. data/lib/capybara/selector/filter.rb +3 -46
  86. data/lib/capybara/selector/filter_set.rb +124 -0
  87. data/lib/capybara/selector/filters/base.rb +77 -0
  88. data/lib/capybara/selector/filters/expression_filter.rb +22 -0
  89. data/lib/capybara/selector/filters/locator_filter.rb +29 -0
  90. data/lib/capybara/selector/filters/node_filter.rb +31 -0
  91. data/lib/capybara/selector/regexp_disassembler.rb +214 -0
  92. data/lib/capybara/selector/selector.rb +155 -0
  93. data/lib/capybara/selector/xpath_extensions.rb +17 -0
  94. data/lib/capybara/selector.rb +232 -369
  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 +380 -142
  100. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +117 -0
  101. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +124 -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 +76 -0
  110. data/lib/capybara/selenium/logger_suppressor.rb +40 -0
  111. data/lib/capybara/selenium/node.rb +528 -97
  112. data/lib/capybara/selenium/nodes/chrome_node.rb +137 -0
  113. data/lib/capybara/selenium/nodes/edge_node.rb +104 -0
  114. data/lib/capybara/selenium/nodes/firefox_node.rb +136 -0
  115. data/lib/capybara/selenium/nodes/ie_node.rb +22 -0
  116. data/lib/capybara/selenium/nodes/safari_node.rb +118 -0
  117. data/lib/capybara/selenium/patches/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 +45 -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/animation_disabler.rb +63 -0
  124. data/lib/capybara/server/checker.rb +44 -0
  125. data/lib/capybara/server/middleware.rb +71 -0
  126. data/lib/capybara/server.rb +74 -71
  127. data/lib/capybara/session/config.rb +126 -0
  128. data/lib/capybara/session/matchers.rb +44 -27
  129. data/lib/capybara/session.rb +500 -297
  130. data/lib/capybara/spec/fixtures/no_extension +1 -0
  131. data/lib/capybara/spec/public/jquery.js +5 -5
  132. data/lib/capybara/spec/public/offset.js +6 -0
  133. data/lib/capybara/spec/public/test.js +168 -14
  134. data/lib/capybara/spec/session/accept_alert_spec.rb +37 -14
  135. data/lib/capybara/spec/session/accept_confirm_spec.rb +7 -6
  136. data/lib/capybara/spec/session/accept_prompt_spec.rb +38 -10
  137. data/lib/capybara/spec/session/all_spec.rb +179 -59
  138. data/lib/capybara/spec/session/ancestor_spec.rb +88 -0
  139. data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +140 -0
  140. data/lib/capybara/spec/session/assert_current_path_spec.rb +75 -0
  141. data/lib/capybara/spec/session/assert_selector_spec.rb +143 -0
  142. data/lib/capybara/spec/session/assert_style_spec.rb +26 -0
  143. data/lib/capybara/spec/session/assert_text_spec.rb +258 -0
  144. data/lib/capybara/spec/session/assert_title_spec.rb +93 -0
  145. data/lib/capybara/spec/session/attach_file_spec.rb +154 -48
  146. data/lib/capybara/spec/session/body_spec.rb +12 -13
  147. data/lib/capybara/spec/session/check_spec.rb +168 -41
  148. data/lib/capybara/spec/session/choose_spec.rb +75 -23
  149. data/lib/capybara/spec/session/click_button_spec.rb +243 -175
  150. data/lib/capybara/spec/session/click_link_or_button_spec.rb +57 -32
  151. data/lib/capybara/spec/session/click_link_spec.rb +100 -53
  152. data/lib/capybara/spec/session/current_scope_spec.rb +11 -10
  153. data/lib/capybara/spec/session/current_url_spec.rb +61 -35
  154. data/lib/capybara/spec/session/dismiss_confirm_spec.rb +7 -7
  155. data/lib/capybara/spec/session/dismiss_prompt_spec.rb +5 -4
  156. data/lib/capybara/spec/session/element/{assert_match_selector.rb → assert_match_selector_spec.rb} +13 -6
  157. data/lib/capybara/spec/session/element/match_css_spec.rb +21 -7
  158. data/lib/capybara/spec/session/element/match_xpath_spec.rb +9 -7
  159. data/lib/capybara/spec/session/element/matches_selector_spec.rb +91 -34
  160. data/lib/capybara/spec/session/evaluate_async_script_spec.rb +23 -0
  161. data/lib/capybara/spec/session/evaluate_script_spec.rb +45 -3
  162. data/lib/capybara/spec/session/execute_script_spec.rb +24 -4
  163. data/lib/capybara/spec/session/fill_in_spec.rb +166 -64
  164. data/lib/capybara/spec/session/find_button_spec.rb +37 -18
  165. data/lib/capybara/spec/session/find_by_id_spec.rb +10 -9
  166. data/lib/capybara/spec/session/find_field_spec.rb +57 -34
  167. data/lib/capybara/spec/session/find_link_spec.rb +47 -10
  168. data/lib/capybara/spec/session/find_spec.rb +290 -144
  169. data/lib/capybara/spec/session/first_spec.rb +91 -48
  170. data/lib/capybara/spec/session/frame/frame_title_spec.rb +23 -0
  171. data/lib/capybara/spec/session/frame/frame_url_spec.rb +23 -0
  172. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +116 -0
  173. data/lib/capybara/spec/session/frame/within_frame_spec.rb +112 -0
  174. data/lib/capybara/spec/session/go_back_spec.rb +3 -2
  175. data/lib/capybara/spec/session/go_forward_spec.rb +3 -2
  176. data/lib/capybara/spec/session/has_all_selectors_spec.rb +69 -0
  177. data/lib/capybara/spec/session/has_ancestor_spec.rb +46 -0
  178. data/lib/capybara/spec/session/has_any_selectors_spec.rb +25 -0
  179. data/lib/capybara/spec/session/has_button_spec.rb +76 -19
  180. data/lib/capybara/spec/session/has_css_spec.rb +277 -131
  181. data/lib/capybara/spec/session/has_current_path_spec.rb +98 -26
  182. data/lib/capybara/spec/session/has_field_spec.rb +177 -107
  183. data/lib/capybara/spec/session/has_link_spec.rb +13 -12
  184. data/lib/capybara/spec/session/has_none_selectors_spec.rb +78 -0
  185. data/lib/capybara/spec/session/has_select_spec.rb +191 -95
  186. data/lib/capybara/spec/session/has_selector_spec.rb +128 -64
  187. data/lib/capybara/spec/session/has_sibling_spec.rb +50 -0
  188. data/lib/capybara/spec/session/has_table_spec.rb +172 -5
  189. data/lib/capybara/spec/session/has_text_spec.rb +126 -60
  190. data/lib/capybara/spec/session/has_title_spec.rb +35 -12
  191. data/lib/capybara/spec/session/has_xpath_spec.rb +74 -53
  192. data/lib/capybara/spec/session/{headers.rb → headers_spec.rb} +3 -2
  193. data/lib/capybara/spec/session/html_spec.rb +14 -6
  194. data/lib/capybara/spec/session/matches_style_spec.rb +35 -0
  195. data/lib/capybara/spec/session/node_spec.rb +1028 -131
  196. data/lib/capybara/spec/session/node_wrapper_spec.rb +39 -0
  197. data/lib/capybara/spec/session/refresh_spec.rb +34 -0
  198. data/lib/capybara/spec/session/reset_session_spec.rb +75 -34
  199. data/lib/capybara/spec/session/{response_code.rb → response_code_spec.rb} +2 -1
  200. data/lib/capybara/spec/session/save_and_open_page_spec.rb +3 -2
  201. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +11 -15
  202. data/lib/capybara/spec/session/save_page_spec.rb +42 -55
  203. data/lib/capybara/spec/session/save_screenshot_spec.rb +16 -14
  204. data/lib/capybara/spec/session/screenshot_spec.rb +2 -2
  205. data/lib/capybara/spec/session/scroll_spec.rb +117 -0
  206. data/lib/capybara/spec/session/select_spec.rb +112 -85
  207. data/lib/capybara/spec/session/selectors_spec.rb +71 -8
  208. data/lib/capybara/spec/session/sibling_spec.rb +52 -0
  209. data/lib/capybara/spec/session/text_spec.rb +38 -23
  210. data/lib/capybara/spec/session/title_spec.rb +17 -5
  211. data/lib/capybara/spec/session/uncheck_spec.rb +71 -12
  212. data/lib/capybara/spec/session/unselect_spec.rb +44 -43
  213. data/lib/capybara/spec/session/visit_spec.rb +99 -32
  214. data/lib/capybara/spec/session/window/become_closed_spec.rb +33 -29
  215. data/lib/capybara/spec/session/window/current_window_spec.rb +5 -3
  216. data/lib/capybara/spec/session/window/open_new_window_spec.rb +5 -3
  217. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +39 -30
  218. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +17 -10
  219. data/lib/capybara/spec/session/window/window_spec.rb +121 -73
  220. data/lib/capybara/spec/session/window/windows_spec.rb +12 -10
  221. data/lib/capybara/spec/session/window/within_window_spec.rb +52 -82
  222. data/lib/capybara/spec/session/within_spec.rb +76 -43
  223. data/lib/capybara/spec/spec_helper.rb +67 -33
  224. data/lib/capybara/spec/test_app.rb +85 -36
  225. data/lib/capybara/spec/views/animated.erb +49 -0
  226. data/lib/capybara/spec/views/buttons.erb +1 -1
  227. data/lib/capybara/spec/views/fieldsets.erb +1 -1
  228. data/lib/capybara/spec/views/form.erb +227 -20
  229. data/lib/capybara/spec/views/frame_child.erb +10 -2
  230. data/lib/capybara/spec/views/frame_one.erb +2 -1
  231. data/lib/capybara/spec/views/frame_parent.erb +2 -2
  232. data/lib/capybara/spec/views/frame_two.erb +1 -1
  233. data/lib/capybara/spec/views/header_links.erb +1 -1
  234. data/lib/capybara/spec/views/host_links.erb +1 -1
  235. data/lib/capybara/spec/views/initial_alert.erb +10 -0
  236. data/lib/capybara/spec/views/obscured.erb +47 -0
  237. data/lib/capybara/spec/views/offset.erb +32 -0
  238. data/lib/capybara/spec/views/path.erb +1 -1
  239. data/lib/capybara/spec/views/popup_one.erb +1 -1
  240. data/lib/capybara/spec/views/popup_two.erb +1 -1
  241. data/lib/capybara/spec/views/postback.erb +1 -1
  242. data/lib/capybara/spec/views/react.erb +45 -0
  243. data/lib/capybara/spec/views/scroll.erb +20 -0
  244. data/lib/capybara/spec/views/spatial.erb +31 -0
  245. data/lib/capybara/spec/views/tables.erb +69 -2
  246. data/lib/capybara/spec/views/with_animation.erb +82 -0
  247. data/lib/capybara/spec/views/with_base_tag.erb +1 -1
  248. data/lib/capybara/spec/views/with_count.erb +1 -1
  249. data/lib/capybara/spec/views/with_dragula.erb +24 -0
  250. data/lib/capybara/spec/views/with_fixed_header_footer.erb +17 -0
  251. data/lib/capybara/spec/views/with_hover.erb +7 -1
  252. data/lib/capybara/spec/views/with_hover1.erb +10 -0
  253. data/lib/capybara/spec/views/with_html.erb +100 -10
  254. data/lib/capybara/spec/views/with_html5_svg.erb +20 -0
  255. data/lib/capybara/spec/views/with_html_entities.erb +1 -1
  256. data/lib/capybara/spec/views/with_jquery_animation.erb +24 -0
  257. data/lib/capybara/spec/views/with_js.erb +49 -3
  258. data/lib/capybara/spec/views/with_jstree.erb +26 -0
  259. data/lib/capybara/spec/views/with_namespace.erb +20 -0
  260. data/lib/capybara/spec/views/with_scope.erb +1 -1
  261. data/lib/capybara/spec/views/with_scope_other.erb +6 -0
  262. data/lib/capybara/spec/views/with_simple_html.erb +1 -1
  263. data/lib/capybara/spec/views/with_sortable_js.erb +21 -0
  264. data/lib/capybara/spec/views/with_title.erb +1 -1
  265. data/lib/capybara/spec/views/with_unload_alert.erb +3 -1
  266. data/lib/capybara/spec/views/with_windows.erb +7 -1
  267. data/lib/capybara/spec/views/within_frames.erb +6 -3
  268. data/lib/capybara/version.rb +2 -1
  269. data/lib/capybara/window.rb +39 -21
  270. data/lib/capybara.rb +208 -186
  271. data/spec/basic_node_spec.rb +52 -39
  272. data/spec/capybara_spec.rb +72 -50
  273. data/spec/css_builder_spec.rb +101 -0
  274. data/spec/css_splitter_spec.rb +38 -0
  275. data/spec/dsl_spec.rb +81 -61
  276. data/spec/filter_set_spec.rb +46 -0
  277. data/spec/fixtures/capybara.csv +1 -0
  278. data/spec/fixtures/certificate.pem +25 -0
  279. data/spec/fixtures/key.pem +27 -0
  280. data/spec/fixtures/selenium_driver_rspec_failure.rb +7 -3
  281. data/spec/fixtures/selenium_driver_rspec_success.rb +7 -3
  282. data/spec/minitest_spec.rb +164 -0
  283. data/spec/minitest_spec_spec.rb +162 -0
  284. data/spec/per_session_config_spec.rb +68 -0
  285. data/spec/rack_test_spec.rb +189 -96
  286. data/spec/regexp_dissassembler_spec.rb +250 -0
  287. data/spec/result_spec.rb +143 -13
  288. data/spec/rspec/features_spec.rb +38 -32
  289. data/spec/rspec/scenarios_spec.rb +9 -7
  290. data/spec/rspec/shared_spec_matchers.rb +959 -0
  291. data/spec/rspec/views_spec.rb +9 -3
  292. data/spec/rspec_matchers_spec.rb +62 -0
  293. data/spec/rspec_spec.rb +127 -30
  294. data/spec/sauce_spec_chrome.rb +43 -0
  295. data/spec/selector_spec.rb +458 -37
  296. data/spec/selenium_spec_chrome.rb +196 -9
  297. data/spec/selenium_spec_chrome_remote.rb +100 -0
  298. data/spec/selenium_spec_edge.rb +47 -0
  299. data/spec/selenium_spec_firefox.rb +210 -0
  300. data/spec/selenium_spec_firefox_remote.rb +80 -0
  301. data/spec/selenium_spec_ie.rb +150 -0
  302. data/spec/selenium_spec_safari.rb +148 -0
  303. data/spec/server_spec.rb +200 -101
  304. data/spec/session_spec.rb +91 -0
  305. data/spec/shared_selenium_node.rb +83 -0
  306. data/spec/shared_selenium_session.rb +558 -0
  307. data/spec/spec_helper.rb +94 -2
  308. data/spec/xpath_builder_spec.rb +93 -0
  309. metadata +420 -60
  310. data/lib/capybara/query.rb +0 -7
  311. data/lib/capybara/spec/session/assert_current_path.rb +0 -60
  312. data/lib/capybara/spec/session/assert_selector.rb +0 -148
  313. data/lib/capybara/spec/session/assert_text.rb +0 -196
  314. data/lib/capybara/spec/session/assert_title.rb +0 -70
  315. data/lib/capybara/spec/session/source_spec.rb +0 -0
  316. data/lib/capybara/spec/session/within_frame_spec.rb +0 -53
  317. data/spec/rspec/matchers_spec.rb +0 -827
  318. data/spec/selenium_spec.rb +0 -151
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capybara::RackTest::Errors
4
+ class StaleElementReferenceError < StandardError
5
+ end
6
+ end
@@ -1,85 +1,50 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  class Capybara::RackTest::Form < Capybara::RackTest::Node
3
4
  # This only needs to inherit from Rack::Test::UploadedFile because Rack::Test checks for
4
5
  # the class specifically when determining whether to construct the request as multipart.
5
6
  # That check should be based solely on the form element's 'enctype' attribute value,
6
7
  # which should probably be provided to Rack::Test in its non-GET request methods.
7
8
  class NilUploadedFile < Rack::Test::UploadedFile
8
- def initialize
9
- @empty_file = Tempfile.new("nil_uploaded_file")
9
+ def initialize # rubocop:disable Lint/MissingSuper
10
+ @empty_file = Tempfile.new('nil_uploaded_file')
10
11
  @empty_file.close
11
12
  end
12
13
 
13
- def original_filename; ""; end
14
- def content_type; "application/octet-stream"; end
14
+ def original_filename; ''; end
15
+ def content_type; 'application/octet-stream'; end
15
16
  def path; @empty_file.path; end
17
+ def size; 0; end
18
+ def read; ''; end
16
19
  end
17
20
 
18
21
  def params(button)
19
- params = make_params
20
-
21
- form_element_types=[:input, :select, :textarea]
22
- form_elements_xpath=XPath.generate do |x|
23
- xpath=x.descendant(*form_element_types).where(~x.attr(:form))
24
- xpath=xpath.union(x.anywhere(*form_element_types).where(x.attr(:form) == native[:id])) if native[:id]
25
- xpath.where(~x.attr(:disabled))
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))
26
27
  end.to_s
27
28
 
28
- native.xpath(form_elements_xpath).map do |field|
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|
29
32
  case field.name
30
- when 'input'
31
- if %w(radio checkbox).include? field['type']
32
- if field['checked']
33
- node=Capybara::RackTest::Node.new(self.driver, field)
34
- merge_param!(params, field['name'].to_s, node.value.to_s)
35
- end
36
- elsif %w(submit image).include? field['type']
37
- # TO DO identify the click button here (in document order, rather
38
- # than leaving until the end of the params)
39
- elsif field['type'] =='file'
40
- if multipart?
41
- file = \
42
- if (value = field['value']).to_s.empty?
43
- NilUploadedFile.new
44
- else
45
- content_type = MIME::Types.type_for(value).first.to_s
46
- Rack::Test::UploadedFile.new(value, content_type)
47
- end
48
- merge_param!(params, field['name'].to_s, file)
49
- else
50
- merge_param!(params, field['name'].to_s, File.basename(field['value'].to_s))
51
- end
52
- else
53
- merge_param!(params, field['name'].to_s, field['value'].to_s)
54
- end
55
- when 'select'
56
- if field['multiple'] == 'multiple'
57
- options = field.xpath(".//option[@selected]")
58
- options.each do |option|
59
- merge_param!(params, field['name'].to_s, (option['value'] || option.text).to_s)
60
- end
61
- else
62
- option = field.xpath(".//option[@selected]").first
63
- option ||= field.xpath('.//option').first
64
- merge_param!(params, field['name'].to_s, (option['value'] || option.text).to_s) if option
65
- end
66
- when 'textarea'
67
- merge_param!(params, field['name'].to_s, field.text.to_s.gsub(/\n/, "\r\n"))
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)
68
36
  end
69
- end
70
- merge_param!(params, button[:name], button[:value] || "") if button[:name]
71
-
72
- params.to_params_hash
37
+ end.to_params_hash
73
38
  end
74
39
 
75
40
  def submit(button)
76
- action = (button && button['formaction']) || native['action']
77
- method = (button && button['formmethod']) || request_method
41
+ action = button&.[]('formaction') || native['action']
42
+ method = button&.[]('formmethod') || request_method
78
43
  driver.submit(method, action.to_s, params(button))
79
44
  end
80
45
 
81
46
  def multipart?
82
- self[:enctype] == "multipart/form-data"
47
+ self[:enctype] == 'multipart/form-data'
83
48
  end
84
49
 
85
50
  private
@@ -91,10 +56,11 @@ private
91
56
  end
92
57
 
93
58
  def request_method
94
- self[:method] =~ /post/i ? :post : :get
59
+ /post/i.match?(self[:method] || '') ? :post : :get
95
60
  end
96
61
 
97
62
  def merge_param!(params, key, value)
63
+ key = key.to_s
98
64
  if Rack::Utils.respond_to?(:default_query_parser)
99
65
  Rack::Utils.default_query_parser.normalize_params(params, key, value, Rack::Utils.param_depth_limit)
100
66
  else
@@ -109,4 +75,53 @@ private
109
75
  ParamsHash.new
110
76
  end
111
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
112
127
  end
@@ -1,65 +1,84 @@
1
1
  # frozen_string_literal: true
2
+
3
+ require 'capybara/rack_test/errors'
4
+
2
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
+
3
8
  def all_text
4
- Capybara::Helpers.normalize_whitespace(native.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", ' ')
5
15
  end
6
16
 
7
17
  def visible_text
8
- Capybara::Helpers.normalize_whitespace(unnormalized_text)
18
+ displayed_text.squeeze(' ')
19
+ .gsub(/[\ \n]*\n[\ \n]*/, "\n")
20
+ .gsub(/\A[[:space:]&&[^\u00a0]]+/, '')
21
+ .gsub(/[[:space:]&&[^\u00a0]]+\z/, '')
22
+ .tr("\u00a0", ' ')
9
23
  end
10
24
 
11
25
  def [](name)
12
26
  string_node[name]
13
27
  end
14
28
 
29
+ def style(_styles)
30
+ raise NotImplementedError, 'The rack_test driver does not process CSS'
31
+ end
32
+
15
33
  def value
16
34
  string_node.value
17
35
  end
18
36
 
19
- def set(value)
20
- if (Array === value) && !self[:multiple]
21
- raise ArgumentError.new "Value cannot be an Array when 'multiple' attribute is not present. Not a #{value.class}"
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}"
22
44
  end
23
45
 
24
- if radio?
25
- set_radio(value)
26
- elsif checkbox?
27
- set_checkbox(value)
28
- elsif input_field?
29
- set_input(value)
30
- elsif textarea?
31
- if self[:readonly]
32
- warn "Attempt to set readonly element with value: #{value} \n * This will raise an exception in a future version of Capybara"
33
- else
34
- native.content = value.to_s
35
- end
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
36
51
  end
37
52
  end
38
53
 
39
54
  def select_option
40
55
  return if disabled?
41
- if select_node['multiple'] != 'multiple'
42
- select_node.find_xpath(".//option[@selected]").each { |node| node.native.remove_attribute("selected") }
43
- end
44
- native["selected"] = 'selected'
56
+
57
+ deselect_options unless select_node.multiple?
58
+ native['selected'] = 'selected'
45
59
  end
46
60
 
47
61
  def unselect_option
48
- if select_node['multiple'] != 'multiple'
49
- raise Capybara::UnselectNotAllowed, "Cannot unselect option from single select box."
50
- end
62
+ raise Capybara::UnselectNotAllowed, 'Cannot unselect option from single select box.' unless select_node.multiple?
63
+
51
64
  native.remove_attribute('selected')
52
65
  end
53
66
 
54
- def click
55
- if tag_name == 'a' && !self[:href].nil?
56
- method = self["data-method"] if driver.options[:respect_data_method]
57
- method ||= :get
58
- driver.follow(method, self[:href].to_s)
59
- elsif (tag_name == 'input' and %w(submit image).include?(type)) or
60
- ((tag_name == 'button') and type.nil? or type == "submit")
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?
61
74
  associated_form = form
62
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)
63
82
  end
64
83
  end
65
84
 
@@ -80,10 +99,12 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
80
99
  end
81
100
 
82
101
  def disabled?
83
- if %w(option optgroup).include? tag_name
84
- string_node.disabled? || find_xpath("parent::*")[0].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?
85
106
  else
86
- string_node.disabled?
107
+ !find_xpath(DISABLED_BY_FIELDSET_XPATH).empty?
87
108
  end
88
109
  end
89
110
 
@@ -91,12 +112,31 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
91
112
  native.path
92
113
  end
93
114
 
94
- def find_xpath(locator)
95
- native.xpath(locator).map { |n| self.class.new(driver, n) }
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) }
96
121
  end
97
122
 
98
- def find_css(locator)
99
- native.css(locator, Capybara::RackTest::CSSHandlers.new).map { |n| self.class.new(driver, n) }
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
100
140
  end
101
141
 
102
142
  def ==(other)
@@ -105,15 +145,20 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
105
145
 
106
146
  protected
107
147
 
108
- def unnormalized_text(check_ancestor_visibility = true)
109
- if !string_node.visible?(check_ancestor_visibility)
148
+ # @api private
149
+ def displayed_text(check_ancestor: true)
150
+ if !string_node.visible?(check_ancestor)
110
151
  ''
111
152
  elsif native.text?
112
153
  native.text
154
+ .gsub(/[\u200b\u200e\u200f]/, '')
155
+ .gsub(/[\ \n\f\t\v\u2028\u2029]+/, ' ')
113
156
  elsif native.element?
114
- native.children.map do |child|
115
- Capybara::RackTest::Node.new(driver, child).unnormalized_text(false)
116
- end.join
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
117
162
  else
118
163
  ''
119
164
  end
@@ -121,13 +166,21 @@ protected
121
166
 
122
167
  private
123
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
+
124
177
  def string_node
125
178
  @string_node ||= Capybara::Node::Simple.new(native)
126
179
  end
127
180
 
128
181
  # a reference to the select node if this is an option node
129
182
  def select_node
130
- find_xpath('./ancestor::select').first
183
+ find_xpath('./ancestor::select[1]').first
131
184
  end
132
185
 
133
186
  def type
@@ -136,19 +189,19 @@ private
136
189
 
137
190
  def form
138
191
  if native[:form]
139
- native.xpath("//form[@id='#{native[:form]}']").first
192
+ native.xpath("//form[@id='#{native[:form]}']")
140
193
  else
141
- native.ancestors('form').first
142
- end
194
+ native.ancestors('form')
195
+ end.first
143
196
  end
144
197
 
145
- def set_radio(value)
146
- other_radios_xpath = XPath.generate { |x| x.anywhere(:input)[x.attr(:name).equals(self[:name])] }.to_s
147
- driver.dom.xpath(other_radios_xpath).each { |node| node.remove_attribute("checked") }
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') }
148
201
  native['checked'] = 'checked'
149
202
  end
150
203
 
151
- def set_checkbox(value)
204
+ def set_checkbox(value) # rubocop:disable Naming/AccessorMethodName
152
205
  if value && !native['checked']
153
206
  native['checked'] = 'checked'
154
207
  elsif !value && native['checked']
@@ -156,26 +209,30 @@ private
156
209
  end
157
210
  end
158
211
 
159
- def set_input(value)
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
160
221
  if text_or_password? && attribute_is_not_blank?(:maxlength)
161
222
  # Browser behavior for maxlength="0" is inconsistent, so we stick with
162
223
  # Firefox, allowing no input
163
224
  value = value.to_s[0...self[:maxlength].to_i]
164
225
  end
165
- if Array === value #Assert multiple attribute is present
166
- value.each do |v|
226
+ if value.is_a?(Array) # Assert multiple attribute is present
227
+ value.each do |val|
167
228
  new_native = native.clone
168
229
  new_native.remove_attribute('value')
169
230
  native.add_next_sibling(new_native)
170
- new_native['value'] = v.to_s
231
+ new_native['value'] = val.to_s
171
232
  end
172
233
  native.remove
173
234
  else
174
- if self[:readonly]
175
- warn "Attempt to set readonly element with value: #{value} \n *This will raise an exception in a future version of Capybara"
176
- else
177
- native['value'] = value.to_s
178
- end
235
+ native['value'] = value.to_s
179
236
  end
180
237
  end
181
238
 
@@ -183,23 +240,86 @@ private
183
240
  self[attribute] && !self[attribute].empty?
184
241
  end
185
242
 
186
- def checkbox?
187
- input_field? && type == 'checkbox'
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)
188
247
  end
189
248
 
190
- def input_field?
191
- tag_name == 'input'
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'
192
290
  end
193
291
 
194
292
  def radio?
195
293
  input_field? && type == 'radio'
196
294
  end
197
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
+
198
304
  def textarea?
199
- tag_name == "textarea"
305
+ tag_name == 'textarea'
200
306
  end
201
307
 
202
- def text_or_password?
203
- input_field? && (type == 'text' || type == 'password')
308
+ def range?
309
+ input_field? && type == 'range'
204
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
205
325
  end