capybara 2.15.0 → 3.0.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 (177) hide show
  1. checksums.yaml +5 -5
  2. data/History.md +137 -2
  3. data/README.md +36 -25
  4. data/lib/capybara/config.rb +11 -57
  5. data/lib/capybara/cucumber.rb +2 -3
  6. data/lib/capybara/driver/base.rb +19 -16
  7. data/lib/capybara/driver/node.rb +5 -4
  8. data/lib/capybara/dsl.rb +1 -0
  9. data/lib/capybara/helpers.rb +19 -29
  10. data/lib/capybara/minitest/spec.rb +16 -13
  11. data/lib/capybara/minitest.rb +140 -137
  12. data/lib/capybara/node/actions.rb +68 -89
  13. data/lib/capybara/node/base.rb +11 -18
  14. data/lib/capybara/node/document.rb +2 -2
  15. data/lib/capybara/node/document_matchers.rb +8 -8
  16. data/lib/capybara/node/element.rb +32 -42
  17. data/lib/capybara/node/finders.rb +64 -71
  18. data/lib/capybara/node/matchers.rb +50 -71
  19. data/lib/capybara/node/simple.rb +11 -17
  20. data/lib/capybara/queries/ancestor_query.rb +12 -8
  21. data/lib/capybara/queries/base_query.rb +22 -18
  22. data/lib/capybara/queries/current_path_query.rb +12 -25
  23. data/lib/capybara/queries/match_query.rb +3 -7
  24. data/lib/capybara/queries/selector_query.rb +100 -96
  25. data/lib/capybara/queries/sibling_query.rb +5 -5
  26. data/lib/capybara/queries/text_query.rb +35 -35
  27. data/lib/capybara/queries/title_query.rb +8 -11
  28. data/lib/capybara/rack_test/browser.rb +15 -18
  29. data/lib/capybara/rack_test/css_handlers.rb +6 -4
  30. data/lib/capybara/rack_test/driver.rb +6 -10
  31. data/lib/capybara/rack_test/form.rb +52 -39
  32. data/lib/capybara/rack_test/node.rb +93 -63
  33. data/lib/capybara/rails.rb +2 -6
  34. data/lib/capybara/result.rb +22 -22
  35. data/lib/capybara/rspec/compound.rb +5 -10
  36. data/lib/capybara/rspec/features.rb +17 -48
  37. data/lib/capybara/rspec/matcher_proxies.rb +31 -15
  38. data/lib/capybara/rspec/matchers.rb +116 -58
  39. data/lib/capybara/rspec.rb +5 -10
  40. data/lib/capybara/selector/css.rb +6 -11
  41. data/lib/capybara/selector/filter.rb +1 -17
  42. data/lib/capybara/selector/filter_set.rb +18 -15
  43. data/lib/capybara/selector/filters/base.rb +7 -6
  44. data/lib/capybara/selector/filters/expression_filter.rb +6 -23
  45. data/lib/capybara/selector/filters/node_filter.rb +2 -12
  46. data/lib/capybara/selector/selector.rb +28 -34
  47. data/lib/capybara/selector.rb +129 -117
  48. data/lib/capybara/selenium/driver.rb +172 -163
  49. data/lib/capybara/selenium/node.rb +218 -104
  50. data/lib/capybara/server.rb +3 -2
  51. data/lib/capybara/session/config.rb +47 -59
  52. data/lib/capybara/session/matchers.rb +23 -14
  53. data/lib/capybara/session.rb +175 -229
  54. data/lib/capybara/spec/fixtures/no_extension +1 -0
  55. data/lib/capybara/spec/public/test.js +38 -6
  56. data/lib/capybara/spec/session/accept_alert_spec.rb +1 -0
  57. data/lib/capybara/spec/session/accept_confirm_spec.rb +3 -2
  58. data/lib/capybara/spec/session/accept_prompt_spec.rb +30 -1
  59. data/lib/capybara/spec/session/all_spec.rb +31 -18
  60. data/lib/capybara/spec/session/ancestor_spec.rb +6 -8
  61. data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +6 -5
  62. data/lib/capybara/spec/session/assert_current_path.rb +12 -11
  63. data/lib/capybara/spec/session/assert_selector.rb +1 -0
  64. data/lib/capybara/spec/session/assert_text.rb +31 -23
  65. data/lib/capybara/spec/session/assert_title.rb +13 -3
  66. data/lib/capybara/spec/session/attach_file_spec.rb +57 -29
  67. data/lib/capybara/spec/session/body_spec.rb +1 -0
  68. data/lib/capybara/spec/session/check_spec.rb +7 -6
  69. data/lib/capybara/spec/session/choose_spec.rb +5 -4
  70. data/lib/capybara/spec/session/click_button_spec.rb +24 -32
  71. data/lib/capybara/spec/session/click_link_or_button_spec.rb +8 -7
  72. data/lib/capybara/spec/session/click_link_spec.rb +8 -7
  73. data/lib/capybara/spec/session/current_scope_spec.rb +4 -3
  74. data/lib/capybara/spec/session/current_url_spec.rb +19 -8
  75. data/lib/capybara/spec/session/dismiss_confirm_spec.rb +1 -1
  76. data/lib/capybara/spec/session/dismiss_prompt_spec.rb +1 -0
  77. data/lib/capybara/spec/session/element/assert_match_selector.rb +1 -1
  78. data/lib/capybara/spec/session/element/match_xpath_spec.rb +1 -1
  79. data/lib/capybara/spec/session/element/matches_selector_spec.rb +5 -5
  80. data/lib/capybara/spec/session/evaluate_async_script_spec.rb +23 -0
  81. data/lib/capybara/spec/session/evaluate_script_spec.rb +5 -4
  82. data/lib/capybara/spec/session/execute_script_spec.rb +4 -3
  83. data/lib/capybara/spec/session/fill_in_spec.rb +30 -5
  84. data/lib/capybara/spec/session/find_button_spec.rb +4 -3
  85. data/lib/capybara/spec/session/find_by_id_spec.rb +2 -1
  86. data/lib/capybara/spec/session/find_field_spec.rb +9 -15
  87. data/lib/capybara/spec/session/find_link_spec.rb +6 -5
  88. data/lib/capybara/spec/session/find_spec.rb +37 -31
  89. data/lib/capybara/spec/session/first_spec.rb +60 -33
  90. data/lib/capybara/spec/session/frame/frame_title_spec.rb +23 -0
  91. data/lib/capybara/spec/session/frame/frame_url_spec.rb +23 -0
  92. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +2 -1
  93. data/lib/capybara/spec/session/frame/within_frame_spec.rb +9 -16
  94. data/lib/capybara/spec/session/go_back_spec.rb +1 -0
  95. data/lib/capybara/spec/session/go_forward_spec.rb +1 -0
  96. data/lib/capybara/spec/session/has_all_selectors_spec.rb +69 -0
  97. data/lib/capybara/spec/session/has_button_spec.rb +2 -1
  98. data/lib/capybara/spec/session/has_css_spec.rb +3 -2
  99. data/lib/capybara/spec/session/has_current_path_spec.rb +49 -22
  100. data/lib/capybara/spec/session/has_field_spec.rb +4 -3
  101. data/lib/capybara/spec/session/has_link_spec.rb +5 -4
  102. data/lib/capybara/spec/session/has_none_selectors_spec.rb +76 -0
  103. data/lib/capybara/spec/session/has_select_spec.rb +32 -31
  104. data/lib/capybara/spec/session/has_selector_spec.rb +5 -4
  105. data/lib/capybara/spec/session/has_table_spec.rb +2 -1
  106. data/lib/capybara/spec/session/has_text_spec.rb +9 -13
  107. data/lib/capybara/spec/session/has_title_spec.rb +1 -0
  108. data/lib/capybara/spec/session/has_xpath_spec.rb +1 -0
  109. data/lib/capybara/spec/session/headers.rb +2 -1
  110. data/lib/capybara/spec/session/html_spec.rb +1 -0
  111. data/lib/capybara/spec/session/node_spec.rb +107 -58
  112. data/lib/capybara/spec/session/node_wrapper_spec.rb +36 -0
  113. data/lib/capybara/spec/session/refresh_spec.rb +6 -2
  114. data/lib/capybara/spec/session/reset_session_spec.rb +19 -0
  115. data/lib/capybara/spec/session/response_code.rb +1 -0
  116. data/lib/capybara/spec/session/save_and_open_page_spec.rb +1 -0
  117. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +6 -11
  118. data/lib/capybara/spec/session/save_page_spec.rb +1 -17
  119. data/lib/capybara/spec/session/save_screenshot_spec.rb +3 -3
  120. data/lib/capybara/spec/session/select_spec.rb +21 -20
  121. data/lib/capybara/spec/session/selectors_spec.rb +2 -2
  122. data/lib/capybara/spec/session/sibling_spec.rb +1 -1
  123. data/lib/capybara/spec/session/text_spec.rb +17 -3
  124. data/lib/capybara/spec/session/title_spec.rb +11 -1
  125. data/lib/capybara/spec/session/uncheck_spec.rb +4 -3
  126. data/lib/capybara/spec/session/unselect_spec.rb +7 -6
  127. data/lib/capybara/spec/session/visit_spec.rb +64 -3
  128. data/lib/capybara/spec/session/window/become_closed_spec.rb +2 -1
  129. data/lib/capybara/spec/session/window/current_window_spec.rb +1 -0
  130. data/lib/capybara/spec/session/window/open_new_window_spec.rb +1 -0
  131. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +2 -1
  132. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +2 -1
  133. data/lib/capybara/spec/session/window/window_spec.rb +12 -12
  134. data/lib/capybara/spec/session/window/windows_spec.rb +2 -3
  135. data/lib/capybara/spec/session/window/within_window_spec.rb +15 -71
  136. data/lib/capybara/spec/session/within_spec.rb +1 -0
  137. data/lib/capybara/spec/spec_helper.rb +36 -18
  138. data/lib/capybara/spec/test_app.rb +17 -9
  139. data/lib/capybara/spec/views/form.erb +7 -0
  140. data/lib/capybara/spec/views/initial_alert.erb +10 -0
  141. data/lib/capybara/spec/views/with_fixed_header_footer.erb +17 -0
  142. data/lib/capybara/spec/views/with_hover.erb +5 -0
  143. data/lib/capybara/spec/views/with_html.erb +27 -1
  144. data/lib/capybara/spec/views/with_js.erb +11 -0
  145. data/lib/capybara/spec/views/within_frames.erb +4 -1
  146. data/lib/capybara/version.rb +2 -1
  147. data/lib/capybara/window.rb +6 -10
  148. data/lib/capybara.rb +29 -26
  149. data/spec/basic_node_spec.rb +1 -0
  150. data/spec/capybara_spec.rb +16 -69
  151. data/spec/dsl_spec.rb +5 -13
  152. data/spec/filter_set_spec.rb +5 -4
  153. data/spec/fixtures/selenium_driver_rspec_failure.rb +2 -1
  154. data/spec/fixtures/selenium_driver_rspec_success.rb +3 -2
  155. data/spec/minitest_spec.rb +13 -4
  156. data/spec/minitest_spec_spec.rb +12 -3
  157. data/spec/per_session_config_spec.rb +9 -8
  158. data/spec/rack_test_spec.rb +21 -20
  159. data/spec/result_spec.rb +17 -16
  160. data/spec/rspec/features_spec.rb +17 -14
  161. data/spec/rspec/scenarios_spec.rb +5 -7
  162. data/spec/rspec/shared_spec_matchers.rb +96 -99
  163. data/spec/rspec/views_spec.rb +2 -1
  164. data/spec/rspec_matchers_spec.rb +18 -2
  165. data/spec/rspec_spec.rb +11 -15
  166. data/spec/selector_spec.rb +5 -6
  167. data/spec/selenium_spec_chrome.rb +20 -11
  168. data/spec/selenium_spec_edge.rb +27 -0
  169. data/spec/selenium_spec_ie.rb +31 -0
  170. data/spec/selenium_spec_marionette.rb +38 -12
  171. data/spec/server_spec.rb +33 -33
  172. data/spec/session_spec.rb +2 -1
  173. data/spec/shared_selenium_session.rb +82 -22
  174. data/spec/spec_helper.rb +3 -6
  175. metadata +76 -81
  176. data/lib/capybara/query.rb +0 -7
  177. data/spec/selenium_spec_firefox.rb +0 -68
@@ -1,13 +1,30 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  class Capybara::Selenium::Node < Capybara::Driver::Node
4
+ SET_FORMATS = Hash.new(date: '%Y-%m-%d', time: '%H:%M', datetime: "%m%d%Y\t%I%M%P").merge(
5
+ firefox: {
6
+ date: '%Y-%m-%d',
7
+ time: '%H:%M',
8
+ datetime: "%m%d%Y\t%I%M%P"
9
+ },
10
+ chrome: {
11
+ date: '%m%d%Y',
12
+ time: '%I%M%P',
13
+ datetime: "%m%d%Y\t%I%M%P"
14
+ }
15
+ )
16
+
3
17
  def visible_text
4
- # Selenium doesn't normalize Unicode whitespace.
5
- Capybara::Helpers.normalize_whitespace(native.text)
18
+ native.text
6
19
  end
7
20
 
8
21
  def all_text
9
22
  text = driver.execute_script("return arguments[0].textContent", self)
10
- Capybara::Helpers.normalize_whitespace(text)
23
+ text.gsub(/[\u200b\u200e\u200f]/, '')
24
+ .gsub(/[\ \n\f\t\v\u2028\u2029]+/, ' ')
25
+ .gsub(/\A[[:space:]&&[^\u00a0]]+/, "")
26
+ .gsub(/[[:space:]&&[^\u00a0]]+\z/, "")
27
+ .tr("\u00a0", ' ')
11
28
  end
12
29
 
13
30
  def [](name)
@@ -35,57 +52,31 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
35
52
  # :none => append the new value to the existing value <br/>
36
53
  # :backspace => send backspace keystrokes to clear the field <br/>
37
54
  # Array => an array of keys to send before the value being set, e.g. [[:command, 'a'], :backspace]
38
- def set(value, options={})
39
- tag_name = self.tag_name
40
- type = self[:type]
41
-
42
- if (Array === value) && !multiple?
43
- raise ArgumentError.new "Value cannot be an Array when 'multiple' attribute is not present. Not a #{value.class}"
44
- end
55
+ def set(value, **options)
56
+ raise ArgumentError, "Value cannot be an Array when 'multiple' attribute is not present. Not a #{value.class}" if value.is_a?(Array) && !multiple?
45
57
 
46
58
  case tag_name
47
59
  when 'input'
48
- case type
60
+ case self[:type]
49
61
  when 'radio'
50
62
  click
51
63
  when 'checkbox'
52
- click if value ^ native.attribute('checked').to_s.eql?("true")
64
+ click if value ^ checked?
53
65
  when 'file'
54
- path_names = value.to_s.empty? ? [] : value
55
- if driver.chrome?
56
- native.send_keys(Array(path_names).join("\n"))
57
- else
58
- native.send_keys(*path_names)
59
- end
66
+ set_file(value)
67
+ when 'date'
68
+ set_date(value)
69
+ when 'time'
70
+ set_time(value)
71
+ when 'datetime-local'
72
+ set_datetime_local(value)
60
73
  else
61
74
  set_text(value, options)
62
75
  end
63
76
  when 'textarea'
64
77
  set_text(value, options)
65
78
  else
66
- if content_editable?
67
- #ensure we are focused on the element
68
- click
69
-
70
- script = <<-JS
71
- var range = document.createRange();
72
- var sel = window.getSelection();
73
- arguments[0].focus();
74
- range.selectNodeContents(arguments[0]);
75
- sel.removeAllRanges();
76
- sel.addRange(range);
77
- JS
78
- driver.execute_script script, self
79
-
80
- if (driver.chrome?) || (driver.firefox? && !driver.marionette?)
81
- # chromedriver raises a can't focus element for child elements if we use native.send_keys
82
- # we've already focused it so just use action api
83
- driver.browser.action.send_keys(value.to_s).perform
84
- else
85
- # action api is really slow here just use native.send_keys
86
- native.send_keys(value.to_s)
87
- end
88
- end
79
+ set_content_editable(value) if content_editable?
89
80
  end
90
81
  end
91
82
 
@@ -94,20 +85,57 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
94
85
  end
95
86
 
96
87
  def unselect_option
97
- raise Capybara::UnselectNotAllowed, "Cannot unselect option from single select box." if !select_node.multiple?
88
+ raise Capybara::UnselectNotAllowed, "Cannot unselect option from single select box." unless select_node.multiple?
98
89
  native.click if selected?
99
90
  end
100
91
 
101
- def click
102
- native.click
92
+ def click(keys = [], options = {})
93
+ if keys.empty? && !(options[:x] && options[:y])
94
+ native.click
95
+ else
96
+ scroll_if_needed do
97
+ action_with_modifiers(keys, options) do |a|
98
+ if options[:x] && options[:y]
99
+ a.click
100
+ else
101
+ a.click(native)
102
+ end
103
+ end
104
+ end
105
+ end
106
+ rescue => e
107
+ if e.is_a?(::Selenium::WebDriver::Error::ElementClickInterceptedError) ||
108
+ e.message =~ /Other element would receive the click/
109
+ begin
110
+ driver.execute_script("arguments[0].scrollIntoView({behavior: 'instant', block: 'center', inline: 'center'})", self)
111
+ rescue # Swallow error if scrollIntoView with options isn't supported
112
+ end
113
+ end
114
+ raise e
103
115
  end
104
116
 
105
- def right_click
106
- driver.browser.action.context_click(native).perform
117
+ def right_click(keys = [], options = {})
118
+ scroll_if_needed do
119
+ action_with_modifiers(keys, options) do |a|
120
+ if options[:x] && options[:y]
121
+ a.context_click
122
+ else
123
+ a.context_click(native)
124
+ end
125
+ end
126
+ end
107
127
  end
108
128
 
109
- def double_click
110
- driver.browser.action.double_click(native).perform
129
+ def double_click(keys = [], options = {})
130
+ scroll_if_needed do
131
+ action_with_modifiers(keys, options) do |a|
132
+ if options[:x] && options[:y]
133
+ a.double_click
134
+ else
135
+ a.double_click(native)
136
+ end
137
+ end
138
+ end
111
139
  end
112
140
 
113
141
  def send_keys(*args)
@@ -115,51 +143,38 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
115
143
  end
116
144
 
117
145
  def hover
118
- driver.browser.action.move_to(native).perform
146
+ scroll_if_needed { driver.browser.action.move_to(native).perform }
119
147
  end
120
148
 
121
149
  def drag_to(element)
122
- driver.browser.action.drag_and_drop(native, element.native).perform
150
+ scroll_if_needed { driver.browser.action.drag_and_drop(native, element.native).perform }
123
151
  end
124
152
 
125
153
  def tag_name
126
154
  native.tag_name.downcase
127
155
  end
128
156
 
129
- def visible?
130
- displayed = native.displayed?
131
- displayed and displayed != "false"
132
- end
133
-
134
- def selected?
135
- selected = native.selected?
136
- selected and selected != "false"
137
- end
157
+ def visible?; boolean_attr(native.displayed?); end
158
+ def readonly?; boolean_attr(self[:readonly]); end
159
+ def multiple?; boolean_attr(self[:multiple]); end
160
+ def selected?; boolean_attr(native.selected?); end
138
161
  alias :checked? :selected?
139
162
 
140
163
  def disabled?
164
+ return true unless native.enabled?
165
+
141
166
  # workaround for selenium-webdriver/geckodriver reporting elements as enabled when they are nested in disabling elements
142
167
  if driver.marionette?
143
- if %w(option optgroup).include? tag_name
144
- !native.enabled? || find_xpath("parent::*[self::optgroup or self::select]")[0].disabled?
168
+ if %w[option optgroup].include? tag_name
169
+ find_xpath("parent::*[self::optgroup or self::select]")[0].disabled?
145
170
  else
146
- !native.enabled? || !find_xpath("parent::fieldset[@disabled] | ancestor::*[not(self::legend) or preceding-sibling::legend][parent::fieldset[@disabled]]").empty?
171
+ !find_xpath("parent::fieldset[@disabled] | ancestor::*[not(self::legend) or preceding-sibling::legend][parent::fieldset[@disabled]]").empty?
147
172
  end
148
173
  else
149
- !native.enabled?
174
+ false
150
175
  end
151
176
  end
152
177
 
153
- def readonly?
154
- readonly = self[:readonly]
155
- readonly and readonly != "false"
156
- end
157
-
158
- def multiple?
159
- multiple = self[:multiple]
160
- multiple and multiple != "false"
161
- end
162
-
163
178
  def content_editable?
164
179
  native.attribute('isContentEditable')
165
180
  end
@@ -177,56 +192,155 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
177
192
  end
178
193
 
179
194
  def path
180
- path = find_xpath('ancestor::*').reverse
181
- path.unshift self
195
+ path = find_xpath(XPath.ancestor_or_self).reverse
182
196
 
183
197
  result = []
184
- while node = path.shift
198
+ while (node = path.shift)
185
199
  parent = path.first
186
-
200
+ selector = node.tag_name
187
201
  if parent
188
202
  siblings = parent.find_xpath(node.tag_name)
189
- if siblings.size == 1
190
- result.unshift node.tag_name
191
- else
192
- index = siblings.index(node)
193
- result.unshift "#{node.tag_name}[#{index+1}]"
194
- end
195
- else
196
- result.unshift node.tag_name
203
+ selector += "[#{siblings.index(node) + 1}]" unless siblings.size == 1
197
204
  end
205
+ result.push selector
198
206
  end
199
207
 
200
- '/' + result.join('/')
208
+ '/' + result.reverse.join('/')
201
209
  end
202
210
 
203
211
  private
212
+
213
+ def boolean_attr(val)
214
+ val and val != "false"
215
+ end
216
+
204
217
  # a reference to the select node if this is an option node
205
218
  def select_node
206
- find_xpath('./ancestor::select[1]').first
219
+ find_xpath(XPath.ancestor(:select)[1]).first
220
+ end
221
+
222
+ def set_text(value, clear: nil, **_unused)
223
+ if value.to_s.empty? && clear.nil?
224
+ native.clear
225
+ elsif clear == :backspace
226
+ # Clear field by sending the correct number of backspace keys.
227
+ backspaces = [:backspace] * self.value.to_s.length
228
+ native.send_keys(*(backspaces + [value.to_s]))
229
+ elsif clear == :none
230
+ native.send_keys(value.to_s)
231
+ elsif clear.is_a? Array
232
+ native.send_keys(*clear, value.to_s)
233
+ else
234
+ # Clear field by JavaScript assignment of the value property.
235
+ # Script can change a readonly element which user input cannot, so
236
+ # don't execute if readonly.
237
+ driver.execute_script "arguments[0].value = ''", self
238
+ native.send_keys(value.to_s)
239
+ end
240
+ end
241
+
242
+ def scroll_if_needed
243
+ yield
244
+ rescue ::Selenium::WebDriver::Error::MoveTargetOutOfBoundsError
245
+ script = <<-'JS'
246
+ try {
247
+ arguments[0].scrollIntoView({behavior: 'instant', block: 'center', inline: 'center'});
248
+ } catch(e) {
249
+ arguments[0].scrollIntoView(true);
250
+ }
251
+ JS
252
+ driver.execute_script(script, self)
253
+ yield
254
+ end
255
+
256
+ def set_date(value) # rubocop:disable Naming/AccessorMethodName
257
+ if value.respond_to?(:to_date)
258
+ set_text(value.to_date.strftime(SET_FORMATS[driver.options[:browser].to_sym][:date]))
259
+ else
260
+ set_text(value)
261
+ end
262
+ end
263
+
264
+ def set_time(value) # rubocop:disable Naming/AccessorMethodName
265
+ if value.respond_to?(:to_time)
266
+ set_text(value.to_time.strftime(SET_FORMATS[driver.options[:browser].to_sym][:time]))
267
+ else
268
+ set_text(value)
269
+ end
270
+ end
271
+
272
+ def set_datetime_local(value) # rubocop:disable Naming/AccessorMethodName
273
+ if value.respond_to?(:to_time)
274
+ set_text(value.to_time.strftime(SET_FORMATS[driver.options[:browser].to_sym][:datetime]))
275
+ else
276
+ set_text(value)
277
+ end
207
278
  end
208
279
 
209
- def set_text(value, options)
210
- if readonly?
211
- warn "Attempt to set readonly element with value: #{value} \n *This will raise an exception in a future version of Capybara"
212
- elsif value.to_s.empty?
280
+ def set_file(value) # rubocop:disable Naming/AccessorMethodName
281
+ path_names = value.to_s.empty? ? [] : value
282
+ if driver.marionette?
213
283
  native.clear
284
+ Array(path_names).each { |p| native.send_keys(p) }
214
285
  else
215
- if options[:clear] == :backspace
216
- # Clear field by sending the correct number of backspace keys.
217
- backspaces = [:backspace] * self.value.to_s.length
218
- native.send_keys(*(backspaces + [value.to_s]))
219
- elsif options[:clear] == :none
220
- native.send_keys(value.to_s)
221
- elsif options[:clear].is_a? Array
222
- native.send_keys(*options[:clear], value.to_s)
286
+ native.send_keys(Array(path_names).join("\n"))
287
+ end
288
+ end
289
+
290
+ def set_content_editable(value) # rubocop:disable Naming/AccessorMethodName
291
+ # Ensure we are focused on the element
292
+ click
293
+
294
+ script = <<-JS
295
+ var range = document.createRange();
296
+ var sel = window.getSelection();
297
+ arguments[0].focus();
298
+ range.selectNodeContents(arguments[0]);
299
+ sel.removeAllRanges();
300
+ sel.addRange(range);
301
+ JS
302
+ driver.execute_script script, self
303
+
304
+ # The action api has a speed problem but both chrome and firefox 58 raise errors
305
+ # if we use the faster direct send_keys. For now just send_keys to the element
306
+ # we've already focused.
307
+ # native.send_keys(value.to_s)
308
+ driver.browser.action.send_keys(value.to_s).perform
309
+ end
310
+
311
+ def action_with_modifiers(keys, x: nil, y: nil)
312
+ actions = driver.browser.action
313
+ actions.move_to(native, x, y)
314
+ modifiers_down(actions, keys)
315
+ yield actions
316
+ modifiers_up(actions, keys)
317
+ actions.perform
318
+ ensure
319
+ a = driver.browser.action
320
+ a.release_actions if a.respond_to?(:release_actions)
321
+ end
322
+
323
+ def modifiers_down(actions, keys)
324
+ keys.each do |key|
325
+ key = case key
326
+ when :ctrl then :control
327
+ when :command, :cmd then :meta
328
+ else
329
+ key
330
+ end
331
+ actions.key_down(key)
332
+ end
333
+ end
334
+
335
+ def modifiers_up(actions, keys)
336
+ keys.each do |key|
337
+ key = case key
338
+ when :ctrl then :control
339
+ when :command, :cmd then :meta
223
340
  else
224
- # Clear field by JavaScript assignment of the value property.
225
- # Script can change a readonly element which user input cannot, so
226
- # don't execute if readonly.
227
- driver.execute_script "arguments[0].value = ''", self
228
- native.send_keys(value.to_s)
341
+ key
229
342
  end
343
+ actions.key_up(key)
230
344
  end
231
345
  end
232
346
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'uri'
3
4
  require 'net/http'
4
5
  require 'rack'
@@ -43,7 +44,7 @@ module Capybara
43
44
  begin
44
45
  @app.call(env)
45
46
  rescue *@server_errors => e
46
- @error = e unless @error
47
+ @error ||= e
47
48
  raise e
48
49
  ensure
49
50
  @counter.decrement
@@ -60,7 +61,7 @@ module Capybara
60
61
 
61
62
  attr_reader :app, :port, :host
62
63
 
63
- def initialize(app, port=Capybara.server_port, host=Capybara.server_host, server_errors=Capybara.server_errors)
64
+ def initialize(app, port = Capybara.server_port, host = Capybara.server_host, server_errors = Capybara.server_errors)
64
65
  @app = app
65
66
  @server_thread = nil # suppress warnings
66
67
  @host, @port, @server_errors = host, port, server_errors
@@ -1,60 +1,55 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'delegate'
3
4
 
4
5
  module Capybara
5
6
  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]
7
+ OPTIONS = %i[always_include_port run_server default_selector default_max_wait_time ignore_hidden_elements
8
+ automatic_reload match exact exact_text raise_server_errors visible_text_only
9
+ automatic_label_click enable_aria_label save_path asset_host default_host app_host
10
+ server_host server_port server_errors].freeze
10
11
 
11
12
  attr_accessor(*OPTIONS)
12
13
 
13
14
  ##
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
- #@!method exact_options
43
- # See {Capybara.configure}
44
- #@!method asset_host
45
- # See {Capybara.configure}
46
- #@!method default_host
47
- # See {Capybara.configure}
48
- #@!method app_host
49
- # See {Capybara.configure}
50
- #@!method save_and_open_page_path
51
- # See {Capybara.configure}
52
- #@!method server_host
53
- # See {Capybara.configure}
54
- #@!method server_port
55
- # See {Capybara.configure}
56
- #@!method server_errors
57
- # See {Capybara.configure}
15
+ # @!method always_include_port
16
+ # See {Capybara.configure}
17
+ # @!method run_server
18
+ # See {Capybara.configure}
19
+ # @!method default_selector
20
+ # See {Capybara.configure}
21
+ # @!method default_max_wait_time
22
+ # See {Capybara.configure}
23
+ # @!method ignore_hidden_elements
24
+ # See {Capybara.configure}
25
+ # @!method automatic_reload
26
+ # See {Capybara.configure}
27
+ # @!method match
28
+ # See {Capybara.configure}
29
+ # @!method exact
30
+ # See {Capybara.configure}
31
+ # @!method raise_server_errors
32
+ # See {Capybara.configure}
33
+ # @!method visible_text_only
34
+ # See {Capybara.configure}
35
+ # @!method automatic_label_click
36
+ # See {Capybara.configure}
37
+ # @!method enable_aria_label
38
+ # See {Capybara.configure}
39
+ # @!method save_path
40
+ # See {Capybara.configure}
41
+ # @!method asset_host
42
+ # See {Capybara.configure}
43
+ # @!method default_host
44
+ # See {Capybara.configure}
45
+ # @!method app_host
46
+ # See {Capybara.configure}
47
+ # @!method server_host
48
+ # See {Capybara.configure}
49
+ # @!method server_port
50
+ # See {Capybara.configure}
51
+ # @!method server_errors
52
+ # See {Capybara.configure}
58
53
 
59
54
  remove_method :server_host
60
55
 
@@ -73,23 +68,16 @@ module Capybara
73
68
 
74
69
  remove_method :app_host=
75
70
  def app_host=(url)
76
- 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)
71
+ raise ArgumentError, "Capybara.app_host should be set to a url (http://www.example.com). Attempted to set #{url.inspect}." if url && url !~ URI::DEFAULT_PARSER.make_regexp
77
72
  @app_host = url
78
73
  end
79
74
 
80
75
  remove_method :default_host=
81
76
  def default_host=(url)
82
- 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)
77
+ raise ArgumentError, "Capybara.default_host should be set to a url (http://www.example.com). Attempted to set #{url.inspect}." if url && url !~ URI::DEFAULT_PARSER.make_regexp
83
78
  @default_host = url
84
79
  end
85
80
 
86
- remove_method :save_and_open_page_path=
87
- def save_and_open_page_path=(path)
88
- warn "DEPRECATED: #save_and_open_page_path is deprecated, please use #save_path instead. \n"\
89
- "Note: Behavior is slightly different with relative paths - see documentation" unless path.nil?
90
- @save_and_open_page_path = path
91
- end
92
-
93
81
  def initialize_copy(other)
94
82
  super
95
83
  @server_errors = @server_errors.dup
@@ -98,9 +86,9 @@ module Capybara
98
86
 
99
87
  class ReadOnlySessionConfig < SimpleDelegator
100
88
  SessionConfig::OPTIONS.each do |m|
101
- define_method "#{m}=" do |val|
89
+ define_method "#{m}=" do |_|
102
90
  raise "Per session settings are only supported when Capybara.threadsafe == true"
103
91
  end
104
92
  end
105
93
  end
106
- end
94
+ end
@@ -1,45 +1,52 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Capybara
3
4
  module SessionMatchers
4
5
  ##
5
6
  # Asserts that the page has the given path.
6
- # By default this will compare against the path+query portion of the full url
7
+ # By default, if passed a full url this will compare against the full url,
8
+ # if passed a path only the path+query portion will be compared, if passed a regexp
9
+ # the comparison will depend on the :url option
7
10
  #
8
11
  # @!macro current_path_query_params
9
12
  # @overload $0(string, options = {})
10
13
  # @param string [String] The string that the current 'path' should equal
11
14
  # @overload $0(regexp, options = {})
12
15
  # @param regexp [Regexp] The regexp that the current 'path' should match to
13
- # @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
14
- # @option options [Boolean] :url (false) Whether the compare should be done against the full url
15
- # @option options [Boolean] :only_path (false) Whether the compare should be done against just the path protion of the url
16
+ # @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
17
+ # @option options [Boolean] :ignore_query (false) Whether the query portion of the current url/path should be ignored
18
+ # @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
16
19
  # @raise [Capybara::ExpectationNotMet] if the assertion hasn't succeeded during wait time
17
20
  # @return [true]
18
21
  #
19
- def assert_current_path(path, options={})
20
- _verify_current_path(path,options) { |query| raise Capybara::ExpectationNotMet, query.failure_message unless query.resolves_for?(self) }
22
+ def assert_current_path(path, **options)
23
+ _verify_current_path(path, options) { |query| raise Capybara::ExpectationNotMet, query.failure_message unless query.resolves_for?(self) }
21
24
  end
22
25
 
23
26
  ##
24
27
  # Asserts that the page doesn't have the given path.
25
- # By default this will compare against the path+query portion of the full url
28
+ # By default, if passed a full url this will compare against the full url,
29
+ # if passed a path only the path+query portion will be compared, if passed a regexp
30
+ # the comparison will depend on the :url option
26
31
  #
27
32
  # @macro current_path_query_params
28
33
  # @raise [Capybara::ExpectationNotMet] if the assertion hasn't succeeded during wait time
29
34
  # @return [true]
30
35
  #
31
- def assert_no_current_path(path, options={})
32
- _verify_current_path(path,options) { |query| raise Capybara::ExpectationNotMet, query.negative_failure_message if query.resolves_for?(self) }
36
+ def assert_no_current_path(path, **options)
37
+ _verify_current_path(path, options) { |query| raise Capybara::ExpectationNotMet, query.negative_failure_message if query.resolves_for?(self) }
33
38
  end
34
39
 
35
40
  ##
36
41
  # Checks if the page has the given path.
37
- # By default this will compare against the path+query portion of the full url
42
+ # By default, if passed a full url this will compare against the full url,
43
+ # if passed a path only the path+query portion will be compared, if passed a regexp
44
+ # the comparison will depend on the :url option
38
45
  #
39
46
  # @macro current_path_query_params
40
47
  # @return [Boolean]
41
48
  #
42
- def has_current_path?(path, options={})
49
+ def has_current_path?(path, **options)
43
50
  assert_current_path(path, options)
44
51
  rescue Capybara::ExpectationNotMet
45
52
  return false
@@ -47,18 +54,20 @@ module Capybara
47
54
 
48
55
  ##
49
56
  # Checks if the page doesn't have the given path.
50
- # By default this will compare against the path+query portion of the full url
57
+ # By default, if passed a full url this will compare against the full url,
58
+ # if passed a path only the path+query portion will be compared, if passed a regexp
59
+ # the comparison will depend on the :url option
51
60
  #
52
61
  # @macro current_path_query_params
53
62
  # @return [Boolean]
54
63
  #
55
- def has_no_current_path?(path, options={})
64
+ def has_no_current_path?(path, **options)
56
65
  assert_no_current_path(path, options)
57
66
  rescue Capybara::ExpectationNotMet
58
67
  return false
59
68
  end
60
69
 
61
- private
70
+ private
62
71
 
63
72
  def _verify_current_path(path, options)
64
73
  query = Capybara::Queries::CurrentPathQuery.new(path, options)