capybara 3.3.1 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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