capybara 2.18.0 → 3.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (168) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +26 -1
  3. data/README.md +12 -12
  4. data/lib/capybara.rb +13 -25
  5. data/lib/capybara/config.rb +11 -57
  6. data/lib/capybara/cucumber.rb +2 -3
  7. data/lib/capybara/driver/base.rb +5 -16
  8. data/lib/capybara/driver/node.rb +5 -4
  9. data/lib/capybara/dsl.rb +1 -0
  10. data/lib/capybara/helpers.rb +16 -28
  11. data/lib/capybara/minitest.rb +139 -138
  12. data/lib/capybara/minitest/spec.rb +15 -14
  13. data/lib/capybara/node/actions.rb +59 -81
  14. data/lib/capybara/node/base.rb +11 -18
  15. data/lib/capybara/node/document.rb +2 -2
  16. data/lib/capybara/node/document_matchers.rb +8 -8
  17. data/lib/capybara/node/element.rb +30 -40
  18. data/lib/capybara/node/finders.rb +62 -70
  19. data/lib/capybara/node/matchers.rb +48 -71
  20. data/lib/capybara/node/simple.rb +11 -17
  21. data/lib/capybara/queries/ancestor_query.rb +4 -6
  22. data/lib/capybara/queries/base_query.rb +18 -17
  23. data/lib/capybara/queries/current_path_query.rb +8 -24
  24. data/lib/capybara/queries/match_query.rb +3 -7
  25. data/lib/capybara/queries/selector_query.rb +92 -95
  26. data/lib/capybara/queries/sibling_query.rb +4 -4
  27. data/lib/capybara/queries/text_query.rb +37 -34
  28. data/lib/capybara/queries/title_query.rb +8 -11
  29. data/lib/capybara/rack_test/browser.rb +15 -18
  30. data/lib/capybara/rack_test/css_handlers.rb +6 -4
  31. data/lib/capybara/rack_test/driver.rb +6 -10
  32. data/lib/capybara/rack_test/form.rb +50 -40
  33. data/lib/capybara/rack_test/node.rb +70 -56
  34. data/lib/capybara/rails.rb +2 -6
  35. data/lib/capybara/result.rb +22 -22
  36. data/lib/capybara/rspec.rb +5 -10
  37. data/lib/capybara/rspec/compound.rb +5 -10
  38. data/lib/capybara/rspec/features.rb +17 -48
  39. data/lib/capybara/rspec/matcher_proxies.rb +31 -15
  40. data/lib/capybara/rspec/matchers.rb +70 -60
  41. data/lib/capybara/selector.rb +129 -117
  42. data/lib/capybara/selector/css.rb +6 -11
  43. data/lib/capybara/selector/filter.rb +1 -17
  44. data/lib/capybara/selector/filter_set.rb +17 -14
  45. data/lib/capybara/selector/filters/base.rb +7 -6
  46. data/lib/capybara/selector/filters/expression_filter.rb +6 -23
  47. data/lib/capybara/selector/filters/node_filter.rb +2 -12
  48. data/lib/capybara/selector/selector.rb +27 -33
  49. data/lib/capybara/selenium/driver.rb +113 -127
  50. data/lib/capybara/selenium/node.rb +148 -113
  51. data/lib/capybara/server.rb +3 -2
  52. data/lib/capybara/session.rb +137 -223
  53. data/lib/capybara/session/config.rb +47 -67
  54. data/lib/capybara/session/matchers.rb +8 -7
  55. data/lib/capybara/spec/public/test.js +26 -4
  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 +1 -0
  59. data/lib/capybara/spec/session/all_spec.rb +31 -18
  60. data/lib/capybara/spec/session/ancestor_spec.rb +2 -4
  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 +18 -17
  65. data/lib/capybara/spec/session/assert_title.rb +1 -0
  66. data/lib/capybara/spec/session/attach_file_spec.rb +14 -13
  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 +20 -28
  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 +7 -6
  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 +3 -2
  81. data/lib/capybara/spec/session/evaluate_script_spec.rb +4 -3
  82. data/lib/capybara/spec/session/execute_script_spec.rb +4 -3
  83. data/lib/capybara/spec/session/fill_in_spec.rb +6 -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 +8 -14
  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/switch_to_frame_spec.rb +2 -1
  91. data/lib/capybara/spec/session/frame/within_frame_spec.rb +9 -16
  92. data/lib/capybara/spec/session/go_back_spec.rb +1 -0
  93. data/lib/capybara/spec/session/go_forward_spec.rb +1 -0
  94. data/lib/capybara/spec/session/has_all_selectors_spec.rb +15 -15
  95. data/lib/capybara/spec/session/has_button_spec.rb +2 -1
  96. data/lib/capybara/spec/session/has_css_spec.rb +3 -2
  97. data/lib/capybara/spec/session/has_current_path_spec.rb +12 -28
  98. data/lib/capybara/spec/session/has_field_spec.rb +4 -3
  99. data/lib/capybara/spec/session/has_link_spec.rb +1 -0
  100. data/lib/capybara/spec/session/has_none_selectors_spec.rb +17 -17
  101. data/lib/capybara/spec/session/has_select_spec.rb +30 -29
  102. data/lib/capybara/spec/session/has_selector_spec.rb +5 -4
  103. data/lib/capybara/spec/session/has_table_spec.rb +2 -1
  104. data/lib/capybara/spec/session/has_text_spec.rb +6 -5
  105. data/lib/capybara/spec/session/has_title_spec.rb +1 -0
  106. data/lib/capybara/spec/session/has_xpath_spec.rb +1 -0
  107. data/lib/capybara/spec/session/headers.rb +2 -1
  108. data/lib/capybara/spec/session/html_spec.rb +1 -0
  109. data/lib/capybara/spec/session/node_spec.rb +91 -56
  110. data/lib/capybara/spec/session/node_wrapper_spec.rb +36 -0
  111. data/lib/capybara/spec/session/refresh_spec.rb +4 -2
  112. data/lib/capybara/spec/session/reset_session_spec.rb +1 -0
  113. data/lib/capybara/spec/session/response_code.rb +1 -0
  114. data/lib/capybara/spec/session/save_and_open_page_spec.rb +1 -0
  115. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +6 -11
  116. data/lib/capybara/spec/session/save_page_spec.rb +1 -17
  117. data/lib/capybara/spec/session/save_screenshot_spec.rb +1 -1
  118. data/lib/capybara/spec/session/select_spec.rb +20 -20
  119. data/lib/capybara/spec/session/selectors_spec.rb +2 -2
  120. data/lib/capybara/spec/session/sibling_spec.rb +1 -1
  121. data/lib/capybara/spec/session/text_spec.rb +1 -0
  122. data/lib/capybara/spec/session/title_spec.rb +1 -1
  123. data/lib/capybara/spec/session/uncheck_spec.rb +4 -3
  124. data/lib/capybara/spec/session/unselect_spec.rb +6 -5
  125. data/lib/capybara/spec/session/visit_spec.rb +9 -3
  126. data/lib/capybara/spec/session/window/become_closed_spec.rb +2 -1
  127. data/lib/capybara/spec/session/window/current_window_spec.rb +1 -0
  128. data/lib/capybara/spec/session/window/open_new_window_spec.rb +1 -0
  129. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +2 -1
  130. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +2 -1
  131. data/lib/capybara/spec/session/window/window_spec.rb +12 -12
  132. data/lib/capybara/spec/session/window/windows_spec.rb +2 -3
  133. data/lib/capybara/spec/session/window/within_window_spec.rb +13 -68
  134. data/lib/capybara/spec/session/within_spec.rb +1 -0
  135. data/lib/capybara/spec/spec_helper.rb +26 -18
  136. data/lib/capybara/spec/test_app.rb +8 -9
  137. data/lib/capybara/spec/views/form.erb +1 -0
  138. data/lib/capybara/spec/views/with_html.erb +3 -1
  139. data/lib/capybara/spec/views/within_frames.erb +4 -1
  140. data/lib/capybara/version.rb +2 -1
  141. data/lib/capybara/window.rb +6 -10
  142. data/spec/basic_node_spec.rb +1 -0
  143. data/spec/capybara_spec.rb +9 -32
  144. data/spec/dsl_spec.rb +5 -13
  145. data/spec/filter_set_spec.rb +5 -4
  146. data/spec/fixtures/selenium_driver_rspec_failure.rb +2 -1
  147. data/spec/fixtures/selenium_driver_rspec_success.rb +3 -2
  148. data/spec/minitest_spec.rb +4 -3
  149. data/spec/minitest_spec_spec.rb +3 -2
  150. data/spec/per_session_config_spec.rb +9 -8
  151. data/spec/rack_test_spec.rb +21 -20
  152. data/spec/result_spec.rb +17 -16
  153. data/spec/rspec/features_spec.rb +17 -14
  154. data/spec/rspec/scenarios_spec.rb +5 -7
  155. data/spec/rspec/shared_spec_matchers.rb +96 -99
  156. data/spec/rspec/views_spec.rb +2 -1
  157. data/spec/rspec_matchers_spec.rb +19 -2
  158. data/spec/rspec_spec.rb +11 -15
  159. data/spec/selector_spec.rb +5 -6
  160. data/spec/selenium_spec_chrome.rb +7 -4
  161. data/spec/selenium_spec_marionette.rb +26 -12
  162. data/spec/server_spec.rb +33 -33
  163. data/spec/session_spec.rb +2 -1
  164. data/spec/shared_selenium_session.rb +27 -21
  165. data/spec/spec_helper.rb +2 -5
  166. metadata +66 -87
  167. data/lib/capybara/query.rb +0 -7
  168. data/spec/selenium_spec_firefox.rb +0 -68
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
- class Capybara::Selenium::Node < Capybara::Driver::Node
3
2
 
3
+ class Capybara::Selenium::Node < Capybara::Driver::Node
4
4
  def visible_text
5
5
  # Selenium doesn't normalize Unicode whitespace.
6
6
  Capybara::Helpers.normalize_whitespace(native.text)
@@ -36,57 +36,25 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
36
36
  # :none => append the new value to the existing value <br/>
37
37
  # :backspace => send backspace keystrokes to clear the field <br/>
38
38
  # Array => an array of keys to send before the value being set, e.g. [[:command, 'a'], :backspace]
39
- def set(value, options={})
40
- tag_name = self.tag_name
41
- type = self[:type]
42
-
43
- if (Array === value) && !multiple?
44
- raise ArgumentError.new "Value cannot be an Array when 'multiple' attribute is not present. Not a #{value.class}"
45
- end
39
+ def set(value, **options)
40
+ raise ArgumentError, "Value cannot be an Array when 'multiple' attribute is not present. Not a #{value.class}" if value.is_a?(Array) && !multiple?
46
41
 
47
42
  case tag_name
48
43
  when 'input'
49
- case type
44
+ case self[:type]
50
45
  when 'radio'
51
46
  click
52
47
  when 'checkbox'
53
- click if value ^ native.attribute('checked').to_s.eql?("true")
48
+ click if value ^ checked?
54
49
  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
50
+ set_file(value)
61
51
  else
62
52
  set_text(value, options)
63
53
  end
64
54
  when 'textarea'
65
55
  set_text(value, options)
66
56
  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
85
- else
86
- # action api is really slow here just use native.send_keys
87
- native.send_keys(value.to_s)
88
- end
89
- end
57
+ set_content_editable(value) if content_editable?
90
58
  end
91
59
  end
92
60
 
@@ -95,32 +63,56 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
95
63
  end
96
64
 
97
65
  def unselect_option
98
- raise Capybara::UnselectNotAllowed, "Cannot unselect option from single select box." if !select_node.multiple?
66
+ raise Capybara::UnselectNotAllowed, "Cannot unselect option from single select box." unless select_node.multiple?
99
67
  native.click if selected?
100
68
  end
101
69
 
102
- def click
103
- native.click
70
+ def click(keys = [], options = {})
71
+ if keys.empty? && !(options[:x] && options[:y])
72
+ native.click
73
+ else
74
+ scroll_if_needed do
75
+ action_with_modifiers(keys, options) do |a|
76
+ if options[:x] && options[:y]
77
+ a.click
78
+ else
79
+ a.click(native)
80
+ end
81
+ end
82
+ end
83
+ end
104
84
  rescue => e
105
85
  if e.is_a?(::Selenium::WebDriver::Error::ElementClickInterceptedError) ||
106
86
  e.message =~ /Other element would receive the click/
107
87
  begin
108
88
  driver.execute_script("arguments[0].scrollIntoView({behavior: 'instant', block: 'center', inline: 'center'})", self)
109
- rescue
89
+ rescue # Swallow error if scrollIntoView with options isn't supported
110
90
  end
111
91
  end
112
92
  raise e
113
93
  end
114
94
 
115
- def right_click
95
+ def right_click(keys = [], options = {})
116
96
  scroll_if_needed do
117
- driver.browser.action.context_click(native).perform
97
+ action_with_modifiers(keys, options) do |a|
98
+ if options[:x] && options[:y]
99
+ a.context_click
100
+ else
101
+ a.context_click(native)
102
+ end
103
+ end
118
104
  end
119
105
  end
120
106
 
121
- def double_click
107
+ def double_click(keys = [], options = {})
122
108
  scroll_if_needed do
123
- driver.browser.action.double_click(native).perform
109
+ action_with_modifiers(keys, options) do |a|
110
+ if options[:x] && options[:y]
111
+ a.double_click
112
+ else
113
+ a.double_click(native)
114
+ end
115
+ end
124
116
  end
125
117
  end
126
118
 
@@ -129,55 +121,38 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
129
121
  end
130
122
 
131
123
  def hover
132
- scroll_if_needed do
133
- driver.browser.action.move_to(native).perform
134
- end
124
+ scroll_if_needed { driver.browser.action.move_to(native).perform }
135
125
  end
136
126
 
137
127
  def drag_to(element)
138
- scroll_if_needed do
139
- driver.browser.action.drag_and_drop(native, element.native).perform
140
- end
128
+ scroll_if_needed { driver.browser.action.drag_and_drop(native, element.native).perform }
141
129
  end
142
130
 
143
131
  def tag_name
144
132
  native.tag_name.downcase
145
133
  end
146
134
 
147
- def visible?
148
- displayed = native.displayed?
149
- displayed and displayed != "false"
150
- end
151
-
152
- def selected?
153
- selected = native.selected?
154
- selected and selected != "false"
155
- end
135
+ def visible?; boolean_attr(native.displayed?); end
136
+ def readonly?; boolean_attr(self[:readonly]); end
137
+ def multiple?; boolean_attr(self[:multiple]); end
138
+ def selected?; boolean_attr(native.selected?); end
156
139
  alias :checked? :selected?
157
140
 
158
141
  def disabled?
142
+ return true unless native.enabled?
143
+
159
144
  # workaround for selenium-webdriver/geckodriver reporting elements as enabled when they are nested in disabling elements
160
145
  if driver.marionette?
161
- if %w(option optgroup).include? tag_name
162
- !native.enabled? || find_xpath("parent::*[self::optgroup or self::select]")[0].disabled?
146
+ if %w[option optgroup].include? tag_name
147
+ find_xpath("parent::*[self::optgroup or self::select]")[0].disabled?
163
148
  else
164
- !native.enabled? || !find_xpath("parent::fieldset[@disabled] | ancestor::*[not(self::legend) or preceding-sibling::legend][parent::fieldset[@disabled]]").empty?
149
+ !find_xpath("parent::fieldset[@disabled] | ancestor::*[not(self::legend) or preceding-sibling::legend][parent::fieldset[@disabled]]").empty?
165
150
  end
166
151
  else
167
- !native.enabled?
152
+ false
168
153
  end
169
154
  end
170
155
 
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
156
  def content_editable?
182
157
  native.attribute('isContentEditable')
183
158
  end
@@ -195,63 +170,57 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
195
170
  end
196
171
 
197
172
  def path
198
- path = find_xpath('ancestor::*').reverse
199
- path.unshift self
173
+ path = find_xpath(XPath.ancestor_or_self).reverse
200
174
 
201
175
  result = []
202
- while node = path.shift
176
+ while (node = path.shift)
203
177
  parent = path.first
204
-
178
+ selector = node.tag_name
205
179
  if parent
206
180
  siblings = parent.find_xpath(node.tag_name)
207
- if siblings.size == 1
208
- result.unshift node.tag_name
209
- else
210
- index = siblings.index(node)
211
- result.unshift "#{node.tag_name}[#{index+1}]"
212
- end
213
- else
214
- result.unshift node.tag_name
181
+ selector += "[#{siblings.index(node) + 1}]" unless siblings.size == 1
215
182
  end
183
+ result.push selector
216
184
  end
217
185
 
218
- '/' + result.join('/')
186
+ '/' + result.reverse.join('/')
219
187
  end
220
188
 
221
189
  private
190
+
191
+ def boolean_attr(val)
192
+ val and val != "false"
193
+ end
194
+
222
195
  # a reference to the select node if this is an option node
223
196
  def select_node
224
- find_xpath('./ancestor::select[1]').first
197
+ find_xpath(XPath.ancestor(:select)[1]).first
225
198
  end
226
199
 
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?
200
+ def set_text(value, clear: nil, **)
201
+ if value.to_s.empty? && clear.nil?
231
202
  native.clear
203
+ elsif clear == :backspace
204
+ # Clear field by sending the correct number of backspace keys.
205
+ backspaces = [:backspace] * self.value.to_s.length
206
+ native.send_keys(*(backspaces + [value.to_s]))
207
+ elsif clear == :none
208
+ native.send_keys(value.to_s)
209
+ elsif clear.is_a? Array
210
+ native.send_keys(*clear, value.to_s)
232
211
  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
212
+ # Clear field by JavaScript assignment of the value property.
213
+ # Script can change a readonly element which user input cannot, so
214
+ # don't execute if readonly.
215
+ driver.execute_script "arguments[0].value = ''", self
216
+ native.send_keys(value.to_s)
248
217
  end
249
218
  end
250
219
 
251
- def scroll_if_needed(&block)
252
- block.call
220
+ def scroll_if_needed
221
+ yield
253
222
  rescue ::Selenium::WebDriver::Error::MoveTargetOutOfBoundsError
254
- script = <<-JS
223
+ script = <<-'JS'
255
224
  try {
256
225
  arguments[0].scrollIntoView({behavior: 'instant', block: 'center', inline: 'center'});
257
226
  } catch(e) {
@@ -259,6 +228,72 @@ private
259
228
  }
260
229
  JS
261
230
  driver.execute_script(script, self)
262
- block.call
231
+ yield
232
+ end
233
+
234
+ def set_file(value) # rubocop:disable Naming/AccessorMethodName
235
+ path_names = value.to_s.empty? ? [] : value
236
+ if driver.chrome?
237
+ native.send_keys(Array(path_names).join("\n"))
238
+ else
239
+ native.send_keys(*path_names)
240
+ end
241
+ end
242
+
243
+ def set_content_editable(value) # rubocop:disable Naming/AccessorMethodName
244
+ # Ensure we are focused on the element
245
+ click
246
+
247
+ script = <<-JS
248
+ var range = document.createRange();
249
+ var sel = window.getSelection();
250
+ arguments[0].focus();
251
+ range.selectNodeContents(arguments[0]);
252
+ sel.removeAllRanges();
253
+ sel.addRange(range);
254
+ JS
255
+ driver.execute_script script, self
256
+
257
+ # The action api has a speed problem but both chrome and firefox 58 raise errors
258
+ # if we use the faster direct send_keys. For now just send_keys to the element
259
+ # we've already focused.
260
+ # native.send_keys(value.to_s)
261
+ driver.browser.action.send_keys(value.to_s).perform
262
+ end
263
+
264
+ def action_with_modifiers(keys, x: nil, y: nil)
265
+ actions = driver.browser.action
266
+ actions.move_to(native, x, y)
267
+ modifiers_down(actions, keys)
268
+ yield actions
269
+ modifiers_up(actions, keys)
270
+ actions.perform
271
+ ensure
272
+ a = driver.browser.action
273
+ a.release_actions if a.respond_to?(:release_actions)
274
+ end
275
+
276
+ def modifiers_down(actions, keys)
277
+ keys.each do |key|
278
+ key = case key
279
+ when :ctrl then :control
280
+ when :command, :cmd then :meta
281
+ else
282
+ key
283
+ end
284
+ actions.key_down(key)
285
+ end
286
+ end
287
+
288
+ def modifiers_up(actions, keys)
289
+ keys.each do |key|
290
+ key = case key
291
+ when :ctrl then :control
292
+ when :command, :cmd then :meta
293
+ else
294
+ key
295
+ end
296
+ actions.key_up(key)
297
+ end
263
298
  end
264
299
  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,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'capybara/session/matchers'
3
4
  require 'addressable/uri'
4
5
 
5
6
  module Capybara
6
-
7
7
  ##
8
8
  #
9
9
  # The Session class represents a single user's interaction with the system. The Session can use
@@ -38,64 +38,63 @@ module Capybara
38
38
  class Session
39
39
  include Capybara::SessionMatchers
40
40
 
41
- NODE_METHODS = [
42
- :all, :first, :attach_file, :text, :check, :choose,
43
- :click_link_or_button, :click_button, :click_link, :field_labeled,
44
- :fill_in, :find, :find_all, :find_button, :find_by_id, :find_field, :find_link,
45
- :has_content?, :has_text?, :has_css?, :has_no_content?, :has_no_text?,
46
- :has_no_css?, :has_no_xpath?, :resolve, :has_xpath?, :select, :uncheck,
47
- :has_link?, :has_no_link?, :has_button?, :has_no_button?, :has_field?,
48
- :has_no_field?, :has_checked_field?, :has_unchecked_field?,
49
- :has_no_table?, :has_table?, :unselect, :has_select?, :has_no_select?,
50
- :has_selector?, :has_no_selector?, :click_on, :has_no_checked_field?,
51
- :has_no_unchecked_field?, :query, :assert_selector, :assert_no_selector,
52
- :assert_all_of_selectors, :assert_none_of_selectors,
53
- :refute_selector, :assert_text, :assert_no_text
54
- ]
41
+ NODE_METHODS = %i[
42
+ all first attach_file text check choose
43
+ click_link_or_button click_button click_link
44
+ fill_in find find_all find_button find_by_id find_field find_link
45
+ has_content? has_text? has_css? has_no_content? has_no_text?
46
+ has_no_css? has_no_xpath? resolve has_xpath? select uncheck
47
+ has_link? has_no_link? has_button? has_no_button? has_field?
48
+ has_no_field? has_checked_field? has_unchecked_field?
49
+ has_no_table? has_table? unselect has_select? has_no_select?
50
+ has_selector? has_no_selector? click_on has_no_checked_field?
51
+ has_no_unchecked_field? query assert_selector assert_no_selector
52
+ assert_all_of_selectors assert_none_of_selectors
53
+ refute_selector assert_text assert_no_text
54
+ ].freeze
55
55
  # @api private
56
- DOCUMENT_METHODS = [
57
- :title, :assert_title, :assert_no_title, :has_title?, :has_no_title?
58
- ]
59
- SESSION_METHODS = [
60
- :body, :html, :source, :current_url, :current_host, :current_path,
61
- :execute_script, :evaluate_script, :visit, :refresh, :go_back, :go_forward,
62
- :within, :within_element, :within_fieldset, :within_table, :within_frame, :switch_to_frame,
63
- :current_window, :windows, :open_new_window, :switch_to_window, :within_window, :window_opened_by,
64
- :save_page, :save_and_open_page, :save_screenshot,
65
- :save_and_open_screenshot, :reset_session!, :response_headers,
66
- :status_code, :current_scope,
67
- :assert_current_path, :assert_no_current_path, :has_current_path?, :has_no_current_path?
68
- ] + DOCUMENT_METHODS
69
- MODAL_METHODS = [
70
- :accept_alert, :accept_confirm, :dismiss_confirm, :accept_prompt,
71
- :dismiss_prompt
72
- ]
56
+ DOCUMENT_METHODS = %i[
57
+ title assert_title assert_no_title has_title? has_no_title?
58
+ ].freeze
59
+ SESSION_METHODS = %i[
60
+ body html source current_url current_host current_path
61
+ execute_script evaluate_script visit refresh go_back go_forward
62
+ within within_element within_fieldset within_table within_frame switch_to_frame
63
+ current_window windows open_new_window switch_to_window within_window window_opened_by
64
+ save_page save_and_open_page save_screenshot
65
+ save_and_open_screenshot reset_session! response_headers
66
+ status_code current_scope
67
+ assert_current_path assert_no_current_path has_current_path? has_no_current_path?
68
+ ].freeze + DOCUMENT_METHODS
69
+ MODAL_METHODS = %i[
70
+ accept_alert accept_confirm dismiss_confirm accept_prompt dismiss_prompt
71
+ ].freeze
73
72
  DSL_METHODS = NODE_METHODS + SESSION_METHODS + MODAL_METHODS
74
73
 
75
74
  attr_reader :mode, :app, :server
76
75
  attr_accessor :synchronized
77
76
 
78
- def initialize(mode, app=nil)
77
+ def initialize(mode, app = nil)
79
78
  raise TypeError, "The second parameter to Session::new should be a rack app if passed." if app && !app.respond_to?(:call)
80
79
  @@instance_created = true
81
80
  @mode = mode
82
81
  @app = app
83
82
  if block_given?
84
83
  raise "A configuration block is only accepted when Capybara.threadsafe == true" unless Capybara.threadsafe
85
- yield config if block_given?
84
+ yield config
86
85
  end
87
- if config.run_server and @app and driver.needs_server?
88
- @server = Capybara::Server.new(@app, config.server_port, config.server_host, config.server_errors).boot
86
+ @server = if config.run_server and @app and driver.needs_server?
87
+ Capybara::Server.new(@app, config.server_port, config.server_host, config.server_errors).boot
89
88
  else
90
- @server = nil
89
+ nil
91
90
  end
92
91
  @touched = false
93
92
  end
94
93
 
95
94
  def driver
96
95
  @driver ||= begin
97
- unless Capybara.drivers.has_key?(mode)
98
- other_drivers = Capybara.drivers.keys.map { |key| key.inspect }
96
+ unless Capybara.drivers.key?(mode)
97
+ other_drivers = Capybara.drivers.keys.map(&:inspect)
99
98
  raise Capybara::DriverNotFoundError, "no driver called #{mode.inspect} was found, available drivers: #{other_drivers.join(', ')}"
100
99
  end
101
100
  driver = Capybara.drivers[mode].call(app)
@@ -145,7 +144,7 @@ module Capybara
145
144
  raise CapybaraError, "Your application server raised an error - It has been raised in your test code because Capybara.raise_server_errors == true"
146
145
  end
147
146
  rescue CapybaraError
148
- #needed to get the cause set correctly in JRuby -- otherwise we could just do raise @server.error
147
+ # needed to get the cause set correctly in JRuby -- otherwise we could just do raise @server.error
149
148
  raise @server.error, @server.error.message, @server.error.backtrace
150
149
  ensure
151
150
  @server.reset_error!
@@ -196,8 +195,9 @@ module Capybara
196
195
 
197
196
  # Addressable doesn't support opaque URIs - we want nil here
198
197
  return nil if uri.scheme == "about"
198
+
199
199
  path = uri.path
200
- path if path and not path.empty?
200
+ path if path && !path.empty?
201
201
  end
202
202
 
203
203
  ##
@@ -259,15 +259,15 @@ module Capybara
259
259
  if visit_uri.relative?
260
260
  uri_base.port ||= @server.port if @server && config.always_include_port
261
261
 
262
- visit_uri_parts = visit_uri.to_hash.delete_if { |k,v| v.nil? }
262
+ visit_uri_parts = visit_uri.to_hash.delete_if { |_k, v| v.nil? }
263
263
 
264
264
  # Useful to people deploying to a subdirectory
265
265
  # and/or single page apps where only the url fragment changes
266
266
  visit_uri_parts[:path] = uri_base.path + visit_uri.path
267
267
 
268
268
  visit_uri = uri_base.merge(visit_uri_parts)
269
- else
270
- visit_uri.port ||= @server.port if @server && config.always_include_port
269
+ elsif @server && config.always_include_port
270
+ visit_uri.port ||= @server.port
271
271
  end
272
272
  end
273
273
 
@@ -335,7 +335,7 @@ module Capybara
335
335
  # @raise [Capybara::ElementNotFound] If the scope can't be found before time expires
336
336
  #
337
337
  def within(*args)
338
- new_scope = if args.first.is_a?(Capybara::Node::Base) then args.first else find(*args) end
338
+ new_scope = args.first.respond_to?(:to_capybara_node) ? args.first.to_capybara_node : find(*args)
339
339
  begin
340
340
  scopes.push(new_scope)
341
341
  yield
@@ -352,9 +352,7 @@ module Capybara
352
352
  # @param [String] locator Id or legend of the fieldset
353
353
  #
354
354
  def within_fieldset(locator)
355
- within :fieldset, locator do
356
- yield
357
- end
355
+ within(:fieldset, locator) { yield }
358
356
  end
359
357
 
360
358
  ##
@@ -364,9 +362,7 @@ module Capybara
364
362
  # @param [String] locator Id or caption of the table
365
363
  #
366
364
  def within_table(locator)
367
- within :table, locator do
368
- yield
369
- end
365
+ within(:table, locator) { yield }
370
366
  end
371
367
 
372
368
  ##
@@ -390,15 +386,19 @@ module Capybara
390
386
  driver.switch_to_frame(frame)
391
387
  scopes.push(:frame)
392
388
  when :parent
393
- raise Capybara::ScopeError, "`switch_to_frame(:parent)` cannot be called from inside a descendant frame's "\
394
- "`within` block." if scopes.last() != :frame
389
+ if scopes.last != :frame
390
+ raise Capybara::ScopeError, "`switch_to_frame(:parent)` cannot be called from inside a descendant frame's "\
391
+ "`within` block."
392
+ end
395
393
  scopes.pop
396
394
  driver.switch_to_frame(:parent)
397
395
  when :top
398
396
  idx = scopes.index(:frame)
399
397
  if idx
400
- raise Capybara::ScopeError, "`switch_to_frame(:top)` cannot be called from inside a descendant frame's "\
401
- "`within` block." if scopes.slice(idx..-1).any? {|scope| ![:frame, nil].include?(scope)}
398
+ if scopes.slice(idx..-1).any? { |scope| ![:frame, nil].include?(scope) }
399
+ raise Capybara::ScopeError, "`switch_to_frame(:top)` cannot be called from inside a descendant frame's "\
400
+ "`within` block."
401
+ end
402
402
  scopes.slice!(idx..-1)
403
403
  driver.switch_to_frame(:top)
404
404
  end
@@ -418,29 +418,11 @@ module Capybara
418
418
  # @overload within_frame(index)
419
419
  # @param [Integer] index index of a frame (0 based)
420
420
  def within_frame(*args)
421
- frame = _find_frame(*args)
422
-
421
+ switch_to_frame(_find_frame(*args))
423
422
  begin
424
- switch_to_frame(frame)
425
- begin
426
- yield
427
- ensure
428
- switch_to_frame(:parent)
429
- end
430
- rescue Capybara::NotSupportedByDriverError
431
- # Support older driver frame API for now
432
- if driver.respond_to?(:within_frame)
433
- begin
434
- scopes.push(:frame)
435
- driver.within_frame(frame) do
436
- yield
437
- end
438
- ensure
439
- scopes.pop
440
- end
441
- else
442
- raise
443
- end
423
+ yield
424
+ ensure
425
+ switch_to_frame(:parent)
444
426
  end
445
427
  end
446
428
 
@@ -493,15 +475,11 @@ module Capybara
493
475
  # `within_frame` methods
494
476
  # @raise [ArgumentError] if both or neither arguments were provided
495
477
  #
496
- def switch_to_window(window = nil, options= {}, &window_locator)
497
- options, window = window, nil if window.is_a? Hash
498
-
478
+ def switch_to_window(window = nil, **options, &window_locator)
499
479
  block_given = block_given?
500
- if window && block_given
501
- raise ArgumentError, "`switch_to_window` can take either a block or a window, not both"
502
- elsif !window && !block_given
503
- raise ArgumentError, "`switch_to_window`: either window or block should be provided"
504
- elsif !scopes.last.nil?
480
+ raise ArgumentError, "`switch_to_window` can take either a block or a window, not both" if window && block_given
481
+ raise ArgumentError, "`switch_to_window`: either window or block should be provided" if !window && !block_given
482
+ unless scopes.last.nil?
505
483
  raise Capybara::ScopeError, "`switch_to_window` is not supposed to be invoked from "\
506
484
  "`within` or `within_frame` blocks."
507
485
  end
@@ -526,51 +504,30 @@ module Capybara
526
504
  # @example
527
505
  # within_window(->{ page.title == 'Page title' }) { click_button 'Submit' }
528
506
  # @raise [Capybara::WindowError] if no window matching lambda was found
529
- # @overload within_window(string) { do_something }
530
- # @deprecated Pass window or lambda instead
531
- # @param [String] handle, name, url or title of the window
532
507
  #
533
508
  # @raise [Capybara::ScopeError] if this method is invoked inside `within_frame` method
534
509
  # @return value returned by the block
535
510
  #
536
- def within_window(window_or_handle)
537
- if window_or_handle.instance_of?(Capybara::Window)
538
- original = current_window
539
- scopes << nil
540
- begin
541
- _switch_to_window(window_or_handle) unless original == window_or_handle
542
- begin
543
- yield
544
- ensure
545
- _switch_to_window(original) unless original == window_or_handle
546
- end
547
- ensure
548
- scopes.pop
549
- end
550
- elsif window_or_handle.is_a?(Proc)
551
- original = current_window
552
- scopes << nil
553
- begin
554
- _switch_to_window { window_or_handle.call }
555
- begin
556
- yield
557
- ensure
558
- _switch_to_window(original)
559
- end
560
- ensure
561
- scopes.pop
511
+ def within_window(window_or_proc)
512
+ original = current_window
513
+ scopes << nil
514
+ begin
515
+ case window_or_proc
516
+ when Capybara::Window
517
+ _switch_to_window(window_or_proc) unless original == window_or_proc
518
+ when Proc
519
+ _switch_to_window { window_or_proc.call }
520
+ else
521
+ raise ArgumentError("`#within_window` requires a `Capybara::Window` instance or a lambda")
562
522
  end
563
- else
564
- offending_line = caller.first
565
- file_line = offending_line.match(/^(.+?):(\d+)/)[0]
566
- warn "DEPRECATION WARNING: Passing string argument to #within_window is deprecated. "\
567
- "Pass window object or lambda. (called from #{file_line})"
523
+
568
524
  begin
569
- scopes << nil
570
- driver.within_window(window_or_handle) { yield }
525
+ yield
571
526
  ensure
572
- scopes.pop
527
+ _switch_to_window(original) unless original == window_or_proc
573
528
  end
529
+ ensure
530
+ scopes.pop
574
531
  end
575
532
  end
576
533
 
@@ -580,15 +537,16 @@ module Capybara
580
537
  # It's better to use this method than `windows.last`
581
538
  # {https://dvcs.w3.org/hg/webdriver/raw-file/default/webdriver-spec.html#h_note_10 as order of windows isn't defined in some drivers}
582
539
  #
583
- # @param options [Hash]
584
- # @option options [Numeric] :wait (Capybara.default_max_wait_time) maximum wait time
585
- # @return [Capybara::Window] the window that has been opened within a block
586
- # @raise [Capybara::WindowError] if block passed to window hasn't opened window
587
- # or opened more than one window
540
+ # @overload window_opened_by(**options, &block)
541
+ # @param options [Hash]
542
+ # @option options [Numeric] :wait (Capybara.default_max_wait_time) maximum wait time
543
+ # @return [Capybara::Window] the window that has been opened within a block
544
+ # @raise [Capybara::WindowError] if block passed to window hasn't opened window
545
+ # or opened more than one window
588
546
  #
589
- def window_opened_by(options = {}, &block)
547
+ def window_opened_by(**options)
590
548
  old_handles = driver.window_handles
591
- block.call
549
+ yield
592
550
 
593
551
  wait_time = Capybara::Queries::BaseQuery.wait(options, config.default_max_wait_time)
594
552
  document.synchronize(wait_time, errors: [Capybara::WindowError]) do
@@ -612,12 +570,7 @@ module Capybara
612
570
  #
613
571
  def execute_script(script, *args)
614
572
  @touched = true
615
- if args.empty?
616
- driver.execute_script(script)
617
- else
618
- raise Capybara::NotSupportedByDriverError, "The current driver does not support execute_script arguments" if driver.method(:execute_script).arity == 1
619
- driver.execute_script(script, *args.map { |arg| arg.is_a?(Capybara::Node::Element) ? arg.base : arg} )
620
- end
573
+ driver.execute_script(script, *args.map { |arg| arg.is_a?(Capybara::Node::Element) ? arg.base : arg })
621
574
  end
622
575
 
623
576
  ##
@@ -631,12 +584,7 @@ module Capybara
631
584
  #
632
585
  def evaluate_script(script, *args)
633
586
  @touched = true
634
- result = if args.empty?
635
- driver.evaluate_script(script)
636
- else
637
- raise Capybara::NotSupportedByDriverError, "The current driver does not support evaluate_script arguments" if driver.method(:evaluate_script).arity == 1
638
- driver.evaluate_script(script, *args.map { |arg| arg.is_a?(Capybara::Node::Element) ? arg.base : arg} )
639
- end
587
+ result = driver.evaluate_script(script, *args.map { |arg| arg.is_a?(Capybara::Node::Element) ? arg.base : arg })
640
588
  element_script_result(result)
641
589
  end
642
590
 
@@ -649,12 +597,7 @@ module Capybara
649
597
  #
650
598
  def evaluate_async_script(script, *args)
651
599
  @touched = true
652
- result = if args.empty?
653
- driver.evaluate_async_script(script)
654
- else
655
- raise Capybara::NotSupportedByDriverError, "The current driver does not support evaluate_async_script arguments" if driver.method(:evaluate_async_script).arity == 1
656
- driver.evaluate_async_script(script, *args.map { |arg| arg.is_a?(Capybara::Node::Element) ? arg.base : arg} )
657
- end
600
+ result = driver.evaluate_async_script(script, *args.map { |arg| arg.is_a?(Capybara::Node::Element) ? arg.base : arg })
658
601
  element_script_result(result)
659
602
  end
660
603
 
@@ -678,9 +621,8 @@ module Capybara
678
621
  # @return [String] the message shown in the modal
679
622
  # @raise [Capybara::ModalNotFound] if modal dialog hasn't been found
680
623
  #
681
- #
682
- def accept_alert(text_or_options=nil, options={}, &blk)
683
- accept_modal(:alert, text_or_options, options, &blk)
624
+ def accept_alert(text = nil, **options, &blk)
625
+ accept_modal(:alert, text, options, &blk)
684
626
  end
685
627
 
686
628
  ##
@@ -689,8 +631,8 @@ module Capybara
689
631
  #
690
632
  # @macro modal_params
691
633
  #
692
- def accept_confirm(text_or_options=nil, options={}, &blk)
693
- accept_modal(:confirm, text_or_options, options, &blk)
634
+ def accept_confirm(text = nil, **options, &blk)
635
+ accept_modal(:confirm, text, options, &blk)
694
636
  end
695
637
 
696
638
  ##
@@ -699,8 +641,8 @@ module Capybara
699
641
  #
700
642
  # @macro modal_params
701
643
  #
702
- def dismiss_confirm(text_or_options=nil, options={}, &blk)
703
- dismiss_modal(:confirm, text_or_options, options, &blk)
644
+ def dismiss_confirm(text = nil, **options, &blk)
645
+ dismiss_modal(:confirm, text, options, &blk)
704
646
  end
705
647
 
706
648
  ##
@@ -710,8 +652,8 @@ module Capybara
710
652
  # @macro modal_params
711
653
  # @option options [String] :with Response to provide to the prompt
712
654
  #
713
- def accept_prompt(text_or_options=nil, options={}, &blk)
714
- accept_modal(:prompt, text_or_options, options, &blk)
655
+ def accept_prompt(text = nil, **options, &blk)
656
+ accept_modal(:prompt, text, options, &blk)
715
657
  end
716
658
 
717
659
  ##
@@ -720,8 +662,8 @@ module Capybara
720
662
  #
721
663
  # @macro modal_params
722
664
  #
723
- def dismiss_prompt(text_or_options=nil, options={}, &blk)
724
- dismiss_modal(:prompt, text_or_options, options, &blk)
665
+ def dismiss_prompt(text = nil, **options, &blk)
666
+ dismiss_modal(:prompt, text, options, &blk)
725
667
  end
726
668
 
727
669
  ##
@@ -731,17 +673,15 @@ module Capybara
731
673
  #
732
674
  # If invoked without arguments it will save file to `Capybara.save_path`
733
675
  # and file will be given randomly generated filename. If invoked with a relative path
734
- # the path will be relative to `Capybara.save_path`, which is different from
735
- # the previous behavior with `Capybara.save_and_open_page_path` where the relative path was
736
- # relative to Dir.pwd
676
+ # the path will be relative to `Capybara.save_path`
737
677
  #
738
678
  # @param [String] path the path to where it should be saved
739
679
  # @return [String] the path to which the file was saved
740
680
  #
741
681
  def save_page(path = nil)
742
- path = prepare_path(path, 'html')
743
- File.write(path, Capybara::Helpers.inject_asset_host(body, config.asset_host), mode: 'wb')
744
- path
682
+ prepare_path(path, 'html').tap do |p|
683
+ File.write(p, Capybara::Helpers.inject_asset_host(body, host: config.asset_host), mode: 'wb')
684
+ end
745
685
  end
746
686
 
747
687
  ##
@@ -750,15 +690,12 @@ module Capybara
750
690
  #
751
691
  # If invoked without arguments it will save file to `Capybara.save_path`
752
692
  # and file will be given randomly generated filename. If invoked with a relative path
753
- # the path will be relative to `Capybara.save_path`, which is different from
754
- # the previous behavior with `Capybara.save_and_open_page_path` where the relative path was
755
- # relative to Dir.pwd
693
+ # the path will be relative to `Capybara.save_path`
756
694
  #
757
695
  # @param [String] path the path to where it should be saved
758
696
  #
759
697
  def save_and_open_page(path = nil)
760
- path = save_page(path)
761
- open_file(path)
698
+ save_page(path).tap { |p| open_file(p) }
762
699
  end
763
700
 
764
701
  ##
@@ -767,17 +704,13 @@ module Capybara
767
704
  #
768
705
  # If invoked without arguments it will save file to `Capybara.save_path`
769
706
  # and file will be given randomly generated filename. If invoked with a relative path
770
- # the path will be relative to `Capybara.save_path`, which is different from
771
- # the previous behavior with `Capybara.save_and_open_page_path` where the relative path was
772
- # relative to Dir.pwd
707
+ # the path will be relative to `Capybara.save_path`
773
708
  #
774
709
  # @param [String] path the path to where it should be saved
775
710
  # @param [Hash] options a customizable set of options
776
711
  # @return [String] the path to which the file was saved
777
- def save_screenshot(path = nil, options = {})
778
- path = prepare_path(path, 'png')
779
- driver.save_screenshot(path, options)
780
- path
712
+ def save_screenshot(path = nil, **options)
713
+ prepare_path(path, 'png').tap { |p| driver.save_screenshot(p, options) }
781
714
  end
782
715
 
783
716
  ##
@@ -786,16 +719,15 @@ module Capybara
786
719
  #
787
720
  # If invoked without arguments it will save file to `Capybara.save_path`
788
721
  # and file will be given randomly generated filename. If invoked with a relative path
789
- # the path will be relative to `Capybara.save_path`, which is different from
790
- # the previous behavior with `Capybara.save_and_open_page_path` where the relative path was
791
- # relative to Dir.pwd
722
+ # the path will be relative to `Capybara.save_path`
792
723
  #
793
724
  # @param [String] path the path to where it should be saved
794
725
  # @param [Hash] options a customizable set of options
795
726
  #
796
- def save_and_open_screenshot(path = nil, options = {})
797
- path = save_screenshot(path, options)
798
- open_file(path)
727
+ def save_and_open_screenshot(path = nil, **options)
728
+ # rubocop:disable Lint/Debugger
729
+ save_screenshot(path, options).tap { |p| open_file(p) }
730
+ # rubocop:enable Lint/Debugger
799
731
  end
800
732
 
801
733
  def document
@@ -821,8 +753,7 @@ module Capybara
821
753
 
822
754
  def current_scope
823
755
  scope = scopes.last
824
- scope = document if [nil, :frame].include? scope
825
- scope
756
+ [nil, :frame].include?(scope) ? document : scope
826
757
  end
827
758
 
828
759
  ##
@@ -864,6 +795,7 @@ module Capybara
864
795
  Capybara::ReadOnlySessionConfig.new(Capybara.session_options)
865
796
  end
866
797
  end
798
+
867
799
  private
868
800
 
869
801
  @@instance_created = false
@@ -876,32 +808,21 @@ module Capybara
876
808
  driver.dismiss_modal(type, modal_options(text_or_options, options), &blk)
877
809
  end
878
810
 
879
- def modal_options(text_or_options, options)
880
- text_or_options, options = nil, text_or_options if text_or_options.is_a?(Hash)
881
- options[:text] ||= text_or_options unless text_or_options.nil?
811
+ def modal_options(text = nil, **options)
812
+ options[:text] ||= text unless text.nil?
882
813
  options[:wait] ||= config.default_max_wait_time
883
814
  options
884
815
  end
885
816
 
886
-
887
817
  def open_file(path)
888
- begin
889
- require "launchy"
890
- Launchy.open(path)
891
- rescue LoadError
892
- warn "File saved to #{path}."
893
- warn "Please install the launchy gem to open the file automatically."
894
- end
818
+ require "launchy"
819
+ Launchy.open(path)
820
+ rescue LoadError
821
+ warn "File saved to #{path}.\nPlease install the launchy gem to open the file automatically."
895
822
  end
896
823
 
897
824
  def prepare_path(path, extension)
898
- if config.save_path || config.save_and_open_page_path.nil?
899
- path = File.expand_path(path || default_fn(extension), config.save_path)
900
- else
901
- path = File.expand_path(default_fn(extension), config.save_and_open_page_path) if path.nil?
902
- end
903
- FileUtils.mkdir_p(File.dirname(path))
904
- path
825
+ File.expand_path(path || default_fn(extension), config.save_path).tap { |p| FileUtils.mkdir_p(File.dirname(p)) }
905
826
  end
906
827
 
907
828
  def default_fn(extension)
@@ -927,26 +848,22 @@ module Capybara
927
848
  end
928
849
 
929
850
  def _find_frame(*args)
930
- within(document) do # Previous 2.x versions ignored current scope when finding frames - consider changing in 3.0
931
- case args[0]
932
- when Capybara::Node::Element
933
- args[0]
934
- when String, Hash
935
- find(:frame, *args)
936
- when Symbol
937
- find(*args)
938
- when Integer
939
- idx = args[0]
940
- all(:frame, minimum: idx+1)[idx]
941
- else
942
- raise TypeError
943
- end
851
+ case args[0]
852
+ when Capybara::Node::Element
853
+ args[0]
854
+ when String, Hash
855
+ find(:frame, *args)
856
+ when Symbol
857
+ find(*args)
858
+ when Integer
859
+ idx = args[0]
860
+ all(:frame, minimum: idx + 1)[idx]
861
+ else
862
+ raise TypeError
944
863
  end
945
864
  end
946
865
 
947
- def _switch_to_window(window = nil, options= {})
948
- options, window = window, nil if window.is_a? Hash
949
-
866
+ def _switch_to_window(window = nil, **options)
950
867
  raise Capybara::ScopeError, "Window cannot be switched inside a `within_frame` block" if scopes.include?(:frame)
951
868
  raise Capybara::ScopeError, "Window cannot be switch inside a `within` block" unless scopes.last.nil?
952
869
 
@@ -960,9 +877,7 @@ module Capybara
960
877
  begin
961
878
  driver.window_handles.each do |handle|
962
879
  driver.switch_to_window handle
963
- if yield
964
- return Window.new(self, handle)
965
- end
880
+ return Window.new(self, handle) if yield
966
881
  end
967
882
  rescue => e
968
883
  driver.switch_to_window(original_window_handle)
@@ -974,6 +889,5 @@ module Capybara
974
889
  end
975
890
  end
976
891
  end
977
-
978
892
  end
979
893
  end