capybara 2.15.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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)