capybara 3.30.0 → 3.35.3

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 (146) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +153 -13
  3. data/README.md +9 -4
  4. data/lib/capybara.rb +18 -8
  5. data/lib/capybara/config.rb +4 -6
  6. data/lib/capybara/cucumber.rb +1 -1
  7. data/lib/capybara/driver/base.rb +4 -0
  8. data/lib/capybara/dsl.rb +10 -2
  9. data/lib/capybara/helpers.rb +25 -1
  10. data/lib/capybara/minitest.rb +232 -144
  11. data/lib/capybara/minitest/spec.rb +156 -97
  12. data/lib/capybara/node/actions.rb +16 -21
  13. data/lib/capybara/node/base.rb +6 -6
  14. data/lib/capybara/node/element.rb +14 -13
  15. data/lib/capybara/node/finders.rb +12 -7
  16. data/lib/capybara/node/matchers.rb +36 -27
  17. data/lib/capybara/node/simple.rb +6 -2
  18. data/lib/capybara/queries/ancestor_query.rb +1 -1
  19. data/lib/capybara/queries/base_query.rb +2 -1
  20. data/lib/capybara/queries/current_path_query.rb +14 -4
  21. data/lib/capybara/queries/selector_query.rb +40 -18
  22. data/lib/capybara/queries/sibling_query.rb +1 -1
  23. data/lib/capybara/queries/style_query.rb +1 -1
  24. data/lib/capybara/queries/text_query.rb +7 -1
  25. data/lib/capybara/rack_test/browser.rb +9 -3
  26. data/lib/capybara/rack_test/driver.rb +1 -0
  27. data/lib/capybara/rack_test/form.rb +1 -1
  28. data/lib/capybara/rack_test/node.rb +35 -10
  29. data/lib/capybara/registration_container.rb +44 -0
  30. data/lib/capybara/registrations/drivers.rb +18 -12
  31. data/lib/capybara/registrations/patches/puma_ssl.rb +3 -1
  32. data/lib/capybara/registrations/servers.rb +3 -2
  33. data/lib/capybara/result.rb +35 -15
  34. data/lib/capybara/rspec.rb +2 -0
  35. data/lib/capybara/rspec/matcher_proxies.rb +5 -5
  36. data/lib/capybara/rspec/matchers.rb +33 -32
  37. data/lib/capybara/rspec/matchers/base.rb +12 -6
  38. data/lib/capybara/rspec/matchers/count_sugar.rb +2 -1
  39. data/lib/capybara/rspec/matchers/have_ancestor.rb +4 -3
  40. data/lib/capybara/rspec/matchers/have_current_path.rb +2 -2
  41. data/lib/capybara/rspec/matchers/have_selector.rb +15 -7
  42. data/lib/capybara/rspec/matchers/have_sibling.rb +3 -3
  43. data/lib/capybara/rspec/matchers/have_text.rb +3 -3
  44. data/lib/capybara/rspec/matchers/have_title.rb +2 -2
  45. data/lib/capybara/rspec/matchers/match_selector.rb +3 -3
  46. data/lib/capybara/rspec/matchers/match_style.rb +7 -2
  47. data/lib/capybara/rspec/matchers/spatial_sugar.rb +2 -1
  48. data/lib/capybara/selector.rb +14 -3
  49. data/lib/capybara/selector/builders/css_builder.rb +1 -1
  50. data/lib/capybara/selector/builders/xpath_builder.rb +3 -1
  51. data/lib/capybara/selector/definition.rb +11 -9
  52. data/lib/capybara/selector/definition/button.rb +26 -14
  53. data/lib/capybara/selector/definition/css.rb +1 -1
  54. data/lib/capybara/selector/definition/datalist_input.rb +1 -1
  55. data/lib/capybara/selector/definition/element.rb +2 -1
  56. data/lib/capybara/selector/definition/fillable_field.rb +1 -1
  57. data/lib/capybara/selector/definition/label.rb +2 -2
  58. data/lib/capybara/selector/definition/link.rb +8 -0
  59. data/lib/capybara/selector/definition/select.rb +32 -13
  60. data/lib/capybara/selector/definition/table.rb +1 -1
  61. data/lib/capybara/selector/definition/table_row.rb +2 -2
  62. data/lib/capybara/selector/filter_set.rb +2 -2
  63. data/lib/capybara/selector/selector.rb +9 -1
  64. data/lib/capybara/selenium/atoms/getAttribute.min.js +1 -1
  65. data/lib/capybara/selenium/atoms/src/getAttribute.js +1 -1
  66. data/lib/capybara/selenium/atoms/src/isDisplayed.js +1 -1
  67. data/lib/capybara/selenium/driver.rb +52 -7
  68. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +10 -12
  69. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +9 -11
  70. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +3 -3
  71. data/lib/capybara/selenium/extensions/find.rb +4 -4
  72. data/lib/capybara/selenium/extensions/html5_drag.rb +24 -8
  73. data/lib/capybara/selenium/extensions/scroll.rb +8 -10
  74. data/lib/capybara/selenium/logger_suppressor.rb +8 -2
  75. data/lib/capybara/selenium/node.rb +96 -16
  76. data/lib/capybara/selenium/nodes/chrome_node.rb +27 -16
  77. data/lib/capybara/selenium/nodes/edge_node.rb +1 -1
  78. data/lib/capybara/selenium/nodes/firefox_node.rb +9 -4
  79. data/lib/capybara/selenium/nodes/safari_node.rb +1 -1
  80. data/lib/capybara/selenium/patches/action_pauser.rb +26 -0
  81. data/lib/capybara/selenium/patches/atoms.rb +4 -4
  82. data/lib/capybara/selenium/patches/logs.rb +7 -9
  83. data/lib/capybara/server/animation_disabler.rb +8 -3
  84. data/lib/capybara/server/middleware.rb +4 -2
  85. data/lib/capybara/session.rb +53 -29
  86. data/lib/capybara/session/config.rb +3 -1
  87. data/lib/capybara/session/matchers.rb +11 -11
  88. data/lib/capybara/spec/public/test.js +64 -7
  89. data/lib/capybara/spec/session/accept_alert_spec.rb +1 -1
  90. data/lib/capybara/spec/session/all_spec.rb +45 -5
  91. data/lib/capybara/spec/session/assert_text_spec.rb +5 -5
  92. data/lib/capybara/spec/session/check_spec.rb +6 -0
  93. data/lib/capybara/spec/session/click_button_spec.rb +11 -0
  94. data/lib/capybara/spec/session/click_link_or_button_spec.rb +9 -0
  95. data/lib/capybara/spec/session/current_url_spec.rb +11 -1
  96. data/lib/capybara/spec/session/fill_in_spec.rb +29 -0
  97. data/lib/capybara/spec/session/find_spec.rb +11 -8
  98. data/lib/capybara/spec/session/has_button_spec.rb +51 -0
  99. data/lib/capybara/spec/session/has_css_spec.rb +14 -10
  100. data/lib/capybara/spec/session/has_current_path_spec.rb +15 -2
  101. data/lib/capybara/spec/session/has_field_spec.rb +16 -0
  102. data/lib/capybara/spec/session/has_select_spec.rb +32 -4
  103. data/lib/capybara/spec/session/has_selector_spec.rb +4 -4
  104. data/lib/capybara/spec/session/has_text_spec.rb +5 -12
  105. data/lib/capybara/spec/session/html_spec.rb +1 -1
  106. data/lib/capybara/spec/session/matches_style_spec.rb +2 -2
  107. data/lib/capybara/spec/session/node_spec.rb +169 -33
  108. data/lib/capybara/spec/session/refresh_spec.rb +2 -1
  109. data/lib/capybara/spec/session/save_page_spec.rb +4 -4
  110. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +1 -1
  111. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +1 -1
  112. data/lib/capybara/spec/session/window/window_spec.rb +8 -8
  113. data/lib/capybara/spec/session/window/windows_spec.rb +1 -1
  114. data/lib/capybara/spec/spec_helper.rb +13 -14
  115. data/lib/capybara/spec/test_app.rb +23 -21
  116. data/lib/capybara/spec/views/form.erb +36 -3
  117. data/lib/capybara/spec/views/with_animation.erb +8 -0
  118. data/lib/capybara/spec/views/with_dragula.erb +3 -1
  119. data/lib/capybara/spec/views/with_html.erb +2 -2
  120. data/lib/capybara/spec/views/with_jquery_animation.erb +24 -0
  121. data/lib/capybara/spec/views/with_js.erb +3 -0
  122. data/lib/capybara/spec/views/with_sortable_js.erb +1 -1
  123. data/lib/capybara/version.rb +1 -1
  124. data/lib/capybara/window.rb +3 -7
  125. data/spec/basic_node_spec.rb +9 -8
  126. data/spec/capybara_spec.rb +1 -1
  127. data/spec/dsl_spec.rb +14 -1
  128. data/spec/fixtures/selenium_driver_rspec_success.rb +1 -1
  129. data/spec/minitest_spec.rb +3 -2
  130. data/spec/rack_test_spec.rb +28 -6
  131. data/spec/regexp_dissassembler_spec.rb +0 -4
  132. data/spec/result_spec.rb +40 -29
  133. data/spec/rspec/features_spec.rb +3 -1
  134. data/spec/rspec/scenarios_spec.rb +4 -0
  135. data/spec/rspec/shared_spec_matchers.rb +63 -51
  136. data/spec/rspec_spec.rb +4 -0
  137. data/spec/selector_spec.rb +17 -2
  138. data/spec/selenium_spec_chrome.rb +45 -21
  139. data/spec/selenium_spec_chrome_remote.rb +7 -1
  140. data/spec/selenium_spec_firefox.rb +15 -13
  141. data/spec/server_spec.rb +60 -49
  142. data/spec/shared_selenium_node.rb +18 -0
  143. data/spec/shared_selenium_session.rb +98 -7
  144. data/spec/spec_helper.rb +1 -1
  145. metadata +50 -14
  146. data/lib/capybara/spec/session/source_spec.rb +0 -0
@@ -3,7 +3,7 @@
3
3
  Capybara.add_selector(:css, locator_type: [String, Symbol], raw_locator: true) do
4
4
  css do |css|
5
5
  if css.is_a? Symbol
6
- warn "DEPRECATED: Passing a symbol (#{css.inspect}) as the CSS locator is deprecated - please pass a string instead."
6
+ Capybara::Helpers.warn "DEPRECATED: Passing a symbol (#{css.inspect}) as the CSS locator is deprecated - please pass a string instead : #{Capybara::Helpers.filter_backtrace(caller)}"
7
7
  end
8
8
  css
9
9
  end
@@ -19,7 +19,7 @@ Capybara.add_selector(:datalist_input, locator_type: [String, Symbol]) do
19
19
 
20
20
  expression_filter(:with_options) do |expr, options|
21
21
  options.inject(expr) do |xpath, option|
22
- xpath[XPath.attr(:list) == XPath.anywhere(:datalist)[expression_for(:datalist_option, option)].attr(:id)]
22
+ xpath.where(XPath.attr(:list) == XPath.anywhere(:datalist)[expression_for(:datalist_option, option)].attr(:id))
23
23
  end
24
24
  end
25
25
 
@@ -18,7 +18,8 @@ Capybara.add_selector(:element, locator_type: [String, Symbol]) do
18
18
  end
19
19
 
20
20
  describe_expression_filters do |**options|
21
- booleans, values = options.partition { |_k, v| [true, false].include? v }.map(&:to_h)
21
+ boolean_values = [true, false]
22
+ booleans, values = options.partition { |_k, v| boolean_values.include? v }.map(&:to_h)
22
23
  desc = describe_all_expression_filters(**values)
23
24
  desc + booleans.map do |k, v|
24
25
  v ? " with #{k} attribute" : "without #{k} attribute"
@@ -18,7 +18,7 @@ Capybara.add_selector(:fillable_field, locator_type: [String, Symbol]) do
18
18
  end
19
19
  end
20
20
 
21
- filter_set(:_field, %i[disabled multiple name placeholder valid])
21
+ filter_set(:_field, %i[disabled multiple name placeholder valid validation_message])
22
22
 
23
23
  node_filter(:with) do |node, with|
24
24
  val = node.value
@@ -2,7 +2,7 @@
2
2
 
3
3
  Capybara.add_selector(:label, locator_type: [String, Symbol]) do
4
4
  label 'label'
5
- xpath(:for) do |locator, options|
5
+ xpath(:for) do |locator, **options|
6
6
  xpath = XPath.descendant(:label)
7
7
  unless locator.nil?
8
8
  locator_matchers = XPath.string.n.is(locator.to_s) | (XPath.attr(:id) == locator.to_s)
@@ -53,7 +53,7 @@ Capybara.add_selector(:label, locator_type: [String, Symbol]) do
53
53
  end
54
54
  end
55
55
  describe_node_filters do |**options|
56
- " for element #{options[:for]}" if options[:for]&.is_a?(Capybara::Node::Element)
56
+ " for element #{options[:for]}" if options[:for].is_a?(Capybara::Node::Element)
57
57
  end
58
58
 
59
59
  def labelable_elements
@@ -5,6 +5,13 @@ Capybara.add_selector(:link, locator_type: [String, Symbol]) do
5
5
  xpath = XPath.descendant(:a)
6
6
  xpath = builder(xpath).add_attribute_conditions(href: href) unless href == false
7
7
 
8
+ if enable_aria_role
9
+ role_path = XPath.descendant[XPath.attr(:role).equals('link')]
10
+ role_path = builder(role_path).add_attribute_conditions(href: href) unless [true, false].include? href
11
+
12
+ xpath += role_path
13
+ end
14
+
8
15
  unless locator.nil?
9
16
  locator = locator.to_s
10
17
  matchers = [XPath.attr(:id) == locator,
@@ -18,6 +25,7 @@ Capybara.add_selector(:link, locator_type: [String, Symbol]) do
18
25
 
19
26
  xpath = xpath[find_by_attr(:title, title)]
20
27
  xpath = xpath[XPath.descendant(:img)[XPath.attr(:alt) == alt]] if alt
28
+
21
29
  xpath
22
30
  end
23
31
 
@@ -11,35 +11,41 @@ Capybara.add_selector(:select, locator_type: [String, Symbol]) do
11
11
  filter_set(:_field, %i[disabled multiple name placeholder])
12
12
 
13
13
  node_filter(:options) do |node, options|
14
- actual = if node.visible?
15
- node.all(:xpath, './/option', wait: false).map(&:text)
16
- else
17
- node.all(:xpath, './/option', visible: false, wait: false).map { |option| option.text(:all) }
18
- end
14
+ actual = options_text(node)
19
15
  (options.sort == actual.sort).tap do |res|
20
16
  add_error("Expected options #{options.inspect} found #{actual.inspect}") unless res
21
17
  end
22
18
  end
23
19
 
20
+ node_filter(:enabled_options) do |node, options|
21
+ actual = options_text(node) { |o| !o.disabled? }
22
+ (options.sort == actual.sort).tap do |res|
23
+ add_error("Expected enabled options #{options.inspect} found #{actual.inspect}") unless res
24
+ end
25
+ end
26
+
27
+ node_filter(:disabled_options) do |node, options|
28
+ actual = options_text(node, &:disabled?)
29
+ (options.sort == actual.sort).tap do |res|
30
+ add_error("Expected disabled options #{options.inspect} found #{actual.inspect}") unless res
31
+ end
32
+ end
33
+
24
34
  expression_filter(:with_options) do |expr, options|
25
35
  options.inject(expr) do |xpath, option|
26
- xpath[expression_for(:option, option)]
36
+ xpath.where(expression_for(:option, option))
27
37
  end
28
38
  end
29
39
 
30
40
  node_filter(:selected) do |node, selected|
31
- actual = node.all(:xpath, './/option', visible: false, wait: false)
32
- .select(&:selected?)
33
- .map { |option| option.text(:all) }
41
+ actual = options_text(node, visible: false, &:selected?)
34
42
  (Array(selected).sort == actual.sort).tap do |res|
35
43
  add_error("Expected #{selected.inspect} to be selected found #{actual.inspect}") unless res
36
44
  end
37
45
  end
38
46
 
39
47
  node_filter(:with_selected) do |node, selected|
40
- actual = node.all(:xpath, './/option', visible: false, wait: false)
41
- .select(&:selected?)
42
- .map { |option| option.text(:all) }
48
+ actual = options_text(node, visible: false, &:selected?)
43
49
  (Array(selected) - actual).empty?.tap do |res|
44
50
  add_error("Expected at least #{selected.inspect} to be selected found #{actual.inspect}") unless res
45
51
  end
@@ -51,12 +57,25 @@ Capybara.add_selector(:select, locator_type: [String, Symbol]) do
51
57
  desc
52
58
  end
53
59
 
54
- describe_node_filters do |options: nil, selected: nil, with_selected: nil, disabled: nil, **|
60
+ describe_node_filters do |
61
+ options: nil, disabled_options: nil, enabled_options: nil,
62
+ selected: nil, with_selected: nil,
63
+ disabled: nil, **|
55
64
  desc = +''
56
65
  desc << " with options #{options.inspect}" if options
66
+ desc << " with disabled options #{disabled_options.inspect}}" if disabled_options
67
+ desc << " with enabled options #{enabled_options.inspect}" if enabled_options
57
68
  desc << " with #{selected.inspect} selected" if selected
58
69
  desc << " with at least #{with_selected.inspect} selected" if with_selected
59
70
  desc << ' which is disabled' if disabled
60
71
  desc
61
72
  end
73
+
74
+ def options_text(node, **opts, &filter_block)
75
+ opts[:wait] = false
76
+ opts[:visible] = false unless node.visible?
77
+ node.all(:xpath, './/option', **opts, &filter_block).map do |o|
78
+ o.text((:all if opts[:visible] == false))
79
+ end
80
+ end
62
81
  end
@@ -43,7 +43,7 @@ Capybara.add_selector(:table, locator_type: [String, Symbol]) do
43
43
  end
44
44
 
45
45
  expression_filter(:cols, valid_values: [Array]) do |xpath, cols|
46
- raise ArgumentError, ':cols must be an Array of Arrays' unless cols.all? { |col| col.is_a? Array }
46
+ raise ArgumentError, ':cols must be an Array of Arrays' unless cols.all?(Array)
47
47
 
48
48
  rows = cols.transpose
49
49
  col_conditions = rows.map { |row| match_row(row, match_size: true) }.reduce(:&)
@@ -9,12 +9,12 @@ Capybara.add_selector(:table_row, locator_type: [Array, Hash]) do
9
9
  cell_xp = XPath.descendant(:td)[
10
10
  XPath.string.n.is(cell) & XPath.position.equals(header_xp.preceding_sibling.count.plus(1))
11
11
  ]
12
- xp[cell_xp]
12
+ xp.where(cell_xp)
13
13
  end
14
14
  else
15
15
  initial_td = XPath.descendant(:td)[XPath.string.n.is(locator.shift)]
16
16
  tds = locator.reverse.map { |cell| XPath.following_sibling(:td)[XPath.string.n.is(cell)] }
17
- .reduce { |xp, cell| xp[cell] }
17
+ .reduce { |xp, cell| xp.where(cell) }
18
18
  xpath[initial_td[tds]]
19
19
  end
20
20
  end
@@ -12,7 +12,7 @@ module Capybara
12
12
  @node_filters = {}
13
13
  @expression_filters = {}
14
14
  @descriptions = Hash.new { |hsh, key| hsh[key] = [] }
15
- instance_eval(&block)
15
+ instance_eval(&block) if block
16
16
  end
17
17
 
18
18
  def node_filter(names, *types, **options, &block)
@@ -49,7 +49,7 @@ module Capybara
49
49
  end
50
50
 
51
51
  def descriptions
52
- warn 'DEPRECATED: FilterSet#descriptions is deprecated without replacement'
52
+ Capybara::Helpers.warn 'DEPRECATED: FilterSet#descriptions is deprecated without replacement'
53
53
  [undeclared_descriptions, node_filter_descriptions, expression_filter_descriptions].flatten
54
54
  end
55
55
 
@@ -48,6 +48,10 @@ module Capybara
48
48
  @config[:enable_aria_label]
49
49
  end
50
50
 
51
+ def enable_aria_role
52
+ @config[:enable_aria_role]
53
+ end
54
+
51
55
  def test_id
52
56
  @config[:test_id]
53
57
  end
@@ -128,7 +132,11 @@ module Capybara
128
132
  attr_matchers |= XPath.attr(test_id) == locator if test_id
129
133
 
130
134
  locate_xpath = locate_xpath[attr_matchers]
131
- locate_xpath + XPath.descendant(:label)[XPath.string.n.is(locator)].descendant(xpath)
135
+ locate_xpath + locate_label(locator).descendant(xpath)
136
+ end
137
+
138
+ def locate_label(locator)
139
+ XPath.descendant(:label)[XPath.string.n.is(locator)]
132
140
  end
133
141
 
134
142
  def find_by_attr(attribute, value)
@@ -1 +1 @@
1
- (function(){function u(e){var t=e.tagName.toUpperCase();if("OPTION"==t)return!0;if("INPUT"!=t)return!1;var r=e.type.toLowerCase();return"checkbox"==r||"radio"==r}function s(e){var t="selected",r=e.type&&e.type.toLowerCase();return"checkbox"!=r&&"radio"!=r||(t="checked"),!!e[t]}function c(e,t){var r=e.getAttributeNode(t);return r&&r.specified?r.value:null}var i=["allowfullscreen","allowpaymentrequest","allowusermedia","async","autofocus","autoplay","checked","compact","complete","controls","declare","default","defaultchecked","defaultselected","defer","disabled","ended","formnovalidate","hidden","indeterminate","iscontenteditable","ismap","itemscope","loop","multiple","muted","nohref","nomodule","noresize","noshade","novalidate","nowrap","open","paused","playsinline","pubdate","readonly","required","reversed","scoped","seamless","seeking","selected","truespeed","typemustmatch","willvalidate"],d={"class":"className",readonly:"readOnly"};return function f(e,t){var r=null,a=t.toLowerCase();if("style"==a)return(r=e.style)&&"string"!=typeof r&&(r=r.cssText),r;if(("selected"==a||"checked"==a)&&u(e))return s(e)?"true":null;if(tagName=e.tagName.toUpperCase(),"IMG"==tagName&&"src"==a||"A"==tagName&&"href"==a)return(r=c(e,a))&&(r=e[a]),r;if("spellcheck"==a){if(null===!(r=c(e,a))){if("false"==r.toLowerCase())return"false";if("true"==r.toLowerCase())return"true"}return e[a]+""}var l,n=d[t]||t;if(i.some(function(e){e==a}))return(r=!(null===(r=c(e,a)))||e[n])?"true":null;try{l=e[n]}catch(o){}return null!=(r=null==l||"object"==typeof l||"function"==typeof l?c(e,t):l)?r.toString():null}})()
1
+ (function(){function u(e){var t=e.tagName.toUpperCase();if("OPTION"==t)return!0;if("INPUT"!=t)return!1;var r=e.type.toLowerCase();return"checkbox"==r||"radio"==r}function s(e){var t="selected",r=e.type&&e.type.toLowerCase();return"checkbox"!=r&&"radio"!=r||(t="checked"),!!e[t]}function c(e,t){var r=e.getAttributeNode(t);return r&&r.specified?r.value:null}var i=["allowfullscreen","allowpaymentrequest","allowusermedia","async","autofocus","autoplay","checked","compact","complete","controls","declare","default","defaultchecked","defaultselected","defer","disabled","ended","formnovalidate","hidden","indeterminate","iscontenteditable","ismap","itemscope","loop","multiple","muted","nohref","nomodule","noresize","noshade","novalidate","nowrap","open","paused","playsinline","pubdate","readonly","required","reversed","scoped","seamless","seeking","selected","truespeed","typemustmatch","willvalidate"],d={"class":"className",readonly:"readOnly"};return function f(e,t){var r=null,a=t.toLowerCase();if("style"==a)return(r=e.style)&&"string"!=typeof r&&(r=r.cssText),r;if(("selected"==a||"checked"==a)&&u(e))return s(e)?"true":null;if(tagName=e.tagName.toUpperCase(),"IMG"==tagName&&"src"==a||"A"==tagName&&"href"==a)return(r=c(e,a))&&(r=e[a]),r;if("spellcheck"==a){if(null!==(r=c(e,a))){if("false"==r.toLowerCase())return"false";if("true"==r.toLowerCase())return"true"}return e[a]+""}var l,n=d[t]||t;if(i.some(function(e){e==a}))return(r=!(null===(r=c(e,a)))||e[n])?"true":null;try{l=e[n]}catch(o){}return null!=(r=null==l||"object"==typeof l||"function"==typeof l?c(e,t):l)?r.toString():null}})()
@@ -117,7 +117,7 @@
117
117
 
118
118
  if ("spellcheck" == name) {
119
119
  value = getAttributeValue(element, name);
120
- if (!value === null) {
120
+ if (!(value === null)) {
121
121
  if (value.toLowerCase() == "false") {
122
122
  return "false";
123
123
  } else if (value.toLowerCase() == "true") {
@@ -158,7 +158,7 @@
158
158
  // the overflow style of the body, and the body is really overflow:visible.
159
159
  var overflowElem = e;
160
160
  if (htmlOverflowStyle == "visible") {
161
- // Note: bodyElem will be null/undefined in SVG documents.
161
+ // NOTE: bodyElem will be null/undefined in SVG documents.
162
162
  if (e == htmlElem && bodyElem) {
163
163
  overflowElem = bodyElem;
164
164
  } else if (e == bodyElem) {
@@ -12,19 +12,44 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
12
12
  clear_session_storage: nil
13
13
  }.freeze
14
14
  SPECIAL_OPTIONS = %i[browser clear_local_storage clear_session_storage timeout native_displayed].freeze
15
+ CAPS_VERSION = Gem::Requirement.new('~> 4.0.0.alpha6')
16
+
15
17
  attr_reader :app, :options
16
18
 
17
19
  class << self
20
+ attr_reader :selenium_webdriver_version
21
+
18
22
  def load_selenium
19
23
  require 'selenium-webdriver'
20
24
  require 'capybara/selenium/logger_suppressor'
21
25
  require 'capybara/selenium/patches/atoms'
22
26
  require 'capybara/selenium/patches/is_displayed'
23
- if Gem.loaded_specs['selenium-webdriver'].version < Gem::Version.new('3.5.0')
27
+ require 'capybara/selenium/patches/action_pauser'
28
+
29
+ # Look up the version of `selenium-webdriver` to
30
+ # see if it's a version we support.
31
+ #
32
+ # By default, we use Gem.loaded_specs to determine
33
+ # the version number. However, in some cases, such
34
+ # as when loading `selenium-webdriver` outside of
35
+ # Rubygems, we fall back to referencing
36
+ # Selenium::WebDriver::VERSION. Ideally we'd
37
+ # use the constant in all cases, but earlier versions
38
+ # of `selenium-webdriver` didn't provide the constant.
39
+ @selenium_webdriver_version =
40
+ if Gem.loaded_specs['selenium-webdriver']
41
+ Gem.loaded_specs['selenium-webdriver'].version
42
+ else
43
+ Gem::Version.new(Selenium::WebDriver::VERSION)
44
+ end
45
+
46
+ unless Gem::Requirement.new('>= 3.5.0').satisfied_by? @selenium_webdriver_version
24
47
  warn "Warning: You're using an unsupported version of selenium-webdriver, please upgrade."
25
48
  end
49
+
50
+ @selenium_webdriver_version
26
51
  rescue LoadError => e
27
- raise e unless e.message.match?(/selenium-webdriver/)
52
+ raise e unless e.message.include?('selenium-webdriver')
28
53
 
29
54
  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."
30
55
  end
@@ -48,7 +73,15 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
48
73
  end
49
74
  end
50
75
  processed_options = options.reject { |key, _val| SPECIAL_OPTIONS.include?(key) }
51
- @browser = Selenium::WebDriver.for(options[:browser], processed_options)
76
+
77
+ @browser = if options[:browser] == :firefox &&
78
+ RUBY_VERSION >= '3.0' &&
79
+ Capybara::Selenium::Driver.selenium_webdriver_version <= Gem::Version.new('4.0.0.alpha1')
80
+ # selenium-webdriver 3.x doesn't correctly pass options through for Firefox with Ruby 3 so workaround that
81
+ Selenium::WebDriver::Firefox::Driver.new(**processed_options)
82
+ else
83
+ Selenium::WebDriver.for(options[:browser], processed_options)
84
+ end
52
85
 
53
86
  specialize_driver
54
87
  setup_exit_handler
@@ -57,6 +90,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
57
90
  end
58
91
 
59
92
  def initialize(app, **options)
93
+ super()
60
94
  self.class.load_selenium
61
95
  @app = app
62
96
  @browser = nil
@@ -84,6 +118,8 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
84
118
 
85
119
  def html
86
120
  browser.page_source
121
+ rescue Selenium::WebDriver::Error::JavascriptError => e
122
+ raise unless e.message.include?('documentElement is null')
87
123
  end
88
124
 
89
125
  def title
@@ -112,6 +148,10 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
112
148
  unwrap_script_result(result)
113
149
  end
114
150
 
151
+ def send_keys(*args)
152
+ active_element.send_keys(*args)
153
+ end
154
+
115
155
  def save_screenshot(path, **_options)
116
156
  browser.save_screenshot(path)
117
157
  end
@@ -240,7 +280,8 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
240
280
 
241
281
  def quit
242
282
  @browser&.quit
243
- rescue Selenium::WebDriver::Error::SessionNotCreatedError, Errno::ECONNREFUSED # rubocop:disable Lint/SuppressedException
283
+ rescue Selenium::WebDriver::Error::SessionNotCreatedError, Errno::ECONNREFUSED,
284
+ Selenium::WebDriver::Error::InvalidSessionIdError
244
285
  # Browser must have already gone
245
286
  rescue Selenium::WebDriver::Error::UnknownError => e
246
287
  unless silenced_unknown_error_message?(e.message) # Most likely already gone
@@ -292,7 +333,7 @@ private
292
333
  def clear_browser_state
293
334
  delete_all_cookies
294
335
  clear_storage
295
- rescue *clear_browser_state_errors # rubocop:disable Lint/SuppressedException
336
+ rescue *clear_browser_state_errors
296
337
  # delete_all_cookies fails when we've previously gone
297
338
  # to about:blank, so we rescue this error and do nothing
298
339
  # instead.
@@ -316,7 +357,7 @@ private
316
357
  def clear_storage
317
358
  clear_session_storage unless options[:clear_session_storage] == false
318
359
  clear_local_storage unless options[:clear_local_storage] == false
319
- rescue Selenium::WebDriver::Error::JavascriptError # rubocop:disable Lint/SuppressedException
360
+ rescue Selenium::WebDriver::Error::JavascriptError
320
361
  # session/local storage may not be available if on non-http pages (e.g. about:blank)
321
362
  end
322
363
 
@@ -352,7 +393,7 @@ private
352
393
  @browser.navigate.to(url)
353
394
  sleep 0.1 # slight wait for alert
354
395
  @browser.switch_to.alert.accept
355
- rescue modal_error # rubocop:disable Lint/SuppressedException
396
+ rescue modal_error
356
397
  # alert now gone, should mean navigation happened
357
398
  end
358
399
 
@@ -434,6 +475,10 @@ private
434
475
  browser
435
476
  end
436
477
 
478
+ def active_element
479
+ browser.switch_to.active_element
480
+ end
481
+
437
482
  def build_node(native_node, initial_cache = {})
438
483
  ::Capybara::Selenium::Node.new(self, native_node, initial_cache)
439
484
  end
@@ -7,27 +7,25 @@ module Capybara::Selenium::Driver::ChromeDriver
7
7
  def self.extended(base)
8
8
  bridge = base.send(:bridge)
9
9
  bridge.extend Capybara::Selenium::ChromeLogs unless bridge.respond_to?(:log)
10
- bridge.extend Capybara::Selenium::IsDisplayed unless bridge.commands(:is_element_displayed)
10
+ bridge.extend Capybara::Selenium::IsDisplayed unless bridge.send(:commands, :is_element_displayed)
11
11
  base.options[:native_displayed] = false if base.options[:native_displayed].nil?
12
12
  end
13
13
 
14
14
  def fullscreen_window(handle)
15
15
  within_given_window(handle) do
16
- begin
17
- super
18
- rescue NoMethodError => e
19
- raise unless e.message.match?(/full_screen_window/)
20
-
21
- result = bridge.http.call(:post, "session/#{bridge.session_id}/window/fullscreen", {})
22
- result['value']
23
- end
16
+ super
17
+ rescue NoMethodError => e
18
+ raise unless e.message.include?('full_screen_window')
19
+
20
+ result = bridge.http.call(:post, "session/#{bridge.session_id}/window/fullscreen", {})
21
+ result['value']
24
22
  end
25
23
  end
26
24
 
27
25
  def resize_window_to(handle, width, height)
28
26
  super
29
27
  rescue Selenium::WebDriver::Error::UnknownError => e
30
- raise unless e.message.match?(/failed to change window state/)
28
+ raise unless e.message.include?('failed to change window state')
31
29
 
32
30
  # Chromedriver doesn't wait long enough for state to change when coming out of fullscreen
33
31
  # and raises unnecessary error. Wait a bit and try again.
@@ -65,7 +63,7 @@ private
65
63
  end
66
64
 
67
65
  def clear_all_storage?
68
- storage_clears.none? { |s| s == false }
66
+ storage_clears.none? false
69
67
  end
70
68
 
71
69
  def uniform_storage_clear?
@@ -96,7 +94,7 @@ private
96
94
 
97
95
  def execute_cdp(cmd, params = {})
98
96
  if browser.respond_to? :execute_cdp
99
- browser.execute_cdp(cmd, params)
97
+ browser.execute_cdp(cmd, **params)
100
98
  else
101
99
  args = { cmd: cmd, params: params }
102
100
  result = bridge.http.call(:post, "session/#{bridge.session_id}/goog/cdp/execute", args)