capybara 2.5.0 → 2.18.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 (205) hide show
  1. checksums.yaml +5 -5
  2. data/.yard/templates_custom/default/class/html/selectors.erb +38 -0
  3. data/.yard/templates_custom/default/class/html/setup.rb +17 -0
  4. data/.yard/yard_extensions.rb +78 -0
  5. data/.yardopts +1 -0
  6. data/History.md +413 -10
  7. data/License.txt +1 -1
  8. data/README.md +237 -130
  9. data/lib/capybara/config.rb +132 -0
  10. data/lib/capybara/cucumber.rb +3 -1
  11. data/lib/capybara/driver/base.rb +27 -6
  12. data/lib/capybara/driver/node.rb +14 -5
  13. data/lib/capybara/dsl.rb +2 -3
  14. data/lib/capybara/helpers.rb +13 -65
  15. data/lib/capybara/minitest/spec.rb +177 -0
  16. data/lib/capybara/minitest.rb +278 -0
  17. data/lib/capybara/node/actions.rb +180 -24
  18. data/lib/capybara/node/base.rb +17 -5
  19. data/lib/capybara/node/document.rb +5 -0
  20. data/lib/capybara/node/document_matchers.rb +15 -14
  21. data/lib/capybara/node/element.rb +55 -7
  22. data/lib/capybara/node/finders.rb +179 -67
  23. data/lib/capybara/node/matchers.rb +301 -105
  24. data/lib/capybara/node/simple.rb +15 -4
  25. data/lib/capybara/queries/ancestor_query.rb +25 -0
  26. data/lib/capybara/queries/base_query.rb +69 -3
  27. data/lib/capybara/queries/current_path_query.rb +17 -8
  28. data/lib/capybara/queries/match_query.rb +19 -0
  29. data/lib/capybara/queries/selector_query.rb +251 -0
  30. data/lib/capybara/queries/sibling_query.rb +25 -0
  31. data/lib/capybara/queries/text_query.rb +67 -16
  32. data/lib/capybara/queries/title_query.rb +4 -2
  33. data/lib/capybara/query.rb +3 -131
  34. data/lib/capybara/rack_test/browser.rb +14 -5
  35. data/lib/capybara/rack_test/css_handlers.rb +1 -0
  36. data/lib/capybara/rack_test/driver.rb +15 -8
  37. data/lib/capybara/rack_test/form.rb +34 -12
  38. data/lib/capybara/rack_test/node.rb +29 -12
  39. data/lib/capybara/rails.rb +3 -3
  40. data/lib/capybara/result.rb +104 -9
  41. data/lib/capybara/rspec/compound.rb +95 -0
  42. data/lib/capybara/rspec/features.rb +17 -6
  43. data/lib/capybara/rspec/matcher_proxies.rb +45 -0
  44. data/lib/capybara/rspec/matchers.rb +199 -80
  45. data/lib/capybara/rspec.rb +4 -2
  46. data/lib/capybara/selector/css.rb +30 -0
  47. data/lib/capybara/selector/filter.rb +20 -0
  48. data/lib/capybara/selector/filter_set.rb +74 -0
  49. data/lib/capybara/selector/filters/base.rb +33 -0
  50. data/lib/capybara/selector/filters/expression_filter.rb +40 -0
  51. data/lib/capybara/selector/filters/node_filter.rb +27 -0
  52. data/lib/capybara/selector/selector.rb +276 -0
  53. data/lib/capybara/selector.rb +452 -157
  54. data/lib/capybara/selenium/driver.rb +282 -81
  55. data/lib/capybara/selenium/node.rb +144 -46
  56. data/lib/capybara/server.rb +59 -16
  57. data/lib/capybara/session/config.rb +114 -0
  58. data/lib/capybara/session/matchers.rb +29 -19
  59. data/lib/capybara/session.rb +378 -143
  60. data/lib/capybara/spec/fixtures/no_extension +1 -0
  61. data/lib/capybara/spec/public/jquery-ui.js +13 -791
  62. data/lib/capybara/spec/public/jquery.js +4 -9045
  63. data/lib/capybara/spec/public/test.js +45 -11
  64. data/lib/capybara/spec/session/accept_alert_spec.rb +30 -7
  65. data/lib/capybara/spec/session/accept_confirm_spec.rb +14 -2
  66. data/lib/capybara/spec/session/accept_prompt_spec.rb +35 -6
  67. data/lib/capybara/spec/session/all_spec.rb +45 -32
  68. data/lib/capybara/spec/session/ancestor_spec.rb +85 -0
  69. data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +110 -0
  70. data/lib/capybara/spec/session/assert_current_path.rb +15 -2
  71. data/lib/capybara/spec/session/assert_selector.rb +29 -28
  72. data/lib/capybara/spec/session/assert_text.rb +59 -20
  73. data/lib/capybara/spec/session/assert_title.rb +25 -11
  74. data/lib/capybara/spec/session/attach_file_spec.rb +42 -4
  75. data/lib/capybara/spec/session/body_spec.rb +1 -0
  76. data/lib/capybara/spec/session/check_spec.rb +90 -14
  77. data/lib/capybara/spec/session/choose_spec.rb +31 -5
  78. data/lib/capybara/spec/session/click_button_spec.rb +20 -9
  79. data/lib/capybara/spec/session/click_link_or_button_spec.rb +15 -9
  80. data/lib/capybara/spec/session/click_link_spec.rb +39 -15
  81. data/lib/capybara/spec/session/current_scope_spec.rb +2 -1
  82. data/lib/capybara/spec/session/current_url_spec.rb +12 -3
  83. data/lib/capybara/spec/session/dismiss_confirm_spec.rb +6 -5
  84. data/lib/capybara/spec/session/dismiss_prompt_spec.rb +4 -3
  85. data/lib/capybara/spec/session/element/assert_match_selector.rb +36 -0
  86. data/lib/capybara/spec/session/element/match_css_spec.rb +23 -0
  87. data/lib/capybara/spec/session/element/match_xpath_spec.rb +23 -0
  88. data/lib/capybara/spec/session/element/matches_selector_spec.rb +106 -0
  89. data/lib/capybara/spec/session/evaluate_async_script_spec.rb +22 -0
  90. data/lib/capybara/spec/session/evaluate_script_spec.rb +23 -1
  91. data/lib/capybara/spec/session/execute_script_spec.rb +22 -3
  92. data/lib/capybara/spec/session/fill_in_spec.rb +50 -32
  93. data/lib/capybara/spec/session/find_button_spec.rb +43 -2
  94. data/lib/capybara/spec/session/find_by_id_spec.rb +3 -2
  95. data/lib/capybara/spec/session/find_field_spec.rb +42 -6
  96. data/lib/capybara/spec/session/find_link_spec.rb +22 -3
  97. data/lib/capybara/spec/session/find_spec.rb +103 -57
  98. data/lib/capybara/spec/session/first_spec.rb +34 -18
  99. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +103 -0
  100. data/lib/capybara/spec/session/{within_frame_spec.rb → frame/within_frame_spec.rb} +44 -2
  101. data/lib/capybara/spec/session/go_back_spec.rb +2 -1
  102. data/lib/capybara/spec/session/go_forward_spec.rb +2 -1
  103. data/lib/capybara/spec/session/has_all_selectors_spec.rb +69 -0
  104. data/lib/capybara/spec/session/has_button_spec.rb +17 -8
  105. data/lib/capybara/spec/session/has_css_spec.rb +85 -73
  106. data/lib/capybara/spec/session/has_current_path_spec.rb +91 -7
  107. data/lib/capybara/spec/session/has_field_spec.rb +93 -58
  108. data/lib/capybara/spec/session/has_link_spec.rb +9 -8
  109. data/lib/capybara/spec/session/has_none_selectors_spec.rb +76 -0
  110. data/lib/capybara/spec/session/has_select_spec.rb +159 -59
  111. data/lib/capybara/spec/session/has_selector_spec.rb +64 -28
  112. data/lib/capybara/spec/session/has_table_spec.rb +1 -0
  113. data/lib/capybara/spec/session/has_text_spec.rb +27 -12
  114. data/lib/capybara/spec/session/has_title_spec.rb +22 -4
  115. data/lib/capybara/spec/session/has_xpath_spec.rb +32 -29
  116. data/lib/capybara/spec/session/headers.rb +2 -1
  117. data/lib/capybara/spec/session/html_spec.rb +4 -3
  118. data/lib/capybara/spec/session/node_spec.rb +198 -38
  119. data/lib/capybara/spec/session/refresh_spec.rb +28 -0
  120. data/lib/capybara/spec/session/reset_session_spec.rb +46 -5
  121. data/lib/capybara/spec/session/response_code.rb +2 -1
  122. data/lib/capybara/spec/session/save_and_open_page_spec.rb +1 -0
  123. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +6 -5
  124. data/lib/capybara/spec/session/save_page_spec.rb +34 -2
  125. data/lib/capybara/spec/session/save_screenshot_spec.rb +31 -1
  126. data/lib/capybara/spec/session/screenshot_spec.rb +4 -2
  127. data/lib/capybara/spec/session/select_spec.rb +34 -32
  128. data/lib/capybara/spec/session/selectors_spec.rb +65 -0
  129. data/lib/capybara/spec/session/sibling_spec.rb +52 -0
  130. data/lib/capybara/spec/session/text_spec.rb +4 -4
  131. data/lib/capybara/spec/session/title_spec.rb +2 -1
  132. data/lib/capybara/spec/session/uncheck_spec.rb +42 -2
  133. data/lib/capybara/spec/session/unselect_spec.rb +17 -16
  134. data/lib/capybara/spec/session/visit_spec.rb +77 -2
  135. data/lib/capybara/spec/session/window/become_closed_spec.rb +12 -11
  136. data/lib/capybara/spec/session/window/current_window_spec.rb +1 -0
  137. data/lib/capybara/spec/session/window/open_new_window_spec.rb +1 -0
  138. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +16 -11
  139. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +7 -4
  140. data/lib/capybara/spec/session/window/window_spec.rb +36 -29
  141. data/lib/capybara/spec/session/window/windows_spec.rb +1 -0
  142. data/lib/capybara/spec/session/window/within_window_spec.rb +31 -7
  143. data/lib/capybara/spec/session/within_spec.rb +14 -6
  144. data/lib/capybara/spec/spec_helper.rb +37 -4
  145. data/lib/capybara/spec/test_app.rb +15 -3
  146. data/lib/capybara/spec/views/buttons.erb +1 -0
  147. data/lib/capybara/spec/views/fieldsets.erb +2 -1
  148. data/lib/capybara/spec/views/form.erb +169 -9
  149. data/lib/capybara/spec/views/frame_child.erb +10 -2
  150. data/lib/capybara/spec/views/frame_one.erb +2 -1
  151. data/lib/capybara/spec/views/frame_parent.erb +3 -2
  152. data/lib/capybara/spec/views/frame_two.erb +2 -1
  153. data/lib/capybara/spec/views/header_links.erb +1 -0
  154. data/lib/capybara/spec/views/host_links.erb +1 -0
  155. data/lib/capybara/spec/views/initial_alert.erb +10 -0
  156. data/lib/capybara/spec/views/path.erb +1 -0
  157. data/lib/capybara/spec/views/popup_one.erb +1 -0
  158. data/lib/capybara/spec/views/popup_two.erb +1 -0
  159. data/lib/capybara/spec/views/postback.erb +2 -1
  160. data/lib/capybara/spec/views/tables.erb +1 -0
  161. data/lib/capybara/spec/views/with_base_tag.erb +1 -0
  162. data/lib/capybara/spec/views/with_count.erb +2 -1
  163. data/lib/capybara/spec/views/with_fixed_header_footer.erb +17 -0
  164. data/lib/capybara/spec/views/with_hover.erb +7 -1
  165. data/lib/capybara/spec/views/with_html.erb +40 -2
  166. data/lib/capybara/spec/views/with_html_entities.erb +1 -0
  167. data/lib/capybara/spec/views/with_js.erb +32 -1
  168. data/lib/capybara/spec/views/with_scope.erb +1 -0
  169. data/lib/capybara/spec/views/with_simple_html.erb +2 -1
  170. data/lib/capybara/spec/views/with_slow_unload.erb +17 -0
  171. data/lib/capybara/spec/views/with_title.erb +2 -1
  172. data/lib/capybara/spec/views/with_unload_alert.erb +14 -0
  173. data/lib/capybara/spec/views/with_windows.erb +7 -0
  174. data/lib/capybara/spec/views/within_frames.erb +3 -2
  175. data/lib/capybara/version.rb +2 -1
  176. data/lib/capybara/window.rb +20 -3
  177. data/lib/capybara.rb +189 -93
  178. data/spec/basic_node_spec.rb +7 -6
  179. data/spec/capybara_spec.rb +90 -4
  180. data/spec/dsl_spec.rb +3 -1
  181. data/spec/filter_set_spec.rb +28 -0
  182. data/spec/fixtures/capybara.csv +1 -0
  183. data/spec/fixtures/selenium_driver_rspec_failure.rb +5 -1
  184. data/spec/fixtures/selenium_driver_rspec_success.rb +5 -1
  185. data/spec/minitest_spec.rb +130 -0
  186. data/spec/minitest_spec_spec.rb +135 -0
  187. data/spec/per_session_config_spec.rb +67 -0
  188. data/spec/rack_test_spec.rb +50 -7
  189. data/spec/result_spec.rb +76 -0
  190. data/spec/rspec/features_spec.rb +21 -8
  191. data/spec/rspec/scenarios_spec.rb +21 -0
  192. data/spec/rspec/{matchers_spec.rb → shared_spec_matchers.rb} +160 -54
  193. data/spec/rspec/views_spec.rb +5 -0
  194. data/spec/rspec_matchers_spec.rb +46 -0
  195. data/spec/rspec_spec.rb +79 -1
  196. data/spec/selector_spec.rb +199 -0
  197. data/spec/selenium_spec_chrome.rb +54 -9
  198. data/spec/selenium_spec_firefox.rb +68 -0
  199. data/spec/selenium_spec_marionette.rb +127 -0
  200. data/spec/server_spec.rb +102 -14
  201. data/spec/session_spec.rb +54 -0
  202. data/spec/shared_selenium_session.rb +215 -0
  203. data/spec/spec_helper.rb +7 -0
  204. metadata +140 -15
  205. data/spec/selenium_spec.rb +0 -128
@@ -1,11 +1,13 @@
1
+ # frozen_string_literal: true
1
2
  class Capybara::Selenium::Node < Capybara::Driver::Node
3
+
2
4
  def visible_text
3
5
  # Selenium doesn't normalize Unicode whitespace.
4
6
  Capybara::Helpers.normalize_whitespace(native.text)
5
7
  end
6
8
 
7
9
  def all_text
8
- text = driver.browser.execute_script("return arguments[0].textContent", native)
10
+ text = driver.execute_script("return arguments[0].textContent", self)
9
11
  Capybara::Helpers.normalize_whitespace(text)
10
12
  end
11
13
 
@@ -16,77 +18,110 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
16
18
  end
17
19
 
18
20
  def value
19
- if tag_name == "select" and self[:multiple] and not self[:multiple] == "false"
20
- native.find_elements(:xpath, ".//option").select { |n| n.selected? }.map { |n| n[:value] || n.text }
21
+ if tag_name == "select" and multiple?
22
+ native.find_elements(:css, "option:checked").map { |n| n[:value] || n.text }
21
23
  else
22
24
  native[:value]
23
25
  end
24
26
  end
25
27
 
26
- def set(value, fill_options={})
28
+ ##
29
+ #
30
+ # Set the value of the form element to the given value.
31
+ #
32
+ # @param [String] value The new value
33
+ # @param [Hash{}] options Driver specific options for how to set the value
34
+ # @option options [Symbol,Array] :clear (nil) The method used to clear the previous value <br/>
35
+ # nil => clear via javascript <br/>
36
+ # :none => append the new value to the existing value <br/>
37
+ # :backspace => send backspace keystrokes to clear the field <br/>
38
+ # Array => an array of keys to send before the value being set, e.g. [[:command, 'a'], :backspace]
39
+ def set(value, options={})
27
40
  tag_name = self.tag_name
28
41
  type = self[:type]
29
- if (Array === value) && !self[:multiple]
42
+
43
+ if (Array === value) && !multiple?
30
44
  raise ArgumentError.new "Value cannot be an Array when 'multiple' attribute is not present. Not a #{value.class}"
31
45
  end
32
- if tag_name == 'input' and type == 'radio'
33
- click
34
- elsif tag_name == 'input' and type == 'checkbox'
35
- click if value ^ native.attribute('checked').to_s.eql?("true")
36
- elsif tag_name == 'input' and type == 'file'
37
- path_names = value.to_s.empty? ? [] : value
38
- native.send_keys(*path_names)
39
- elsif tag_name == 'textarea' or tag_name == 'input'
40
- if self[:readonly]
41
- warn "Attempt to set readonly element with value: #{value} \n *This will raise an exception in a future version of Capybara"
42
- elsif value.to_s.empty?
43
- native.clear
46
+
47
+ case tag_name
48
+ when 'input'
49
+ case type
50
+ when 'radio'
51
+ click
52
+ when 'checkbox'
53
+ click if value ^ native.attribute('checked').to_s.eql?("true")
54
+ when 'file'
55
+ path_names = value.to_s.empty? ? [] : value
56
+ if driver.chrome?
57
+ native.send_keys(Array(path_names).join("\n"))
58
+ else
59
+ native.send_keys(*path_names)
60
+ end
44
61
  else
45
- if fill_options[:clear] == :backspace
46
- # Clear field by sending the correct number of backspace keys.
47
- backspaces = [:backspace] * self.value.to_s.length
48
- native.send_keys(*(backspaces + [value.to_s]))
62
+ set_text(value, options)
63
+ end
64
+ when 'textarea'
65
+ set_text(value, options)
66
+ else
67
+ if content_editable?
68
+ #ensure we are focused on the element
69
+ click
70
+
71
+ script = <<-JS
72
+ var range = document.createRange();
73
+ var sel = window.getSelection();
74
+ arguments[0].focus();
75
+ range.selectNodeContents(arguments[0]);
76
+ sel.removeAllRanges();
77
+ sel.addRange(range);
78
+ JS
79
+ driver.execute_script script, self
80
+
81
+ if driver.chrome? || driver.firefox?
82
+ # chromedriver raises a can't focus element for child elements if we use native.send_keys
83
+ # we've already focused it so just use action api
84
+ driver.browser.action.send_keys(value.to_s).perform
49
85
  else
50
- # Clear field by JavaScript assignment of the value property.
51
- # Script can change a readonly element which user input cannot, so
52
- # don't execute if readonly.
53
- driver.browser.execute_script "arguments[0].value = ''", native
86
+ # action api is really slow here just use native.send_keys
54
87
  native.send_keys(value.to_s)
55
88
  end
56
89
  end
57
- elsif native.attribute('isContentEditable')
58
- #ensure we are focused on the element
59
- script = <<-JS
60
- var range = document.createRange();
61
- range.selectNodeContents(arguments[0]);
62
- window.getSelection().addRange(range);
63
- JS
64
- driver.browser.execute_script script, native
65
- native.send_keys(value.to_s)
66
90
  end
67
91
  end
68
92
 
69
93
  def select_option
70
- native.click unless selected?
94
+ native.click unless selected? || disabled?
71
95
  end
72
96
 
73
97
  def unselect_option
74
- if select_node['multiple'] != 'multiple' and select_node['multiple'] != 'true'
75
- raise Capybara::UnselectNotAllowed, "Cannot unselect option from single select box."
76
- end
98
+ raise Capybara::UnselectNotAllowed, "Cannot unselect option from single select box." if !select_node.multiple?
77
99
  native.click if selected?
78
100
  end
79
101
 
80
102
  def click
81
103
  native.click
104
+ rescue => e
105
+ if e.is_a?(::Selenium::WebDriver::Error::ElementClickInterceptedError) ||
106
+ e.message =~ /Other element would receive the click/
107
+ begin
108
+ driver.execute_script("arguments[0].scrollIntoView({behavior: 'instant', block: 'center', inline: 'center'})", self)
109
+ rescue
110
+ end
111
+ end
112
+ raise e
82
113
  end
83
114
 
84
115
  def right_click
85
- driver.browser.action.context_click(native).perform
116
+ scroll_if_needed do
117
+ driver.browser.action.context_click(native).perform
118
+ end
86
119
  end
87
120
 
88
121
  def double_click
89
- driver.browser.action.double_click(native).perform
122
+ scroll_if_needed do
123
+ driver.browser.action.double_click(native).perform
124
+ end
90
125
  end
91
126
 
92
127
  def send_keys(*args)
@@ -94,11 +129,15 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
94
129
  end
95
130
 
96
131
  def hover
97
- driver.browser.action.move_to(native).perform
132
+ scroll_if_needed do
133
+ driver.browser.action.move_to(native).perform
134
+ end
98
135
  end
99
136
 
100
137
  def drag_to(element)
101
- driver.browser.action.drag_and_drop(native, element.native).perform
138
+ scroll_if_needed do
139
+ driver.browser.action.drag_and_drop(native, element.native).perform
140
+ end
102
141
  end
103
142
 
104
143
  def tag_name
@@ -114,12 +153,34 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
114
153
  selected = native.selected?
115
154
  selected and selected != "false"
116
155
  end
156
+ alias :checked? :selected?
117
157
 
118
158
  def disabled?
119
- !native.enabled?
159
+ # workaround for selenium-webdriver/geckodriver reporting elements as enabled when they are nested in disabling elements
160
+ if driver.marionette?
161
+ if %w(option optgroup).include? tag_name
162
+ !native.enabled? || find_xpath("parent::*[self::optgroup or self::select]")[0].disabled?
163
+ else
164
+ !native.enabled? || !find_xpath("parent::fieldset[@disabled] | ancestor::*[not(self::legend) or preceding-sibling::legend][parent::fieldset[@disabled]]").empty?
165
+ end
166
+ else
167
+ !native.enabled?
168
+ end
120
169
  end
121
170
 
122
- alias :checked? :selected?
171
+ def readonly?
172
+ readonly = self[:readonly]
173
+ readonly and readonly != "false"
174
+ end
175
+
176
+ def multiple?
177
+ multiple = self[:multiple]
178
+ multiple and multiple != "false"
179
+ end
180
+
181
+ def content_editable?
182
+ native.attribute('isContentEditable')
183
+ end
123
184
 
124
185
  def find_xpath(locator)
125
186
  native.find_elements(:xpath, locator).map { |n| self.class.new(driver, n) }
@@ -158,9 +219,46 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
158
219
  end
159
220
 
160
221
  private
161
-
162
222
  # a reference to the select node if this is an option node
163
223
  def select_node
164
- find_xpath('./ancestor::select').first
224
+ find_xpath('./ancestor::select[1]').first
225
+ end
226
+
227
+ def set_text(value, options)
228
+ if readonly?
229
+ warn "Attempt to set readonly element with value: #{value} \n *This will raise an exception in a future version of Capybara"
230
+ elsif value.to_s.empty? && options[:clear].nil?
231
+ native.clear
232
+ else
233
+ if options[:clear] == :backspace
234
+ # Clear field by sending the correct number of backspace keys.
235
+ backspaces = [:backspace] * self.value.to_s.length
236
+ native.send_keys(*(backspaces + [value.to_s]))
237
+ elsif options[:clear] == :none
238
+ native.send_keys(value.to_s)
239
+ elsif options[:clear].is_a? Array
240
+ native.send_keys(*options[:clear], value.to_s)
241
+ else
242
+ # Clear field by JavaScript assignment of the value property.
243
+ # Script can change a readonly element which user input cannot, so
244
+ # don't execute if readonly.
245
+ driver.execute_script "arguments[0].value = ''", self
246
+ native.send_keys(value.to_s)
247
+ end
248
+ end
249
+ end
250
+
251
+ def scroll_if_needed(&block)
252
+ block.call
253
+ rescue ::Selenium::WebDriver::Error::MoveTargetOutOfBoundsError
254
+ script = <<-JS
255
+ try {
256
+ arguments[0].scrollIntoView({behavior: 'instant', block: 'center', inline: 'center'});
257
+ } catch(e) {
258
+ arguments[0].scrollIntoView(true);
259
+ }
260
+ JS
261
+ driver.execute_script(script, self)
262
+ block.call
165
263
  end
166
264
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'uri'
2
3
  require 'net/http'
3
4
  require 'rack'
@@ -5,21 +6,47 @@ require 'rack'
5
6
  module Capybara
6
7
  class Server
7
8
  class Middleware
9
+ class Counter
10
+ attr_reader :value
11
+
12
+ def initialize
13
+ @value = 0
14
+ @mutex = Mutex.new
15
+ end
16
+
17
+ def increment
18
+ @mutex.synchronize { @value += 1 }
19
+ end
20
+
21
+ def decrement
22
+ @mutex.synchronize { @value -= 1 }
23
+ end
24
+ end
25
+
8
26
  attr_accessor :error
9
27
 
10
- def initialize(app)
28
+ def initialize(app, server_errors)
11
29
  @app = app
30
+ @counter = Counter.new
31
+ @server_errors = server_errors
32
+ end
33
+
34
+ def pending_requests?
35
+ @counter.value > 0
12
36
  end
13
37
 
14
38
  def call(env)
15
39
  if env["PATH_INFO"] == "/__identify__"
16
40
  [200, {}, [@app.object_id.to_s]]
17
41
  else
42
+ @counter.increment
18
43
  begin
19
44
  @app.call(env)
20
- rescue *Capybara.server_errors => e
45
+ rescue *@server_errors => e
21
46
  @error = e unless @error
22
47
  raise e
48
+ ensure
49
+ @counter.decrement
23
50
  end
24
51
  end
25
52
  end
@@ -33,41 +60,46 @@ module Capybara
33
60
 
34
61
  attr_reader :app, :port, :host
35
62
 
36
- def initialize(app, port=Capybara.server_port, host=Capybara.server_host)
63
+ def initialize(app, port=Capybara.server_port, host=Capybara.server_host, server_errors=Capybara.server_errors)
37
64
  @app = app
38
- @middleware = Middleware.new(@app)
39
65
  @server_thread = nil # suppress warnings
40
- @host, @port = host, port
41
- @port ||= Capybara::Server.ports[@app.object_id]
42
- @port ||= find_available_port
66
+ @host, @port, @server_errors = host, port, server_errors
67
+ @port ||= Capybara::Server.ports[port_key]
68
+ @port ||= find_available_port(host)
43
69
  end
44
70
 
45
71
  def reset_error!
46
- @middleware.error = nil
72
+ middleware.error = nil
47
73
  end
48
74
 
49
75
  def error
50
- @middleware.error
76
+ middleware.error
51
77
  end
52
78
 
53
79
  def responsive?
54
80
  return false if @server_thread && @server_thread.join(0)
55
81
 
56
- res = Net::HTTP.start(host, @port) { |http| http.get('/__identify__') }
82
+ res = Net::HTTP.start(host, port) { |http| http.get('/__identify__') }
57
83
 
58
84
  if res.is_a?(Net::HTTPSuccess) or res.is_a?(Net::HTTPRedirection)
59
- return res.body == @app.object_id.to_s
85
+ return res.body == app.object_id.to_s
60
86
  end
61
87
  rescue SystemCallError
62
88
  return false
63
89
  end
64
90
 
91
+ def wait_for_pending_requests
92
+ Timeout.timeout(60) { sleep(0.01) while pending_requests? }
93
+ rescue Timeout::Error
94
+ raise "Requests did not finish in 60 seconds"
95
+ end
96
+
65
97
  def boot
66
98
  unless responsive?
67
- Capybara::Server.ports[@app.object_id] = @port
99
+ Capybara::Server.ports[port_key] = port
68
100
 
69
101
  @server_thread = Thread.new do
70
- Capybara.server.call(@middleware, @port)
102
+ Capybara.server.call(middleware, port, host)
71
103
  end
72
104
 
73
105
  Timeout.timeout(60) { @server_thread.join(0.1) until responsive? }
@@ -80,12 +112,23 @@ module Capybara
80
112
 
81
113
  private
82
114
 
83
- def find_available_port
84
- server = TCPServer.new('127.0.0.1', 0)
115
+ def middleware
116
+ @middleware ||= Middleware.new(app, @server_errors)
117
+ end
118
+
119
+ def port_key
120
+ Capybara.reuse_server ? app.object_id : middleware.object_id
121
+ end
122
+
123
+ def pending_requests?
124
+ middleware.pending_requests?
125
+ end
126
+
127
+ def find_available_port(host)
128
+ server = TCPServer.new(host, 0)
85
129
  server.addr[1]
86
130
  ensure
87
131
  server.close if server
88
132
  end
89
-
90
133
  end
91
134
  end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+ require 'delegate'
3
+
4
+ module Capybara
5
+ class SessionConfig
6
+ OPTIONS = [:always_include_port, :run_server, :default_selector, :default_max_wait_time, :ignore_hidden_elements,
7
+ :automatic_reload, :match, :exact, :exact_text, :raise_server_errors, :visible_text_only, :wait_on_first_by_default,
8
+ :automatic_label_click, :enable_aria_label, :save_path, :exact_options, :asset_host, :default_host, :app_host,
9
+ :save_and_open_page_path, :server_host, :server_port, :server_errors]
10
+
11
+ attr_accessor(*OPTIONS)
12
+
13
+ ##
14
+ #@!method always_include_port
15
+ # See {Capybara.configure}
16
+ #@!method run_server
17
+ # See {Capybara.configure}
18
+ #@!method default_selector
19
+ # See {Capybara.configure}
20
+ #@!method default_max_wait_time
21
+ # See {Capybara.configure}
22
+ #@!method ignore_hidden_elements
23
+ # See {Capybara.configure}
24
+ #@!method automatic_reload
25
+ # See {Capybara.configure}
26
+ #@!method match
27
+ # See {Capybara.configure}
28
+ #@!method exact
29
+ # See {Capybara.configure}
30
+ #@!method raise_server_errors
31
+ # See {Capybara.configure}
32
+ #@!method visible_text_only
33
+ # See {Capybara.configure}
34
+ #@!method wait_on_first_by_default
35
+ # See {Capybara.configure}
36
+ #@!method automatic_label_click
37
+ # See {Capybara.configure}
38
+ #@!method enable_aria_label
39
+ # See {Capybara.configure}
40
+ #@!method save_path
41
+ # See {Capybara.configure}
42
+ #@deprecated
43
+ #@!method exact_options
44
+ # See {Capybara.configure}
45
+ #@!method asset_host
46
+ # See {Capybara.configure}
47
+ #@!method default_host
48
+ # See {Capybara.configure}
49
+ #@!method app_host
50
+ # See {Capybara.configure}
51
+ #@!method save_and_open_page_path
52
+ # See {Capybara.configure}
53
+ #@!method server_host
54
+ # See {Capybara.configure}
55
+ #@!method server_port
56
+ # See {Capybara.configure}
57
+ #@!method server_errors
58
+ # See {Capybara.configure}
59
+
60
+ remove_method :server_host
61
+
62
+ ##
63
+ #
64
+ # @return [String] The IP address bound by default server
65
+ #
66
+ def server_host
67
+ @server_host || '127.0.0.1'
68
+ end
69
+
70
+ remove_method :server_errors=
71
+ def server_errors=(errors)
72
+ (@server_errors ||= []).replace(errors.dup)
73
+ end
74
+
75
+ remove_method :app_host=
76
+ def app_host=(url)
77
+ raise ArgumentError.new("Capybara.app_host should be set to a url (http://www.example.com)") unless url.nil? || (url =~ URI::Parser.new.make_regexp)
78
+ @app_host = url
79
+ end
80
+
81
+ remove_method :default_host=
82
+ def default_host=(url)
83
+ raise ArgumentError.new("Capybara.default_host should be set to a url (http://www.example.com)") unless url.nil? || (url =~ URI::Parser.new.make_regexp)
84
+ @default_host = url
85
+ end
86
+
87
+ remove_method :save_and_open_page_path=
88
+ def save_and_open_page_path=(path)
89
+ warn "DEPRECATED: #save_and_open_page_path is deprecated, please use #save_path instead. \n"\
90
+ "Note: Behavior is slightly different with relative paths - see documentation" unless path.nil?
91
+ @save_and_open_page_path = path
92
+ end
93
+
94
+ remove_method :exact_options=
95
+ def exact_options=(opt)
96
+ @exact_options = opt
97
+ warn "DEPRECATED: #exact_options is deprecated, please scope your findes/actions and use the `:exact` "\
98
+ "option if similar functionality is needed."
99
+ end
100
+
101
+ def initialize_copy(other)
102
+ super
103
+ @server_errors = @server_errors.dup
104
+ end
105
+ end
106
+
107
+ class ReadOnlySessionConfig < SimpleDelegator
108
+ SessionConfig::OPTIONS.each do |m|
109
+ define_method "#{m}=" do |val|
110
+ raise "Per session settings are only supported when Capybara.threadsafe == true"
111
+ end
112
+ end
113
+ end
114
+ end
@@ -1,49 +1,46 @@
1
+ # frozen_string_literal: true
1
2
  module Capybara
2
3
  module SessionMatchers
3
4
  ##
4
5
  # Asserts that the page has the given path.
5
- # By default this will compare against the path+query portion of the full url
6
+ # By default, if passed a full url this will compare against the full url,
7
+ # if passed a path only the path+query portion will be compared, if passed a regexp
8
+ # the comparison will depend on the :url option
6
9
  #
7
10
  # @!macro current_path_query_params
8
11
  # @overload $0(string, options = {})
9
12
  # @param string [String] The string that the current 'path' should equal
10
13
  # @overload $0(regexp, options = {})
11
14
  # @param regexp [Regexp] The regexp that the current 'path' should match to
12
- # @option options [Numeric] :wait (Capybara.default_max_wait_time) Maximum time that Capybara will wait for the current path to eq/match given string/regexp argument
13
- # @option options [Boolean] :url (false) Whether the compare should be done against the full url
14
- # @option options [Boolean] :only_path (false) Whether the compare should be done against just the path protion of the url
15
+ # @option options [Boolean] :url (true if `string` ia a full url, otherwise false) Whether the compare should be done against the full current url or just the path
16
+ # @option options [Boolean] :ignore_query (false) Whether the query portion of the current url/path should be ignored
17
+ # @option options [Numeric] :wait (Capybara.default_max_wait_time) Maximum time that Capybara will wait for the current url/path to eq/match given string/regexp argument
15
18
  # @raise [Capybara::ExpectationNotMet] if the assertion hasn't succeeded during wait time
16
19
  # @return [true]
17
20
  #
18
21
  def assert_current_path(path, options={})
19
- query = Capybara::Queries::CurrentPathQuery.new(path, options)
20
- document.synchronize(query.wait) do
21
- unless query.resolves_for?(self)
22
- raise Capybara::ExpectationNotMet, query.failure_message
23
- end
24
- end
25
- return true
22
+ _verify_current_path(path,options) { |query| raise Capybara::ExpectationNotMet, query.failure_message unless query.resolves_for?(self) }
26
23
  end
27
24
 
28
25
  ##
29
26
  # Asserts that the page doesn't have the given path.
27
+ # By default, if passed a full url this will compare against the full url,
28
+ # if passed a path only the path+query portion will be compared, if passed a regexp
29
+ # the comparison will depend on the :url option
30
30
  #
31
31
  # @macro current_path_query_params
32
32
  # @raise [Capybara::ExpectationNotMet] if the assertion hasn't succeeded during wait time
33
33
  # @return [true]
34
34
  #
35
35
  def assert_no_current_path(path, options={})
36
- query = Capybara::Queries::CurrentPathQuery.new(path, options)
37
- document.synchronize(query.wait) do
38
- if query.resolves_for?(self)
39
- raise Capybara::ExpectationNotMet, query.negative_failure_message
40
- end
41
- end
42
- return true
36
+ _verify_current_path(path,options) { |query| raise Capybara::ExpectationNotMet, query.negative_failure_message if query.resolves_for?(self) }
43
37
  end
44
38
 
45
39
  ##
46
40
  # Checks if the page has the given path.
41
+ # By default, if passed a full url this will compare against the full url,
42
+ # if passed a path only the path+query portion will be compared, if passed a regexp
43
+ # the comparison will depend on the :url option
47
44
  #
48
45
  # @macro current_path_query_params
49
46
  # @return [Boolean]
@@ -56,6 +53,9 @@ module Capybara
56
53
 
57
54
  ##
58
55
  # Checks if the page doesn't have the given path.
56
+ # By default, if passed a full url this will compare against the full url,
57
+ # if passed a path only the path+query portion will be compared, if passed a regexp
58
+ # the comparison will depend on the :url option
59
59
  #
60
60
  # @macro current_path_query_params
61
61
  # @return [Boolean]
@@ -65,5 +65,15 @@ module Capybara
65
65
  rescue Capybara::ExpectationNotMet
66
66
  return false
67
67
  end
68
+
69
+ private
70
+
71
+ def _verify_current_path(path, options)
72
+ query = Capybara::Queries::CurrentPathQuery.new(path, options)
73
+ document.synchronize(query.wait) do
74
+ yield(query)
75
+ end
76
+ return true
77
+ end
68
78
  end
69
- end
79
+ end