capybara 2.13.0 → 2.18.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (122) hide show
  1. checksums.yaml +5 -5
  2. data/History.md +218 -18
  3. data/README.md +54 -23
  4. data/lib/capybara/config.rb +132 -0
  5. data/lib/capybara/cucumber.rb +1 -0
  6. data/lib/capybara/driver/base.rb +14 -0
  7. data/lib/capybara/dsl.rb +1 -3
  8. data/lib/capybara/helpers.rb +3 -3
  9. data/lib/capybara/minitest/spec.rb +14 -37
  10. data/lib/capybara/minitest.rb +95 -114
  11. data/lib/capybara/node/actions.rb +10 -10
  12. data/lib/capybara/node/base.rb +7 -2
  13. data/lib/capybara/node/element.rb +9 -3
  14. data/lib/capybara/node/finders.rb +92 -18
  15. data/lib/capybara/node/matchers.rb +21 -9
  16. data/lib/capybara/node/simple.rb +5 -0
  17. data/lib/capybara/queries/ancestor_query.rb +25 -0
  18. data/lib/capybara/queries/base_query.rb +12 -3
  19. data/lib/capybara/queries/current_path_query.rb +13 -9
  20. data/lib/capybara/queries/selector_query.rb +62 -23
  21. data/lib/capybara/queries/sibling_query.rb +25 -0
  22. data/lib/capybara/queries/text_query.rb +10 -5
  23. data/lib/capybara/queries/title_query.rb +1 -0
  24. data/lib/capybara/rack_test/browser.rb +13 -5
  25. data/lib/capybara/rack_test/driver.rb +6 -1
  26. data/lib/capybara/rack_test/form.rb +4 -3
  27. data/lib/capybara/rack_test/node.rb +1 -1
  28. data/lib/capybara/rspec/compound.rb +95 -0
  29. data/lib/capybara/rspec/matcher_proxies.rb +45 -0
  30. data/lib/capybara/rspec/matchers.rb +108 -7
  31. data/lib/capybara/rspec.rb +3 -1
  32. data/lib/capybara/selector/filter.rb +13 -41
  33. data/lib/capybara/selector/filter_set.rb +30 -4
  34. data/lib/capybara/selector/filters/base.rb +33 -0
  35. data/lib/capybara/selector/filters/expression_filter.rb +40 -0
  36. data/lib/capybara/selector/filters/node_filter.rb +27 -0
  37. data/lib/capybara/selector/selector.rb +36 -15
  38. data/lib/capybara/selector.rb +63 -42
  39. data/lib/capybara/selenium/driver.rb +177 -33
  40. data/lib/capybara/selenium/node.rb +106 -55
  41. data/lib/capybara/server.rb +6 -5
  42. data/lib/capybara/session/config.rb +114 -0
  43. data/lib/capybara/session/matchers.rb +15 -4
  44. data/lib/capybara/session.rb +178 -65
  45. data/lib/capybara/spec/fixtures/no_extension +1 -0
  46. data/lib/capybara/spec/public/test.js +18 -3
  47. data/lib/capybara/spec/session/accept_alert_spec.rb +9 -1
  48. data/lib/capybara/spec/session/accept_prompt_spec.rb +29 -1
  49. data/lib/capybara/spec/session/all_spec.rb +13 -1
  50. data/lib/capybara/spec/session/ancestor_spec.rb +85 -0
  51. data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +24 -8
  52. data/lib/capybara/spec/session/assert_selector.rb +1 -1
  53. data/lib/capybara/spec/session/assert_text.rb +8 -0
  54. data/lib/capybara/spec/session/assert_title.rb +22 -9
  55. data/lib/capybara/spec/session/attach_file_spec.rb +8 -1
  56. data/lib/capybara/spec/session/check_spec.rb +4 -4
  57. data/lib/capybara/spec/session/choose_spec.rb +2 -2
  58. data/lib/capybara/spec/session/click_button_spec.rb +1 -1
  59. data/lib/capybara/spec/session/click_link_or_button_spec.rb +3 -3
  60. data/lib/capybara/spec/session/click_link_spec.rb +1 -1
  61. data/lib/capybara/spec/session/current_url_spec.rb +3 -3
  62. data/lib/capybara/spec/session/dismiss_confirm_spec.rb +3 -3
  63. data/lib/capybara/spec/session/dismiss_prompt_spec.rb +1 -1
  64. data/lib/capybara/spec/session/evaluate_async_script_spec.rb +22 -0
  65. data/lib/capybara/spec/session/evaluate_script_spec.rb +1 -1
  66. data/lib/capybara/spec/session/fill_in_spec.rb +8 -2
  67. data/lib/capybara/spec/session/find_field_spec.rb +1 -0
  68. data/lib/capybara/spec/session/find_spec.rb +8 -6
  69. data/lib/capybara/spec/session/first_spec.rb +10 -5
  70. data/lib/capybara/spec/session/has_all_selectors_spec.rb +69 -0
  71. data/lib/capybara/spec/session/has_css_spec.rb +11 -0
  72. data/lib/capybara/spec/session/has_current_path_spec.rb +52 -7
  73. data/lib/capybara/spec/session/has_link_spec.rb +4 -4
  74. data/lib/capybara/spec/session/has_none_selectors_spec.rb +76 -0
  75. data/lib/capybara/spec/session/has_select_spec.rb +64 -6
  76. data/lib/capybara/spec/session/has_selector_spec.rb +1 -3
  77. data/lib/capybara/spec/session/has_text_spec.rb +5 -3
  78. data/lib/capybara/spec/session/has_title_spec.rb +4 -2
  79. data/lib/capybara/spec/session/has_xpath_spec.rb +5 -3
  80. data/lib/capybara/spec/session/node_spec.rb +50 -26
  81. data/lib/capybara/spec/session/refresh_spec.rb +28 -0
  82. data/lib/capybara/spec/session/reset_session_spec.rb +3 -3
  83. data/lib/capybara/spec/session/select_spec.rb +3 -2
  84. data/lib/capybara/spec/session/sibling_spec.rb +52 -0
  85. data/lib/capybara/spec/session/uncheck_spec.rb +2 -2
  86. data/lib/capybara/spec/session/unselect_spec.rb +2 -2
  87. data/lib/capybara/spec/session/visit_spec.rb +56 -1
  88. data/lib/capybara/spec/session/window/become_closed_spec.rb +11 -11
  89. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +11 -9
  90. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +4 -4
  91. data/lib/capybara/spec/session/window/within_window_spec.rb +27 -2
  92. data/lib/capybara/spec/spec_helper.rb +28 -4
  93. data/lib/capybara/spec/test_app.rb +3 -1
  94. data/lib/capybara/spec/views/form.erb +27 -1
  95. data/lib/capybara/spec/views/initial_alert.erb +10 -0
  96. data/lib/capybara/spec/views/with_fixed_header_footer.erb +17 -0
  97. data/lib/capybara/spec/views/with_hover.erb +5 -0
  98. data/lib/capybara/spec/views/with_html.erb +33 -2
  99. data/lib/capybara/spec/views/with_js.erb +12 -0
  100. data/lib/capybara/spec/views/with_windows.erb +4 -0
  101. data/lib/capybara/version.rb +1 -1
  102. data/lib/capybara/window.rb +1 -1
  103. data/lib/capybara.rb +102 -124
  104. data/spec/capybara_spec.rb +43 -21
  105. data/spec/dsl_spec.rb +1 -0
  106. data/spec/filter_set_spec.rb +28 -0
  107. data/spec/minitest_spec.rb +9 -1
  108. data/spec/minitest_spec_spec.rb +19 -5
  109. data/spec/per_session_config_spec.rb +67 -0
  110. data/spec/result_spec.rb +20 -0
  111. data/spec/rspec/shared_spec_matchers.rb +148 -44
  112. data/spec/rspec/views_spec.rb +4 -0
  113. data/spec/rspec_matchers_spec.rb +46 -0
  114. data/spec/rspec_spec.rb +77 -0
  115. data/spec/selector_spec.rb +2 -1
  116. data/spec/selenium_spec_chrome.rb +25 -17
  117. data/spec/selenium_spec_firefox.rb +2 -1
  118. data/spec/selenium_spec_marionette.rb +18 -5
  119. data/spec/session_spec.rb +44 -0
  120. data/spec/shared_selenium_session.rb +72 -8
  121. data/spec/spec_helper.rb +4 -0
  122. metadata +55 -8
@@ -55,7 +55,7 @@ module Capybara
55
55
  # @return [String] The text of the element
56
56
  #
57
57
  def text(type=nil)
58
- type ||= :all unless Capybara.ignore_hidden_elements or Capybara.visible_text_only
58
+ type ||= :all unless session_options.ignore_hidden_elements or session_options.visible_text_only
59
59
  synchronize do
60
60
  if type == :all
61
61
  base.all_text
@@ -371,9 +371,15 @@ module Capybara
371
371
  end
372
372
 
373
373
  def inspect
374
- %(#<Capybara::Node::Element tag="#{tag_name}" path="#{path}">)
374
+ %(#<Capybara::Node::Element tag="#{base.tag_name}" path="#{base.path}">)
375
375
  rescue NotSupportedByDriverError
376
- %(#<Capybara::Node::Element tag="#{tag_name}">)
376
+ %(#<Capybara::Node::Element tag="#{base.tag_name}">)
377
+ rescue => e
378
+ if session.driver.invalid_element_errors.any? { |et| e.is_a?(et)}
379
+ %(Obsolete #<Capybara::Node::Element>)
380
+ else
381
+ raise
382
+ end
377
383
  end
378
384
  end
379
385
  end
@@ -29,22 +29,71 @@ module Capybara
29
29
  # @raise [Capybara::ElementNotFound] If the element can't be found before time expires
30
30
  #
31
31
  def find(*args, &optional_filter_block)
32
- query = Capybara::Queries::SelectorQuery.new(*args, &optional_filter_block)
33
- synchronize(query.wait) do
34
- if (query.match == :smart or query.match == :prefer_exact) and query.supports_exact?
35
- result = query.resolve_for(self, true)
36
- result = query.resolve_for(self, false) if result.empty? && !query.exact?
37
- else
38
- result = query.resolve_for(self)
39
- end
40
- if query.match == :one or query.match == :smart and result.size > 1
41
- raise Capybara::Ambiguous.new("Ambiguous match, found #{result.size} elements matching #{query.description}")
42
- end
43
- if result.empty?
44
- raise Capybara::ElementNotFound.new("Unable to find #{query.description}")
45
- end
46
- result.first
47
- end.tap(&:allow_reload!)
32
+ if args.last.is_a? Hash
33
+ args.last[:session_options] = session_options
34
+ else
35
+ args.push(session_options: session_options)
36
+ end
37
+ synced_resolve Capybara::Queries::SelectorQuery.new(*args, &optional_filter_block)
38
+ end
39
+
40
+ ##
41
+ #
42
+ # Find an {Capybara::Node::Element} based on the given arguments that is also an ancestor of the element called on. +ancestor+ will raise an error if the element
43
+ # is not found.
44
+ #
45
+ # +ancestor+ takes the same options as +find+.
46
+ #
47
+ # element.ancestor('#foo').find('.bar')
48
+ # element.ancestor(:xpath, './/div[contains(., "bar")]')
49
+ # element.ancestor('ul', text: 'Quox').click_link('Delete')
50
+ #
51
+ # @param (see Capybara::Node::Finders#find)
52
+ #
53
+ # @!macro waiting_behavior
54
+ #
55
+ # @option options [Boolean] match The matching strategy to use.
56
+ #
57
+ # @return [Capybara::Node::Element] The found element
58
+ # @raise [Capybara::ElementNotFound] If the element can't be found before time expires
59
+ #
60
+ def ancestor(*args, &optional_filter_block)
61
+ if args.last.is_a? Hash
62
+ args.last[:session_options] = session_options
63
+ else
64
+ args.push(session_options: session_options)
65
+ end
66
+ synced_resolve Capybara::Queries::AncestorQuery.new(*args, &optional_filter_block)
67
+ end
68
+
69
+ ##
70
+ #
71
+ # Find an {Capybara::Node::Element} based on the given arguments that is also a sibling of the element called on. +sibling+ will raise an error if the element
72
+ # is not found.
73
+ #
74
+ #
75
+ # +sibling+ takes the same options as +find+.
76
+ #
77
+ # element.sibling('#foo').find('.bar')
78
+ # element.sibling(:xpath, './/div[contains(., "bar")]')
79
+ # element.sibling('ul', text: 'Quox').click_link('Delete')
80
+ #
81
+ # @param (see Capybara::Node::Finders#find)
82
+ #
83
+ # @macro waiting_behavior
84
+ #
85
+ # @option options [Boolean] match The matching strategy to use.
86
+ #
87
+ # @return [Capybara::Node::Element] The found element
88
+ # @raise [Capybara::ElementNotFound] If the element can't be found before time expires
89
+ #
90
+ def sibling(*args, &optional_filter_block)
91
+ if args.last.is_a? Hash
92
+ args.last[:session_options] = session_options
93
+ else
94
+ args.push(session_options: session_options)
95
+ end
96
+ synced_resolve Capybara::Queries::SiblingQuery.new(*args, &optional_filter_block)
48
97
  end
49
98
 
50
99
  ##
@@ -122,7 +171,7 @@ module Capybara
122
171
  # @option options [String] id Match buttons with the id provided
123
172
  # @option options [String] title Match buttons with the title provided
124
173
  # @option options [String] value Match buttons with the value provided
125
- # @option options [String, Array<String>] class Match links that match the class(es) provided
174
+ # @option options [String, Array<String>] class Match buttons that match the class(es) provided
126
175
  # @return [Capybara::Node::Element] The found element
127
176
  #
128
177
  def find_button(locator=nil, options={}, &optional_filter_block)
@@ -208,6 +257,11 @@ module Capybara
208
257
  # @return [Capybara::Result] A collection of found elements
209
258
  #
210
259
  def all(*args, &optional_filter_block)
260
+ if args.last.is_a? Hash
261
+ args.last[:session_options] = session_options
262
+ else
263
+ args.push(session_options: session_options)
264
+ end
211
265
  query = Capybara::Queries::SelectorQuery.new(*args, &optional_filter_block)
212
266
  synchronize(query.wait) do
213
267
  result = query.resolve_for(self)
@@ -233,7 +287,7 @@ module Capybara
233
287
  # @return [Capybara::Node::Element] The found element or nil
234
288
  #
235
289
  def first(*args, &optional_filter_block)
236
- if Capybara.wait_on_first_by_default
290
+ if session_options.wait_on_first_by_default
237
291
  options = if args.last.is_a?(Hash) then args.pop.dup else {} end
238
292
  args.push({minimum: 1}.merge(options))
239
293
  end
@@ -242,6 +296,26 @@ module Capybara
242
296
  nil
243
297
  end
244
298
 
299
+ private
300
+
301
+ def synced_resolve(query)
302
+ synchronize(query.wait) do
303
+ if (query.match == :smart or query.match == :prefer_exact)
304
+ result = query.resolve_for(self, true)
305
+ result = query.resolve_for(self, false) if result.empty? && query.supports_exact? && !query.exact?
306
+ else
307
+ result = query.resolve_for(self)
308
+ end
309
+
310
+ if query.match == :one or query.match == :smart and result.size > 1
311
+ raise Capybara::Ambiguous.new("Ambiguous match, found #{result.size} elements matching #{query.description}")
312
+ end
313
+ if result.empty?
314
+ raise Capybara::ElementNotFound.new("Unable to find #{query.description}")
315
+ end
316
+ result.first
317
+ end.tap(&:allow_reload!)
318
+ end
245
319
  end
246
320
  end
247
321
  end
@@ -114,8 +114,8 @@ module Capybara
114
114
  #
115
115
  def assert_all_of_selectors(*args, &optional_filter_block)
116
116
  options = if args.last.is_a?(Hash) then args.pop.dup else {} end
117
- selector = if args.first.is_a?(Symbol) then args.shift else Capybara.default_selector end
118
- wait = options.fetch(:wait, Capybara.default_max_wait_time)
117
+ selector = if args.first.is_a?(Symbol) then args.shift else session_options.default_selector end
118
+ wait = options.fetch(:wait, session_options.default_max_wait_time)
119
119
  synchronize(wait) do
120
120
  args.each do |locator|
121
121
  assert_selector(selector, locator, options, &optional_filter_block)
@@ -140,8 +140,8 @@ module Capybara
140
140
  #
141
141
  def assert_none_of_selectors(*args, &optional_filter_block)
142
142
  options = if args.last.is_a?(Hash) then args.pop.dup else {} end
143
- selector = if args.first.is_a?(Symbol) then args.shift else Capybara.default_selector end
144
- wait = options.fetch(:wait, Capybara.default_max_wait_time)
143
+ selector = if args.first.is_a?(Symbol) then args.shift else session_options.default_selector end
144
+ wait = options.fetch(:wait, session_options.default_max_wait_time)
145
145
  synchronize(wait) do
146
146
  args.each do |locator|
147
147
  assert_no_selector(selector, locator, options, &optional_filter_block)
@@ -432,11 +432,12 @@ module Capybara
432
432
  #
433
433
  # page.has_select?('Language', with_options: ['English', 'German'])
434
434
  #
435
- # @param [String] locator The label, name or id of a select box
436
- # @option options [Array] :options Options which should be contained in this select box
437
- # @option options [Array] :with_options Partial set of options which should be contained in this select box
438
- # @option options [String, Array] :selected Options which should be selected
439
- # @return [Boolean] Whether it exists
435
+ # @param [String] locator The label, name or id of a select box
436
+ # @option options [Array] :options Options which should be contained in this select box
437
+ # @option options [Array] :with_options Partial set of options which should be contained in this select box
438
+ # @option options [String, Array] :selected Options which should be selected
439
+ # @option options [String, Array] :with_selected Partial set of options which should minimally be selected
440
+ # @return [Boolean] Whether it exists
440
441
  #
441
442
  def has_select?(locator=nil, options={}, &optional_filter_block)
442
443
  locator, options = nil, locator if locator.is_a? Hash
@@ -680,6 +681,7 @@ module Capybara
680
681
  private
681
682
 
682
683
  def _verify_selector_result(query_args, optional_filter_block, &result_block)
684
+ _set_query_session_options(query_args)
683
685
  query = Capybara::Queries::SelectorQuery.new(*query_args, &optional_filter_block)
684
686
  synchronize(query.wait) do
685
687
  result = query.resolve_for(self)
@@ -689,6 +691,7 @@ module Capybara
689
691
  end
690
692
 
691
693
  def _verify_match_result(query_args, optional_filter_block, &result_block)
694
+ _set_query_session_options(query_args)
692
695
  query = Capybara::Queries::MatchQuery.new(*query_args, &optional_filter_block)
693
696
  synchronize(query.wait) do
694
697
  result = query.resolve_for(self.query_scope)
@@ -698,6 +701,7 @@ module Capybara
698
701
  end
699
702
 
700
703
  def _verify_text(query_args)
704
+ _set_query_session_options(query_args)
701
705
  query = Capybara::Queries::TextQuery.new(*query_args)
702
706
  synchronize(query.wait) do
703
707
  count = query.resolve_for(self)
@@ -706,6 +710,14 @@ module Capybara
706
710
  return true
707
711
  end
708
712
 
713
+ def _set_query_session_options(query_args)
714
+ if query_args.last.is_a? Hash
715
+ query_args.last[:session_options] = session_options
716
+ else
717
+ query_args.push(session_options: session_options)
718
+ end
719
+ query_args
720
+ end
709
721
  end
710
722
  end
711
723
  end
@@ -173,6 +173,11 @@ module Capybara
173
173
  def find_xpath(xpath)
174
174
  native.xpath(xpath)
175
175
  end
176
+
177
+ # @api private
178
+ def session_options
179
+ Capybara.session_options
180
+ end
176
181
  end
177
182
  end
178
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
@@ -6,13 +6,22 @@ module Capybara
6
6
  COUNT_KEYS = [:count, :minimum, :maximum, :between]
7
7
 
8
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
9
18
 
10
19
  def wait
11
- self.class.wait(options)
20
+ self.class.wait(options, session_options.default_max_wait_time)
12
21
  end
13
22
 
14
- def self.wait(options)
15
- options.fetch(:wait, Capybara.default_max_wait_time) || 0
23
+ def self.wait(options, default=Capybara.default_max_wait_time)
24
+ options.fetch(:wait, default) || 0
16
25
  end
17
26
 
18
27
  ##
@@ -6,28 +6,32 @@ module Capybara
6
6
  module Queries
7
7
  class CurrentPathQuery < BaseQuery
8
8
  def initialize(expected_path, options = {})
9
+ super(options)
9
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
+
10
13
  @options = {
11
- url: false,
12
- 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)
13
17
  assert_valid_keys
14
18
  end
15
19
 
16
20
  def resolves_for?(session)
21
+ uri = ::Addressable::URI.parse(session.current_url)
22
+ uri.query = nil if uri && options[:ignore_query]
17
23
  @actual_path = if options[:url]
18
- session.current_url
24
+ uri.to_s
19
25
  else
20
- uri = ::Addressable::URI.parse(session.current_url)
21
-
22
26
  if options[:only_path]
23
- uri.path unless uri.nil? # Ensure the parsed url isn't nil.
27
+ uri && uri.path
24
28
  else
25
- uri.request_uri unless uri.nil? # Ensure the parsed url isn't nil.
29
+ uri && uri.request_uri
26
30
  end
27
31
  end
28
32
 
29
33
  if @expected_path.is_a? Regexp
30
- @actual_path.match(@expected_path)
34
+ @actual_path.to_s.match(@expected_path)
31
35
  else
32
36
  ::Addressable::URI.parse(@expected_path) == ::Addressable::URI.parse(@actual_path)
33
37
  end
@@ -49,7 +53,7 @@ module Capybara
49
53
  end
50
54
 
51
55
  def valid_keys
52
- [:wait, :url, :only_path]
56
+ [:wait, :url, :only_path, :ignore_query]
53
57
  end
54
58
 
55
59
  def assert_valid_keys
@@ -8,29 +8,31 @@ module Capybara
8
8
  VALID_MATCH = [:first, :smart, :prefer_exact, :one]
9
9
 
10
10
  def initialize(*args, &filter_block)
11
+ @resolved_node = nil
11
12
  @options = if args.last.is_a?(Hash) then args.pop.dup else {} end
13
+ super(@options)
14
+
12
15
  @filter_block = filter_block
13
16
 
14
17
  if args[0].is_a?(Symbol)
15
18
  @selector = Selector.all.fetch(args.shift) do |selector_type|
16
- warn "Unknown selector type (:#{selector_type}), defaulting to :#{Capybara.default_selector} - This will raise an exception in a future version of Capybara"
17
- nil
19
+ raise ArgumentError, "Unknown selector type (:#{selector_type})"
18
20
  end
19
21
  @locator = args.shift
20
22
  else
21
23
  @selector = Selector.all.values.find { |s| s.match?(args[0]) }
22
24
  @locator = args.shift
23
25
  end
24
- @selector ||= Selector.all[Capybara.default_selector]
26
+ @selector ||= Selector.all[session_options.default_selector]
25
27
 
26
- warn "Unused parameters passed to #{self.class.name} : #{args.to_s}" unless args.empty?
28
+ warn "Unused parameters passed to #{self.class.name} : #{args}" unless args.empty?
27
29
 
28
30
  # for compatibility with Capybara 2.0
29
- if Capybara.exact_options and @selector == Selector.all[:option]
31
+ if session_options.exact_options and @selector == Selector.all[:option]
30
32
  @options[:exact] = true
31
33
  end
32
34
 
33
- @expression = @selector.call(@locator, @options)
35
+ @expression = @selector.call(@locator, @options.merge(enable_aria_label: session_options.enable_aria_label))
34
36
 
35
37
  warn_exact_usage
36
38
 
@@ -41,13 +43,17 @@ module Capybara
41
43
  def label; selector.label or selector.name; end
42
44
 
43
45
  def description
44
- @description = String.new("#{label} #{locator.inspect}")
46
+ @description = String.new()
47
+ @description << "visible " if visible == :visible
48
+ @description << "non-visible " if visible == :hidden
49
+ @description << "#{label} #{locator.inspect}"
45
50
  @description << " with#{" exact" if exact_text == true} text #{options[:text].inspect}" if options[:text]
46
51
  @description << " with exact text #{options[:exact_text]}" if options[:exact_text].is_a?(String)
47
52
  @description << " with id #{options[:id]}" if options[:id]
48
- @description << " with classes #{Array(options[:class]).join(',')}]" if options[:class]
53
+ @description << " with classes [#{Array(options[:class]).join(',')}]" if options[:class]
49
54
  @description << selector.description(options)
50
55
  @description << " that also matches the custom filter block" if @filter_block
56
+ @description << " within #{@resolved_node.inspect}" if describe_within?
51
57
  @description
52
58
  end
53
59
 
@@ -79,7 +85,7 @@ module Capybara
79
85
  when :hidden then return false if node.visible?
80
86
  end
81
87
 
82
- res = query_filters.all? do |name, filter|
88
+ res = node_filters.all? do |name, filter|
83
89
  if options.has_key?(name)
84
90
  filter.matches?(node, options[name])
85
91
  elsif filter.default?
@@ -89,12 +95,20 @@ module Capybara
89
95
  end
90
96
  end
91
97
 
92
- res &&= Capybara.using_wait_time(0){ @filter_block.call(node)} unless @filter_block.nil?
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
+
93
104
  res
105
+
106
+ rescue *(node.respond_to?(:session) ? node.session.driver.invalid_element_errors : [])
107
+ return false
94
108
  end
95
109
 
96
110
  def visible
97
- case (vis = options.fetch(:visible){ @selector.default_visibility })
111
+ case (vis = options.fetch(:visible){ @selector.default_visibility(session_options.ignore_hidden_elements) })
98
112
  when true then :visible
99
113
  when false then :all
100
114
  else vis
@@ -103,29 +117,31 @@ module Capybara
103
117
 
104
118
  def exact?
105
119
  return false if !supports_exact?
106
- options.fetch(:exact, Capybara.exact)
120
+ options.fetch(:exact, session_options.exact)
107
121
  end
108
122
 
109
123
  def match
110
- options.fetch(:match, Capybara.match)
124
+ options.fetch(:match, session_options.match)
111
125
  end
112
126
 
113
127
  def xpath(exact=nil)
114
128
  exact = self.exact? if exact.nil?
115
- expr = if @expression.respond_to?(:to_xpath) and exact
116
- @expression.to_xpath(:exact)
129
+ expr = apply_expression_filters(@expression)
130
+ expr = if expr.respond_to?(:to_xpath) and exact
131
+ expr.to_xpath(:exact)
117
132
  else
118
- @expression.to_s
133
+ expr.to_s
119
134
  end
120
135
  filtered_xpath(expr)
121
136
  end
122
137
 
123
138
  def css
124
- filtered_css(@expression)
139
+ filtered_css(apply_expression_filters(@expression))
125
140
  end
126
141
 
127
142
  # @api private
128
143
  def resolve_for(node, exact = nil)
144
+ @resolved_node = node
129
145
  node.synchronize do
130
146
  children = if selector.format == :css
131
147
  node.find_css(self.css)
@@ -153,16 +169,22 @@ module Capybara
153
169
  VALID_KEYS + custom_keys
154
170
  end
155
171
 
156
- def query_filters
172
+ def node_filters
157
173
  if options.has_key?(:filter_set)
158
- Capybara::Selector::FilterSet.all[options[:filter_set]].filters
174
+ ::Capybara::Selector::FilterSet.all[options[:filter_set]].node_filters
159
175
  else
160
- @selector.custom_filters
176
+ @selector.node_filters
161
177
  end
162
178
  end
163
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
+
164
186
  def custom_keys
165
- @custom_keys ||= query_filters.keys + @selector.expression_filters
187
+ @custom_keys ||= node_filters.keys + expression_filters.keys
166
188
  end
167
189
 
168
190
  def assert_valid_keys
@@ -198,14 +220,31 @@ module Capybara
198
220
  expr
199
221
  end
200
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
+
201
235
  def warn_exact_usage
202
236
  if options.has_key?(:exact) && !supports_exact?
203
- warn "The :exact option only has an effect on queries using the XPath#is method. Using it with the query \"#{expression.to_s}\" has no effect."
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."
204
238
  end
205
239
  end
206
240
 
207
241
  def exact_text
208
- options.fetch(:exact_text, Capybara.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 == '/'))
209
248
  end
210
249
  end
211
250
  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
@@ -5,13 +5,18 @@ module Capybara
5
5
  class TextQuery < BaseQuery
6
6
  def initialize(*args)
7
7
  @type = (args.first.is_a?(Symbol) || args.first.nil?) ? args.shift : nil
8
- @type = (Capybara.ignore_hidden_elements or Capybara.visible_text_only) ? :visible : :all if @type.nil?
9
- @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
10
15
  unless @expected_text.is_a?(Regexp)
11
16
  @expected_text = Capybara::Helpers.normalize_whitespace(@expected_text)
12
17
  end
13
- @options ||= {}
14
18
  @search_regexp = Capybara::Helpers.to_regexp(@expected_text, nil, exact?)
19
+ warn "Unused parameters passed to #{self.class.name} : #{args}" unless args.empty?
15
20
  assert_valid_keys
16
21
  end
17
22
 
@@ -40,7 +45,7 @@ module Capybara
40
45
  private
41
46
 
42
47
  def exact?
43
- options.fetch(:exact, Capybara.exact_text)
48
+ options.fetch(:exact, session_options.exact_text)
44
49
  end
45
50
 
46
51
  def build_message(report_on_invisible)
@@ -65,7 +70,7 @@ module Capybara
65
70
  invisible_text = text(@node, :all)
66
71
  invisible_count = invisible_text.scan(@search_regexp).size
67
72
  if invisible_count != @count
68
- details_message << ". it was found #{invisible_count} #{Capybara::Helpers.declension("time", "times", invisible_count)} including non-visible text"
73
+ details_message << "it was found #{invisible_count} #{Capybara::Helpers.declension("time", "times", invisible_count)} including non-visible text"
69
74
  end
70
75
  rescue
71
76
  # An error getting the non-visible text (if element goes out of scope) should not affect the response
@@ -6,6 +6,7 @@ module Capybara
6
6
  def initialize(expected_title, options = {})
7
7
  @expected_title = expected_title
8
8
  @options = options
9
+ super(@options)
9
10
  unless @expected_title.is_a?(Regexp)
10
11
  @expected_title = Capybara::Helpers.normalize_whitespace(@expected_title)
11
12
  end
@@ -22,6 +22,11 @@ class Capybara::RackTest::Browser
22
22
  process_and_follow_redirects(:get, path, attributes)
23
23
  end
24
24
 
25
+ def refresh
26
+ reset_cache!
27
+ request(last_request.fullpath, last_request.env)
28
+ end
29
+
25
30
  def submit(method, path, attributes)
26
31
  path = request_path if not path or path.empty?
27
32
  process_and_follow_redirects(method, path, attributes, {'HTTP_REFERER' => current_url})
@@ -45,10 +50,13 @@ class Capybara::RackTest::Browser
45
50
  def process(method, path, attributes = {}, env = {})
46
51
  new_uri = URI.parse(path)
47
52
  method.downcase! unless method.is_a? Symbol
48
-
49
- new_uri.path = request_path if path.start_with?("?")
50
- new_uri.path = "/" if new_uri.path.empty?
51
- new_uri.path = request_path.sub(%r(/[^/]*$), '/') + new_uri.path unless new_uri.path.start_with?('/')
53
+ if path.empty?
54
+ new_uri.path = request_path
55
+ else
56
+ new_uri.path = request_path if path.start_with?("?")
57
+ new_uri.path = "/" if new_uri.path.empty?
58
+ new_uri.path = request_path.sub(%r(/[^/]*$), '/') + new_uri.path unless new_uri.path.start_with?('/')
59
+ end
52
60
  new_uri.scheme ||= @current_scheme
53
61
  new_uri.host ||= @current_host
54
62
  new_uri.port ||= @current_port unless new_uri.default_port == @current_port
@@ -68,7 +76,7 @@ class Capybara::RackTest::Browser
68
76
  end
69
77
 
70
78
  def reset_host!
71
- uri = URI.parse(Capybara.app_host || Capybara.default_host)
79
+ uri = URI.parse(driver.session_options.app_host || driver.session_options.default_host)
72
80
  @current_scheme = uri.scheme
73
81
  @current_host = uri.host
74
82
  @current_port = uri.port