capybara 3.13.2 → 3.40.0

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 (260) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -0
  3. data/History.md +587 -16
  4. data/README.md +240 -90
  5. data/lib/capybara/config.rb +24 -11
  6. data/lib/capybara/cucumber.rb +1 -1
  7. data/lib/capybara/driver/base.rb +8 -0
  8. data/lib/capybara/driver/node.rb +20 -4
  9. data/lib/capybara/dsl.rb +5 -3
  10. data/lib/capybara/helpers.rb +25 -4
  11. data/lib/capybara/minitest/spec.rb +174 -90
  12. data/lib/capybara/minitest.rb +256 -142
  13. data/lib/capybara/node/actions.rb +123 -77
  14. data/lib/capybara/node/base.rb +20 -12
  15. data/lib/capybara/node/document.rb +2 -2
  16. data/lib/capybara/node/document_matchers.rb +3 -3
  17. data/lib/capybara/node/element.rb +223 -117
  18. data/lib/capybara/node/finders.rb +81 -71
  19. data/lib/capybara/node/matchers.rb +271 -134
  20. data/lib/capybara/node/simple.rb +18 -5
  21. data/lib/capybara/node/whitespace_normalizer.rb +81 -0
  22. data/lib/capybara/queries/active_element_query.rb +18 -0
  23. data/lib/capybara/queries/ancestor_query.rb +8 -9
  24. data/lib/capybara/queries/base_query.rb +3 -2
  25. data/lib/capybara/queries/current_path_query.rb +15 -5
  26. data/lib/capybara/queries/selector_query.rb +364 -54
  27. data/lib/capybara/queries/sibling_query.rb +8 -6
  28. data/lib/capybara/queries/style_query.rb +2 -2
  29. data/lib/capybara/queries/text_query.rb +13 -1
  30. data/lib/capybara/queries/title_query.rb +1 -1
  31. data/lib/capybara/rack_test/browser.rb +76 -11
  32. data/lib/capybara/rack_test/driver.rb +10 -5
  33. data/lib/capybara/rack_test/errors.rb +6 -0
  34. data/lib/capybara/rack_test/form.rb +31 -9
  35. data/lib/capybara/rack_test/node.rb +74 -23
  36. data/lib/capybara/registration_container.rb +41 -0
  37. data/lib/capybara/registrations/drivers.rb +42 -0
  38. data/lib/capybara/registrations/patches/puma_ssl.rb +29 -0
  39. data/lib/capybara/registrations/servers.rb +66 -0
  40. data/lib/capybara/result.rb +44 -20
  41. data/lib/capybara/rspec/matcher_proxies.rb +13 -11
  42. data/lib/capybara/rspec/matchers/base.rb +31 -16
  43. data/lib/capybara/rspec/matchers/compound.rb +1 -1
  44. data/lib/capybara/rspec/matchers/count_sugar.rb +37 -0
  45. data/lib/capybara/rspec/matchers/have_ancestor.rb +28 -0
  46. data/lib/capybara/rspec/matchers/have_current_path.rb +2 -2
  47. data/lib/capybara/rspec/matchers/have_selector.rb +21 -21
  48. data/lib/capybara/rspec/matchers/have_sibling.rb +27 -0
  49. data/lib/capybara/rspec/matchers/have_text.rb +4 -4
  50. data/lib/capybara/rspec/matchers/have_title.rb +2 -2
  51. data/lib/capybara/rspec/matchers/match_selector.rb +3 -3
  52. data/lib/capybara/rspec/matchers/match_style.rb +7 -2
  53. data/lib/capybara/rspec/matchers/spatial_sugar.rb +39 -0
  54. data/lib/capybara/rspec/matchers.rb +111 -68
  55. data/lib/capybara/rspec.rb +2 -0
  56. data/lib/capybara/selector/builders/css_builder.rb +11 -7
  57. data/lib/capybara/selector/builders/xpath_builder.rb +5 -3
  58. data/lib/capybara/selector/css.rb +11 -9
  59. data/lib/capybara/selector/definition/button.rb +68 -0
  60. data/lib/capybara/selector/definition/checkbox.rb +26 -0
  61. data/lib/capybara/selector/definition/css.rb +10 -0
  62. data/lib/capybara/selector/definition/datalist_input.rb +35 -0
  63. data/lib/capybara/selector/definition/datalist_option.rb +25 -0
  64. data/lib/capybara/selector/definition/element.rb +28 -0
  65. data/lib/capybara/selector/definition/field.rb +40 -0
  66. data/lib/capybara/selector/definition/fieldset.rb +14 -0
  67. data/lib/capybara/selector/definition/file_field.rb +13 -0
  68. data/lib/capybara/selector/definition/fillable_field.rb +33 -0
  69. data/lib/capybara/selector/definition/frame.rb +17 -0
  70. data/lib/capybara/selector/definition/id.rb +6 -0
  71. data/lib/capybara/selector/definition/label.rb +62 -0
  72. data/lib/capybara/selector/definition/link.rb +55 -0
  73. data/lib/capybara/selector/definition/link_or_button.rb +16 -0
  74. data/lib/capybara/selector/definition/option.rb +27 -0
  75. data/lib/capybara/selector/definition/radio_button.rb +27 -0
  76. data/lib/capybara/selector/definition/select.rb +81 -0
  77. data/lib/capybara/selector/definition/table.rb +109 -0
  78. data/lib/capybara/selector/definition/table_row.rb +21 -0
  79. data/lib/capybara/selector/definition/xpath.rb +5 -0
  80. data/lib/capybara/selector/definition.rb +280 -0
  81. data/lib/capybara/selector/filter_set.rb +19 -18
  82. data/lib/capybara/selector/filters/base.rb +11 -2
  83. data/lib/capybara/selector/filters/locator_filter.rb +13 -3
  84. data/lib/capybara/selector/regexp_disassembler.rb +11 -7
  85. data/lib/capybara/selector/selector.rb +50 -440
  86. data/lib/capybara/selector/xpath_extensions.rb +17 -0
  87. data/lib/capybara/selector.rb +473 -482
  88. data/lib/capybara/selenium/atoms/getAttribute.min.js +1 -0
  89. data/lib/capybara/selenium/atoms/isDisplayed.min.js +1 -0
  90. data/lib/capybara/selenium/atoms/src/getAttribute.js +161 -0
  91. data/lib/capybara/selenium/atoms/src/isDisplayed.js +454 -0
  92. data/lib/capybara/selenium/driver.rb +174 -62
  93. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +74 -18
  94. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +128 -0
  95. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +37 -3
  96. data/lib/capybara/selenium/driver_specializations/internet_explorer_driver.rb +14 -1
  97. data/lib/capybara/selenium/driver_specializations/safari_driver.rb +24 -0
  98. data/lib/capybara/selenium/extensions/file_input_click_emulation.rb +34 -0
  99. data/lib/capybara/selenium/extensions/find.rb +68 -45
  100. data/lib/capybara/selenium/extensions/html5_drag.rb +192 -22
  101. data/lib/capybara/selenium/extensions/modifier_keys_stack.rb +28 -0
  102. data/lib/capybara/selenium/extensions/scroll.rb +8 -10
  103. data/lib/capybara/selenium/node.rb +268 -72
  104. data/lib/capybara/selenium/nodes/chrome_node.rb +105 -9
  105. data/lib/capybara/selenium/nodes/edge_node.rb +110 -0
  106. data/lib/capybara/selenium/nodes/firefox_node.rb +51 -61
  107. data/lib/capybara/selenium/nodes/ie_node.rb +22 -0
  108. data/lib/capybara/selenium/nodes/safari_node.rb +118 -0
  109. data/lib/capybara/selenium/patches/atoms.rb +18 -0
  110. data/lib/capybara/selenium/patches/is_displayed.rb +16 -0
  111. data/lib/capybara/selenium/patches/logs.rb +45 -0
  112. data/lib/capybara/selenium/patches/pause_duration_fix.rb +1 -1
  113. data/lib/capybara/selenium/patches/persistent_client.rb +20 -0
  114. data/lib/capybara/server/animation_disabler.rb +43 -21
  115. data/lib/capybara/server/checker.rb +6 -2
  116. data/lib/capybara/server/middleware.rb +25 -13
  117. data/lib/capybara/server.rb +20 -4
  118. data/lib/capybara/session/config.rb +15 -11
  119. data/lib/capybara/session/matchers.rb +11 -11
  120. data/lib/capybara/session.rb +162 -131
  121. data/lib/capybara/spec/public/offset.js +6 -0
  122. data/lib/capybara/spec/public/test.js +105 -6
  123. data/lib/capybara/spec/session/accept_alert_spec.rb +1 -1
  124. data/lib/capybara/spec/session/active_element_spec.rb +31 -0
  125. data/lib/capybara/spec/session/all_spec.rb +89 -15
  126. data/lib/capybara/spec/session/ancestor_spec.rb +5 -0
  127. data/lib/capybara/spec/session/assert_current_path_spec.rb +5 -2
  128. data/lib/capybara/spec/session/assert_text_spec.rb +26 -22
  129. data/lib/capybara/spec/session/attach_file_spec.rb +64 -31
  130. data/lib/capybara/spec/session/check_spec.rb +26 -4
  131. data/lib/capybara/spec/session/choose_spec.rb +14 -2
  132. data/lib/capybara/spec/session/click_button_spec.rb +109 -61
  133. data/lib/capybara/spec/session/click_link_or_button_spec.rb +9 -0
  134. data/lib/capybara/spec/session/click_link_spec.rb +23 -1
  135. data/lib/capybara/spec/session/current_scope_spec.rb +1 -1
  136. data/lib/capybara/spec/session/current_url_spec.rb +11 -1
  137. data/lib/capybara/spec/session/element/matches_selector_spec.rb +40 -39
  138. data/lib/capybara/spec/session/evaluate_script_spec.rb +12 -0
  139. data/lib/capybara/spec/session/fill_in_spec.rb +46 -5
  140. data/lib/capybara/spec/session/find_link_spec.rb +10 -0
  141. data/lib/capybara/spec/session/find_spec.rb +80 -7
  142. data/lib/capybara/spec/session/first_spec.rb +2 -2
  143. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +14 -1
  144. data/lib/capybara/spec/session/frame/within_frame_spec.rb +14 -1
  145. data/lib/capybara/spec/session/has_all_selectors_spec.rb +5 -5
  146. data/lib/capybara/spec/session/has_ancestor_spec.rb +46 -0
  147. data/lib/capybara/spec/session/has_any_selectors_spec.rb +6 -2
  148. data/lib/capybara/spec/session/has_button_spec.rb +81 -0
  149. data/lib/capybara/spec/session/has_css_spec.rb +45 -8
  150. data/lib/capybara/spec/session/has_current_path_spec.rb +22 -7
  151. data/lib/capybara/spec/session/has_element_spec.rb +47 -0
  152. data/lib/capybara/spec/session/has_field_spec.rb +59 -1
  153. data/lib/capybara/spec/session/has_link_spec.rb +40 -0
  154. data/lib/capybara/spec/session/has_none_selectors_spec.rb +7 -7
  155. data/lib/capybara/spec/session/has_select_spec.rb +42 -8
  156. data/lib/capybara/spec/session/has_selector_spec.rb +19 -4
  157. data/lib/capybara/spec/session/has_sibling_spec.rb +50 -0
  158. data/lib/capybara/spec/session/has_table_spec.rb +177 -0
  159. data/lib/capybara/spec/session/has_text_spec.rb +31 -3
  160. data/lib/capybara/spec/session/html_spec.rb +1 -1
  161. data/lib/capybara/spec/session/matches_style_spec.rb +6 -4
  162. data/lib/capybara/spec/session/node_spec.rb +697 -23
  163. data/lib/capybara/spec/session/node_wrapper_spec.rb +1 -1
  164. data/lib/capybara/spec/session/refresh_spec.rb +2 -1
  165. data/lib/capybara/spec/session/reset_session_spec.rb +21 -7
  166. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +2 -2
  167. data/lib/capybara/spec/session/save_page_spec.rb +4 -4
  168. data/lib/capybara/spec/session/save_screenshot_spec.rb +4 -4
  169. data/lib/capybara/spec/session/scroll_spec.rb +9 -7
  170. data/lib/capybara/spec/session/select_spec.rb +5 -10
  171. data/lib/capybara/spec/session/selectors_spec.rb +24 -3
  172. data/lib/capybara/spec/session/uncheck_spec.rb +3 -3
  173. data/lib/capybara/spec/session/unselect_spec.rb +1 -1
  174. data/lib/capybara/spec/session/visit_spec.rb +20 -0
  175. data/lib/capybara/spec/session/window/become_closed_spec.rb +20 -17
  176. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +1 -1
  177. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +1 -1
  178. data/lib/capybara/spec/session/window/window_spec.rb +54 -57
  179. data/lib/capybara/spec/session/window/windows_spec.rb +2 -2
  180. data/lib/capybara/spec/session/within_spec.rb +36 -0
  181. data/lib/capybara/spec/spec_helper.rb +30 -19
  182. data/lib/capybara/spec/test_app.rb +122 -34
  183. data/lib/capybara/spec/views/animated.erb +49 -0
  184. data/lib/capybara/spec/views/form.erb +86 -8
  185. data/lib/capybara/spec/views/frame_child.erb +3 -2
  186. data/lib/capybara/spec/views/frame_one.erb +2 -1
  187. data/lib/capybara/spec/views/frame_parent.erb +1 -1
  188. data/lib/capybara/spec/views/frame_two.erb +1 -1
  189. data/lib/capybara/spec/views/initial_alert.erb +2 -1
  190. data/lib/capybara/spec/views/layout.erb +10 -0
  191. data/lib/capybara/spec/views/obscured.erb +10 -10
  192. data/lib/capybara/spec/views/offset.erb +33 -0
  193. data/lib/capybara/spec/views/path.erb +2 -2
  194. data/lib/capybara/spec/views/popup_one.erb +1 -1
  195. data/lib/capybara/spec/views/popup_two.erb +1 -1
  196. data/lib/capybara/spec/views/react.erb +45 -0
  197. data/lib/capybara/spec/views/scroll.erb +2 -1
  198. data/lib/capybara/spec/views/spatial.erb +31 -0
  199. data/lib/capybara/spec/views/tables.erb +67 -0
  200. data/lib/capybara/spec/views/with_animation.erb +39 -4
  201. data/lib/capybara/spec/views/with_base_tag.erb +2 -2
  202. data/lib/capybara/spec/views/with_dragula.erb +24 -0
  203. data/lib/capybara/spec/views/with_fixed_header_footer.erb +2 -1
  204. data/lib/capybara/spec/views/with_hover.erb +3 -2
  205. data/lib/capybara/spec/views/with_hover1.erb +10 -0
  206. data/lib/capybara/spec/views/with_html.erb +34 -6
  207. data/lib/capybara/spec/views/with_jquery_animation.erb +24 -0
  208. data/lib/capybara/spec/views/with_js.erb +7 -4
  209. data/lib/capybara/spec/views/with_jstree.erb +26 -0
  210. data/lib/capybara/spec/views/with_namespace.erb +1 -0
  211. data/lib/capybara/spec/views/with_scope.erb +2 -2
  212. data/lib/capybara/spec/views/with_scope_other.erb +6 -0
  213. data/lib/capybara/spec/views/with_shadow.erb +31 -0
  214. data/lib/capybara/spec/views/with_slow_unload.erb +2 -1
  215. data/lib/capybara/spec/views/with_sortable_js.erb +21 -0
  216. data/lib/capybara/spec/views/with_unload_alert.erb +1 -0
  217. data/lib/capybara/spec/views/with_windows.erb +1 -1
  218. data/lib/capybara/spec/views/within_frames.erb +1 -1
  219. data/lib/capybara/version.rb +1 -1
  220. data/lib/capybara/window.rb +14 -18
  221. data/lib/capybara.rb +91 -126
  222. data/spec/basic_node_spec.rb +30 -16
  223. data/spec/capybara_spec.rb +40 -28
  224. data/spec/counter_spec.rb +35 -0
  225. data/spec/css_builder_spec.rb +3 -1
  226. data/spec/css_splitter_spec.rb +1 -1
  227. data/spec/dsl_spec.rb +33 -22
  228. data/spec/filter_set_spec.rb +5 -5
  229. data/spec/fixtures/selenium_driver_rspec_failure.rb +3 -3
  230. data/spec/fixtures/selenium_driver_rspec_success.rb +3 -3
  231. data/spec/minitest_spec.rb +24 -2
  232. data/spec/minitest_spec_spec.rb +60 -45
  233. data/spec/per_session_config_spec.rb +1 -1
  234. data/spec/rack_test_spec.rb +131 -98
  235. data/spec/regexp_dissassembler_spec.rb +53 -39
  236. data/spec/result_spec.rb +68 -66
  237. data/spec/rspec/features_spec.rb +9 -4
  238. data/spec/rspec/scenarios_spec.rb +6 -2
  239. data/spec/rspec/shared_spec_matchers.rb +137 -98
  240. data/spec/rspec_matchers_spec.rb +25 -0
  241. data/spec/rspec_spec.rb +23 -21
  242. data/spec/sauce_spec_chrome.rb +43 -0
  243. data/spec/selector_spec.rb +77 -21
  244. data/spec/selenium_spec_chrome.rb +141 -39
  245. data/spec/selenium_spec_chrome_remote.rb +32 -17
  246. data/spec/selenium_spec_edge.rb +36 -8
  247. data/spec/selenium_spec_firefox.rb +110 -68
  248. data/spec/selenium_spec_firefox_remote.rb +22 -15
  249. data/spec/selenium_spec_ie.rb +29 -22
  250. data/spec/selenium_spec_safari.rb +162 -0
  251. data/spec/server_spec.rb +153 -81
  252. data/spec/session_spec.rb +11 -4
  253. data/spec/shared_selenium_node.rb +79 -0
  254. data/spec/shared_selenium_session.rb +179 -74
  255. data/spec/spec_helper.rb +80 -5
  256. data/spec/whitespace_normalizer_spec.rb +54 -0
  257. data/spec/xpath_builder_spec.rb +3 -1
  258. metadata +218 -30
  259. data/lib/capybara/spec/session/source_spec.rb +0 -0
  260. data/lib/capybara/spec/views/with_title.erb +0 -5
@@ -0,0 +1,162 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'selenium-webdriver'
5
+ require 'shared_selenium_session'
6
+ require 'shared_selenium_node'
7
+ require 'rspec/shared_spec_matchers'
8
+
9
+ SAFARI_DRIVER = :selenium_safari
10
+
11
+ # if ::Selenium::WebDriver::Service.respond_to? :driver_path=
12
+ # ::Selenium::WebDriver::Safari::Service
13
+ # else
14
+ # ::Selenium::WebDriver::Safari
15
+ # end.driver_path = '/Applications/Safari Technology Preview.app/Contents/MacOS/safaridriver'
16
+
17
+ browser_options = Selenium::WebDriver::Safari::Options.new
18
+ # browser_options.headless! if ENV['HEADLESS']
19
+
20
+ Capybara.register_driver :selenium_safari do |app|
21
+ version = Capybara::Selenium::Driver.load_selenium
22
+ options_key = Capybara::Selenium::Driver::CAPS_VERSION.satisfied_by?(version) ? :capabilities : :options
23
+ driver_options = { browser: :safari, timeout: 30 }.tap do |opts|
24
+ opts[options_key] = browser_options
25
+ end
26
+
27
+ Capybara::Selenium::Driver.new(app, **driver_options).tap do |driver|
28
+ # driver.browser.download_path = Capybara.save_path
29
+ end
30
+ end
31
+
32
+ Capybara.register_driver :selenium_safari_not_clear_storage do |app|
33
+ version = Capybara::Selenium::Driver.load_selenium
34
+ options_key = Capybara::Selenium::Driver::CAPS_VERSION.satisfied_by?(version) ? :capabilities : :options
35
+ driver_options = {
36
+ browser: :safari,
37
+ clear_local_storage: false,
38
+ clear_session_storage: false,
39
+ timeout: 30
40
+ }.tap do |opts|
41
+ opts[options_key] = browser_options
42
+ end
43
+
44
+ Capybara::Selenium::Driver.new(app, **driver_options)
45
+ end
46
+
47
+ module TestSessions
48
+ Safari = Capybara::Session.new(SAFARI_DRIVER, TestApp)
49
+ end
50
+
51
+ skipped_tests = %i[response_headers status_code trigger windows drag]
52
+
53
+ Capybara::SpecHelper.log_selenium_driver_version(Selenium::WebDriver::Safari) if ENV['CI']
54
+
55
+ Capybara::SpecHelper.run_specs TestSessions::Safari, SAFARI_DRIVER.to_s, capybara_skip: skipped_tests do |example|
56
+ case example.metadata[:full_description]
57
+ when /click_link can download a file/
58
+ skip "safaridriver doesn't provide a way to set the download directory"
59
+ when /Capybara::Session selenium_safari Capybara::Window#maximize/
60
+ pending "Safari headless doesn't support maximize" if ENV['HEADLESS']
61
+ when /Capybara::Session selenium_safari #visit without a server/,
62
+ /Capybara::Session selenium_safari #visit with Capybara.app_host set should override server/,
63
+ /Capybara::Session selenium_safari #reset_session! When reuse_server == false raises any standard errors caught inside the server during a second session/
64
+ skip "Safari webdriver doesn't support multiple sessions"
65
+ when /Capybara::Session selenium_safari #click_link with alternative text given to a contained image/,
66
+ 'Capybara::Session selenium_safari #click_link_or_button with enable_aria_label should click on link'
67
+ pending 'safaridriver thinks these links are non-interactable for some unknown reason'
68
+ when /Capybara::Session selenium_safari #attach_file with a block can upload by clicking the file input/
69
+ skip "safaridriver doesn't allow clicking on file inputs"
70
+ when /Capybara::Session selenium_safari #within_frame works if the frame is closed/,
71
+ /Capybara::Session selenium_safari #switch_to_frame works if the frame is closed/
72
+ skip 'Safari has a race condition when clicking an element that causes the frame to close. It will sometimes raise a NoSuchFrameError'
73
+ when /Capybara::Session selenium_safari #reset_session! removes ALL cookies/
74
+ skip 'Safari webdriver can only remove cookies for the current domain'
75
+ when /Capybara::Session selenium_safari #refresh it reposts/
76
+ skip "Safari opens an alert that can't be closed"
77
+ when 'Capybara::Session selenium_safari node #double_click should allow to adjust the offset',
78
+ 'Capybara::Session selenium_safari node #double_click should double click an element'
79
+ pending "safardriver doesn't generate a double click event"
80
+ when 'Capybara::Session selenium_safari node #double_click should allow modifiers'
81
+ pending "safaridriver doesn't generate double click with key modifiers"
82
+ when /when w3c_click_offset is true should offset/
83
+ pending 'w3c_click_offset is not currently supported with safaridriver'
84
+ when 'Capybara::Session selenium_safari #go_back should fetch a response from the driver from the previous page',
85
+ 'Capybara::Session selenium_safari #go_forward should fetch a response from the driver from the previous page'
86
+ skip 'safaridriver loses the ability to find elements in the document after `go_back`'
87
+ when 'Capybara::Session selenium node #shadow_root should get the shadow root',
88
+ 'Capybara::Session selenium node #shadow_root should find elements inside the shadow dom using CSS',
89
+ 'Capybara::Session selenium node #shadow_root should find nested shadow roots'
90
+ pending "Safari doesn't yet have W3C shadow root support"
91
+ when /Capybara::Session selenium_chrome node #shadow_root should get visible text/
92
+ pending "Selenium doesn't currently support getting visible text for shadow root elements"
93
+ end
94
+ end
95
+
96
+ RSpec.describe 'Capybara::Session with safari' do
97
+ include Capybara::SpecHelper
98
+ ['Capybara::Session', 'Capybara::Node', Capybara::RSpecMatchers].each do |examples|
99
+ include_examples examples, TestSessions::Safari, SAFARI_DRIVER
100
+ end
101
+
102
+ context 'storage' do
103
+ describe '#reset!' do
104
+ it 'clears storage by default' do
105
+ session = TestSessions::Safari
106
+ session.visit('/with_js')
107
+ session.find(:css, '#set-storage').click
108
+ session.reset!
109
+ session.visit('/with_js')
110
+ expect(session.evaluate_script('Object.keys(localStorage)')).to be_empty
111
+ expect(session.evaluate_script('Object.keys(sessionStorage)')).to be_empty
112
+ end
113
+
114
+ it 'does not clear storage when false' do
115
+ skip "Safari webdriver doesn't support multiple sessions"
116
+ session = Capybara::Session.new(:selenium_safari_not_clear_storage, TestApp)
117
+ session.visit('/with_js')
118
+ session.find(:css, '#set-storage').click
119
+ session.reset!
120
+ session.visit('/with_js')
121
+ expect(session.evaluate_script('Object.keys(localStorage)')).not_to be_empty
122
+ expect(session.evaluate_script('Object.keys(sessionStorage)')).not_to be_empty
123
+ end
124
+ end
125
+ end
126
+
127
+ context 'timeout' do
128
+ it 'sets the http client read timeout' do
129
+ expect(TestSessions::Safari.driver.browser.send(:bridge).http.read_timeout).to eq 30
130
+ end
131
+ end
132
+
133
+ describe 'filling in Safari-specific date and time fields with keystrokes' do
134
+ let(:datetime) { Time.new(1983, 6, 19, 6, 30) }
135
+ let(:session) { TestSessions::Safari }
136
+
137
+ before do
138
+ session.visit('/form')
139
+ end
140
+
141
+ it 'should fill in a date input with a String' do
142
+ session.fill_in('form_date', with: '06/19/1983')
143
+ session.click_button('awesome')
144
+ expect(Date.parse(extract_results(session)['date'])).to eq datetime.to_date
145
+ end
146
+
147
+ it 'should fill in a time input with a String' do
148
+ # Safari doesn't support time inputs - so this is just a text input
149
+ session.fill_in('form_time', with: '06:30A')
150
+ session.click_button('awesome')
151
+ results = extract_results(session)['time']
152
+ expect(Time.parse(results).strftime('%r')).to eq datetime.strftime('%r')
153
+ end
154
+
155
+ it 'should fill in a datetime input with a String' do
156
+ pending "Safari doesn't support datetime inputs"
157
+ session.fill_in('form_datetime', with: "06/19/1983\t06:30A")
158
+ session.click_button('awesome')
159
+ expect(Time.parse(extract_results(session)['datetime'])).to eq datetime
160
+ end
161
+ end
162
+ end
data/spec/server_spec.rb CHANGED
@@ -4,34 +4,34 @@ require 'spec_helper'
4
4
 
5
5
  RSpec.describe Capybara::Server do
6
6
  it 'should spool up a rack server' do
7
- @app = proc { |_env| [200, {}, ['Hello Server!']] }
8
- @server = Capybara::Server.new(@app).boot
7
+ app = proc { |_env| [200, {}, ['Hello Server!']] }
8
+ server = described_class.new(app).boot
9
9
 
10
- @res = Net::HTTP.start(@server.host, @server.port) { |http| http.get('/') }
10
+ res = Net::HTTP.start(server.host, server.port) { |http| http.get('/') }
11
11
 
12
- expect(@res.body).to include('Hello Server')
12
+ expect(res.body).to include('Hello Server')
13
13
  end
14
14
 
15
15
  it 'should do nothing when no server given' do
16
16
  expect do
17
- @server = Capybara::Server.new(nil).boot
17
+ described_class.new(nil).boot
18
18
  end.not_to raise_error
19
19
  end
20
20
 
21
21
  it 'should bind to the specified host' do
22
22
  # TODO: travis with jruby in container mode has an issue with this test
23
- skip 'This platform has an issue with this test' if (ENV['TRAVIS'] && (RUBY_ENGINE == 'jruby')) || Gem.win_platform?
23
+ skip 'This platform has an issue with this test' if (ENV.fetch('TRAVIS', nil) && (RUBY_ENGINE == 'jruby')) || Gem.win_platform?
24
24
 
25
25
  begin
26
26
  app = proc { |_env| [200, {}, ['Hello Server!']] }
27
27
 
28
28
  Capybara.server_host = '127.0.0.1'
29
- server = Capybara::Server.new(app).boot
29
+ server = described_class.new(app).boot
30
30
  res = Net::HTTP.get(URI("http://127.0.0.1:#{server.port}"))
31
31
  expect(res).to eq('Hello Server!')
32
32
 
33
33
  Capybara.server_host = '0.0.0.0'
34
- server = Capybara::Server.new(app).boot
34
+ server = described_class.new(app).boot
35
35
  res = Net::HTTP.get(URI("http://127.0.0.1:#{server.port}"))
36
36
  expect(res).to eq('Hello Server!')
37
37
  ensure
@@ -42,84 +42,140 @@ RSpec.describe Capybara::Server do
42
42
  it 'should use specified port' do
43
43
  Capybara.server_port = 22789
44
44
 
45
- @app = proc { |_env| [200, {}, ['Hello Server!']] }
46
- @server = Capybara::Server.new(@app).boot
45
+ app = proc { |_env| [200, {}, ['Hello Server!']] }
46
+ server = described_class.new(app).boot
47
47
 
48
- @res = Net::HTTP.start(@server.host, 22789) { |http| http.get('/') }
49
- expect(@res.body).to include('Hello Server')
48
+ res = Net::HTTP.start(server.host, 22789) { |http| http.get('/') }
49
+ expect(res.body).to include('Hello Server')
50
50
 
51
51
  Capybara.server_port = nil
52
52
  end
53
53
 
54
54
  it 'should use given port' do
55
- @app = proc { |_env| [200, {}, ['Hello Server!']] }
56
- @server = Capybara::Server.new(@app, port: 22790).boot
55
+ app = proc { |_env| [200, {}, ['Hello Server!']] }
56
+ server = described_class.new(app, port: 22790).boot
57
57
 
58
- @res = Net::HTTP.start(@server.host, 22790) { |http| http.get('/') }
59
- expect(@res.body).to include('Hello Server')
58
+ res = Net::HTTP.start(server.host, 22790) { |http| http.get('/') }
59
+ expect(res.body).to include('Hello Server')
60
60
 
61
61
  Capybara.server_port = nil
62
62
  end
63
63
 
64
64
  it 'should find an available port' do
65
- @app1 = proc { |_env| [200, {}, ['Hello Server!']] }
66
- @app2 = proc { |_env| [200, {}, ['Hello Second Server!']] }
65
+ responses = ['Hello Server!', 'Hello Second Server!']
66
+ apps = responses.map do |response|
67
+ proc { |_env| [200, {}, [response]] }
68
+ end
69
+ servers = apps.map { |app| described_class.new(app).boot }
67
70
 
68
- @server1 = Capybara::Server.new(@app1).boot
69
- @server2 = Capybara::Server.new(@app2).boot
71
+ servers.each_with_index do |server, idx|
72
+ result = Net::HTTP.start(server.host, server.port) { |http| http.get('/') }
73
+ expect(result.body).to include(responses[idx])
74
+ end
75
+ end
70
76
 
71
- @res1 = Net::HTTP.start(@server1.host, @server1.port) { |http| http.get('/') }
72
- expect(@res1.body).to include('Hello Server')
77
+ it 'should handle that getting available ports fails randomly' do
78
+ # Use a port to force a EADDRINUSE error to be generated
79
+ server = TCPServer.new('0.0.0.0', 0)
80
+ server_port = server.addr[1]
81
+ d_server = instance_double(TCPServer, addr: [nil, server_port, nil, nil], close: nil)
82
+ call_count = 0
83
+ allow(TCPServer).to receive(:new).and_wrap_original do |m, *args|
84
+ call_count.zero? ? d_server : m.call(*args)
85
+ ensure
86
+ call_count += 1
87
+ end
73
88
 
74
- @res2 = Net::HTTP.start(@server2.host, @server2.port) { |http| http.get('/') }
75
- expect(@res2.body).to include('Hello Second Server')
89
+ port = described_class.new(Object.new, host: '0.0.0.0').port
90
+ expect(port).not_to eq(server_port)
91
+ ensure
92
+ server&.close
76
93
  end
77
94
 
78
- it 'should support SSL' do
79
- begin
80
- key = File.join(Dir.pwd, 'spec', 'fixtures', 'key.pem')
81
- cert = File.join(Dir.pwd, 'spec', 'fixtures', 'certificate.pem')
82
- Capybara.server = :puma, { Host: "ssl://#{Capybara.server_host}?key=#{key}&cert=#{cert}" }
83
- app = proc { |_env| [200, {}, ['Hello SSL Server!']] }
84
- server = Capybara::Server.new(app).boot
95
+ it 'should return its #base_url' do
96
+ app = proc { |_env| [200, {}, ['Hello Server!']] }
97
+ server = described_class.new(app).boot
98
+ uri = Addressable::URI.parse(server.base_url)
99
+ expect(uri.to_hash).to include(scheme: 'http', host: server.host, port: server.port)
100
+ end
85
101
 
86
- expect do
87
- Net::HTTP.start(server.host, server.port) { |http| http.get('/__idntify__') }
88
- end.to raise_error(EOFError)
102
+ it 'should call #clamp on the puma configuration to ensure that environment is a string' do
103
+ Capybara.server = :puma
104
+ app_proc = proc { |_env| [200, {}, ['Hello Puma!']] }
105
+ require 'puma'
106
+ allow(Puma::Server).to receive(:new).and_wrap_original do |method, app, events, options|
107
+ # If #clamp is not called on the puma config then this will be a Proc
108
+ expect(options.fetch(:environment)).to be_a(String)
109
+ method.call(app, events, options)
110
+ end
111
+ server = described_class.new(app_proc).boot
112
+ expect(Puma::Server).to have_received(:new).with(
113
+ anything,
114
+ anything,
115
+ satisfy { |opts| opts.final_options[:Port] == server.port }
116
+ )
117
+ ensure
118
+ Capybara.server = :default
119
+ end
89
120
 
90
- res = Net::HTTP.start(server.host, server.port, use_ssl: true, verify_mode: OpenSSL::SSL::VERIFY_NONE) do |https|
91
- https.get('/')
92
- end
121
+ it 'should not emit any warnings when booting puma' do
122
+ Capybara.server = :puma
123
+ app_proc = proc { |_env| [200, {}, ['Hello Puma!']] }
124
+ require 'puma'
93
125
 
94
- expect(res.body).to include('Hello SSL Server!')
95
- ensure
96
- Capybara.server = :default
126
+ expect do
127
+ described_class.new(app_proc).boot
128
+ end.not_to output.to_stderr
129
+ ensure
130
+ Capybara.server = :default
131
+ end
132
+
133
+ it 'should support SSL' do
134
+ key = File.join(Dir.pwd, 'spec', 'fixtures', 'key.pem')
135
+ cert = File.join(Dir.pwd, 'spec', 'fixtures', 'certificate.pem')
136
+ Capybara.server = :puma, { Host: "ssl://#{Capybara.server_host}?key=#{key}&cert=#{cert}" }
137
+ app = proc { |_env| [200, {}, ['Hello SSL Server!']] }
138
+ server = described_class.new(app).boot
139
+
140
+ expect do
141
+ Net::HTTP.start(server.host, server.port, max_retries: 0) { |http| http.get('/__identify__') }
142
+ end.to(raise_error do |e|
143
+ expect(e.is_a?(EOFError) || e.is_a?(Net::ReadTimeout)).to be true
144
+ end)
145
+
146
+ res = Net::HTTP.start(server.host, server.port, use_ssl: true, verify_mode: OpenSSL::SSL::VERIFY_NONE) do |https|
147
+ https.get('/')
97
148
  end
149
+
150
+ expect(res.body).to include('Hello SSL Server!')
151
+ uri = Addressable::URI.parse(server.base_url)
152
+ expect(uri.to_hash).to include(scheme: 'https', host: server.host, port: server.port)
153
+ ensure
154
+ Capybara.server = :default
98
155
  end
99
156
 
100
157
  context 'When Capybara.reuse_server is true' do
158
+ let!(:old_reuse_server) { Capybara.reuse_server }
159
+
101
160
  before do
102
- @old_reuse_server = Capybara.reuse_server
103
161
  Capybara.reuse_server = true
104
162
  end
105
163
 
106
164
  after do
107
- Capybara.reuse_server = @old_reuse_server
165
+ Capybara.reuse_server = old_reuse_server
108
166
  end
109
167
 
110
168
  it 'should use the existing server if it already running' do
111
- @app = proc { |_env| [200, {}, ['Hello Server!']] }
112
-
113
- @server1 = Capybara::Server.new(@app).boot
114
- @server2 = Capybara::Server.new(@app).boot
169
+ app = proc { |_env| [200, {}, ['Hello Server!']] }
115
170
 
116
- res = Net::HTTP.start(@server1.host, @server1.port) { |http| http.get('/') }
117
- expect(res.body).to include('Hello Server')
171
+ servers = Array.new(2) { described_class.new(app).boot }
118
172
 
119
- res = Net::HTTP.start(@server2.host, @server2.port) { |http| http.get('/') }
120
- expect(res.body).to include('Hello Server')
173
+ servers.each do |server|
174
+ res = Net::HTTP.start(server.host, server.port) { |http| http.get('/') }
175
+ expect(res.body).to include('Hello Server')
176
+ end
121
177
 
122
- expect(@server1.port).to eq(@server2.port)
178
+ expect(servers[0].port).to eq(servers[1].port)
123
179
  end
124
180
 
125
181
  it 'detects and waits for all reused server sessions pending requests' do
@@ -132,15 +188,15 @@ RSpec.describe Capybara::Server do
132
188
  [200, {}, ['Hello Server!']]
133
189
  end
134
190
 
135
- server1 = Capybara::Server.new(app).boot
136
- server2 = Capybara::Server.new(app).boot
191
+ server1 = described_class.new(app).boot
192
+ server2 = described_class.new(app).boot
137
193
 
138
194
  expect do
139
195
  start_request(server1, 1.0)
140
196
  start_request(server2, 3.0)
141
197
  server1.wait_for_pending_requests
142
198
  end.to change { done }.from(0).to(2)
143
- expect(server2.send(:pending_requests?)).to eq(false)
199
+ expect(server2.send(:pending_requests?)).to be(false)
144
200
  end
145
201
  end
146
202
 
@@ -151,22 +207,20 @@ RSpec.describe Capybara::Server do
151
207
  end
152
208
 
153
209
  after do
154
- Capybara.reuse_server = @old_reuse_server
210
+ Capybara.reuse_server = @old_reuse_server # rubocop:disable RSpec/InstanceVariable
155
211
  end
156
212
 
157
213
  it 'should not reuse an already running server' do
158
- @app = proc { |_env| [200, {}, ['Hello Server!']] }
159
-
160
- @server1 = Capybara::Server.new(@app).boot
161
- @server2 = Capybara::Server.new(@app).boot
214
+ app = proc { |_env| [200, {}, ['Hello Server!']] }
162
215
 
163
- res = Net::HTTP.start(@server1.host, @server1.port) { |http| http.get('/') }
164
- expect(res.body).to include('Hello Server')
216
+ servers = Array.new(2) { described_class.new(app).boot }
165
217
 
166
- res = Net::HTTP.start(@server2.host, @server2.port) { |http| http.get('/') }
167
- expect(res.body).to include('Hello Server')
218
+ servers.each do |server|
219
+ res = Net::HTTP.start(server.host, server.port) { |http| http.get('/') }
220
+ expect(res.body).to include('Hello Server')
221
+ end
168
222
 
169
- expect(@server1.port).not_to eq(@server2.port)
223
+ expect(servers[0].port).not_to eq(servers[1].port)
170
224
  end
171
225
 
172
226
  it 'detects and waits for only one sessions pending requests' do
@@ -179,15 +233,15 @@ RSpec.describe Capybara::Server do
179
233
  [200, {}, ['Hello Server!']]
180
234
  end
181
235
 
182
- server1 = Capybara::Server.new(app).boot
183
- server2 = Capybara::Server.new(app).boot
236
+ server1 = described_class.new(app).boot
237
+ server2 = described_class.new(app).boot
184
238
 
185
239
  expect do
186
240
  start_request(server1, 1.0)
187
241
  start_request(server2, 3.0)
188
242
  server1.wait_for_pending_requests
189
243
  end.to change { done }.from(0).to(1)
190
- expect(server2.send(:pending_requests?)).to eq(true)
244
+ expect(server2.send(:pending_requests?)).to be(true)
191
245
  expect do
192
246
  server2.wait_for_pending_requests
193
247
  end.to change { done }.from(1).to(2)
@@ -195,26 +249,44 @@ RSpec.describe Capybara::Server do
195
249
  end
196
250
 
197
251
  it 'should raise server errors when the server errors before the timeout' do
198
- begin
199
- Capybara.register_server :kaboom do
200
- sleep 0.1
201
- raise 'kaboom'
202
- end
203
- Capybara.server = :kaboom
252
+ Capybara.register_server :kaboom do
253
+ sleep 0.1
254
+ raise 'kaboom'
255
+ end
256
+ Capybara.server = :kaboom
204
257
 
205
- expect do
206
- Capybara::Server.new(proc { |e| }).boot
207
- end.to raise_error(RuntimeError, 'kaboom')
208
- ensure
209
- Capybara.server = :default
258
+ expect do
259
+ described_class.new(proc { |e| }).boot
260
+ end.to raise_error(RuntimeError, 'kaboom')
261
+ ensure
262
+ Capybara.server = :default
263
+ end
264
+
265
+ it 'should raise an error when there are pending requests' do
266
+ app = proc do |env|
267
+ request = Rack::Request.new(env)
268
+ sleep request.params['wait_time'].to_f
269
+ [200, {}, ['Hello Server!']]
210
270
  end
271
+
272
+ server = described_class.new(app).boot
273
+
274
+ expect do
275
+ start_request(server, 59.0)
276
+ server.wait_for_pending_requests
277
+ end.not_to raise_error
278
+
279
+ expect do
280
+ start_request(server, 61.0)
281
+ server.wait_for_pending_requests
282
+ end.to raise_error('Requests did not finish in 60 seconds: ["/?wait_time=61.0"]')
211
283
  end
212
284
 
213
285
  it 'is not #responsive? when Net::HTTP raises a SystemCallError' do
214
286
  app = -> { [200, {}, ['Hello, world']] }
215
- server = Capybara::Server.new(app)
287
+ server = described_class.new(app)
216
288
  allow(Net::HTTP).to receive(:start).and_raise(SystemCallError.allocate)
217
- expect(server.responsive?).to eq false
289
+ expect(server.responsive?).to be false
218
290
  end
219
291
 
220
292
  [EOFError, Net::ReadTimeout].each do |err|
@@ -227,7 +299,7 @@ RSpec.describe Capybara::Server do
227
299
  response = Net::HTTPSuccess.allocate
228
300
  allow(response).to receive(:body).and_return app.object_id.to_s
229
301
  allow(Net::HTTP).to receive(:start).with(anything, anything, hash_including(use_ssl: true)).and_return(response).once
230
- Capybara::Server.new(app).boot
302
+ described_class.new(app).boot
231
303
  expect(Net::HTTP).to have_received(:start).exactly(3).times
232
304
  end
233
305
  end
data/spec/session_spec.rb CHANGED
@@ -3,16 +3,16 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  RSpec.describe Capybara::Session do
6
- context '#new' do
6
+ describe '#new' do
7
7
  it 'should raise an error if passed non-existent driver' do
8
8
  expect do
9
- Capybara::Session.new(:quox, TestApp).driver
9
+ described_class.new(:quox, TestApp).driver
10
10
  end.to raise_error(Capybara::DriverNotFoundError)
11
11
  end
12
12
 
13
13
  it 'verifies a passed app is a rack app' do
14
14
  expect do
15
- Capybara::Session.new(:unknown, random: 'hash')
15
+ described_class.new(:unknown, random: 'hash')
16
16
  end.to raise_error TypeError, 'The second parameter to Session::new should be a rack app if passed.'
17
17
  end
18
18
  end
@@ -75,10 +75,17 @@ RSpec.describe Capybara::Session do
75
75
 
76
76
  context 'quit' do
77
77
  it 'will reset the driver' do
78
- session = Capybara::Session.new(:rack_test, TestApp)
78
+ session = described_class.new(:rack_test, TestApp)
79
79
  driver = session.driver
80
80
  session.quit
81
81
  expect(session.driver).not_to eql driver
82
82
  end
83
+
84
+ it 'resets the document' do
85
+ session = described_class.new(:rack_test, TestApp)
86
+ document = session.document
87
+ session.quit
88
+ expect(session.document.base).not_to eql document.base
89
+ end
83
90
  end
84
91
  end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'selenium-webdriver'
5
+
6
+ RSpec.shared_examples 'Capybara::Node' do |session, _mode|
7
+ let(:session) { session }
8
+
9
+ describe '#content_editable?' do
10
+ it 'returns true when the element is content editable' do
11
+ session.visit('/with_js')
12
+ expect(session.find(:css, '#existing_content_editable').base.content_editable?).to be true
13
+ expect(session.find(:css, '#existing_content_editable_child').base.content_editable?).to be true
14
+ end
15
+
16
+ it 'returns false when the element is not content editable' do
17
+ session.visit('/with_js')
18
+ expect(session.find(:css, '#drag').base.content_editable?).to be false
19
+ end
20
+ end
21
+
22
+ describe '#send_keys' do
23
+ it 'should process space' do
24
+ session.visit('/form')
25
+ session.find(:css, '#address1_city').send_keys('ocean', [:shift, :space, 'side'])
26
+ expect(session.find(:css, '#address1_city').value).to eq 'ocean SIDE'
27
+ end
28
+ end
29
+
30
+ describe '#[]' do
31
+ it 'should work for spellcheck' do
32
+ session.visit('/with_html')
33
+ expect(session.find('//input[@spellcheck="TRUE"]')[:spellcheck]).to eq('true')
34
+ expect(session.find('//input[@spellcheck="FALSE"]')[:spellcheck]).to eq('false')
35
+ end
36
+ end
37
+
38
+ describe '#set' do
39
+ it 'respects maxlength when using rapid set' do
40
+ session.visit('/form')
41
+ inp = session.find(:css, '#long_length')
42
+ value = (0...50).map { |i| ((i % 26) + 65).chr }.join
43
+ inp.set(value, rapid: true)
44
+ expect(inp.value).to eq value[0...35]
45
+ end
46
+ end
47
+
48
+ describe '#visible?' do
49
+ let(:bridge) do
50
+ session.driver.browser.send(:bridge)
51
+ end
52
+
53
+ around do |example|
54
+ native_displayed = session.driver.options[:native_displayed]
55
+ example.run
56
+ session.driver.options[:native_displayed] = native_displayed
57
+ end
58
+
59
+ before do
60
+ allow(bridge).to receive(:execute_atom).and_call_original
61
+ end
62
+
63
+ it 'will use native displayed if told to' do
64
+ session.driver.options[:native_displayed] = true
65
+ session.visit('/form')
66
+ session.find(:css, '#address1_city', visible: true)
67
+
68
+ expect(bridge).not_to have_received(:execute_atom)
69
+ end
70
+
71
+ it "won't use native displayed if told not to" do
72
+ session.driver.options[:native_displayed] = false
73
+ session.visit('/form')
74
+ session.find(:css, '#address1_city', visible: true)
75
+
76
+ expect(bridge).to have_received(:execute_atom).with(:isDisplayed, any_args)
77
+ end
78
+ end
79
+ end