capybara 2.13.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 (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