capybara 3.3.1 → 3.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (159) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +16 -0
  3. data/README.md +5 -7
  4. data/lib/capybara.rb +7 -6
  5. data/lib/capybara/config.rb +1 -1
  6. data/lib/capybara/dsl.rb +2 -2
  7. data/lib/capybara/helpers.rb +3 -3
  8. data/lib/capybara/minitest/spec.rb +3 -3
  9. data/lib/capybara/node/actions.rb +18 -18
  10. data/lib/capybara/node/base.rb +1 -1
  11. data/lib/capybara/node/element.rb +2 -2
  12. data/lib/capybara/node/finders.rb +6 -6
  13. data/lib/capybara/node/matchers.rb +5 -5
  14. data/lib/capybara/node/simple.rb +2 -2
  15. data/lib/capybara/queries/ancestor_query.rb +1 -1
  16. data/lib/capybara/queries/base_query.rb +12 -11
  17. data/lib/capybara/queries/current_path_query.rb +1 -1
  18. data/lib/capybara/queries/selector_query.rb +39 -15
  19. data/lib/capybara/queries/sibling_query.rb +1 -1
  20. data/lib/capybara/queries/text_query.rb +1 -1
  21. data/lib/capybara/rack_test/browser.rb +7 -7
  22. data/lib/capybara/rack_test/driver.rb +1 -1
  23. data/lib/capybara/rack_test/form.rb +7 -7
  24. data/lib/capybara/rack_test/node.rb +16 -16
  25. data/lib/capybara/rails.rb +1 -1
  26. data/lib/capybara/result.rb +8 -4
  27. data/lib/capybara/rspec/features.rb +4 -4
  28. data/lib/capybara/rspec/matchers.rb +6 -6
  29. data/lib/capybara/selector.rb +106 -90
  30. data/lib/capybara/selector/css.rb +4 -4
  31. data/lib/capybara/selector/filter_set.rb +52 -8
  32. data/lib/capybara/selector/selector.rb +39 -15
  33. data/lib/capybara/selenium/driver.rb +10 -10
  34. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +8 -0
  35. data/lib/capybara/selenium/node.rb +9 -10
  36. data/lib/capybara/selenium/nodes/chrome_node.rb +18 -0
  37. data/lib/capybara/selenium/nodes/marionette_node.rb +32 -7
  38. data/lib/capybara/server.rb +3 -3
  39. data/lib/capybara/server/animation_disabler.rb +1 -1
  40. data/lib/capybara/server/middleware.rb +1 -1
  41. data/lib/capybara/session.rb +23 -19
  42. data/lib/capybara/session/config.rb +18 -3
  43. data/lib/capybara/spec/public/test.js +1 -1
  44. data/lib/capybara/spec/session/accept_alert_spec.rb +10 -10
  45. data/lib/capybara/spec/session/accept_confirm_spec.rb +3 -3
  46. data/lib/capybara/spec/session/accept_prompt_spec.rb +9 -10
  47. data/lib/capybara/spec/session/all_spec.rb +33 -32
  48. data/lib/capybara/spec/session/ancestor_spec.rb +19 -19
  49. data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +38 -38
  50. data/lib/capybara/spec/session/assert_current_path_spec.rb +16 -16
  51. data/lib/capybara/spec/session/assert_selector_spec.rb +53 -53
  52. data/lib/capybara/spec/session/assert_style_spec.rb +3 -3
  53. data/lib/capybara/spec/session/assert_text_spec.rb +31 -30
  54. data/lib/capybara/spec/session/assert_title_spec.rb +12 -12
  55. data/lib/capybara/spec/session/attach_file_spec.rb +51 -52
  56. data/lib/capybara/spec/session/body_spec.rb +6 -6
  57. data/lib/capybara/spec/session/check_spec.rb +52 -47
  58. data/lib/capybara/spec/session/choose_spec.rb +32 -32
  59. data/lib/capybara/spec/session/click_button_spec.rb +103 -103
  60. data/lib/capybara/spec/session/click_link_or_button_spec.rb +24 -23
  61. data/lib/capybara/spec/session/click_link_spec.rb +49 -48
  62. data/lib/capybara/spec/session/current_scope_spec.rb +7 -7
  63. data/lib/capybara/spec/session/current_url_spec.rb +26 -27
  64. data/lib/capybara/spec/session/dismiss_confirm_spec.rb +3 -3
  65. data/lib/capybara/spec/session/dismiss_prompt_spec.rb +2 -2
  66. data/lib/capybara/spec/session/element/assert_match_selector_spec.rb +8 -8
  67. data/lib/capybara/spec/session/element/match_css_spec.rb +10 -10
  68. data/lib/capybara/spec/session/element/match_xpath_spec.rb +6 -6
  69. data/lib/capybara/spec/session/element/matches_selector_spec.rb +51 -51
  70. data/lib/capybara/spec/session/evaluate_async_script_spec.rb +7 -7
  71. data/lib/capybara/spec/session/evaluate_script_spec.rb +15 -8
  72. data/lib/capybara/spec/session/execute_script_spec.rb +7 -7
  73. data/lib/capybara/spec/session/fill_in_spec.rb +43 -42
  74. data/lib/capybara/spec/session/find_button_spec.rb +23 -23
  75. data/lib/capybara/spec/session/find_by_id_spec.rb +7 -7
  76. data/lib/capybara/spec/session/find_field_spec.rb +32 -30
  77. data/lib/capybara/spec/session/find_link_spec.rb +21 -21
  78. data/lib/capybara/spec/session/find_spec.rb +153 -135
  79. data/lib/capybara/spec/session/first_spec.rb +41 -41
  80. data/lib/capybara/spec/session/frame/frame_title_spec.rb +5 -5
  81. data/lib/capybara/spec/session/frame/frame_url_spec.rb +5 -5
  82. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +17 -17
  83. data/lib/capybara/spec/session/frame/within_frame_spec.rb +31 -17
  84. data/lib/capybara/spec/session/go_back_spec.rb +1 -1
  85. data/lib/capybara/spec/session/go_forward_spec.rb +1 -1
  86. data/lib/capybara/spec/session/has_all_selectors_spec.rb +17 -17
  87. data/lib/capybara/spec/session/has_button_spec.rb +13 -13
  88. data/lib/capybara/spec/session/has_css_spec.rb +133 -131
  89. data/lib/capybara/spec/session/has_current_path_spec.rb +29 -29
  90. data/lib/capybara/spec/session/has_field_spec.rb +58 -58
  91. data/lib/capybara/spec/session/has_link_spec.rb +4 -4
  92. data/lib/capybara/spec/session/has_none_selectors_spec.rb +24 -24
  93. data/lib/capybara/spec/session/has_select_spec.rb +43 -43
  94. data/lib/capybara/spec/session/has_selector_spec.rb +71 -71
  95. data/lib/capybara/spec/session/has_style_spec.rb +3 -3
  96. data/lib/capybara/spec/session/has_table_spec.rb +4 -4
  97. data/lib/capybara/spec/session/has_text_spec.rb +53 -52
  98. data/lib/capybara/spec/session/has_title_spec.rb +14 -14
  99. data/lib/capybara/spec/session/has_xpath_spec.rb +39 -38
  100. data/lib/capybara/spec/session/headers_spec.rb +1 -1
  101. data/lib/capybara/spec/session/html_spec.rb +6 -6
  102. data/lib/capybara/spec/session/node_spec.rb +129 -123
  103. data/lib/capybara/spec/session/node_wrapper_spec.rb +10 -7
  104. data/lib/capybara/spec/session/refresh_spec.rb +4 -7
  105. data/lib/capybara/spec/session/reset_session_spec.rb +28 -28
  106. data/lib/capybara/spec/session/response_code_spec.rb +1 -1
  107. data/lib/capybara/spec/session/save_and_open_page_spec.rb +2 -2
  108. data/lib/capybara/spec/session/save_page_spec.rb +37 -37
  109. data/lib/capybara/spec/session/save_screenshot_spec.rb +6 -6
  110. data/lib/capybara/spec/session/screenshot_spec.rb +2 -2
  111. data/lib/capybara/spec/session/select_spec.rb +81 -81
  112. data/lib/capybara/spec/session/selectors_spec.rb +17 -17
  113. data/lib/capybara/spec/session/sibling_spec.rb +9 -9
  114. data/lib/capybara/spec/session/text_spec.rb +23 -23
  115. data/lib/capybara/spec/session/title_spec.rb +5 -5
  116. data/lib/capybara/spec/session/uncheck_spec.rb +24 -20
  117. data/lib/capybara/spec/session/unselect_spec.rb +37 -37
  118. data/lib/capybara/spec/session/visit_spec.rb +48 -49
  119. data/lib/capybara/spec/session/window/current_window_spec.rb +1 -1
  120. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +16 -16
  121. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +2 -2
  122. data/lib/capybara/spec/session/window/window_spec.rb +4 -4
  123. data/lib/capybara/spec/session/window/within_window_spec.rb +14 -14
  124. data/lib/capybara/spec/session/within_spec.rb +41 -41
  125. data/lib/capybara/spec/spec_helper.rb +11 -9
  126. data/lib/capybara/spec/test_app.rb +18 -17
  127. data/lib/capybara/spec/views/form.erb +29 -31
  128. data/lib/capybara/spec/views/with_html.erb +2 -2
  129. data/lib/capybara/version.rb +1 -1
  130. data/spec/basic_node_spec.rb +23 -23
  131. data/spec/capybara_spec.rb +20 -20
  132. data/spec/css_splitter_spec.rb +7 -7
  133. data/spec/dsl_spec.rb +37 -32
  134. data/spec/filter_set_spec.rb +4 -4
  135. data/spec/fixtures/selenium_driver_rspec_failure.rb +1 -1
  136. data/spec/fixtures/selenium_driver_rspec_success.rb +1 -1
  137. data/spec/minitest_spec.rb +4 -4
  138. data/spec/minitest_spec_spec.rb +23 -23
  139. data/spec/per_session_config_spec.rb +5 -5
  140. data/spec/rack_test_spec.rb +44 -44
  141. data/spec/result_spec.rb +14 -14
  142. data/spec/rspec/features_spec.rb +13 -13
  143. data/spec/rspec/scenarios_spec.rb +4 -4
  144. data/spec/rspec/shared_spec_matchers.rb +282 -281
  145. data/spec/rspec/views_spec.rb +3 -3
  146. data/spec/rspec_matchers_spec.rb +10 -10
  147. data/spec/rspec_spec.rb +29 -29
  148. data/spec/selector_spec.rb +64 -64
  149. data/spec/selenium_spec_chrome.rb +14 -22
  150. data/spec/selenium_spec_chrome_remote.rb +28 -8
  151. data/spec/selenium_spec_edge.rb +9 -4
  152. data/spec/selenium_spec_firefox_remote.rb +87 -0
  153. data/spec/selenium_spec_ie.rb +9 -4
  154. data/spec/selenium_spec_marionette.rb +42 -18
  155. data/spec/server_spec.rb +29 -27
  156. data/spec/session_spec.rb +17 -17
  157. data/spec/shared_selenium_session.rb +70 -52
  158. data/spec/spec_helper.rb +1 -1
  159. metadata +4 -2
@@ -19,7 +19,7 @@ module Capybara
19
19
  # * Locator: The id of the element to match
20
20
  #
21
21
  # * **:field** - Select field elements (input [not of type submit, image, or hidden], textarea, select)
22
- # * Locator: Matches against the id, name, or placeholder
22
+ # * Locator: Matches against the id, Capybara.test_id attribute, name, or placeholder
23
23
  # * Filters:
24
24
  # * :id (String) — Matches the id attribute
25
25
  # * :name (String) — Matches the name attribute
@@ -50,7 +50,7 @@ module Capybara
50
50
  # * :href (String, Regexp, nil) — Matches the normalized href of the link, if nil will find <a> elements with no href attribute
51
51
  #
52
52
  # * **:button** - Find buttons ( input [of type submit, reset, image, button] or button elements )
53
- # * Locator: Matches the id, value, or title attributes, string content of a button, or the alt attribute of an image type button or of a descendant image of a button
53
+ # * Locator: Matches the id, Capybara.test_id attribute, value, or title attributes, string content of a button, or the alt attribute of an image type button or of a descendant image of a button
54
54
  # * Filters:
55
55
  # * :id (String) — Matches the id attribute
56
56
  # * :title (String) — Matches the title attribute
@@ -62,7 +62,7 @@ module Capybara
62
62
  # * Locator: See :link and :button selectors
63
63
  #
64
64
  # * **:fillable_field** - Find text fillable fields ( textarea, input [not of type submit, image, radio, checkbox, hidden, file] )
65
- # * Locator: Matches against the id, name, or placeholder
65
+ # * Locator: Matches against the id, Capybara.test_id attribute, name, or placeholder
66
66
  # * Filters:
67
67
  # * :id (String) — Matches the id attribute
68
68
  # * :name (String) — Matches the name attribute
@@ -74,7 +74,7 @@ module Capybara
74
74
  # * :multiple (Boolean) — Match fields that accept multiple values
75
75
  #
76
76
  # * **:radio_button** - Find radio buttons
77
- # * Locator: Match id, name, or associated label text
77
+ # * Locator: Match id, Capybara.test_id attribute, name, or associated label text
78
78
  # * Filters:
79
79
  # * :id (String) — Matches the id attribute
80
80
  # * :name (String) — Matches the name attribute
@@ -85,7 +85,7 @@ module Capybara
85
85
  # * :option (String) — Match the value
86
86
  #
87
87
  # * **:checkbox** - Find checkboxes
88
- # * Locator: Match id, name, or associated label text
88
+ # * Locator: Match id, Capybara.test_id attribute, name, or associated label text
89
89
  # * Filters:
90
90
  # * *:id (String) — Matches the id attribute
91
91
  # * *:name (String) — Matches the name attribute
@@ -96,7 +96,7 @@ module Capybara
96
96
  # * *:option (String) — Match the value
97
97
  #
98
98
  # * **:select** - Find select elements
99
- # * Locator: Match id, name, placeholder, or associated label text
99
+ # * Locator: Match id, Capybara.test_id attribute, name, placeholder, or associated label text
100
100
  # * Filters:
101
101
  # * :id (String) — Matches the id attribute
102
102
  # * :name (String) — Matches the name attribute
@@ -126,7 +126,7 @@ module Capybara
126
126
  # * Locator:
127
127
  #
128
128
  # * **:file_field** - Find file input elements
129
- # * Locator: Match id, name, or associated label text
129
+ # * Locator: Match id, Capybara.test_id attribute, name, or associated label text
130
130
  # * Filters:
131
131
  # * :id (String) — Matches the id attribute
132
132
  # * :name (String) — Matches the name attribute
@@ -190,6 +190,10 @@ module Capybara
190
190
  @expression = nil
191
191
  @expression_filters = {}
192
192
  @default_visibility = nil
193
+ @config = {
194
+ enable_aria_label: false,
195
+ test_id: nil
196
+ }
193
197
  instance_eval(&block)
194
198
  end
195
199
 
@@ -287,11 +291,12 @@ module Capybara
287
291
  # @return [String] Description of the selector when used with the options passed
288
292
  def_delegator :@filter_set, :description
289
293
 
290
- def call(locator, **options)
294
+ def call(locator, selector_config: {}, **options)
295
+ @config.merge! selector_config
291
296
  if format
292
297
  @expression.call(locator, options)
293
298
  else
294
- warn "Selector has no format"
299
+ warn 'Selector has no format'
295
300
  end
296
301
  end
297
302
 
@@ -349,15 +354,25 @@ module Capybara
349
354
  def_delegators :@filter_set, :node_filter, :expression_filter, :filter
350
355
 
351
356
  def filter_set(name, filters_to_use = nil)
352
- f_set = FilterSet.all[name]
353
- filter_selector = filters_to_use.nil? ? ->(*) { true } : ->(n, _) { filters_to_use.include? n }
354
- @filter_set.expression_filters.merge!(f_set.expression_filters.select(&filter_selector))
355
- @filter_set.node_filters.merge!(f_set.node_filters.select(&filter_selector))
356
- f_set.descriptions.each { |desc| @filter_set.describe(&desc) }
357
+ @filter_set.import(name, filters_to_use)
357
358
  end
358
359
 
359
360
  def_delegator :@filter_set, :describe
360
361
 
362
+ def describe_expression_filters(&block)
363
+ if block_given?
364
+ describe(:expression_filters, &block)
365
+ else
366
+ describe(:expression_filters) do |**options|
367
+ describe_all_expression_filters(options)
368
+ end
369
+ end
370
+ end
371
+
372
+ def describe_node_filters(&block)
373
+ describe(:node_filters, &block)
374
+ end
375
+
361
376
  ##
362
377
  #
363
378
  # Set the default visibility mode that shouble be used if no visibile option is passed when using the selector.
@@ -378,7 +393,15 @@ module Capybara
378
393
 
379
394
  private
380
395
 
381
- def locate_field(xpath, locator, enable_aria_label: false, **_options)
396
+ def enable_aria_label
397
+ @config[:enable_aria_label]
398
+ end
399
+
400
+ def test_id
401
+ @config[:test_id]
402
+ end
403
+
404
+ def locate_field(xpath, locator, **_options)
382
405
  return xpath if locator.nil?
383
406
  locate_xpath = xpath # Need to save original xpath for the label wrap
384
407
  locator = locator.to_s
@@ -387,6 +410,7 @@ module Capybara
387
410
  XPath.attr(:placeholder) == locator,
388
411
  XPath.attr(:id) == XPath.anywhere(:label)[XPath.string.n.is(locator)].attr(:for)].reduce(:|)
389
412
  attr_matchers |= XPath.attr(:'aria-label').is(locator) if enable_aria_label
413
+ attr_matchers |= XPath.attr(test_id) == locator if test_id
390
414
 
391
415
  locate_xpath = locate_xpath[attr_matchers]
392
416
  locate_xpath + XPath.descendant(:label)[XPath.string.n.is(locator)].descendant(xpath)
@@ -14,7 +14,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
14
14
 
15
15
  def self.load_selenium
16
16
  require 'selenium-webdriver'
17
- warn "Warning: You're using an unsupported version of selenium-webdriver, please upgrade." if Gem.loaded_specs["selenium-webdriver"].version < Gem::Version.new('3.5.0')
17
+ warn "Warning: You're using an unsupported version of selenium-webdriver, please upgrade." if Gem.loaded_specs['selenium-webdriver'].version < Gem::Version.new('3.5.0')
18
18
  rescue LoadError => e
19
19
  raise e if e.message !~ /selenium-webdriver/
20
20
  raise LoadError, "Capybara's selenium driver is unable to load `selenium-webdriver`, please install the gem and add `gem 'selenium-webdriver'` to your Gemfile if you are using bundler."
@@ -99,7 +99,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
99
99
  end
100
100
 
101
101
  def evaluate_script(script, *args)
102
- result = execute_script("return #{script}", *args)
102
+ result = execute_script("return #{script.strip}", *args)
103
103
  unwrap_script_result(result)
104
104
  end
105
105
 
@@ -135,12 +135,12 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
135
135
  # to about:blank, so we rescue this error and do nothing
136
136
  # instead.
137
137
  end
138
- @browser.navigate.to("about:blank")
138
+ @browser.navigate.to('about:blank')
139
139
  end
140
140
  navigated = true
141
141
 
142
142
  # Ensure the page is empty and trigger an UnhandledAlertError for any modals that appear during unload
143
- until find_xpath("/html/body/*").empty?
143
+ until find_xpath('/html/body/*').empty?
144
144
  raise Capybara::ExpectationNotMet, 'Timed out waiting for Selenium session reset' if timer.expired?
145
145
  sleep 0.05
146
146
  end
@@ -153,11 +153,11 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
153
153
  sleep 0.25 # allow time for the modal to be handled
154
154
  rescue modal_error
155
155
  # The alert is now gone
156
- if current_url != "about:blank"
156
+ if current_url != 'about:blank'
157
157
  begin
158
158
  # If navigation has not occurred attempt again and accept alert
159
159
  # since FF may have dismissed the alert at first attempt
160
- @browser.navigate.to("about:blank")
160
+ @browser.navigate.to('about:blank')
161
161
  sleep 0.1 # slight wait for alert
162
162
  @browser.switch_to.alert.accept
163
163
  rescue modal_error # rubocop:disable Metrics/BlockNesting, Lint/HandleExceptions
@@ -219,7 +219,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
219
219
  end
220
220
 
221
221
  def close_window(handle)
222
- raise ArgumentError, "Not allowed to close the primary window" if handle == window_handles.first
222
+ raise ArgumentError, 'Not allowed to close the primary window' if handle == window_handles.first
223
223
  within_given_window(handle) do
224
224
  browser.close
225
225
  end
@@ -328,15 +328,15 @@ private
328
328
  if @browser.respond_to? :session_storage
329
329
  @browser.session_storage.clear
330
330
  else
331
- warn "sessionStorage clear requested but is not available for this driver"
331
+ warn 'sessionStorage clear requested but is not available for this driver'
332
332
  end
333
333
  end
334
334
 
335
- if options[:clear_local_storage]
335
+ if options[:clear_local_storage] # rubocop:disable Style/GuardClause
336
336
  if @browser.respond_to? :local_storage
337
337
  @browser.local_storage.clear
338
338
  else
339
- warn "localStorage clear requested but is not available for this driver"
339
+ warn 'localStorage clear requested but is not available for this driver'
340
340
  end
341
341
  end
342
342
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'capybara/selenium/nodes/chrome_node'
4
+
3
5
  module Capybara::Selenium::Driver::ChromeDriver
4
6
  def fullscreen_window(handle)
5
7
  within_given_window(handle) do
@@ -32,4 +34,10 @@ module Capybara::Selenium::Driver::ChromeDriver
32
34
  window_handles.slice(1..-1).each { |win| close_window(win) }
33
35
  super
34
36
  end
37
+
38
+ private
39
+
40
+ def build_node(native_node)
41
+ ::Capybara::Selenium::ChromeNode.new(self, native_node)
42
+ end
35
43
  end
@@ -6,11 +6,11 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
6
6
  end
7
7
 
8
8
  def all_text
9
- text = driver.execute_script("return arguments[0].textContent", self)
9
+ text = driver.execute_script('return arguments[0].textContent', self)
10
10
  text.gsub(/[\u200b\u200e\u200f]/, '')
11
11
  .gsub(/[\ \n\f\t\v\u2028\u2029]+/, ' ')
12
- .gsub(/\A[[:space:]&&[^\u00a0]]+/, "")
13
- .gsub(/[[:space:]&&[^\u00a0]]+\z/, "")
12
+ .gsub(/\A[[:space:]&&[^\u00a0]]+/, '')
13
+ .gsub(/[[:space:]&&[^\u00a0]]+\z/, '')
14
14
  .tr("\u00a0", ' ')
15
15
  end
16
16
 
@@ -21,8 +21,8 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
21
21
  end
22
22
 
23
23
  def value
24
- if tag_name == "select" && multiple?
25
- native.find_elements(:css, "option:checked").map { |n| n[:value] || n.text }
24
+ if tag_name == 'select' && multiple?
25
+ native.find_elements(:css, 'option:checked').map { |n| n[:value] || n.text }
26
26
  else
27
27
  native[:value]
28
28
  end
@@ -78,7 +78,7 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
78
78
  end
79
79
 
80
80
  def unselect_option
81
- raise Capybara::UnselectNotAllowed, "Cannot unselect option from single select box." unless select_node.multiple?
81
+ raise Capybara::UnselectNotAllowed, 'Cannot unselect option from single select box.' unless select_node.multiple?
82
82
  native.click if selected?
83
83
  end
84
84
 
@@ -142,8 +142,7 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
142
142
  def disabled?
143
143
  return true unless native.enabled?
144
144
  # WebDriver only defines `disabled?` for form controls but fieldset makes sense too
145
- return boolean_attr(self[:disabled]) if tag_name == 'fieldset'
146
- false
145
+ tag_name == 'fieldset' && find_xpath('ancestor-or-self::fieldset[@disabled]').any?
147
146
  end
148
147
 
149
148
  def content_editable?
@@ -192,7 +191,7 @@ private
192
191
  end
193
192
 
194
193
  def boolean_attr(val)
195
- val && (val != "false")
194
+ val && (val != 'false')
196
195
  end
197
196
 
198
197
  # a reference to the select node if this is an option node
@@ -206,7 +205,7 @@ private
206
205
  elsif clear == :backspace
207
206
  # Clear field by sending the correct number of backspace keys.
208
207
  backspaces = [:backspace] * self.value.to_s.length
209
- native.send_keys(*(backspaces + [value.to_s]))
208
+ native.send_keys(*([:end] + backspaces + [value.to_s]))
210
209
  elsif clear == :none
211
210
  native.send_keys(value.to_s)
212
211
  elsif clear.is_a? Array
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Capybara::Selenium::ChromeNode < Capybara::Selenium::Node
4
+ def set_file(value) # rubocop:disable Naming/AccessorMethodName
5
+ super(value)
6
+ rescue ::Selenium::WebDriver::Error::ExpectedError => e
7
+ if e.message =~ /File not found : .+\n.+/m
8
+ raise ArgumentError, "Selenium with remote Chrome doesn't currently support multiple file upload"
9
+ end
10
+ raise
11
+ end
12
+
13
+ private
14
+
15
+ def bridge
16
+ driver.browser.send(:bridge)
17
+ end
18
+ end
@@ -4,28 +4,53 @@ class Capybara::Selenium::MarionetteNode < Capybara::Selenium::Node
4
4
  def click(keys = [], **options)
5
5
  super
6
6
  rescue ::Selenium::WebDriver::Error::ElementNotInteractableError
7
- if tag_name == "tr"
8
- warn "You are attempting to click a table row which has issues in geckodriver/marionette - see https://github.com/mozilla/geckodriver/issues/1228. " \
9
- "Your test should probably be clicking on a table cell like a user would. Clicking the first cell in the row instead."
7
+ if tag_name == 'tr'
8
+ warn 'You are attempting to click a table row which has issues in geckodriver/marionette - see https://github.com/mozilla/geckodriver/issues/1228. ' \
9
+ 'Your test should probably be clicking on a table cell like a user would. Clicking the first cell in the row instead.'
10
10
  return find_css('th:first-child,td:first-child')[0].click
11
11
  end
12
12
  raise
13
13
  end
14
14
 
15
15
  def disabled?
16
- return true if super
16
+ # Not sure exactly what version of FF fixed the below issue, but it is definitely fixed in 61+
17
+ return super unless driver.browser.capabilities[:browser_version].to_f < 61.0
17
18
 
19
+ return true if super
18
20
  # workaround for selenium-webdriver/geckodriver reporting elements as enabled when they are nested in disabling elements
19
21
  if %w[option optgroup].include? tag_name
20
- find_xpath("parent::*[self::optgroup or self::select]")[0].disabled?
22
+ find_xpath('parent::*[self::optgroup or self::select]')[0].disabled?
21
23
  else
22
- !find_xpath("parent::fieldset[@disabled] | ancestor::*[not(self::legend) or preceding-sibling::legend][parent::fieldset[@disabled]]").empty?
24
+ !find_xpath('parent::fieldset[@disabled] | ancestor::*[not(self::legend) or preceding-sibling::legend][parent::fieldset[@disabled]]').empty?
23
25
  end
24
26
  end
25
27
 
26
28
  def set_file(value) # rubocop:disable Naming/AccessorMethodName
27
29
  path_names = value.to_s.empty? ? [] : value
28
30
  native.clear
29
- Array(path_names).each { |p| native.send_keys(p) }
31
+ Array(path_names).each do |path|
32
+ unless driver.browser.respond_to?(:upload)
33
+ if (fd = bridge.file_detector)
34
+ local_file = fd.call([path])
35
+ path = upload(local_file) if local_file
36
+ end
37
+ end
38
+ native.send_keys(path)
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def bridge
45
+ driver.browser.send(:bridge)
46
+ end
47
+
48
+ def upload(local_file)
49
+ unless File.file?(local_file)
50
+ raise Error::WebDriverError, "you may only upload files: #{local_file.inspect}"
51
+ end
52
+
53
+ result = bridge.http.call(:post, "session/#{bridge.session_id}/file", file: Selenium::WebDriver::Zipper.zip_file(local_file))
54
+ result['value']
30
55
  end
31
56
  end
@@ -18,7 +18,7 @@ module Capybara
18
18
  attr_reader :app, :port, :host
19
19
 
20
20
  def initialize(app, *deprecated_options, port: Capybara.server_port, host: Capybara.server_host, reportable_errors: Capybara.server_errors, extra_middleware: [])
21
- warn "Positional arguments, other than the application, to Server#new are deprecated, please use keyword arguments" unless deprecated_options.empty?
21
+ warn 'Positional arguments, other than the application, to Server#new are deprecated, please use keyword arguments' unless deprecated_options.empty?
22
22
  @app = app
23
23
  @extra_middleware = extra_middleware
24
24
  @server_thread = nil # suppress warnings
@@ -56,7 +56,7 @@ module Capybara
56
56
  def wait_for_pending_requests
57
57
  timer = Capybara::Helpers.timer(expire_in: 60)
58
58
  while pending_requests?
59
- raise "Requests did not finish in 60 seconds" if timer.expired?
59
+ raise 'Requests did not finish in 60 seconds' if timer.expired?
60
60
  sleep 0.01
61
61
  end
62
62
  end
@@ -71,7 +71,7 @@ module Capybara
71
71
 
72
72
  timer = Capybara::Helpers.timer(expire_in: 60)
73
73
  until responsive?
74
- raise "Rack application timed out during boot" if timer.expired?
74
+ raise 'Rack application timed out during boot' if timer.expired?
75
75
  @server_thread.join(0.1)
76
76
  end
77
77
  end
@@ -21,7 +21,7 @@ module Capybara
21
21
  private
22
22
 
23
23
  def html_content?
24
- !!(@headers["Content-Type"] =~ /html/)
24
+ !!(@headers['Content-Type'] =~ /html/)
25
25
  end
26
26
 
27
27
  def insert_disable(html)
@@ -36,7 +36,7 @@ module Capybara
36
36
  end
37
37
 
38
38
  def call(env)
39
- if env["PATH_INFO"] == "/__identify__"
39
+ if env['PATH_INFO'] == '/__identify__'
40
40
  [200, {}, [@app.object_id.to_s]]
41
41
  else
42
42
  @counter.increment
@@ -75,12 +75,12 @@ module Capybara
75
75
  attr_accessor :synchronized
76
76
 
77
77
  def initialize(mode, app = nil)
78
- raise TypeError, "The second parameter to Session::new should be a rack app if passed." if app && !app.respond_to?(:call)
78
+ raise TypeError, 'The second parameter to Session::new should be a rack app if passed.' if app && !app.respond_to?(:call)
79
79
  @@instance_created = true
80
80
  @mode = mode
81
81
  @app = app
82
82
  if block_given?
83
- raise "A configuration block is only accepted when Capybara.threadsafe == true" unless Capybara.threadsafe
83
+ raise 'A configuration block is only accepted when Capybara.threadsafe == true' unless Capybara.threadsafe
84
84
  yield config
85
85
  end
86
86
  @server = if config.run_server && @app && driver.needs_server?
@@ -141,7 +141,7 @@ module Capybara
141
141
  # Force an explanation for the error being raised as the exception cause
142
142
  begin
143
143
  if config.raise_server_errors
144
- raise CapybaraError, "Your application server raised an error - It has been raised in your test code because Capybara.raise_server_errors == true"
144
+ raise CapybaraError, 'Your application server raised an error - It has been raised in your test code because Capybara.raise_server_errors == true'
145
145
  end
146
146
  rescue CapybaraError
147
147
  # needed to get the cause set correctly in JRuby -- otherwise we could just do raise @server.error
@@ -190,7 +190,7 @@ module Capybara
190
190
  uri = ::Addressable::URI.parse(current_url)
191
191
 
192
192
  # Addressable doesn't support opaque URIs - we want nil here
193
- return nil if uri&.scheme == "about"
193
+ return nil if uri&.scheme == 'about'
194
194
 
195
195
  path = uri&.path
196
196
  path unless path&.empty?
@@ -383,7 +383,7 @@ module Capybara
383
383
  when :parent
384
384
  if scopes.last != :frame
385
385
  raise Capybara::ScopeError, "`switch_to_frame(:parent)` cannot be called from inside a descendant frame's "\
386
- "`within` block."
386
+ '`within` block.'
387
387
  end
388
388
  scopes.pop
389
389
  driver.switch_to_frame(:parent)
@@ -392,11 +392,13 @@ module Capybara
392
392
  if idx
393
393
  if scopes.slice(idx..-1).any? { |scope| ![:frame, nil].include?(scope) }
394
394
  raise Capybara::ScopeError, "`switch_to_frame(:top)` cannot be called from inside a descendant frame's "\
395
- "`within` block."
395
+ '`within` block.'
396
396
  end
397
397
  scopes.slice!(idx..-1)
398
398
  driver.switch_to_frame(:top)
399
399
  end
400
+ else
401
+ raise ArgumentError, 'You must provide a frame element, :parent, or :top when calling switch_to_frame'
400
402
  end
401
403
  end
402
404
 
@@ -408,7 +410,7 @@ module Capybara
408
410
  # @overload within_frame(element)
409
411
  # @param [Capybara::Node::Element] frame element
410
412
  # @overload within_frame([kind = :frame], locator, **options)
411
- # @param [Symbol] kind Optional selector type (:css, :xpath, :field, etc.) - Defaults to :frame
413
+ # @param [Symbol] kind Optional selector type (:frame, :css, :xpath, etc.) - Defaults to :frame
412
414
  # @param [String] locator The locator for the given selector kind. For :frame this is the name/id of a frame/iframe element
413
415
  # @overload within_frame(index)
414
416
  # @param [Integer] index index of a frame (0 based)
@@ -471,11 +473,11 @@ module Capybara
471
473
  # @raise [ArgumentError] if both or neither arguments were provided
472
474
  #
473
475
  def switch_to_window(window = nil, **options, &window_locator)
474
- raise ArgumentError, "`switch_to_window` can take either a block or a window, not both" if window && block_given?
475
- raise ArgumentError, "`switch_to_window`: either window or block should be provided" if !window && !block_given?
476
+ raise ArgumentError, '`switch_to_window` can take either a block or a window, not both' if window && block_given?
477
+ raise ArgumentError, '`switch_to_window`: either window or block should be provided' if !window && !block_given?
476
478
  unless scopes.last.nil?
477
- raise Capybara::ScopeError, "`switch_to_window` is not supposed to be invoked from "\
478
- "`within` or `within_frame` blocks."
479
+ raise Capybara::ScopeError, '`switch_to_window` is not supposed to be invoked from '\
480
+ '`within` or `within_frame` blocks.'
479
481
  end
480
482
 
481
483
  _switch_to_window(window, options, &window_locator)
@@ -512,7 +514,7 @@ module Capybara
512
514
  when Proc
513
515
  _switch_to_window { window_or_proc.call }
514
516
  else
515
- raise ArgumentError("`#within_window` requires a `Capybara::Window` instance or a lambda")
517
+ raise ArgumentError('`#within_window` requires a `Capybara::Window` instance or a lambda')
516
518
  end
517
519
 
518
520
  begin
@@ -546,7 +548,7 @@ module Capybara
546
548
  document.synchronize(wait_time, errors: [Capybara::WindowError]) do
547
549
  opened_handles = (driver.window_handles - old_handles)
548
550
  if opened_handles.size != 1
549
- raise Capybara::WindowError, "block passed to #window_opened_by "\
551
+ raise Capybara::WindowError, 'block passed to #window_opened_by '\
550
552
  "opened #{opened_handles.size} windows instead of 1"
551
553
  end
552
554
  Window.new(self, opened_handles.first)
@@ -774,7 +776,7 @@ module Capybara
774
776
  # if set at initialization time, so look at the configuration block that can be passed to the initializer too
775
777
  #
776
778
  def configure
777
- raise "Session configuration is only supported when Capybara.threadsafe == true" unless Capybara.threadsafe
779
+ raise 'Session configuration is only supported when Capybara.threadsafe == true' unless Capybara.threadsafe
778
780
  yield config
779
781
  end
780
782
 
@@ -813,7 +815,7 @@ module Capybara
813
815
  end
814
816
 
815
817
  def open_file(path)
816
- require "launchy"
818
+ require 'launchy'
817
819
  Launchy.open(path)
818
820
  rescue LoadError
819
821
  warn "File saved to #{path}.\nPlease install the launchy gem to open the file automatically."
@@ -824,7 +826,7 @@ module Capybara
824
826
  end
825
827
 
826
828
  def default_fn(extension)
827
- timestamp = Time.new.strftime("%Y%m%d%H%M%S")
829
+ timestamp = Time.new.strftime('%Y%m%d%H%M%S')
828
830
  "capybara-#{timestamp}#{rand(10**10)}.#{extension}"
829
831
  end
830
832
 
@@ -846,6 +848,8 @@ module Capybara
846
848
  end
847
849
 
848
850
  def _find_frame(*args)
851
+ return find(:frame) if args.length.zero?
852
+
849
853
  case args[0]
850
854
  when Capybara::Node::Element
851
855
  args[0]
@@ -862,8 +866,8 @@ module Capybara
862
866
  end
863
867
 
864
868
  def _switch_to_window(window = nil, **options)
865
- raise Capybara::ScopeError, "Window cannot be switched inside a `within_frame` block" if scopes.include?(:frame)
866
- raise Capybara::ScopeError, "Window cannot be switch inside a `within` block" unless scopes.last.nil?
869
+ raise Capybara::ScopeError, 'Window cannot be switched inside a `within_frame` block' if scopes.include?(:frame)
870
+ raise Capybara::ScopeError, 'Window cannot be switch inside a `within` block' unless scopes.last.nil?
867
871
 
868
872
  if window
869
873
  driver.switch_to_window(window.handle)
@@ -882,7 +886,7 @@ module Capybara
882
886
  raise e
883
887
  else
884
888
  driver.switch_to_window(original_window_handle)
885
- raise Capybara::WindowError, "Could not find a window matching block/lambda"
889
+ raise Capybara::WindowError, 'Could not find a window matching block/lambda'
886
890
  end
887
891
  end
888
892
  end