capybara 2.5.0 → 2.18.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 (205) hide show
  1. checksums.yaml +5 -5
  2. data/.yard/templates_custom/default/class/html/selectors.erb +38 -0
  3. data/.yard/templates_custom/default/class/html/setup.rb +17 -0
  4. data/.yard/yard_extensions.rb +78 -0
  5. data/.yardopts +1 -0
  6. data/History.md +413 -10
  7. data/License.txt +1 -1
  8. data/README.md +237 -130
  9. data/lib/capybara/config.rb +132 -0
  10. data/lib/capybara/cucumber.rb +3 -1
  11. data/lib/capybara/driver/base.rb +27 -6
  12. data/lib/capybara/driver/node.rb +14 -5
  13. data/lib/capybara/dsl.rb +2 -3
  14. data/lib/capybara/helpers.rb +13 -65
  15. data/lib/capybara/minitest/spec.rb +177 -0
  16. data/lib/capybara/minitest.rb +278 -0
  17. data/lib/capybara/node/actions.rb +180 -24
  18. data/lib/capybara/node/base.rb +17 -5
  19. data/lib/capybara/node/document.rb +5 -0
  20. data/lib/capybara/node/document_matchers.rb +15 -14
  21. data/lib/capybara/node/element.rb +55 -7
  22. data/lib/capybara/node/finders.rb +179 -67
  23. data/lib/capybara/node/matchers.rb +301 -105
  24. data/lib/capybara/node/simple.rb +15 -4
  25. data/lib/capybara/queries/ancestor_query.rb +25 -0
  26. data/lib/capybara/queries/base_query.rb +69 -3
  27. data/lib/capybara/queries/current_path_query.rb +17 -8
  28. data/lib/capybara/queries/match_query.rb +19 -0
  29. data/lib/capybara/queries/selector_query.rb +251 -0
  30. data/lib/capybara/queries/sibling_query.rb +25 -0
  31. data/lib/capybara/queries/text_query.rb +67 -16
  32. data/lib/capybara/queries/title_query.rb +4 -2
  33. data/lib/capybara/query.rb +3 -131
  34. data/lib/capybara/rack_test/browser.rb +14 -5
  35. data/lib/capybara/rack_test/css_handlers.rb +1 -0
  36. data/lib/capybara/rack_test/driver.rb +15 -8
  37. data/lib/capybara/rack_test/form.rb +34 -12
  38. data/lib/capybara/rack_test/node.rb +29 -12
  39. data/lib/capybara/rails.rb +3 -3
  40. data/lib/capybara/result.rb +104 -9
  41. data/lib/capybara/rspec/compound.rb +95 -0
  42. data/lib/capybara/rspec/features.rb +17 -6
  43. data/lib/capybara/rspec/matcher_proxies.rb +45 -0
  44. data/lib/capybara/rspec/matchers.rb +199 -80
  45. data/lib/capybara/rspec.rb +4 -2
  46. data/lib/capybara/selector/css.rb +30 -0
  47. data/lib/capybara/selector/filter.rb +20 -0
  48. data/lib/capybara/selector/filter_set.rb +74 -0
  49. data/lib/capybara/selector/filters/base.rb +33 -0
  50. data/lib/capybara/selector/filters/expression_filter.rb +40 -0
  51. data/lib/capybara/selector/filters/node_filter.rb +27 -0
  52. data/lib/capybara/selector/selector.rb +276 -0
  53. data/lib/capybara/selector.rb +452 -157
  54. data/lib/capybara/selenium/driver.rb +282 -81
  55. data/lib/capybara/selenium/node.rb +144 -46
  56. data/lib/capybara/server.rb +59 -16
  57. data/lib/capybara/session/config.rb +114 -0
  58. data/lib/capybara/session/matchers.rb +29 -19
  59. data/lib/capybara/session.rb +378 -143
  60. data/lib/capybara/spec/fixtures/no_extension +1 -0
  61. data/lib/capybara/spec/public/jquery-ui.js +13 -791
  62. data/lib/capybara/spec/public/jquery.js +4 -9045
  63. data/lib/capybara/spec/public/test.js +45 -11
  64. data/lib/capybara/spec/session/accept_alert_spec.rb +30 -7
  65. data/lib/capybara/spec/session/accept_confirm_spec.rb +14 -2
  66. data/lib/capybara/spec/session/accept_prompt_spec.rb +35 -6
  67. data/lib/capybara/spec/session/all_spec.rb +45 -32
  68. data/lib/capybara/spec/session/ancestor_spec.rb +85 -0
  69. data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +110 -0
  70. data/lib/capybara/spec/session/assert_current_path.rb +15 -2
  71. data/lib/capybara/spec/session/assert_selector.rb +29 -28
  72. data/lib/capybara/spec/session/assert_text.rb +59 -20
  73. data/lib/capybara/spec/session/assert_title.rb +25 -11
  74. data/lib/capybara/spec/session/attach_file_spec.rb +42 -4
  75. data/lib/capybara/spec/session/body_spec.rb +1 -0
  76. data/lib/capybara/spec/session/check_spec.rb +90 -14
  77. data/lib/capybara/spec/session/choose_spec.rb +31 -5
  78. data/lib/capybara/spec/session/click_button_spec.rb +20 -9
  79. data/lib/capybara/spec/session/click_link_or_button_spec.rb +15 -9
  80. data/lib/capybara/spec/session/click_link_spec.rb +39 -15
  81. data/lib/capybara/spec/session/current_scope_spec.rb +2 -1
  82. data/lib/capybara/spec/session/current_url_spec.rb +12 -3
  83. data/lib/capybara/spec/session/dismiss_confirm_spec.rb +6 -5
  84. data/lib/capybara/spec/session/dismiss_prompt_spec.rb +4 -3
  85. data/lib/capybara/spec/session/element/assert_match_selector.rb +36 -0
  86. data/lib/capybara/spec/session/element/match_css_spec.rb +23 -0
  87. data/lib/capybara/spec/session/element/match_xpath_spec.rb +23 -0
  88. data/lib/capybara/spec/session/element/matches_selector_spec.rb +106 -0
  89. data/lib/capybara/spec/session/evaluate_async_script_spec.rb +22 -0
  90. data/lib/capybara/spec/session/evaluate_script_spec.rb +23 -1
  91. data/lib/capybara/spec/session/execute_script_spec.rb +22 -3
  92. data/lib/capybara/spec/session/fill_in_spec.rb +50 -32
  93. data/lib/capybara/spec/session/find_button_spec.rb +43 -2
  94. data/lib/capybara/spec/session/find_by_id_spec.rb +3 -2
  95. data/lib/capybara/spec/session/find_field_spec.rb +42 -6
  96. data/lib/capybara/spec/session/find_link_spec.rb +22 -3
  97. data/lib/capybara/spec/session/find_spec.rb +103 -57
  98. data/lib/capybara/spec/session/first_spec.rb +34 -18
  99. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +103 -0
  100. data/lib/capybara/spec/session/{within_frame_spec.rb → frame/within_frame_spec.rb} +44 -2
  101. data/lib/capybara/spec/session/go_back_spec.rb +2 -1
  102. data/lib/capybara/spec/session/go_forward_spec.rb +2 -1
  103. data/lib/capybara/spec/session/has_all_selectors_spec.rb +69 -0
  104. data/lib/capybara/spec/session/has_button_spec.rb +17 -8
  105. data/lib/capybara/spec/session/has_css_spec.rb +85 -73
  106. data/lib/capybara/spec/session/has_current_path_spec.rb +91 -7
  107. data/lib/capybara/spec/session/has_field_spec.rb +93 -58
  108. data/lib/capybara/spec/session/has_link_spec.rb +9 -8
  109. data/lib/capybara/spec/session/has_none_selectors_spec.rb +76 -0
  110. data/lib/capybara/spec/session/has_select_spec.rb +159 -59
  111. data/lib/capybara/spec/session/has_selector_spec.rb +64 -28
  112. data/lib/capybara/spec/session/has_table_spec.rb +1 -0
  113. data/lib/capybara/spec/session/has_text_spec.rb +27 -12
  114. data/lib/capybara/spec/session/has_title_spec.rb +22 -4
  115. data/lib/capybara/spec/session/has_xpath_spec.rb +32 -29
  116. data/lib/capybara/spec/session/headers.rb +2 -1
  117. data/lib/capybara/spec/session/html_spec.rb +4 -3
  118. data/lib/capybara/spec/session/node_spec.rb +198 -38
  119. data/lib/capybara/spec/session/refresh_spec.rb +28 -0
  120. data/lib/capybara/spec/session/reset_session_spec.rb +46 -5
  121. data/lib/capybara/spec/session/response_code.rb +2 -1
  122. data/lib/capybara/spec/session/save_and_open_page_spec.rb +1 -0
  123. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +6 -5
  124. data/lib/capybara/spec/session/save_page_spec.rb +34 -2
  125. data/lib/capybara/spec/session/save_screenshot_spec.rb +31 -1
  126. data/lib/capybara/spec/session/screenshot_spec.rb +4 -2
  127. data/lib/capybara/spec/session/select_spec.rb +34 -32
  128. data/lib/capybara/spec/session/selectors_spec.rb +65 -0
  129. data/lib/capybara/spec/session/sibling_spec.rb +52 -0
  130. data/lib/capybara/spec/session/text_spec.rb +4 -4
  131. data/lib/capybara/spec/session/title_spec.rb +2 -1
  132. data/lib/capybara/spec/session/uncheck_spec.rb +42 -2
  133. data/lib/capybara/spec/session/unselect_spec.rb +17 -16
  134. data/lib/capybara/spec/session/visit_spec.rb +77 -2
  135. data/lib/capybara/spec/session/window/become_closed_spec.rb +12 -11
  136. data/lib/capybara/spec/session/window/current_window_spec.rb +1 -0
  137. data/lib/capybara/spec/session/window/open_new_window_spec.rb +1 -0
  138. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +16 -11
  139. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +7 -4
  140. data/lib/capybara/spec/session/window/window_spec.rb +36 -29
  141. data/lib/capybara/spec/session/window/windows_spec.rb +1 -0
  142. data/lib/capybara/spec/session/window/within_window_spec.rb +31 -7
  143. data/lib/capybara/spec/session/within_spec.rb +14 -6
  144. data/lib/capybara/spec/spec_helper.rb +37 -4
  145. data/lib/capybara/spec/test_app.rb +15 -3
  146. data/lib/capybara/spec/views/buttons.erb +1 -0
  147. data/lib/capybara/spec/views/fieldsets.erb +2 -1
  148. data/lib/capybara/spec/views/form.erb +169 -9
  149. data/lib/capybara/spec/views/frame_child.erb +10 -2
  150. data/lib/capybara/spec/views/frame_one.erb +2 -1
  151. data/lib/capybara/spec/views/frame_parent.erb +3 -2
  152. data/lib/capybara/spec/views/frame_two.erb +2 -1
  153. data/lib/capybara/spec/views/header_links.erb +1 -0
  154. data/lib/capybara/spec/views/host_links.erb +1 -0
  155. data/lib/capybara/spec/views/initial_alert.erb +10 -0
  156. data/lib/capybara/spec/views/path.erb +1 -0
  157. data/lib/capybara/spec/views/popup_one.erb +1 -0
  158. data/lib/capybara/spec/views/popup_two.erb +1 -0
  159. data/lib/capybara/spec/views/postback.erb +2 -1
  160. data/lib/capybara/spec/views/tables.erb +1 -0
  161. data/lib/capybara/spec/views/with_base_tag.erb +1 -0
  162. data/lib/capybara/spec/views/with_count.erb +2 -1
  163. data/lib/capybara/spec/views/with_fixed_header_footer.erb +17 -0
  164. data/lib/capybara/spec/views/with_hover.erb +7 -1
  165. data/lib/capybara/spec/views/with_html.erb +40 -2
  166. data/lib/capybara/spec/views/with_html_entities.erb +1 -0
  167. data/lib/capybara/spec/views/with_js.erb +32 -1
  168. data/lib/capybara/spec/views/with_scope.erb +1 -0
  169. data/lib/capybara/spec/views/with_simple_html.erb +2 -1
  170. data/lib/capybara/spec/views/with_slow_unload.erb +17 -0
  171. data/lib/capybara/spec/views/with_title.erb +2 -1
  172. data/lib/capybara/spec/views/with_unload_alert.erb +14 -0
  173. data/lib/capybara/spec/views/with_windows.erb +7 -0
  174. data/lib/capybara/spec/views/within_frames.erb +3 -2
  175. data/lib/capybara/version.rb +2 -1
  176. data/lib/capybara/window.rb +20 -3
  177. data/lib/capybara.rb +189 -93
  178. data/spec/basic_node_spec.rb +7 -6
  179. data/spec/capybara_spec.rb +90 -4
  180. data/spec/dsl_spec.rb +3 -1
  181. data/spec/filter_set_spec.rb +28 -0
  182. data/spec/fixtures/capybara.csv +1 -0
  183. data/spec/fixtures/selenium_driver_rspec_failure.rb +5 -1
  184. data/spec/fixtures/selenium_driver_rspec_success.rb +5 -1
  185. data/spec/minitest_spec.rb +130 -0
  186. data/spec/minitest_spec_spec.rb +135 -0
  187. data/spec/per_session_config_spec.rb +67 -0
  188. data/spec/rack_test_spec.rb +50 -7
  189. data/spec/result_spec.rb +76 -0
  190. data/spec/rspec/features_spec.rb +21 -8
  191. data/spec/rspec/scenarios_spec.rb +21 -0
  192. data/spec/rspec/{matchers_spec.rb → shared_spec_matchers.rb} +160 -54
  193. data/spec/rspec/views_spec.rb +5 -0
  194. data/spec/rspec_matchers_spec.rb +46 -0
  195. data/spec/rspec_spec.rb +79 -1
  196. data/spec/selector_spec.rb +199 -0
  197. data/spec/selenium_spec_chrome.rb +54 -9
  198. data/spec/selenium_spec_firefox.rb +68 -0
  199. data/spec/selenium_spec_marionette.rb +127 -0
  200. data/spec/server_spec.rb +102 -14
  201. data/spec/session_spec.rb +54 -0
  202. data/spec/shared_selenium_session.rb +215 -0
  203. data/spec/spec_helper.rb +7 -0
  204. metadata +140 -15
  205. data/spec/selenium_spec.rb +0 -128
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Capybara
2
3
  module Node
3
4
 
@@ -75,7 +76,7 @@ module Capybara
75
76
  #
76
77
  def value
77
78
  if tag_name == 'textarea'
78
- native.content
79
+ native['_capybara_raw_value']
79
80
  elsif tag_name == 'select'
80
81
  if native['multiple'] == 'multiple'
81
82
  native.xpath(".//option[@selected='selected']").map { |option| option[:value] || option.content }
@@ -99,6 +100,8 @@ module Capybara
99
100
  # @return [Boolean] Whether the element is visible
100
101
  #
101
102
  def visible?(check_ancestors = true)
103
+ return false if (tag_name == 'input') && (native[:type]=="hidden")
104
+
102
105
  if check_ancestors
103
106
  #check size because oldest supported nokogiri doesnt support xpath boolean() function
104
107
  native.xpath("./ancestor-or-self::*[contains(@style, 'display:none') or contains(@style, 'display: none') or @hidden or name()='script' or name()='head']").size() == 0
@@ -115,7 +118,7 @@ module Capybara
115
118
  # @return [Boolean] Whether the element is checked
116
119
  #
117
120
  def checked?
118
- native[:checked]
121
+ native.has_attribute?('checked')
119
122
  end
120
123
 
121
124
  ##
@@ -124,7 +127,7 @@ module Capybara
124
127
  #
125
128
  # @return [Boolean] Whether the element is disabled
126
129
  def disabled?
127
- native[:disabled]
130
+ native.has_attribute?('disabled')
128
131
  end
129
132
 
130
133
  ##
@@ -134,7 +137,7 @@ module Capybara
134
137
  # @return [Boolean] Whether the element is selected
135
138
  #
136
139
  def selected?
137
- native[:selected]
140
+ native.has_attribute?('selected')
138
141
  end
139
142
 
140
143
  def synchronize(seconds=nil)
@@ -145,6 +148,9 @@ module Capybara
145
148
  # no op
146
149
  end
147
150
 
151
+ ##
152
+ #
153
+ # @return [String] The title of the document
148
154
  def title
149
155
  if native.respond_to? :title
150
156
  native.title
@@ -167,6 +173,11 @@ module Capybara
167
173
  def find_xpath(xpath)
168
174
  native.xpath(xpath)
169
175
  end
176
+
177
+ # @api private
178
+ def session_options
179
+ Capybara.session_options
180
+ end
170
181
  end
171
182
  end
172
183
  end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+ module Capybara
3
+ module Queries
4
+ class AncestorQuery < MatchQuery
5
+ # @api private
6
+ def resolve_for(node, exact = nil)
7
+ @child_node = node
8
+ node.synchronize do
9
+ match_results = super(node.session.current_scope, exact)
10
+ node.all(:xpath, XPath.ancestor) do |el|
11
+ match_results.include?(el)
12
+ end
13
+ end
14
+ end
15
+
16
+ def description
17
+ desc = super
18
+ if @child_node && (child_query = @child_node.instance_variable_get(:@query))
19
+ desc += " that is an ancestor of #{child_query.description}"
20
+ end
21
+ desc
22
+ end
23
+ end
24
+ end
25
+ end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Capybara
2
3
  # @api private
3
4
  module Queries
@@ -5,17 +6,82 @@ module Capybara
5
6
  COUNT_KEYS = [:count, :minimum, :maximum, :between]
6
7
 
7
8
  attr_reader :options
9
+ attr_writer :session_options
10
+
11
+ def initialize(options)
12
+ @session_options = options.delete(:session_options)
13
+ end
14
+
15
+ def session_options
16
+ @session_options || Capybara.session_options
17
+ end
8
18
 
9
19
  def wait
10
- if @options.has_key?(:wait)
11
- @options[:wait] || 0
20
+ self.class.wait(options, session_options.default_max_wait_time)
21
+ end
22
+
23
+ def self.wait(options, default=Capybara.default_max_wait_time)
24
+ options.fetch(:wait, default) || 0
25
+ end
26
+
27
+ ##
28
+ #
29
+ # Checks if a count of 0 is valid for the query
30
+ # Returns false if query does not have any count options specified.
31
+ #
32
+ def expects_none?
33
+ if COUNT_KEYS.any? { |k| options.has_key? k }
34
+ matches_count?(0)
12
35
  else
13
- Capybara.default_max_wait_time
36
+ false
14
37
  end
15
38
  end
16
39
 
40
+ ##
41
+ #
42
+ # Checks if the given count matches the query count options.
43
+ # Defaults to true if no count options are specified. If multiple
44
+ # count options exist, it tests that all conditions are met;
45
+ # however, if :count is specified, all other options are ignored.
46
+ #
47
+ # @param [Integer] count The actual number. Should be coercible via Integer()
48
+ #
49
+ def matches_count?(count)
50
+ return (Integer(options[:count]) == count) if options[:count]
51
+ return false if options[:maximum] && (Integer(options[:maximum]) < count)
52
+ return false if options[:minimum] && (Integer(options[:minimum]) > count)
53
+ return false if options[:between] && !(options[:between] === count)
54
+ return true
55
+ end
56
+
57
+ ##
58
+ #
59
+ # Generates a failure message from the query description and count options.
60
+ #
61
+ def failure_message
62
+ String.new("expected to find #{description}") << count_message
63
+ end
64
+
65
+ def negative_failure_message
66
+ String.new("expected not to find #{description}") << count_message
67
+ end
68
+
17
69
  private
18
70
 
71
+ def count_message
72
+ message = String.new()
73
+ if options[:count]
74
+ message << " #{options[:count]} #{Capybara::Helpers.declension('time', 'times', options[:count])}"
75
+ elsif options[:between]
76
+ message << " between #{options[:between].first} and #{options[:between].last} times"
77
+ elsif options[:maximum]
78
+ message << " at most #{options[:maximum]} #{Capybara::Helpers.declension('time', 'times', options[:maximum])}"
79
+ elsif options[:minimum]
80
+ message << " at least #{options[:minimum]} #{Capybara::Helpers.declension('time', 'times', options[:minimum])}"
81
+ end
82
+ message
83
+ end
84
+
19
85
  def assert_valid_keys
20
86
  invalid_keys = @options.keys - valid_keys
21
87
  unless invalid_keys.empty?
@@ -1,30 +1,39 @@
1
+ # frozen_string_literal: true
2
+ require 'addressable/uri'
3
+
1
4
  module Capybara
2
5
  # @api private
3
6
  module Queries
4
7
  class CurrentPathQuery < BaseQuery
5
8
  def initialize(expected_path, options = {})
9
+ super(options)
6
10
  @expected_path = expected_path
11
+ warn "DEPRECATED: The :only_path option is deprecated in favor of the :ignore_query option" if options.has_key?(:only_path)
12
+
7
13
  @options = {
8
- url: false,
9
- only_path: false }.merge(options)
14
+ url: !@expected_path.is_a?(Regexp) && !::Addressable::URI.parse(@expected_path || "").hostname.nil?,
15
+ only_path: false,
16
+ ignore_query: false }.merge(options)
10
17
  assert_valid_keys
11
18
  end
12
19
 
13
20
  def resolves_for?(session)
21
+ uri = ::Addressable::URI.parse(session.current_url)
22
+ uri.query = nil if uri && options[:ignore_query]
14
23
  @actual_path = if options[:url]
15
- session.current_url
24
+ uri.to_s
16
25
  else
17
26
  if options[:only_path]
18
- URI.parse(session.current_url).path
27
+ uri && uri.path
19
28
  else
20
- URI.parse(session.current_url).request_uri
29
+ uri && uri.request_uri
21
30
  end
22
31
  end
23
32
 
24
33
  if @expected_path.is_a? Regexp
25
- @actual_path.match(@expected_path)
34
+ @actual_path.to_s.match(@expected_path)
26
35
  else
27
- @expected_path == @actual_path
36
+ ::Addressable::URI.parse(@expected_path) == ::Addressable::URI.parse(@actual_path)
28
37
  end
29
38
  end
30
39
 
@@ -44,7 +53,7 @@ module Capybara
44
53
  end
45
54
 
46
55
  def valid_keys
47
- [:wait, :url, :only_path]
56
+ [:wait, :url, :only_path, :ignore_query]
48
57
  end
49
58
 
50
59
  def assert_valid_keys
@@ -0,0 +1,19 @@
1
+ module Capybara
2
+ module Queries
3
+ class MatchQuery < Capybara::Queries::SelectorQuery
4
+ def visible
5
+ if options.has_key?(:visible)
6
+ super
7
+ else
8
+ :all
9
+ end
10
+ end
11
+
12
+ private
13
+
14
+ def valid_keys
15
+ super - COUNT_KEYS
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,251 @@
1
+ # frozen_string_literal: true
2
+ module Capybara
3
+ module Queries
4
+ class SelectorQuery < Queries::BaseQuery
5
+ attr_accessor :selector, :locator, :options, :expression, :find, :negative
6
+
7
+ VALID_KEYS = COUNT_KEYS + [:text, :id, :class, :visible, :exact, :exact_text, :match, :wait, :filter_set]
8
+ VALID_MATCH = [:first, :smart, :prefer_exact, :one]
9
+
10
+ def initialize(*args, &filter_block)
11
+ @resolved_node = nil
12
+ @options = if args.last.is_a?(Hash) then args.pop.dup else {} end
13
+ super(@options)
14
+
15
+ @filter_block = filter_block
16
+
17
+ if args[0].is_a?(Symbol)
18
+ @selector = Selector.all.fetch(args.shift) do |selector_type|
19
+ raise ArgumentError, "Unknown selector type (:#{selector_type})"
20
+ end
21
+ @locator = args.shift
22
+ else
23
+ @selector = Selector.all.values.find { |s| s.match?(args[0]) }
24
+ @locator = args.shift
25
+ end
26
+ @selector ||= Selector.all[session_options.default_selector]
27
+
28
+ warn "Unused parameters passed to #{self.class.name} : #{args}" unless args.empty?
29
+
30
+ # for compatibility with Capybara 2.0
31
+ if session_options.exact_options and @selector == Selector.all[:option]
32
+ @options[:exact] = true
33
+ end
34
+
35
+ @expression = @selector.call(@locator, @options.merge(enable_aria_label: session_options.enable_aria_label))
36
+
37
+ warn_exact_usage
38
+
39
+ assert_valid_keys
40
+ end
41
+
42
+ def name; selector.name; end
43
+ def label; selector.label or selector.name; end
44
+
45
+ def description
46
+ @description = String.new()
47
+ @description << "visible " if visible == :visible
48
+ @description << "non-visible " if visible == :hidden
49
+ @description << "#{label} #{locator.inspect}"
50
+ @description << " with#{" exact" if exact_text == true} text #{options[:text].inspect}" if options[:text]
51
+ @description << " with exact text #{options[:exact_text]}" if options[:exact_text].is_a?(String)
52
+ @description << " with id #{options[:id]}" if options[:id]
53
+ @description << " with classes [#{Array(options[:class]).join(',')}]" if options[:class]
54
+ @description << selector.description(options)
55
+ @description << " that also matches the custom filter block" if @filter_block
56
+ @description << " within #{@resolved_node.inspect}" if describe_within?
57
+ @description
58
+ end
59
+
60
+ def matches_filters?(node)
61
+ if options[:text]
62
+ regexp = if options[:text].is_a?(Regexp)
63
+ options[:text]
64
+ else
65
+ if exact_text == true
66
+ /\A#{Regexp.escape(options[:text].to_s)}\z/
67
+ else
68
+ Regexp.escape(options[:text].to_s)
69
+ end
70
+ end
71
+ text_visible = visible
72
+ text_visible = :all if text_visible == :hidden
73
+ return false if not node.text(text_visible).match(regexp)
74
+ end
75
+
76
+ if exact_text.is_a?(String)
77
+ regexp = /\A#{Regexp.escape(options[:exact_text])}\z/
78
+ text_visible = visible
79
+ text_visible = :all if text_visible == :hidden
80
+ return false if not node.text(text_visible).match(regexp)
81
+ end
82
+
83
+ case visible
84
+ when :visible then return false unless node.visible?
85
+ when :hidden then return false if node.visible?
86
+ end
87
+
88
+ res = node_filters.all? do |name, filter|
89
+ if options.has_key?(name)
90
+ filter.matches?(node, options[name])
91
+ elsif filter.default?
92
+ filter.matches?(node, filter.default)
93
+ else
94
+ true
95
+ end
96
+ end
97
+
98
+ res &&= if node.respond_to?(:session)
99
+ node.session.using_wait_time(0){ @filter_block.call(node) }
100
+ else
101
+ @filter_block.call(node)
102
+ end unless @filter_block.nil?
103
+
104
+ res
105
+
106
+ rescue *(node.respond_to?(:session) ? node.session.driver.invalid_element_errors : [])
107
+ return false
108
+ end
109
+
110
+ def visible
111
+ case (vis = options.fetch(:visible){ @selector.default_visibility(session_options.ignore_hidden_elements) })
112
+ when true then :visible
113
+ when false then :all
114
+ else vis
115
+ end
116
+ end
117
+
118
+ def exact?
119
+ return false if !supports_exact?
120
+ options.fetch(:exact, session_options.exact)
121
+ end
122
+
123
+ def match
124
+ options.fetch(:match, session_options.match)
125
+ end
126
+
127
+ def xpath(exact=nil)
128
+ exact = self.exact? if exact.nil?
129
+ expr = apply_expression_filters(@expression)
130
+ expr = if expr.respond_to?(:to_xpath) and exact
131
+ expr.to_xpath(:exact)
132
+ else
133
+ expr.to_s
134
+ end
135
+ filtered_xpath(expr)
136
+ end
137
+
138
+ def css
139
+ filtered_css(apply_expression_filters(@expression))
140
+ end
141
+
142
+ # @api private
143
+ def resolve_for(node, exact = nil)
144
+ @resolved_node = node
145
+ node.synchronize do
146
+ children = if selector.format == :css
147
+ node.find_css(self.css)
148
+ else
149
+ node.find_xpath(self.xpath(exact))
150
+ end.map do |child|
151
+ if node.is_a?(Capybara::Node::Base)
152
+ Capybara::Node::Element.new(node.session, child, node, self)
153
+ else
154
+ Capybara::Node::Simple.new(child)
155
+ end
156
+ end
157
+ Capybara::Result.new(children, self)
158
+ end
159
+ end
160
+
161
+ # @api private
162
+ def supports_exact?
163
+ @expression.respond_to? :to_xpath
164
+ end
165
+
166
+ private
167
+
168
+ def valid_keys
169
+ VALID_KEYS + custom_keys
170
+ end
171
+
172
+ def node_filters
173
+ if options.has_key?(:filter_set)
174
+ ::Capybara::Selector::FilterSet.all[options[:filter_set]].node_filters
175
+ else
176
+ @selector.node_filters
177
+ end
178
+ end
179
+
180
+ def expression_filters
181
+ filters = @selector.expression_filters
182
+ filters.merge ::Capybara::Selector::FilterSet.all[options[:filter_set]].expression_filters if options.has_key?(:filter_set)
183
+ filters
184
+ end
185
+
186
+ def custom_keys
187
+ @custom_keys ||= node_filters.keys + expression_filters.keys
188
+ end
189
+
190
+ def assert_valid_keys
191
+ super
192
+ unless VALID_MATCH.include?(match)
193
+ raise ArgumentError, "invalid option #{match.inspect} for :match, should be one of #{VALID_MATCH.map(&:inspect).join(", ")}"
194
+ end
195
+ end
196
+
197
+ def filtered_xpath(expr)
198
+ if options.has_key?(:id) || options.has_key?(:class)
199
+ expr = "(#{expr})"
200
+ expr = "#{expr}[#{XPath.attr(:id) == options[:id]}]" if options.has_key?(:id) && !custom_keys.include?(:id)
201
+ if options.has_key?(:class) && !custom_keys.include?(:class)
202
+ class_xpath = Array(options[:class]).map do |klass|
203
+ "contains(concat(' ',normalize-space(@class),' '),' #{klass} ')"
204
+ end.join(" and ")
205
+ expr = "#{expr}[#{class_xpath}]"
206
+ end
207
+ end
208
+ expr
209
+ end
210
+
211
+ def filtered_css(expr)
212
+ if options.has_key?(:id) || options.has_key?(:class)
213
+ css_selectors = expr.split(',').map(&:rstrip)
214
+ expr = css_selectors.map do |sel|
215
+ sel += "##{Capybara::Selector::CSS.escape(options[:id])}" if options.has_key?(:id) && !custom_keys.include?(:id)
216
+ sel += Array(options[:class]).map { |k| ".#{Capybara::Selector::CSS.escape(k)}"}.join if options.has_key?(:class) && !custom_keys.include?(:class)
217
+ sel
218
+ end.join(", ")
219
+ end
220
+ expr
221
+ end
222
+
223
+ def apply_expression_filters(expr)
224
+ expression_filters.inject(expr) do |memo, (name, ef)|
225
+ if options.has_key?(name)
226
+ ef.apply_filter(memo, options[name])
227
+ elsif ef.default?
228
+ ef.apply_filter(memo, ef.default)
229
+ else
230
+ memo
231
+ end
232
+ end
233
+ end
234
+
235
+ def warn_exact_usage
236
+ if options.has_key?(:exact) && !supports_exact?
237
+ warn "The :exact option only has an effect on queries using the XPath#is method. Using it with the query \"#{expression}\" has no effect."
238
+ end
239
+ end
240
+
241
+ def exact_text
242
+ options.fetch(:exact_text, session_options.exact_text)
243
+ end
244
+
245
+ def describe_within?
246
+ @resolved_node && !(@resolved_node.is_a?(::Capybara::Node::Document) ||
247
+ (@resolved_node.is_a?(::Capybara::Node::Simple) && @resolved_node.path == '/'))
248
+ end
249
+ end
250
+ end
251
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+ module Capybara
3
+ module Queries
4
+ class SiblingQuery < MatchQuery
5
+ # @api private
6
+ def resolve_for(node, exact = nil)
7
+ @sibling_node = node
8
+ node.synchronize do
9
+ match_results = super(node.session.current_scope, exact)
10
+ node.all(:xpath, XPath.preceding_sibling.union(XPath.following_sibling)) do |el|
11
+ match_results.include?(el)
12
+ end
13
+ end
14
+ end
15
+
16
+ def description
17
+ desc = super
18
+ if @sibling_node && (sibling_query = @sibling_node.instance_variable_get(:@query))
19
+ desc += " that is a sibling of #{sibling_query.description}"
20
+ end
21
+ desc
22
+ end
23
+ end
24
+ end
25
+ end
@@ -1,46 +1,97 @@
1
+ # frozen_string_literal: true
1
2
  module Capybara
2
3
  # @api private
3
4
  module Queries
4
5
  class TextQuery < BaseQuery
5
6
  def initialize(*args)
6
7
  @type = (args.first.is_a?(Symbol) || args.first.nil?) ? args.shift : nil
7
- @expected_text, @options = args
8
+ # @type = (Capybara.ignore_hidden_elements or Capybara.visible_text_only) ? :visible : :all if @type.nil?
9
+ @options = if args.last.is_a?(Hash) then args.pop.dup else {} end
10
+ super(@options)
11
+
12
+ @type = (session_options.ignore_hidden_elements or session_options.visible_text_only) ? :visible : :all if @type.nil?
13
+
14
+ @expected_text = args.shift
8
15
  unless @expected_text.is_a?(Regexp)
9
16
  @expected_text = Capybara::Helpers.normalize_whitespace(@expected_text)
10
17
  end
11
- @search_regexp = Capybara::Helpers.to_regexp(@expected_text)
12
- @options ||= {}
18
+ @search_regexp = Capybara::Helpers.to_regexp(@expected_text, nil, exact?)
19
+ warn "Unused parameters passed to #{self.class.name} : #{args}" unless args.empty?
13
20
  assert_valid_keys
14
21
  end
15
22
 
16
23
  def resolve_for(node)
17
- @actual_text = Capybara::Helpers.normalize_whitespace(node.text(@type))
24
+ @node = node
25
+ @actual_text = text(node, @type)
18
26
  @count = @actual_text.scan(@search_regexp).size
19
27
  end
20
28
 
21
29
  def failure_message
22
- description =
23
- if @expected_text.is_a?(Regexp)
24
- "text matching #{@expected_text.inspect}"
25
- else
26
- "text #{@expected_text.inspect}"
27
- end
30
+ super << build_message(true)
31
+ end
32
+
33
+ def negative_failure_message
34
+ super << build_message(false)
35
+ end
36
+
37
+ def description
38
+ if @expected_text.is_a?(Regexp)
39
+ "text matching #{@expected_text.inspect}"
40
+ else
41
+ "#{"exact " if exact?}text #{@expected_text.inspect}"
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def exact?
48
+ options.fetch(:exact, session_options.exact_text)
49
+ end
28
50
 
29
- message = Capybara::Helpers.failure_message(description, @options)
51
+ def build_message(report_on_invisible)
52
+ message = String.new()
30
53
  unless (COUNT_KEYS & @options.keys).empty?
31
54
  message << " but found #{@count} #{Capybara::Helpers.declension('time', 'times', @count)}"
32
55
  end
33
56
  message << " in #{@actual_text.inspect}"
57
+
58
+ details_message = []
59
+
60
+ if @node and !@expected_text.is_a? Regexp
61
+ insensitive_regexp = Capybara::Helpers.to_regexp(@expected_text, Regexp::IGNORECASE)
62
+ insensitive_count = @actual_text.scan(insensitive_regexp).size
63
+ if insensitive_count != @count
64
+ details_message << "it was found #{insensitive_count} #{Capybara::Helpers.declension("time", "times", insensitive_count)} using a case insensitive search"
65
+ end
66
+ end
67
+
68
+ if @node and check_visible_text? and report_on_invisible
69
+ begin
70
+ invisible_text = text(@node, :all)
71
+ invisible_count = invisible_text.scan(@search_regexp).size
72
+ if invisible_count != @count
73
+ details_message << "it was found #{invisible_count} #{Capybara::Helpers.declension("time", "times", invisible_count)} including non-visible text"
74
+ end
75
+ rescue
76
+ # An error getting the non-visible text (if element goes out of scope) should not affect the response
77
+ end
78
+ end
79
+
80
+ message << ". (However, #{details_message.join(' and ')}.)" unless details_message.empty?
81
+
82
+ message
34
83
  end
35
84
 
36
- def negative_failure_message
37
- failure_message.sub(/(to find)/, 'not \1')
85
+ def valid_keys
86
+ COUNT_KEYS + [:wait, :exact]
38
87
  end
39
88
 
40
- private
89
+ def check_visible_text?
90
+ @type == :visible
91
+ end
41
92
 
42
- def valid_keys
43
- COUNT_KEYS + [:wait]
93
+ def text(node, query_type)
94
+ Capybara::Helpers.normalize_whitespace(node.text(query_type))
44
95
  end
45
96
  end
46
97
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Capybara
2
3
  # @api private
3
4
  module Queries
@@ -5,10 +6,11 @@ module Capybara
5
6
  def initialize(expected_title, options = {})
6
7
  @expected_title = expected_title
7
8
  @options = options
9
+ super(@options)
8
10
  unless @expected_title.is_a?(Regexp)
9
11
  @expected_title = Capybara::Helpers.normalize_whitespace(@expected_title)
10
12
  end
11
- @search_regexp = Capybara::Helpers.to_regexp(@expected_title)
13
+ @search_regexp = Capybara::Helpers.to_regexp(@expected_title, nil, options.fetch(:exact, false))
12
14
  assert_valid_keys
13
15
  end
14
16
 
@@ -33,7 +35,7 @@ module Capybara
33
35
  end
34
36
 
35
37
  def valid_keys
36
- [:wait]
38
+ [:wait, :exact]
37
39
  end
38
40
  end
39
41
  end