capybara 2.18.0 → 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 (316) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -1
  3. data/History.md +945 -12
  4. data/License.txt +1 -1
  5. data/README.md +264 -90
  6. data/lib/capybara/config.rb +29 -57
  7. data/lib/capybara/cucumber.rb +2 -3
  8. data/lib/capybara/driver/base.rb +35 -18
  9. data/lib/capybara/driver/node.rb +40 -10
  10. data/lib/capybara/dsl.rb +10 -7
  11. data/lib/capybara/helpers.rb +70 -31
  12. data/lib/capybara/minitest/spec.rb +185 -83
  13. data/lib/capybara/minitest.rb +232 -112
  14. data/lib/capybara/node/actions.rb +274 -171
  15. data/lib/capybara/node/base.rb +42 -34
  16. data/lib/capybara/node/document.rb +15 -3
  17. data/lib/capybara/node/document_matchers.rb +19 -21
  18. data/lib/capybara/node/element.rb +362 -135
  19. data/lib/capybara/node/finders.rb +151 -137
  20. data/lib/capybara/node/matchers.rb +394 -209
  21. data/lib/capybara/node/simple.rb +59 -26
  22. data/lib/capybara/node/whitespace_normalizer.rb +81 -0
  23. data/lib/capybara/queries/active_element_query.rb +18 -0
  24. data/lib/capybara/queries/ancestor_query.rb +12 -9
  25. data/lib/capybara/queries/base_query.rb +39 -28
  26. data/lib/capybara/queries/current_path_query.rb +21 -27
  27. data/lib/capybara/queries/match_query.rb +14 -7
  28. data/lib/capybara/queries/selector_query.rb +659 -149
  29. data/lib/capybara/queries/sibling_query.rb +11 -9
  30. data/lib/capybara/queries/style_query.rb +45 -0
  31. data/lib/capybara/queries/text_query.rb +56 -38
  32. data/lib/capybara/queries/title_query.rb +8 -11
  33. data/lib/capybara/rack_test/browser.rb +114 -42
  34. data/lib/capybara/rack_test/css_handlers.rb +6 -4
  35. data/lib/capybara/rack_test/driver.rb +22 -17
  36. data/lib/capybara/rack_test/errors.rb +6 -0
  37. data/lib/capybara/rack_test/form.rb +93 -58
  38. data/lib/capybara/rack_test/node.rb +184 -81
  39. data/lib/capybara/rails.rb +3 -7
  40. data/lib/capybara/registration_container.rb +41 -0
  41. data/lib/capybara/registrations/drivers.rb +42 -0
  42. data/lib/capybara/registrations/patches/puma_ssl.rb +29 -0
  43. data/lib/capybara/registrations/servers.rb +66 -0
  44. data/lib/capybara/result.rb +97 -63
  45. data/lib/capybara/rspec/features.rb +17 -50
  46. data/lib/capybara/rspec/matcher_proxies.rb +52 -15
  47. data/lib/capybara/rspec/matchers/base.rb +113 -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 +69 -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 +43 -0
  59. data/lib/capybara/rspec/matchers/spatial_sugar.rb +39 -0
  60. data/lib/capybara/rspec/matchers.rb +146 -310
  61. data/lib/capybara/rspec.rb +7 -11
  62. data/lib/capybara/selector/builders/css_builder.rb +84 -0
  63. data/lib/capybara/selector/builders/xpath_builder.rb +71 -0
  64. data/lib/capybara/selector/css.rb +85 -13
  65. data/lib/capybara/selector/definition/button.rb +68 -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 +28 -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 +55 -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/definition.rb +280 -0
  87. data/lib/capybara/selector/filter.rb +2 -17
  88. data/lib/capybara/selector/filter_set.rb +80 -33
  89. data/lib/capybara/selector/filters/base.rb +50 -6
  90. data/lib/capybara/selector/filters/expression_filter.rb +8 -26
  91. data/lib/capybara/selector/filters/locator_filter.rb +29 -0
  92. data/lib/capybara/selector/filters/node_filter.rb +16 -12
  93. data/lib/capybara/selector/regexp_disassembler.rb +211 -0
  94. data/lib/capybara/selector/selector.rb +93 -210
  95. data/lib/capybara/selector/xpath_extensions.rb +17 -0
  96. data/lib/capybara/selector.rb +475 -523
  97. data/lib/capybara/selenium/atoms/getAttribute.min.js +1 -0
  98. data/lib/capybara/selenium/atoms/isDisplayed.min.js +1 -0
  99. data/lib/capybara/selenium/atoms/src/getAttribute.js +161 -0
  100. data/lib/capybara/selenium/atoms/src/isDisplayed.js +454 -0
  101. data/lib/capybara/selenium/driver.rb +298 -267
  102. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +117 -0
  103. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +128 -0
  104. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +84 -0
  105. data/lib/capybara/selenium/driver_specializations/internet_explorer_driver.rb +26 -0
  106. data/lib/capybara/selenium/driver_specializations/safari_driver.rb +24 -0
  107. data/lib/capybara/selenium/extensions/file_input_click_emulation.rb +34 -0
  108. data/lib/capybara/selenium/extensions/find.rb +110 -0
  109. data/lib/capybara/selenium/extensions/html5_drag.rb +229 -0
  110. data/lib/capybara/selenium/extensions/modifier_keys_stack.rb +28 -0
  111. data/lib/capybara/selenium/extensions/scroll.rb +76 -0
  112. data/lib/capybara/selenium/node.rb +517 -145
  113. data/lib/capybara/selenium/nodes/chrome_node.rb +125 -0
  114. data/lib/capybara/selenium/nodes/edge_node.rb +110 -0
  115. data/lib/capybara/selenium/nodes/firefox_node.rb +136 -0
  116. data/lib/capybara/selenium/nodes/ie_node.rb +22 -0
  117. data/lib/capybara/selenium/nodes/safari_node.rb +118 -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 +80 -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 +59 -67
  127. data/lib/capybara/session/config.rb +81 -67
  128. data/lib/capybara/session/matchers.rb +28 -20
  129. data/lib/capybara/session.rb +337 -365
  130. data/lib/capybara/spec/public/jquery.js +5 -5
  131. data/lib/capybara/spec/public/offset.js +6 -0
  132. data/lib/capybara/spec/public/test.js +151 -12
  133. data/lib/capybara/spec/session/accept_alert_spec.rb +12 -11
  134. data/lib/capybara/spec/session/accept_confirm_spec.rb +6 -5
  135. data/lib/capybara/spec/session/accept_prompt_spec.rb +10 -10
  136. data/lib/capybara/spec/session/active_element_spec.rb +31 -0
  137. data/lib/capybara/spec/session/all_spec.rb +161 -57
  138. data/lib/capybara/spec/session/ancestor_spec.rb +27 -24
  139. data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +68 -38
  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.rb → assert_title_spec.rb} +22 -12
  145. data/lib/capybara/spec/session/attach_file_spec.rb +144 -69
  146. data/lib/capybara/spec/session/body_spec.rb +12 -13
  147. data/lib/capybara/spec/session/check_spec.rb +117 -55
  148. data/lib/capybara/spec/session/choose_spec.rb +64 -31
  149. data/lib/capybara/spec/session/click_button_spec.rb +231 -173
  150. data/lib/capybara/spec/session/click_link_or_button_spec.rb +55 -35
  151. data/lib/capybara/spec/session/click_link_spec.rb +93 -58
  152. data/lib/capybara/spec/session/current_scope_spec.rb +12 -11
  153. data/lib/capybara/spec/session/current_url_spec.rb +57 -39
  154. data/lib/capybara/spec/session/dismiss_confirm_spec.rb +4 -4
  155. data/lib/capybara/spec/session/dismiss_prompt_spec.rb +3 -2
  156. data/lib/capybara/spec/session/element/{assert_match_selector.rb → assert_match_selector_spec.rb} +11 -9
  157. data/lib/capybara/spec/session/element/match_css_spec.rb +18 -10
  158. data/lib/capybara/spec/session/element/match_xpath_spec.rb +9 -7
  159. data/lib/capybara/spec/session/element/matches_selector_spec.rb +71 -57
  160. data/lib/capybara/spec/session/evaluate_async_script_spec.rb +8 -7
  161. data/lib/capybara/spec/session/evaluate_script_spec.rb +29 -8
  162. data/lib/capybara/spec/session/execute_script_spec.rb +10 -8
  163. data/lib/capybara/spec/session/fill_in_spec.rb +134 -43
  164. data/lib/capybara/spec/session/find_button_spec.rb +25 -24
  165. data/lib/capybara/spec/session/find_by_id_spec.rb +10 -9
  166. data/lib/capybara/spec/session/find_field_spec.rb +37 -41
  167. data/lib/capybara/spec/session/find_link_spec.rb +46 -17
  168. data/lib/capybara/spec/session/find_spec.rb +260 -145
  169. data/lib/capybara/spec/session/first_spec.rb +80 -52
  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 +33 -20
  173. data/lib/capybara/spec/session/frame/within_frame_spec.rb +52 -32
  174. data/lib/capybara/spec/session/go_back_spec.rb +2 -1
  175. data/lib/capybara/spec/session/go_forward_spec.rb +2 -1
  176. data/lib/capybara/spec/session/has_all_selectors_spec.rb +31 -31
  177. data/lib/capybara/spec/session/has_ancestor_spec.rb +46 -0
  178. data/lib/capybara/spec/session/has_any_selectors_spec.rb +29 -0
  179. data/lib/capybara/spec/session/has_button_spec.rb +100 -13
  180. data/lib/capybara/spec/session/has_css_spec.rb +272 -137
  181. data/lib/capybara/spec/session/has_current_path_spec.rb +60 -61
  182. data/lib/capybara/spec/session/has_element_spec.rb +47 -0
  183. data/lib/capybara/spec/session/has_field_spec.rb +139 -59
  184. data/lib/capybara/spec/session/has_link_spec.rb +47 -6
  185. data/lib/capybara/spec/session/has_none_selectors_spec.rb +42 -40
  186. data/lib/capybara/spec/session/has_select_spec.rb +107 -72
  187. data/lib/capybara/spec/session/has_selector_spec.rb +120 -71
  188. data/lib/capybara/spec/session/has_sibling_spec.rb +50 -0
  189. data/lib/capybara/spec/session/has_table_spec.rb +183 -5
  190. data/lib/capybara/spec/session/has_text_spec.rb +106 -62
  191. data/lib/capybara/spec/session/has_title_spec.rb +20 -14
  192. data/lib/capybara/spec/session/has_xpath_spec.rb +57 -38
  193. data/lib/capybara/spec/session/{headers.rb → headers_spec.rb} +3 -2
  194. data/lib/capybara/spec/session/html_spec.rb +14 -6
  195. data/lib/capybara/spec/session/matches_style_spec.rb +37 -0
  196. data/lib/capybara/spec/session/node_spec.rb +1024 -153
  197. data/lib/capybara/spec/session/node_wrapper_spec.rb +39 -0
  198. data/lib/capybara/spec/session/refresh_spec.rb +12 -6
  199. data/lib/capybara/spec/session/reset_session_spec.rb +82 -35
  200. data/lib/capybara/spec/session/{response_code.rb → response_code_spec.rb} +2 -1
  201. data/lib/capybara/spec/session/save_and_open_page_spec.rb +3 -2
  202. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +8 -12
  203. data/lib/capybara/spec/session/save_page_spec.rb +42 -55
  204. data/lib/capybara/spec/session/save_screenshot_spec.rb +16 -14
  205. data/lib/capybara/spec/session/screenshot_spec.rb +2 -2
  206. data/lib/capybara/spec/session/scroll_spec.rb +119 -0
  207. data/lib/capybara/spec/session/select_spec.rb +107 -81
  208. data/lib/capybara/spec/session/selectors_spec.rb +52 -19
  209. data/lib/capybara/spec/session/sibling_spec.rb +10 -10
  210. data/lib/capybara/spec/session/text_spec.rb +37 -21
  211. data/lib/capybara/spec/session/title_spec.rb +17 -5
  212. data/lib/capybara/spec/session/uncheck_spec.rb +43 -23
  213. data/lib/capybara/spec/session/unselect_spec.rb +39 -38
  214. data/lib/capybara/spec/session/visit_spec.rb +85 -53
  215. data/lib/capybara/spec/session/window/become_closed_spec.rb +24 -20
  216. data/lib/capybara/spec/session/window/current_window_spec.rb +5 -3
  217. data/lib/capybara/spec/session/window/open_new_window_spec.rb +5 -3
  218. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +27 -22
  219. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +12 -6
  220. data/lib/capybara/spec/session/window/window_spec.rb +97 -63
  221. data/lib/capybara/spec/session/window/windows_spec.rb +12 -10
  222. data/lib/capybara/spec/session/window/within_window_spec.rb +31 -86
  223. data/lib/capybara/spec/session/within_spec.rb +83 -44
  224. data/lib/capybara/spec/spec_helper.rb +54 -44
  225. data/lib/capybara/spec/test_app.rb +158 -43
  226. data/lib/capybara/spec/views/animated.erb +49 -0
  227. data/lib/capybara/spec/views/form.erb +163 -42
  228. data/lib/capybara/spec/views/frame_child.erb +4 -3
  229. data/lib/capybara/spec/views/frame_one.erb +2 -1
  230. data/lib/capybara/spec/views/frame_parent.erb +1 -1
  231. data/lib/capybara/spec/views/frame_two.erb +1 -1
  232. data/lib/capybara/spec/views/initial_alert.erb +2 -1
  233. data/lib/capybara/spec/views/layout.erb +10 -0
  234. data/lib/capybara/spec/views/obscured.erb +47 -0
  235. data/lib/capybara/spec/views/offset.erb +33 -0
  236. data/lib/capybara/spec/views/path.erb +2 -2
  237. data/lib/capybara/spec/views/popup_one.erb +1 -1
  238. data/lib/capybara/spec/views/popup_two.erb +1 -1
  239. data/lib/capybara/spec/views/react.erb +45 -0
  240. data/lib/capybara/spec/views/scroll.erb +21 -0
  241. data/lib/capybara/spec/views/spatial.erb +31 -0
  242. data/lib/capybara/spec/views/tables.erb +68 -1
  243. data/lib/capybara/spec/views/with_animation.erb +81 -0
  244. data/lib/capybara/spec/views/with_base_tag.erb +2 -2
  245. data/lib/capybara/spec/views/with_dragula.erb +24 -0
  246. data/lib/capybara/spec/views/with_fixed_header_footer.erb +2 -1
  247. data/lib/capybara/spec/views/with_hover.erb +3 -2
  248. data/lib/capybara/spec/views/with_hover1.erb +10 -0
  249. data/lib/capybara/spec/views/with_html.erb +69 -12
  250. data/lib/capybara/spec/views/with_html5_svg.erb +20 -0
  251. data/lib/capybara/spec/views/with_jquery_animation.erb +24 -0
  252. data/lib/capybara/spec/views/with_js.erb +30 -5
  253. data/lib/capybara/spec/views/with_jstree.erb +26 -0
  254. data/lib/capybara/spec/views/with_namespace.erb +21 -0
  255. data/lib/capybara/spec/views/with_scope.erb +2 -2
  256. data/lib/capybara/spec/views/with_scope_other.erb +6 -0
  257. data/lib/capybara/spec/views/with_shadow.erb +31 -0
  258. data/lib/capybara/spec/views/with_slow_unload.erb +2 -1
  259. data/lib/capybara/spec/views/with_sortable_js.erb +21 -0
  260. data/lib/capybara/spec/views/with_unload_alert.erb +1 -0
  261. data/lib/capybara/spec/views/with_windows.erb +1 -1
  262. data/lib/capybara/spec/views/within_frames.erb +5 -2
  263. data/lib/capybara/version.rb +2 -1
  264. data/lib/capybara/window.rb +36 -34
  265. data/lib/capybara.rb +134 -107
  266. data/spec/basic_node_spec.rb +60 -34
  267. data/spec/capybara_spec.rb +63 -88
  268. data/spec/counter_spec.rb +35 -0
  269. data/spec/css_builder_spec.rb +101 -0
  270. data/spec/css_splitter_spec.rb +38 -0
  271. data/spec/dsl_spec.rb +85 -64
  272. data/spec/filter_set_spec.rb +27 -9
  273. data/spec/fixtures/certificate.pem +25 -0
  274. data/spec/fixtures/key.pem +27 -0
  275. data/spec/fixtures/selenium_driver_rspec_failure.rb +6 -5
  276. data/spec/fixtures/selenium_driver_rspec_success.rb +6 -5
  277. data/spec/minitest_spec.rb +52 -7
  278. data/spec/minitest_spec_spec.rb +94 -63
  279. data/spec/per_session_config_spec.rb +14 -13
  280. data/spec/rack_test_spec.rb +194 -125
  281. data/spec/regexp_dissassembler_spec.rb +250 -0
  282. data/spec/result_spec.rb +111 -50
  283. data/spec/rspec/features_spec.rb +37 -31
  284. data/spec/rspec/scenarios_spec.rb +10 -8
  285. data/spec/rspec/shared_spec_matchers.rb +473 -422
  286. data/spec/rspec/views_spec.rb +5 -3
  287. data/spec/rspec_matchers_spec.rb +52 -11
  288. data/spec/rspec_spec.rb +109 -89
  289. data/spec/sauce_spec_chrome.rb +43 -0
  290. data/spec/selector_spec.rb +397 -68
  291. data/spec/selenium_spec_chrome.rb +187 -40
  292. data/spec/selenium_spec_chrome_remote.rb +96 -0
  293. data/spec/selenium_spec_edge.rb +60 -0
  294. data/spec/selenium_spec_firefox.rb +201 -41
  295. data/spec/selenium_spec_firefox_remote.rb +94 -0
  296. data/spec/selenium_spec_ie.rb +149 -0
  297. data/spec/selenium_spec_safari.rb +162 -0
  298. data/spec/server_spec.rb +213 -102
  299. data/spec/session_spec.rb +53 -16
  300. data/spec/shared_selenium_node.rb +79 -0
  301. data/spec/shared_selenium_session.rb +473 -122
  302. data/spec/spec_helper.rb +126 -7
  303. data/spec/whitespace_normalizer_spec.rb +54 -0
  304. data/spec/xpath_builder_spec.rb +93 -0
  305. metadata +355 -73
  306. data/.yard/templates_custom/default/class/html/selectors.erb +0 -38
  307. data/.yard/templates_custom/default/class/html/setup.rb +0 -17
  308. data/.yard/yard_extensions.rb +0 -78
  309. data/lib/capybara/query.rb +0 -7
  310. data/lib/capybara/rspec/compound.rb +0 -95
  311. data/lib/capybara/spec/session/assert_current_path.rb +0 -72
  312. data/lib/capybara/spec/session/assert_selector.rb +0 -148
  313. data/lib/capybara/spec/session/assert_text.rb +0 -234
  314. data/lib/capybara/spec/session/source_spec.rb +0 -0
  315. data/lib/capybara/spec/views/with_title.erb +0 -5
  316. data/spec/selenium_spec_marionette.rb +0 -127
@@ -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
@@ -1,203 +1,314 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'spec_helper'
3
4
 
4
5
  RSpec.describe Capybara::Server do
6
+ it 'should spool up a rack server' do
7
+ app = proc { |_env| [200, {}, ['Hello Server!']] }
8
+ server = described_class.new(app).boot
5
9
 
6
- it "should spool up a rack server" do
7
- @app = proc { |env| [200, {}, ["Hello Server!"]]}
8
- @server = Capybara::Server.new(@app).boot
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
- it "should do nothing when no server given" do
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
- it "should bind to the specified host" do
21
+ it 'should bind to the specified host' do
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.fetch('TRAVIS', nil) && (RUBY_ENGINE == 'jruby')) || Gem.win_platform?
24
+
22
25
  begin
23
- app = proc { |env| [200, {}, ['Hello Server!']] }
26
+ app = proc { |_env| [200, {}, ['Hello Server!']] }
24
27
 
25
28
  Capybara.server_host = '127.0.0.1'
26
- server = Capybara::Server.new(app).boot
29
+ server = described_class.new(app).boot
27
30
  res = Net::HTTP.get(URI("http://127.0.0.1:#{server.port}"))
28
31
  expect(res).to eq('Hello Server!')
29
32
 
30
33
  Capybara.server_host = '0.0.0.0'
31
- server = Capybara::Server.new(app).boot
34
+ server = described_class.new(app).boot
32
35
  res = Net::HTTP.get(URI("http://127.0.0.1:#{server.port}"))
33
36
  expect(res).to eq('Hello Server!')
34
37
  ensure
35
38
  Capybara.server_host = nil
36
39
  end
37
- end unless ENV['TRAVIS'] and (RUBY_ENGINE == 'jruby') #TODO travis with jruby in container mode has an issue with this test
40
+ end
38
41
 
39
- it "should use specified port" do
42
+ it 'should use specified port' do
40
43
  Capybara.server_port = 22789
41
44
 
42
- @app = proc { |env| [200, {}, ["Hello Server!"]]}
43
- @server = Capybara::Server.new(@app).boot
45
+ app = proc { |_env| [200, {}, ['Hello Server!']] }
46
+ server = described_class.new(app).boot
44
47
 
45
- @res = Net::HTTP.start(@server.host, 22789) { |http| http.get('/') }
46
- 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')
47
50
 
48
51
  Capybara.server_port = nil
49
52
  end
50
53
 
51
- it "should use given port" do
52
- @app = proc { |env| [200, {}, ["Hello Server!"]]}
53
- @server = Capybara::Server.new(@app, 22790).boot
54
+ it 'should use given port' do
55
+ app = proc { |_env| [200, {}, ['Hello Server!']] }
56
+ server = described_class.new(app, port: 22790).boot
54
57
 
55
- @res = Net::HTTP.start(@server.host, 22790) { |http| http.get('/') }
56
- 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')
57
60
 
58
61
  Capybara.server_port = nil
59
62
  end
60
63
 
61
- it "should find an available port" do
62
- @app1 = proc { |env| [200, {}, ["Hello Server!"]]}
63
- @app2 = proc { |env| [200, {}, ["Hello Second Server!"]]}
64
+ it 'should find an available port' do
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 }
64
70
 
65
- @server1 = Capybara::Server.new(@app1).boot
66
- @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
67
76
 
68
- @res1 = Net::HTTP.start(@server1.host, @server1.port) { |http| http.get('/') }
69
- 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
88
+
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
93
+ end
70
94
 
71
- @res2 = Net::HTTP.start(@server2.host, @server2.port) { |http| http.get('/') }
72
- expect(@res2.body).to include('Hello Second Server')
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)
73
100
  end
74
101
 
75
- context "When Capybara.reuse_server is true" do
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
120
+
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'
125
+
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('/')
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
155
+ end
156
+
157
+ context 'When Capybara.reuse_server is true' do
158
+ let!(:old_reuse_server) { Capybara.reuse_server }
159
+
76
160
  before do
77
- @old_reuse_server = Capybara.reuse_server
78
161
  Capybara.reuse_server = true
79
162
  end
80
163
 
81
164
  after do
82
- Capybara.reuse_server = @old_reuse_server
165
+ Capybara.reuse_server = old_reuse_server
83
166
  end
84
167
 
85
- it "should use the existing server if it already running" do
86
- @app = proc { |env| [200, {}, ["Hello Server!"]]}
87
-
88
- @server1 = Capybara::Server.new(@app).boot
89
- @server2 = Capybara::Server.new(@app).boot
168
+ it 'should use the existing server if it already running' do
169
+ app = proc { |_env| [200, {}, ['Hello Server!']] }
90
170
 
91
- res = Net::HTTP.start(@server1.host, @server1.port) { |http| http.get('/') }
92
- expect(res.body).to include('Hello Server')
171
+ servers = Array.new(2) { described_class.new(app).boot }
93
172
 
94
- res = Net::HTTP.start(@server2.host, @server2.port) { |http| http.get('/') }
95
- 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
96
177
 
97
- expect(@server1.port).to eq(@server2.port)
178
+ expect(servers[0].port).to eq(servers[1].port)
98
179
  end
99
180
 
100
- it "detects and waits for all reused server sessions pending requests" do
101
- done = false
181
+ it 'detects and waits for all reused server sessions pending requests' do
182
+ done = 0
102
183
 
103
184
  app = proc do |env|
104
185
  request = Rack::Request.new(env)
105
186
  sleep request.params['wait_time'].to_f
106
- done = true
107
- [200, {}, ["Hello Server!"]]
187
+ done += 1
188
+ [200, {}, ['Hello Server!']]
108
189
  end
109
190
 
110
- server1 = Capybara::Server.new(app).boot
111
- server2 = Capybara::Server.new(app).boot
191
+ server1 = described_class.new(app).boot
192
+ server2 = described_class.new(app).boot
112
193
 
113
- start_request(server1, 0.5)
114
- start_request(server2, 1.0)
115
-
116
- expect {
194
+ expect do
195
+ start_request(server1, 1.0)
196
+ start_request(server2, 3.0)
117
197
  server1.wait_for_pending_requests
118
- }.to change{done}.from(false).to(true)
119
- expect(server2.send(:pending_requests?)).to eq(false)
198
+ end.to change { done }.from(0).to(2)
199
+ expect(server2.send(:pending_requests?)).to be(false)
120
200
  end
121
-
122
201
  end
123
202
 
124
- context "When Capybara.reuse_server is false" do
203
+ context 'When Capybara.reuse_server is false' do
125
204
  before do
126
205
  @old_reuse_server = Capybara.reuse_server
127
206
  Capybara.reuse_server = false
128
207
  end
129
208
 
130
209
  after do
131
- Capybara.reuse_server = @old_reuse_server
210
+ Capybara.reuse_server = @old_reuse_server # rubocop:disable RSpec/InstanceVariable
132
211
  end
133
212
 
134
- it "should not reuse an already running server" do
135
- @app = proc { |env| [200, {}, ["Hello Server!"]]}
136
-
137
- @server1 = Capybara::Server.new(@app).boot
138
- @server2 = Capybara::Server.new(@app).boot
213
+ it 'should not reuse an already running server' do
214
+ app = proc { |_env| [200, {}, ['Hello Server!']] }
139
215
 
140
- res = Net::HTTP.start(@server1.host, @server1.port) { |http| http.get('/') }
141
- expect(res.body).to include('Hello Server')
216
+ servers = Array.new(2) { described_class.new(app).boot }
142
217
 
143
- res = Net::HTTP.start(@server2.host, @server2.port) { |http| http.get('/') }
144
- 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
145
222
 
146
- expect(@server1.port).not_to eq(@server2.port)
223
+ expect(servers[0].port).not_to eq(servers[1].port)
147
224
  end
148
225
 
149
- it "detects and waits for only one sessions pending requests" do
150
- done = false
226
+ it 'detects and waits for only one sessions pending requests' do
227
+ done = 0
151
228
 
152
229
  app = proc do |env|
153
230
  request = Rack::Request.new(env)
154
231
  sleep request.params['wait_time'].to_f
155
- done = true
156
- [200, {}, ["Hello Server!"]]
232
+ done += 1
233
+ [200, {}, ['Hello Server!']]
157
234
  end
158
235
 
159
- server1 = Capybara::Server.new(app).boot
160
- server2 = Capybara::Server.new(app).boot
161
-
162
- start_request(server1, 0.5)
163
- start_request(server2, 1.0)
236
+ server1 = described_class.new(app).boot
237
+ server2 = described_class.new(app).boot
164
238
 
165
- expect {
239
+ expect do
240
+ start_request(server1, 1.0)
241
+ start_request(server2, 3.0)
166
242
  server1.wait_for_pending_requests
167
- }.to change{done}.from(false).to(true)
168
- expect(server2.send(:pending_requests?)).to eq(true)
243
+ end.to change { done }.from(0).to(1)
244
+ expect(server2.send(:pending_requests?)).to be(true)
245
+ expect do
246
+ server2.wait_for_pending_requests
247
+ end.to change { done }.from(1).to(2)
169
248
  end
170
-
171
249
  end
172
250
 
173
- it "should raise server errors when the server errors before the timeout" do
174
- begin
175
- Capybara.server do
176
- sleep 0.1
177
- raise 'kaboom'
178
- end
251
+ it 'should raise server errors when the server errors before the timeout' do
252
+ Capybara.register_server :kaboom do
253
+ sleep 0.1
254
+ raise 'kaboom'
255
+ end
256
+ Capybara.server = :kaboom
179
257
 
180
- expect do
181
- Capybara::Server.new(proc {|e|}).boot
182
- end.to raise_error(RuntimeError, 'kaboom')
183
- ensure
184
- # TODO refactor out the defaults so it's reliant on unset state instead of
185
- # a one-time call in capybara.rb
186
- Capybara.server {|app, port| Capybara.run_default_server(app, port)}
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!']]
187
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"]')
188
283
  end
189
284
 
190
- it "is not #responsive? when Net::HTTP raises a SystemCallError" do
191
- app = lambda { [200, {}, ['Hello, world']] }
192
- server = Capybara::Server.new(app)
193
- expect(Net::HTTP).to receive(:start).and_raise(SystemCallError.allocate)
194
- expect(server.responsive?).to eq false
285
+ it 'is not #responsive? when Net::HTTP raises a SystemCallError' do
286
+ app = -> { [200, {}, ['Hello, world']] }
287
+ server = described_class.new(app)
288
+ allow(Net::HTTP).to receive(:start).and_raise(SystemCallError.allocate)
289
+ expect(server.responsive?).to be false
290
+ end
291
+
292
+ [EOFError, Net::ReadTimeout].each do |err|
293
+ it "should attempt an HTTPS connection if HTTP connection returns #{err}" do
294
+ app = -> { [200, {}, ['Hello, world']] }
295
+ ordered_errors = [Errno::ECONNREFUSED, err]
296
+ allow(Net::HTTP).to receive(:start).with(anything, anything, hash_excluding(:use_ssl)) do
297
+ raise ordered_errors.shift
298
+ end
299
+ response = Net::HTTPSuccess.allocate
300
+ allow(response).to receive(:body).and_return app.object_id.to_s
301
+ allow(Net::HTTP).to receive(:start).with(anything, anything, hash_including(use_ssl: true)).and_return(response).once
302
+ described_class.new(app).boot
303
+ expect(Net::HTTP).to have_received(:start).exactly(3).times
304
+ end
195
305
  end
196
306
 
197
307
  def start_request(server, wait_time)
198
308
  # Start request, but don't wait for it to finish
199
309
  socket = TCPSocket.new(server.host, server.port)
200
- socket.write "GET /?wait_time=#{wait_time.to_s} HTTP/1.0\r\n\r\n"
310
+ socket.write "GET /?wait_time=#{wait_time} HTTP/1.0\r\n\r\n"
311
+ sleep 0.1
201
312
  socket.close
202
313
  sleep 0.1
203
314
  end