capybara 2.18.0 → 3.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (168) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +26 -1
  3. data/README.md +12 -12
  4. data/lib/capybara.rb +13 -25
  5. data/lib/capybara/config.rb +11 -57
  6. data/lib/capybara/cucumber.rb +2 -3
  7. data/lib/capybara/driver/base.rb +5 -16
  8. data/lib/capybara/driver/node.rb +5 -4
  9. data/lib/capybara/dsl.rb +1 -0
  10. data/lib/capybara/helpers.rb +16 -28
  11. data/lib/capybara/minitest.rb +139 -138
  12. data/lib/capybara/minitest/spec.rb +15 -14
  13. data/lib/capybara/node/actions.rb +59 -81
  14. data/lib/capybara/node/base.rb +11 -18
  15. data/lib/capybara/node/document.rb +2 -2
  16. data/lib/capybara/node/document_matchers.rb +8 -8
  17. data/lib/capybara/node/element.rb +30 -40
  18. data/lib/capybara/node/finders.rb +62 -70
  19. data/lib/capybara/node/matchers.rb +48 -71
  20. data/lib/capybara/node/simple.rb +11 -17
  21. data/lib/capybara/queries/ancestor_query.rb +4 -6
  22. data/lib/capybara/queries/base_query.rb +18 -17
  23. data/lib/capybara/queries/current_path_query.rb +8 -24
  24. data/lib/capybara/queries/match_query.rb +3 -7
  25. data/lib/capybara/queries/selector_query.rb +92 -95
  26. data/lib/capybara/queries/sibling_query.rb +4 -4
  27. data/lib/capybara/queries/text_query.rb +37 -34
  28. data/lib/capybara/queries/title_query.rb +8 -11
  29. data/lib/capybara/rack_test/browser.rb +15 -18
  30. data/lib/capybara/rack_test/css_handlers.rb +6 -4
  31. data/lib/capybara/rack_test/driver.rb +6 -10
  32. data/lib/capybara/rack_test/form.rb +50 -40
  33. data/lib/capybara/rack_test/node.rb +70 -56
  34. data/lib/capybara/rails.rb +2 -6
  35. data/lib/capybara/result.rb +22 -22
  36. data/lib/capybara/rspec.rb +5 -10
  37. data/lib/capybara/rspec/compound.rb +5 -10
  38. data/lib/capybara/rspec/features.rb +17 -48
  39. data/lib/capybara/rspec/matcher_proxies.rb +31 -15
  40. data/lib/capybara/rspec/matchers.rb +70 -60
  41. data/lib/capybara/selector.rb +129 -117
  42. data/lib/capybara/selector/css.rb +6 -11
  43. data/lib/capybara/selector/filter.rb +1 -17
  44. data/lib/capybara/selector/filter_set.rb +17 -14
  45. data/lib/capybara/selector/filters/base.rb +7 -6
  46. data/lib/capybara/selector/filters/expression_filter.rb +6 -23
  47. data/lib/capybara/selector/filters/node_filter.rb +2 -12
  48. data/lib/capybara/selector/selector.rb +27 -33
  49. data/lib/capybara/selenium/driver.rb +113 -127
  50. data/lib/capybara/selenium/node.rb +148 -113
  51. data/lib/capybara/server.rb +3 -2
  52. data/lib/capybara/session.rb +137 -223
  53. data/lib/capybara/session/config.rb +47 -67
  54. data/lib/capybara/session/matchers.rb +8 -7
  55. data/lib/capybara/spec/public/test.js +26 -4
  56. data/lib/capybara/spec/session/accept_alert_spec.rb +1 -0
  57. data/lib/capybara/spec/session/accept_confirm_spec.rb +3 -2
  58. data/lib/capybara/spec/session/accept_prompt_spec.rb +1 -0
  59. data/lib/capybara/spec/session/all_spec.rb +31 -18
  60. data/lib/capybara/spec/session/ancestor_spec.rb +2 -4
  61. data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +6 -5
  62. data/lib/capybara/spec/session/assert_current_path.rb +12 -11
  63. data/lib/capybara/spec/session/assert_selector.rb +1 -0
  64. data/lib/capybara/spec/session/assert_text.rb +18 -17
  65. data/lib/capybara/spec/session/assert_title.rb +1 -0
  66. data/lib/capybara/spec/session/attach_file_spec.rb +14 -13
  67. data/lib/capybara/spec/session/body_spec.rb +1 -0
  68. data/lib/capybara/spec/session/check_spec.rb +7 -6
  69. data/lib/capybara/spec/session/choose_spec.rb +5 -4
  70. data/lib/capybara/spec/session/click_button_spec.rb +20 -28
  71. data/lib/capybara/spec/session/click_link_or_button_spec.rb +8 -7
  72. data/lib/capybara/spec/session/click_link_spec.rb +8 -7
  73. data/lib/capybara/spec/session/current_scope_spec.rb +4 -3
  74. data/lib/capybara/spec/session/current_url_spec.rb +7 -6
  75. data/lib/capybara/spec/session/dismiss_confirm_spec.rb +1 -1
  76. data/lib/capybara/spec/session/dismiss_prompt_spec.rb +1 -0
  77. data/lib/capybara/spec/session/element/assert_match_selector.rb +1 -1
  78. data/lib/capybara/spec/session/element/match_xpath_spec.rb +1 -1
  79. data/lib/capybara/spec/session/element/matches_selector_spec.rb +5 -5
  80. data/lib/capybara/spec/session/evaluate_async_script_spec.rb +3 -2
  81. data/lib/capybara/spec/session/evaluate_script_spec.rb +4 -3
  82. data/lib/capybara/spec/session/execute_script_spec.rb +4 -3
  83. data/lib/capybara/spec/session/fill_in_spec.rb +6 -5
  84. data/lib/capybara/spec/session/find_button_spec.rb +4 -3
  85. data/lib/capybara/spec/session/find_by_id_spec.rb +2 -1
  86. data/lib/capybara/spec/session/find_field_spec.rb +8 -14
  87. data/lib/capybara/spec/session/find_link_spec.rb +6 -5
  88. data/lib/capybara/spec/session/find_spec.rb +37 -31
  89. data/lib/capybara/spec/session/first_spec.rb +60 -33
  90. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +2 -1
  91. data/lib/capybara/spec/session/frame/within_frame_spec.rb +9 -16
  92. data/lib/capybara/spec/session/go_back_spec.rb +1 -0
  93. data/lib/capybara/spec/session/go_forward_spec.rb +1 -0
  94. data/lib/capybara/spec/session/has_all_selectors_spec.rb +15 -15
  95. data/lib/capybara/spec/session/has_button_spec.rb +2 -1
  96. data/lib/capybara/spec/session/has_css_spec.rb +3 -2
  97. data/lib/capybara/spec/session/has_current_path_spec.rb +12 -28
  98. data/lib/capybara/spec/session/has_field_spec.rb +4 -3
  99. data/lib/capybara/spec/session/has_link_spec.rb +1 -0
  100. data/lib/capybara/spec/session/has_none_selectors_spec.rb +17 -17
  101. data/lib/capybara/spec/session/has_select_spec.rb +30 -29
  102. data/lib/capybara/spec/session/has_selector_spec.rb +5 -4
  103. data/lib/capybara/spec/session/has_table_spec.rb +2 -1
  104. data/lib/capybara/spec/session/has_text_spec.rb +6 -5
  105. data/lib/capybara/spec/session/has_title_spec.rb +1 -0
  106. data/lib/capybara/spec/session/has_xpath_spec.rb +1 -0
  107. data/lib/capybara/spec/session/headers.rb +2 -1
  108. data/lib/capybara/spec/session/html_spec.rb +1 -0
  109. data/lib/capybara/spec/session/node_spec.rb +91 -56
  110. data/lib/capybara/spec/session/node_wrapper_spec.rb +36 -0
  111. data/lib/capybara/spec/session/refresh_spec.rb +4 -2
  112. data/lib/capybara/spec/session/reset_session_spec.rb +1 -0
  113. data/lib/capybara/spec/session/response_code.rb +1 -0
  114. data/lib/capybara/spec/session/save_and_open_page_spec.rb +1 -0
  115. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +6 -11
  116. data/lib/capybara/spec/session/save_page_spec.rb +1 -17
  117. data/lib/capybara/spec/session/save_screenshot_spec.rb +1 -1
  118. data/lib/capybara/spec/session/select_spec.rb +20 -20
  119. data/lib/capybara/spec/session/selectors_spec.rb +2 -2
  120. data/lib/capybara/spec/session/sibling_spec.rb +1 -1
  121. data/lib/capybara/spec/session/text_spec.rb +1 -0
  122. data/lib/capybara/spec/session/title_spec.rb +1 -1
  123. data/lib/capybara/spec/session/uncheck_spec.rb +4 -3
  124. data/lib/capybara/spec/session/unselect_spec.rb +6 -5
  125. data/lib/capybara/spec/session/visit_spec.rb +9 -3
  126. data/lib/capybara/spec/session/window/become_closed_spec.rb +2 -1
  127. data/lib/capybara/spec/session/window/current_window_spec.rb +1 -0
  128. data/lib/capybara/spec/session/window/open_new_window_spec.rb +1 -0
  129. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +2 -1
  130. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +2 -1
  131. data/lib/capybara/spec/session/window/window_spec.rb +12 -12
  132. data/lib/capybara/spec/session/window/windows_spec.rb +2 -3
  133. data/lib/capybara/spec/session/window/within_window_spec.rb +13 -68
  134. data/lib/capybara/spec/session/within_spec.rb +1 -0
  135. data/lib/capybara/spec/spec_helper.rb +26 -18
  136. data/lib/capybara/spec/test_app.rb +8 -9
  137. data/lib/capybara/spec/views/form.erb +1 -0
  138. data/lib/capybara/spec/views/with_html.erb +3 -1
  139. data/lib/capybara/spec/views/within_frames.erb +4 -1
  140. data/lib/capybara/version.rb +2 -1
  141. data/lib/capybara/window.rb +6 -10
  142. data/spec/basic_node_spec.rb +1 -0
  143. data/spec/capybara_spec.rb +9 -32
  144. data/spec/dsl_spec.rb +5 -13
  145. data/spec/filter_set_spec.rb +5 -4
  146. data/spec/fixtures/selenium_driver_rspec_failure.rb +2 -1
  147. data/spec/fixtures/selenium_driver_rspec_success.rb +3 -2
  148. data/spec/minitest_spec.rb +4 -3
  149. data/spec/minitest_spec_spec.rb +3 -2
  150. data/spec/per_session_config_spec.rb +9 -8
  151. data/spec/rack_test_spec.rb +21 -20
  152. data/spec/result_spec.rb +17 -16
  153. data/spec/rspec/features_spec.rb +17 -14
  154. data/spec/rspec/scenarios_spec.rb +5 -7
  155. data/spec/rspec/shared_spec_matchers.rb +96 -99
  156. data/spec/rspec/views_spec.rb +2 -1
  157. data/spec/rspec_matchers_spec.rb +19 -2
  158. data/spec/rspec_spec.rb +11 -15
  159. data/spec/selector_spec.rb +5 -6
  160. data/spec/selenium_spec_chrome.rb +7 -4
  161. data/spec/selenium_spec_marionette.rb +26 -12
  162. data/spec/server_spec.rb +33 -33
  163. data/spec/session_spec.rb +2 -1
  164. data/spec/shared_selenium_session.rb +27 -21
  165. data/spec/spec_helper.rb +2 -5
  166. metadata +66 -87
  167. data/lib/capybara/query.rb +0 -7
  168. data/spec/selenium_spec_firefox.rb +0 -68
@@ -2,24 +2,19 @@ module Capybara
2
2
  class Selector
3
3
  class CSS
4
4
  def self.escape(str)
5
- out = String.new("")
6
5
  value = str.dup
6
+ out = "".dup
7
7
  out << value.slice!(0...1) if value =~ /^[-_]/
8
- out << if value[0] =~ NMSTART
9
- value.slice!(0...1)
10
- else
11
- escape_char(value.slice!(0...1))
12
- end
13
- out << value.gsub(/[^a-zA-Z0-9_-]/) {|c| escape_char c}
8
+ out << (value[0] =~ NMSTART ? value.slice!(0...1) : escape_char(value.slice!(0...1)))
9
+ out << value.gsub(/[^a-zA-Z0-9_-]/) { |c| escape_char c }
14
10
  out
15
11
  end
16
12
 
17
13
  def self.escape_char(c)
18
- return "\\%06x" % c.ord() unless c =~ %r{[ -/:-~]}
19
- "\\#{c}"
14
+ c =~ %r{[ -/:-~]} ? "\\#{c}" : format("\\%06x", c.ord)
20
15
  end
21
16
 
22
- S = '\u{80}-\u{D7FF}\u{E000}-\u{FFFD}\u{10000}-\u{10FFFF}'
17
+ S = '\u{80}-\u{D7FF}\u{E000}-\u{FFFD}\u{10000}-\u{10FFFF}'.freeze
23
18
  H = /[0-9a-fA-F]/
24
19
  UNICODE = /\\#{H}{1,6}[ \t\r\n\f]?/
25
20
  NONASCII = /[#{S}]/
@@ -27,4 +22,4 @@ module Capybara
27
22
  NMSTART = /[_a-zA-Z]|#{NONASCII}|#{ESCAPE}/
28
23
  end
29
24
  end
30
- end
25
+ end
@@ -1,20 +1,4 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'capybara/selector/filters/node_filter'
3
4
  require 'capybara/selector/filters/expression_filter'
4
-
5
- module Capybara
6
- class Selector
7
- def self.const_missing(const_name)
8
- case const_name
9
- when :Filter
10
- warn "DEPRECATED: Capybara::Selector::Filter is deprecated, please use Capybara::Selector::Filters::NodeFilter instead"
11
- Filters::NodeFilter
12
- when :ExpressionFilter
13
- warn "DEPRECATED: Capybara::Selector::ExpressionFilter is deprecated, please use Capybara::Selector::Filters::ExpressionFilter instead"
14
- Filters::ExpressionFilter
15
- else
16
- super
17
- end
18
- end
19
- end
20
- end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'capybara/selector/filter'
3
4
 
4
5
  module Capybara
@@ -24,14 +25,10 @@ module Capybara
24
25
  descriptions.push block
25
26
  end
26
27
 
27
- def description(options={})
28
- options_with_defaults = options.dup
29
- filters.each do |name, filter|
30
- options_with_defaults[name] = filter.default if filter.default? && !options_with_defaults.has_key?(name)
31
- end
32
-
28
+ def description(**options)
29
+ opts = options_with_defaults(options)
33
30
  @descriptions.map do |desc|
34
- desc.call(options_with_defaults).to_s
31
+ desc.call(opts).to_s
35
32
  end.join
36
33
  end
37
34
 
@@ -44,11 +41,10 @@ module Capybara
44
41
  end
45
42
 
46
43
  def expression_filters
47
- filters.select { |_n, f| f.nil? || f.is_a?(Filters::ExpressionFilter) }.freeze
44
+ filters.select { |_n, f| f.nil? || f.is_a?(Filters::ExpressionFilter) }.freeze
48
45
  end
49
46
 
50
47
  class << self
51
-
52
48
  def all
53
49
  @filter_sets ||= {}
54
50
  end
@@ -62,13 +58,20 @@ module Capybara
62
58
  end
63
59
  end
64
60
 
65
- private
61
+ private
62
+
63
+ def options_with_defaults(options)
64
+ options = options.dup
65
+ filters.each do |name, filter|
66
+ options[name] = filter.default if filter.default? && !options.key?(name)
67
+ end
68
+ options
69
+ end
66
70
 
67
- def add_filter(name, filter_class, *types_and_options, &block)
68
- options = types_and_options.last.is_a?(Hash) ? types_and_options.pop.dup : {}
69
- types_and_options.each { |k| options[k] = true}
71
+ def add_filter(name, filter_class, *types, **options, &block)
72
+ types.each { |k| options[k] = true }
70
73
  filters[name] = filter_class.new(name, block, options)
71
74
  end
72
75
  end
73
76
  end
74
- end
77
+ end
@@ -1,17 +1,18 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Capybara
3
4
  class Selector
4
5
  module Filters
5
6
  class Base
6
- def initialize(name, block, options={})
7
+ def initialize(name, block, **options)
7
8
  @name = name
8
9
  @block = block
9
10
  @options = options
10
- @options[:valid_values] = [true,false] if options[:boolean]
11
+ @options[:valid_values] = [true, false] if options[:boolean]
11
12
  end
12
13
 
13
14
  def default?
14
- @options.has_key?(:default)
15
+ @options.key?(:default)
15
16
  end
16
17
 
17
18
  def default
@@ -19,13 +20,13 @@ module Capybara
19
20
  end
20
21
 
21
22
  def skip?(value)
22
- @options.has_key?(:skip_if) && value == @options[:skip_if]
23
+ @options.key?(:skip_if) && value == @options[:skip_if]
23
24
  end
24
25
 
25
- private
26
+ private
26
27
 
27
28
  def valid_value?(value)
28
- !@options.has_key?(:valid_values) || Array(@options[:valid_values]).include?(value)
29
+ !@options.key?(:valid_values) || Array(@options[:valid_values]).include?(value)
29
30
  end
30
31
  end
31
32
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'capybara/selector/filters/base'
3
4
 
4
5
  module Capybara
@@ -7,34 +8,16 @@ module Capybara
7
8
  class ExpressionFilter < Base
8
9
  def apply_filter(expr, value)
9
10
  return expr if skip?(value)
10
-
11
- if !valid_value?(value)
12
- msg = "Invalid value #{value.inspect} passed to expression filter #{@name} - "
13
- if default?
14
- warn msg + "defaulting to #{default}"
15
- value = default
16
- else
17
- warn msg + "skipping"
18
- return expr
19
- end
20
- end
21
-
11
+ raise "ArgumentError", "Invalid value #{value.inspect} passed to expression filter #{@name}" unless valid_value?(value)
22
12
  @block.call(expr, value)
23
13
  end
24
14
  end
25
15
 
26
16
  class IdentityExpressionFilter < ExpressionFilter
27
- def initialize
28
- end
29
-
30
- def default?
31
- false
32
- end
33
-
34
- def apply_filter(expr, _value)
35
- return expr
36
- end
17
+ def initialize; end
18
+ def default?; false; end
19
+ def apply_filter(expr, _value); expr; end
37
20
  end
38
21
  end
39
22
  end
40
- end
23
+ end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'capybara/selector/filters/base'
3
4
 
4
5
  module Capybara
@@ -7,18 +8,7 @@ module Capybara
7
8
  class NodeFilter < Base
8
9
  def matches?(node, value)
9
10
  return true if skip?(value)
10
-
11
- if !valid_value?(value)
12
- msg = "Invalid value #{value.inspect} passed to filter #{@name} - "
13
- if default?
14
- warn msg + "defaulting to #{default}"
15
- value = default
16
- else
17
- warn msg + "skipping"
18
- return true
19
- end
20
- end
21
-
11
+ raise ArgumentError, "Invalid value #{value.inspect} passed to filter #{@name}" unless valid_value?(value)
22
12
  @block.call(node, value)
23
13
  end
24
14
  end
@@ -1,9 +1,10 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'capybara/selector/filter_set'
3
4
  require 'capybara/selector/css'
4
5
  require 'xpath'
5
6
 
6
- #Patch XPath to allow a nil condition in where
7
+ # Patch XPath to allow a nil condition in where
7
8
  module XPath
8
9
  class Renderer
9
10
  undef :where if method_defined?(:where)
@@ -12,7 +13,7 @@ module XPath
12
13
  if !condition.empty?
13
14
  "#{on}[#{condition}]"
14
15
  else
15
- "#{on}"
16
+ on.to_s
16
17
  end
17
18
  end
18
19
  end
@@ -20,7 +21,6 @@ end
20
21
 
21
22
  module Capybara
22
23
  class Selector
23
-
24
24
  attr_reader :name, :format
25
25
 
26
26
  class << self
@@ -43,7 +43,7 @@ module Capybara
43
43
 
44
44
  def initialize(name, &block)
45
45
  @name = name
46
- @filter_set = FilterSet.add(name){}
46
+ @filter_set = FilterSet.add(name) {}
47
47
  @match = nil
48
48
  @label = nil
49
49
  @failure_message = nil
@@ -134,7 +134,7 @@ module Capybara
134
134
  # @overload label()
135
135
  # @return [String] The currently set label
136
136
  #
137
- def label(label=nil)
137
+ def label(label = nil)
138
138
  @label = label if label
139
139
  @label
140
140
  end
@@ -146,13 +146,12 @@ module Capybara
146
146
  # @param [Hash] options The options of the query used to generate the description
147
147
  # @return [String] Description of the selector when used with the options passed
148
148
  #
149
- def description(options={})
149
+ def description(**options)
150
150
  @filter_set.description(options)
151
151
  end
152
152
 
153
- def call(locator, options={})
153
+ def call(locator, **options)
154
154
  if format
155
- # @expression.call(locator, options.select {|k,v| @expression_filters.include?(k)})
156
155
  @expression.call(locator, options)
157
156
  else
158
157
  warn "Selector has no format"
@@ -185,15 +184,11 @@ module Capybara
185
184
  # @option options :skip_if Value of the filter that will cause it to be skipped
186
185
  #
187
186
  def filter(name, *types_and_options, &block)
188
- options = types_and_options.last.is_a?(Hash) ? types_and_options.pop.dup : {}
189
- types_and_options.each { |k| options[k] = true }
190
- custom_filters[name] = Filters::NodeFilter.new(name, block, options)
187
+ add_filter(name, Filters::NodeFilter, *types_and_options, &block)
191
188
  end
192
189
 
193
190
  def expression_filter(name, *types_and_options, &block)
194
- options = types_and_options.last.is_a?(Hash) ? types_and_options.pop.dup : {}
195
- types_and_options.each { |k| options[k] = true }
196
- custom_filters[name] = Filters::ExpressionFilter.new(name, block, options)
191
+ add_filter(name, Filters::ExpressionFilter, *types_and_options, &block)
197
192
  end
198
193
 
199
194
  def filter_set(name, filters_to_use = nil)
@@ -205,7 +200,7 @@ module Capybara
205
200
  f_set.descriptions.each { |desc| @filter_set.describe(&desc) }
206
201
  end
207
202
 
208
- def describe &block
203
+ def describe(&block)
209
204
  @filter_set.describe(&block)
210
205
  end
211
206
 
@@ -230,17 +225,22 @@ module Capybara
230
225
  end
231
226
  end
232
227
 
233
- private
228
+ private
229
+
230
+ def add_filter(name, filter_class, *types, **options, &block)
231
+ types.each { |k| options[k] = true }
232
+ custom_filters[name] = filter_class.new(name, block, options)
233
+ end
234
234
 
235
- def locate_field(xpath, locator, options={})
236
- locate_xpath = xpath #need to save original xpath for the label wrap
235
+ def locate_field(xpath, locator, enable_aria_label: false, **_options)
236
+ locate_xpath = xpath # Need to save original xpath for the label wrap
237
237
  if locator
238
238
  locator = locator.to_s
239
- attr_matchers = XPath.attr(:id).equals(locator).or(
240
- XPath.attr(:name).equals(locator)).or(
241
- XPath.attr(:placeholder).equals(locator)).or(
242
- XPath.attr(:id).equals(XPath.anywhere(:label)[XPath.string.n.is(locator)].attr(:for)))
243
- attr_matchers = attr_matchers.or XPath.attr(:'aria-label').is(locator) if options[:enable_aria_label]
239
+ attr_matchers = [XPath.attr(:id) == locator,
240
+ XPath.attr(:name) == locator,
241
+ XPath.attr(:placeholder) == locator,
242
+ XPath.attr(:id) == XPath.anywhere(:label)[XPath.string.n.is(locator)].attr(:for)].reduce(:|)
243
+ attr_matchers |= XPath.attr(:'aria-label').is(locator) if enable_aria_label
244
244
 
245
245
  locate_xpath = locate_xpath[attr_matchers]
246
246
  locate_xpath = locate_xpath.union(XPath.descendant(:label)[XPath.string.n.is(locator)].descendant(xpath))
@@ -250,8 +250,8 @@ module Capybara
250
250
  locate_xpath
251
251
  end
252
252
 
253
- def describe_all_expression_filters(opts={})
254
- expression_filters.map { |ef| " with #{ef} #{opts[ef]}" if opts.has_key?(ef) }.join
253
+ def describe_all_expression_filters(**opts)
254
+ expression_filters.map { |ef| " with #{ef} #{opts[ef]}" if opts.key?(ef) }.join
255
255
  end
256
256
 
257
257
  def find_by_attr(attribute, value)
@@ -259,18 +259,12 @@ module Capybara
259
259
  if respond_to?(finder_name, true)
260
260
  send(finder_name, value)
261
261
  else
262
- value ? XPath.attr(attribute).equals(value) : nil
262
+ value ? XPath.attr(attribute) == value : nil
263
263
  end
264
264
  end
265
265
 
266
266
  def find_by_class_attr(classes)
267
- if classes
268
- Array(classes).map do |klass|
269
- "contains(concat(' ',normalize-space(@class),' '),' #{klass} ')"
270
- end.join(" and ").to_sym
271
- else
272
- nil
273
- end
267
+ Array(classes).map { |klass| XPath.attr(:class).contains_word(klass) }.reduce(:&)
274
268
  end
275
269
  end
276
270
  end
@@ -1,14 +1,15 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require "uri"
4
+ require "English"
3
5
 
4
6
  class Capybara::Selenium::Driver < Capybara::Driver::Base
5
-
6
7
  DEFAULT_OPTIONS = {
7
- :browser => :firefox,
8
+ browser: :firefox,
8
9
  clear_local_storage: false,
9
10
  clear_session_storage: false
10
- }
11
- SPECIAL_OPTIONS = [:browser, :clear_local_storage, :clear_session_storage]
11
+ }.freeze
12
+ SPECIAL_OPTIONS = %i[browser clear_local_storage clear_session_storage].freeze
12
13
 
13
14
  attr_reader :app, :options
14
15
 
@@ -16,10 +17,10 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
16
17
  unless @browser
17
18
  if firefox?
18
19
  options[:desired_capabilities] ||= {}
19
- options[:desired_capabilities].merge!({ unexpectedAlertBehaviour: "ignore" })
20
+ options[:desired_capabilities][:unexpectedAlertBehaviour] = "ignore"
20
21
  end
21
22
 
22
- @processed_options = options.reject { |key,_val| SPECIAL_OPTIONS.include?(key) }
23
+ @processed_options = options.reject { |key, _val| SPECIAL_OPTIONS.include?(key) }
23
24
  @browser = Selenium::WebDriver.for(options[:browser], @processed_options)
24
25
 
25
26
  @w3c = ((defined?(Selenium::WebDriver::Remote::W3CCapabilities) && @browser.capabilities.is_a?(Selenium::WebDriver::Remote::W3CCapabilities)) ||
@@ -27,7 +28,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
27
28
  main = Process.pid
28
29
  at_exit do
29
30
  # Store the exit status of the test run since it goes away after calling the at_exit proc...
30
- @exit_status = $!.status if $!.is_a?(SystemExit)
31
+ @exit_status = $ERROR_INFO.status if $ERROR_INFO.is_a?(SystemExit)
31
32
  quit if Process.pid == main
32
33
  exit @exit_status if @exit_status # Force exit with stored status
33
34
  end
@@ -35,7 +36,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
35
36
  @browser
36
37
  end
37
38
 
38
- def initialize(app, options={})
39
+ def initialize(app, **options)
39
40
  load_selenium
40
41
  @session = nil
41
42
  @app = app
@@ -50,10 +51,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
50
51
  end
51
52
 
52
53
  def refresh
53
- accept_modal(nil, wait: 0.1) do
54
- browser.navigate.refresh
55
- end
56
- rescue Capybara::ModalNotFound
54
+ browser.navigate.refresh
57
55
  end
58
56
 
59
57
  def go_back
@@ -88,7 +86,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
88
86
  def needs_server?; true; end
89
87
 
90
88
  def execute_script(script, *args)
91
- browser.execute_script(script, *args.map { |arg| arg.is_a?(Capybara::Selenium::Node) ? arg.native : arg} )
89
+ browser.execute_script(script, *args.map { |arg| arg.is_a?(Capybara::Selenium::Node) ? arg.native : arg })
92
90
  end
93
91
 
94
92
  def evaluate_script(script, *args)
@@ -98,66 +96,64 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
98
96
 
99
97
  def evaluate_async_script(script, *args)
100
98
  browser.manage.timeouts.script_timeout = Capybara.default_max_wait_time
101
- result = browser.execute_async_script(script, *args.map { |arg| arg.is_a?(Capybara::Selenium::Node) ? arg.native : arg} )
99
+ result = browser.execute_async_script(script, *args.map { |arg| arg.is_a?(Capybara::Selenium::Node) ? arg.native : arg })
102
100
  unwrap_script_result(result)
103
101
  end
104
102
 
105
- def save_screenshot(path, _options={})
103
+ def save_screenshot(path, **_options)
106
104
  browser.save_screenshot(path)
107
105
  end
108
106
 
109
107
  def reset!
110
108
  # Use instance variable directly so we avoid starting the browser just to reset the session
111
- if @browser
112
- navigated = false
113
- start_time = Capybara::Helpers.monotonic_time
109
+ return unless @browser
110
+
111
+ navigated = false
112
+ start_time = Capybara::Helpers.monotonic_time
113
+ begin
114
+ unless navigated
115
+ # Only trigger a navigation if we haven't done it already, otherwise it
116
+ # can trigger an endless series of unload modals
117
+ begin
118
+ @browser.manage.delete_all_cookies
119
+ clear_storage
120
+ rescue Selenium::WebDriver::Error::UnhandledError
121
+ # delete_all_cookies fails when we've previously gone
122
+ # to about:blank, so we rescue this error and do nothing
123
+ # instead.
124
+ end
125
+ @browser.navigate.to("about:blank")
126
+ end
127
+ navigated = true
128
+
129
+ # Ensure the page is empty and trigger an UnhandledAlertError for any modals that appear during unload
130
+ until find_xpath("/html/body/*").empty?
131
+ raise Capybara::ExpectationNotMet, 'Timed out waiting for Selenium session reset' if (Capybara::Helpers.monotonic_time - start_time) >= 10
132
+ sleep 0.05
133
+ end
134
+ rescue Selenium::WebDriver::Error::UnhandledAlertError, Selenium::WebDriver::Error::UnexpectedAlertOpenError
135
+ # This error is thrown if an unhandled alert is on the page
136
+ # Firefox appears to automatically dismiss this alert, chrome does not
137
+ # We'll try to accept it
114
138
  begin
115
- if !navigated
116
- # Only trigger a navigation if we haven't done it already, otherwise it
117
- # can trigger an endless series of unload modals
139
+ @browser.switch_to.alert.accept
140
+ sleep 0.25 # allow time for the modal to be handled
141
+ rescue modal_error
142
+ # The alert is now gone
143
+ if current_url != "about:blank"
118
144
  begin
119
- @browser.manage.delete_all_cookies
120
- if options[:clear_session_storage]
121
- if @browser.respond_to? :session_storage
122
- @browser.session_storage.clear
123
- else
124
- warn "sessionStorage clear requested but is not available for this driver"
125
- end
126
- end
127
- if options[:clear_local_storage]
128
- if @browser.respond_to? :local_storage
129
- @browser.local_storage.clear
130
- else
131
- warn "localStorage clear requested but is not available for this driver"
132
- end
133
- end
134
- rescue Selenium::WebDriver::Error::UnhandledError
135
- # delete_all_cookies fails when we've previously gone
136
- # to about:blank, so we rescue this error and do nothing
137
- # instead.
145
+ # If navigation has not occurred attempt again and accept alert
146
+ # since FF may have dismissed the alert at first attempt
147
+ @browser.navigate.to("about:blank")
148
+ sleep 0.1 # slight wait for alert
149
+ @browser.switch_to.alert.accept
150
+ rescue modal_error # rubocop:disable Metrics/BlockNesting
151
+ # alert now gone, should mean navigation happened
138
152
  end
139
- @browser.navigate.to("about:blank")
140
153
  end
141
- navigated = true
142
-
143
- #Ensure the page is empty and trigger an UnhandledAlertError for any modals that appear during unload
144
- until find_xpath("/html/body/*").empty? do
145
- raise Capybara::ExpectationNotMet.new('Timed out waiting for Selenium session reset') if (Capybara::Helpers.monotonic_time - start_time) >= 10
146
- sleep 0.05
147
- end
148
- rescue Selenium::WebDriver::Error::UnhandledAlertError, Selenium::WebDriver::Error::UnexpectedAlertOpenError
149
- # This error is thrown if an unhandled alert is on the page
150
- # Firefox appears to automatically dismiss this alert, chrome does not
151
- # We'll try to accept it
152
- begin
153
- @browser.switch_to.alert.accept
154
- sleep 0.25 # allow time for the modal to be handled
155
- rescue modal_error
156
- # The alert is now gone - nothing to do
157
- end
158
- # try cleaning up the browser again
159
- retry
160
154
  end
155
+ # try cleaning up the browser again
156
+ retry
161
157
  end
162
158
  end
163
159
 
@@ -226,12 +222,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
226
222
  browser.switch_to.window handle
227
223
  end
228
224
 
229
- def within_window(locator)
230
- handle = find_window(locator)
231
- browser.switch_to.window(handle) { yield }
232
- end
233
-
234
- def accept_modal(_type, options={})
225
+ def accept_modal(_type, **options)
235
226
  yield if block_given?
236
227
  modal = find_modal(options)
237
228
 
@@ -242,7 +233,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
242
233
  message
243
234
  end
244
235
 
245
- def dismiss_modal(_type, options={})
236
+ def dismiss_modal(_type, **options)
246
237
  yield if block_given?
247
238
  modal = find_modal(options)
248
239
  message = modal.text
@@ -264,14 +255,15 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
264
255
  end
265
256
 
266
257
  def invalid_element_errors
267
- [::Selenium::WebDriver::Error::StaleElementReferenceError,
268
- ::Selenium::WebDriver::Error::UnhandledError,
269
- ::Selenium::WebDriver::Error::ElementNotVisibleError,
270
- ::Selenium::WebDriver::Error::InvalidSelectorError, # Work around a race condition that can occur with chromedriver and #go_back/#go_forward
271
- ::Selenium::WebDriver::Error::ElementNotInteractableError,
272
- ::Selenium::WebDriver::Error::ElementClickInterceptedError,
273
- ::Selenium::WebDriver::Error::InvalidElementStateError,
274
- ::Selenium::WebDriver::Error::ElementNotSelectableError,
258
+ [
259
+ ::Selenium::WebDriver::Error::StaleElementReferenceError,
260
+ ::Selenium::WebDriver::Error::UnhandledError,
261
+ ::Selenium::WebDriver::Error::ElementNotVisibleError,
262
+ ::Selenium::WebDriver::Error::InvalidSelectorError, # Work around a race condition that can occur with chromedriver and #go_back/#go_forward
263
+ ::Selenium::WebDriver::Error::ElementNotInteractableError,
264
+ ::Selenium::WebDriver::Error::ElementClickInterceptedError,
265
+ ::Selenium::WebDriver::Error::InvalidElementStateError,
266
+ ::Selenium::WebDriver::Error::ElementNotSelectableError
275
267
  ]
276
268
  end
277
269
 
@@ -294,18 +286,29 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
294
286
  browser_name == "chrome"
295
287
  end
296
288
 
297
- # @deprecated This method is being removed
298
- def browser_initialized?
299
- super && !@browser.nil?
300
- end
301
-
302
- private
289
+ private
303
290
 
304
- # @api private
305
291
  def browser_name
306
292
  options[:browser].to_s
307
293
  end
308
294
 
295
+ def clear_storage
296
+ if options[:clear_session_storage]
297
+ if @browser.respond_to? :session_storage
298
+ @browser.session_storage.clear
299
+ else
300
+ warn "sessionStorage clear requested but is not available for this driver"
301
+ end
302
+ end
303
+ if options[:clear_local_storage]
304
+ if @browser.respond_to? :local_storage
305
+ @browser.local_storage.clear
306
+ else
307
+ warn "localStorage clear requested but is not available for this driver"
308
+ end
309
+ end
310
+ end
311
+
309
312
  def modal_error
310
313
  if defined?(Selenium::WebDriver::Error::NoSuchAlertError)
311
314
  Selenium::WebDriver::Error::NoSuchAlertError
@@ -314,23 +317,6 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
314
317
  end
315
318
  end
316
319
 
317
- def find_window(locator)
318
- handles = browser.window_handles
319
- return locator if handles.include? locator
320
-
321
- original_handle = browser.window_handle
322
- handles.each do |handle|
323
- switch_to_window(handle)
324
- if (locator == browser.execute_script("return window.name") ||
325
- browser.title.include?(locator) ||
326
- browser.current_url.include?(locator))
327
- switch_to_window(original_handle)
328
- return handle
329
- end
330
- end
331
- raise Capybara::ElementNotFound, "Could not find a window identified by #{locator}"
332
- end
333
-
334
320
  def insert_modal_handlers(accept, response_text)
335
321
  prompt_response = if accept
336
322
  if response_text.nil?
@@ -386,7 +372,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
386
372
  end
387
373
 
388
374
  def within_given_window(handle)
389
- original_handle = self.current_window_handle
375
+ original_handle = current_window_handle
390
376
  if handle == original_handle
391
377
  yield
392
378
  else
@@ -397,39 +383,41 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
397
383
  end
398
384
  end
399
385
 
400
- def find_modal(options={})
386
+ def find_modal(text: nil, **options)
401
387
  # Selenium has its own built in wait (2 seconds)for a modal to show up, so this wait is really the minimum time
402
388
  # Actual wait time may be longer than specified
403
389
  wait = Selenium::WebDriver::Wait.new(
404
- timeout: options.fetch(:wait, session_options.default_max_wait_time) || 0 ,
405
- ignore: modal_error)
390
+ timeout: options.fetch(:wait, session_options.default_max_wait_time) || 0,
391
+ ignore: modal_error
392
+ )
406
393
  begin
407
394
  wait.until do
408
395
  alert = @browser.switch_to.alert
409
- regexp = options[:text].is_a?(Regexp) ? options[:text] : Regexp.escape(options[:text].to_s)
396
+ regexp = text.is_a?(Regexp) ? text : Regexp.escape(text.to_s)
410
397
  alert.text.match(regexp) ? alert : nil
411
398
  end
412
399
  rescue Selenium::WebDriver::Error::TimeOutError
413
- raise Capybara::ModalNotFound.new("Unable to find modal dialog#{" with #{options[:text]}" if options[:text]}")
400
+ raise Capybara::ModalNotFound, "Unable to find modal dialog#{" with #{text}" if text}"
414
401
  end
415
402
  end
416
403
 
417
- def find_headless_modal(options={})
404
+ def find_headless_modal(text: nil, **options)
418
405
  # Selenium has its own built in wait (2 seconds)for a modal to show up, so this wait is really the minimum time
419
406
  # Actual wait time may be longer than specified
420
407
  wait = Selenium::WebDriver::Wait.new(
421
- timeout: options.fetch(:wait, session_options.default_max_wait_time) || 0 ,
422
- ignore: modal_error)
408
+ timeout: options.fetch(:wait, session_options.default_max_wait_time) || 0,
409
+ ignore: modal_error
410
+ )
423
411
  begin
424
412
  wait.until do
425
413
  called, alert_text = evaluate_script('window.capybara && window.capybara.current_modal_status()')
426
414
  if called
427
415
  execute_script('window.capybara && window.capybara.modal_handlers.shift()')
428
- regexp = options[:text].is_a?(Regexp) ? options[:text] : Regexp.escape(options[:text].to_s)
416
+ regexp = text.is_a?(Regexp) ? text : Regexp.escape(text.to_s)
429
417
  if alert_text.match(regexp)
430
418
  alert_text
431
419
  else
432
- raise Capybara::ModalNotFound.new("Unable to find modal dialog#{" with #{options[:text]}" if options[:text]}")
420
+ raise Capybara::ModalNotFound, "Unable to find modal dialog#{" with #{text}" if text}"
433
421
  end
434
422
  elsif called.nil?
435
423
  # page changed so modal_handler data has gone away
@@ -440,7 +428,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
440
428
  end
441
429
  end
442
430
  rescue Selenium::WebDriver::Error::TimeOutError
443
- raise Capybara::ModalNotFound.new("Unable to find modal dialog#{" with #{options[:text]}" if options[:text]}")
431
+ raise Capybara::ModalNotFound, "Unable to find modal dialog#{" with #{options[:text]}" if options[:text]}"
444
432
  end
445
433
  end
446
434
 
@@ -449,7 +437,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
449
437
  end
450
438
 
451
439
  def silenced_unknown_error_messages
452
- [ /Error communicating with the remote browser/ ]
440
+ [/Error communicating with the remote browser/]
453
441
  end
454
442
 
455
443
  def unwrap_script_result(arg)
@@ -466,21 +454,19 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
466
454
  end
467
455
 
468
456
  def load_selenium
469
- begin
470
- require 'selenium-webdriver'
471
- # Fix for selenium-webdriver 3.4.0 which misnamed these
472
- if !defined?(::Selenium::WebDriver::Error::ElementNotInteractableError)
473
- ::Selenium::WebDriver::Error.const_set('ElementNotInteractableError', Class.new(::Selenium::WebDriver::Error::WebDriverError))
474
- end
475
- if !defined?(::Selenium::WebDriver::Error::ElementClickInterceptedError)
476
- ::Selenium::WebDriver::Error.const_set('ElementClickInterceptedError', Class.new(::Selenium::WebDriver::Error::WebDriverError))
477
- end
478
- rescue LoadError => e
479
- if e.message =~ /selenium-webdriver/
480
- 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."
481
- else
482
- raise e
483
- end
457
+ require 'selenium-webdriver'
458
+ # Fix for selenium-webdriver 3.4.0 which misnamed these
459
+ unless defined?(::Selenium::WebDriver::Error::ElementNotInteractableError)
460
+ ::Selenium::WebDriver::Error.const_set('ElementNotInteractableError', Class.new(::Selenium::WebDriver::Error::WebDriverError))
461
+ end
462
+ unless defined?(::Selenium::WebDriver::Error::ElementClickInterceptedError)
463
+ ::Selenium::WebDriver::Error.const_set('ElementClickInterceptedError', Class.new(::Selenium::WebDriver::Error::WebDriverError))
464
+ end
465
+ rescue LoadError => e
466
+ if e.message =~ /selenium-webdriver/
467
+ 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."
468
+ else
469
+ raise e
484
470
  end
485
471
  end
486
472
  end