capybara 3.35.2 → 3.37.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (127) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +59 -4
  3. data/README.md +5 -1
  4. data/lib/capybara/config.rb +16 -4
  5. data/lib/capybara/driver/base.rb +4 -0
  6. data/lib/capybara/driver/node.rb +5 -1
  7. data/lib/capybara/dsl.rb +4 -10
  8. data/lib/capybara/helpers.rb +2 -11
  9. data/lib/capybara/minitest/spec.rb +2 -2
  10. data/lib/capybara/node/actions.rb +10 -5
  11. data/lib/capybara/node/document.rb +2 -2
  12. data/lib/capybara/node/element.rb +13 -2
  13. data/lib/capybara/node/finders.rb +2 -2
  14. data/lib/capybara/node/simple.rb +5 -1
  15. data/lib/capybara/queries/active_element_query.rb +18 -0
  16. data/lib/capybara/queries/ancestor_query.rb +2 -1
  17. data/lib/capybara/queries/current_path_query.rb +1 -1
  18. data/lib/capybara/queries/selector_query.rb +34 -8
  19. data/lib/capybara/queries/sibling_query.rb +2 -1
  20. data/lib/capybara/rack_test/browser.rb +41 -6
  21. data/lib/capybara/rack_test/driver.rb +4 -4
  22. data/lib/capybara/rack_test/node.rb +10 -7
  23. data/lib/capybara/registration_container.rb +0 -3
  24. data/lib/capybara/registrations/drivers.rb +3 -3
  25. data/lib/capybara/rspec/matcher_proxies.rb +3 -3
  26. data/lib/capybara/rspec/matchers/have_selector.rb +5 -5
  27. data/lib/capybara/rspec/matchers.rb +14 -14
  28. data/lib/capybara/selector/builders/css_builder.rb +1 -1
  29. data/lib/capybara/selector/builders/xpath_builder.rb +1 -1
  30. data/lib/capybara/selector/css.rb +1 -1
  31. data/lib/capybara/selector/definition/button.rb +9 -4
  32. data/lib/capybara/selector/definition/checkbox.rb +1 -1
  33. data/lib/capybara/selector/definition/file_field.rb +1 -1
  34. data/lib/capybara/selector/definition/fillable_field.rb +1 -1
  35. data/lib/capybara/selector/definition/radio_button.rb +1 -1
  36. data/lib/capybara/selector/definition.rb +3 -1
  37. data/lib/capybara/selector/filter_set.rb +4 -6
  38. data/lib/capybara/selector.rb +1 -0
  39. data/lib/capybara/selenium/driver.rb +25 -11
  40. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +1 -1
  41. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +1 -1
  42. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +1 -1
  43. data/lib/capybara/selenium/node.rb +22 -8
  44. data/lib/capybara/selenium/nodes/chrome_node.rb +1 -1
  45. data/lib/capybara/selenium/nodes/edge_node.rb +1 -1
  46. data/lib/capybara/selenium/nodes/firefox_node.rb +1 -1
  47. data/lib/capybara/selenium/nodes/safari_node.rb +2 -2
  48. data/lib/capybara/server/animation_disabler.rb +35 -17
  49. data/lib/capybara/session/config.rb +1 -1
  50. data/lib/capybara/session.rb +20 -23
  51. data/lib/capybara/spec/session/active_element_spec.rb +31 -0
  52. data/lib/capybara/spec/session/all_spec.rb +9 -13
  53. data/lib/capybara/spec/session/assert_text_spec.rb +17 -17
  54. data/lib/capybara/spec/session/check_spec.rb +9 -0
  55. data/lib/capybara/spec/session/choose_spec.rb +6 -0
  56. data/lib/capybara/spec/session/has_any_selectors_spec.rb +4 -0
  57. data/lib/capybara/spec/session/has_button_spec.rb +24 -0
  58. data/lib/capybara/spec/session/has_current_path_spec.rb +2 -2
  59. data/lib/capybara/spec/session/has_field_spec.rb +25 -1
  60. data/lib/capybara/spec/session/has_link_spec.rb +24 -0
  61. data/lib/capybara/spec/session/has_select_spec.rb +4 -4
  62. data/lib/capybara/spec/session/has_selector_spec.rb +15 -0
  63. data/lib/capybara/spec/session/has_text_spec.rb +2 -6
  64. data/lib/capybara/spec/session/node_spec.rb +43 -1
  65. data/lib/capybara/spec/session/scroll_spec.rb +4 -4
  66. data/lib/capybara/spec/session/visit_spec.rb +14 -0
  67. data/lib/capybara/spec/session/window/window_spec.rb +1 -1
  68. data/lib/capybara/spec/spec_helper.rb +4 -3
  69. data/lib/capybara/spec/test_app.rb +50 -8
  70. data/lib/capybara/spec/views/animated.erb +1 -1
  71. data/lib/capybara/spec/views/form.erb +11 -3
  72. data/lib/capybara/spec/views/frame_child.erb +1 -1
  73. data/lib/capybara/spec/views/frame_one.erb +1 -1
  74. data/lib/capybara/spec/views/frame_parent.erb +1 -1
  75. data/lib/capybara/spec/views/frame_two.erb +1 -1
  76. data/lib/capybara/spec/views/initial_alert.erb +2 -1
  77. data/lib/capybara/spec/views/layout.erb +10 -0
  78. data/lib/capybara/spec/views/obscured.erb +1 -1
  79. data/lib/capybara/spec/views/offset.erb +2 -1
  80. data/lib/capybara/spec/views/path.erb +2 -2
  81. data/lib/capybara/spec/views/popup_one.erb +1 -1
  82. data/lib/capybara/spec/views/popup_two.erb +1 -1
  83. data/lib/capybara/spec/views/react.erb +2 -2
  84. data/lib/capybara/spec/views/scroll.erb +2 -1
  85. data/lib/capybara/spec/views/spatial.erb +1 -1
  86. data/lib/capybara/spec/views/with_animation.erb +2 -3
  87. data/lib/capybara/spec/views/with_base_tag.erb +2 -2
  88. data/lib/capybara/spec/views/with_dragula.erb +2 -2
  89. data/lib/capybara/spec/views/with_fixed_header_footer.erb +2 -1
  90. data/lib/capybara/spec/views/with_hover.erb +2 -2
  91. data/lib/capybara/spec/views/with_html.erb +1 -1
  92. data/lib/capybara/spec/views/with_jquery_animation.erb +1 -1
  93. data/lib/capybara/spec/views/with_js.erb +2 -3
  94. data/lib/capybara/spec/views/with_jstree.erb +1 -1
  95. data/lib/capybara/spec/views/with_namespace.erb +1 -0
  96. data/lib/capybara/spec/views/with_shadow.erb +31 -0
  97. data/lib/capybara/spec/views/with_slow_unload.erb +2 -1
  98. data/lib/capybara/spec/views/with_sortable_js.erb +2 -2
  99. data/lib/capybara/spec/views/with_unload_alert.erb +1 -0
  100. data/lib/capybara/spec/views/with_windows.erb +1 -1
  101. data/lib/capybara/spec/views/within_frames.erb +1 -1
  102. data/lib/capybara/version.rb +1 -1
  103. data/lib/capybara/window.rb +1 -1
  104. data/lib/capybara.rb +19 -22
  105. data/spec/basic_node_spec.rb +16 -3
  106. data/spec/dsl_spec.rb +3 -3
  107. data/spec/fixtures/selenium_driver_rspec_failure.rb +2 -2
  108. data/spec/fixtures/selenium_driver_rspec_success.rb +2 -2
  109. data/spec/rack_test_spec.rb +20 -10
  110. data/spec/result_spec.rb +32 -35
  111. data/spec/rspec/features_spec.rb +3 -3
  112. data/spec/rspec/scenarios_spec.rb +1 -1
  113. data/spec/rspec/shared_spec_matchers.rb +2 -2
  114. data/spec/sauce_spec_chrome.rb +3 -3
  115. data/spec/selector_spec.rb +1 -1
  116. data/spec/selenium_spec_chrome.rb +9 -10
  117. data/spec/selenium_spec_chrome_remote.rb +9 -8
  118. data/spec/selenium_spec_firefox.rb +8 -3
  119. data/spec/selenium_spec_firefox_remote.rb +2 -2
  120. data/spec/selenium_spec_ie.rb +3 -6
  121. data/spec/selenium_spec_safari.rb +31 -19
  122. data/spec/server_spec.rb +5 -5
  123. data/spec/shared_selenium_node.rb +0 -4
  124. data/spec/shared_selenium_session.rb +20 -10
  125. data/spec/spec_helper.rb +1 -1
  126. metadata +37 -14
  127. data/lib/capybara/spec/views/with_title.erb +0 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e707b56213a558121aec02091da3b9558d49231937d45c38dfaf80a2dd29b42c
4
- data.tar.gz: c6fee456f607511bd5e2c2113f92100732c1bc317ad027f457bd762f66374f1d
3
+ metadata.gz: 125837a4db6ae6c9b12ab2339f5daa673397ef9cdb9054689fb1d155a5d6e62f
4
+ data.tar.gz: 445b064e8ae642678457cbf2739a1a6124b7d35dd6aaf295d3d9edce4f6ee8f3
5
5
  SHA512:
6
- metadata.gz: 03f2c85d4a433acc5ae479698b568c90069019fdde4386d7a304c48563ddb2fde06912ca158505734f92879206eb74fa96043e39f8f89c74e76f1a539ed4ae7a
7
- data.tar.gz: aa26c8230d97e0716cb65fac45a660724430ea945a4292afeeba8ef7f49915b8aefb3eda0a592177c6bae8689debab283ea37a7594f01c46a0f453d4954cac54
6
+ metadata.gz: 95154994ad904eae4c9eeb73605bcc273daef240a0cbf241afa8cf1bcfef1ca2de52ecd159fe551ee2d863a0a9a07a2fb9a3beab659f334ec5782f50c5af4ba1
7
+ data.tar.gz: 600e6610c5c48ac5619d1c4cf2ddfc83b86d5315ea259c7d8a6476863a14d4a209bcec0c6334dab5f3ebefac5eaeb1ccaa8a263114a5838f7a49a8483fb44538
data/History.md CHANGED
@@ -1,12 +1,68 @@
1
+ # Version 3.37.0
2
+ Release date: 2022-05-07
3
+
4
+ ### Changed
5
+
6
+ * Ruby 2.7.0+ is now required
7
+
8
+ ### Added
9
+
10
+ * [Beta] CSP nonces inserted into animation disabler additions - Issue #2542
11
+ * Support `<base>` element in rack-test driver - ISsue #2544
12
+ * [Beta] `Element#shadow_root` support. Requires selenium-webdriver 4.1+. Only currently supported with Chrome when using the selenium driver. Note: only CSS can be used to find elements from the shadow root. Therefore you won't be able to use most Capybara helper methods (`fill_in`, `click_link`, `find_field`, etc) directly from the shadow root since those locators are built using XPath. If you first locate a descendant from the shadow root using CSS then you should be able to use all the Capybara methods from there.
13
+ * Regexp now supported for `exact_text` finder option
14
+
15
+ ### Fixed
16
+
17
+ * Fragments in referer headers in rack-test driver - Issue #2525
18
+ * Selenium v4.1 deprecation notice
19
+
20
+ # Version 3.36.0
21
+ Release date: 2021-10-24
22
+
23
+ ### Changed
24
+
25
+ * Ruby 2.6.0+ is now required
26
+ * Minimum selenium-webdriver supported is now 3.142.7
27
+
28
+ ### Added
29
+
30
+ * Support for selenium-webdriver 4.x
31
+ * `allow_label_click` accepts click options to be used when clicking an associated label
32
+ * Deprecated `allow_gumbo=` in favor of `use_html5_parsing=` to enable use of Nokogiri::HTML5 when available
33
+ * `Session#active_element` returns the element with focus - Not supported by the `RackTest` driver [Sean Doyle]
34
+ * Support `focused:` filter for finding interactive elements - Not supported by the `RackTest` driver [Sean Doyle]
35
+
36
+ ### Fixed
37
+
38
+ * Sibling and ancestor queries now work with Simple::Node - Issue #2452
39
+ * rack_test correctly ignores readonly attribute on specific input element types
40
+ * `Node#all_text` always returns a string - Issue #2477
41
+ * `have_any_of_selectors` negated match - Issue #2473
42
+ * `Document#scroll_to` fixed for standards behavior - pass quirks: true if you need the older behavior [Eric Anderson]
43
+ * Use capture on attach file event listener for better React compatibility [Jeff Way]
44
+ * Animation disabler produces valid HTML [Javi Martin]
45
+
46
+ ### Removed
47
+
48
+ * References to non-w3c mode in drivers/tests. Non-w3c mode is obsolete and no one should be using it anymore. Capybara hasn't been testing/supporting it in a while
49
+
50
+ # Version 3.35.3
51
+ Release date: 2021-01-29
52
+
53
+ ### Fixed
54
+
55
+ * Just a release to have the correct dates in the History.md in released gem
56
+
1
57
  # Version 3.35.2
2
- Release date: 2020-01-29
58
+ Release date: 2021-01-29
3
59
 
4
60
  ### Fixed
5
61
 
6
62
  * Selenium deprecation suppressor with Selenium 3.x
7
63
 
8
64
  # Version 3.35.1
9
- Release date: 2020-01-26
65
+ Release date: 2021-01-26
10
66
 
11
67
  ### Fixed
12
68
 
@@ -14,7 +70,7 @@ Release date: 2020-01-26
14
70
  * 'Capybara.test_id' usage with the :button selector - Issue #2443
15
71
 
16
72
  # Version 3.35.0
17
- Release date: 2020-01-25
73
+ Release date: 2021-01-25
18
74
 
19
75
  ### Added
20
76
 
@@ -1892,4 +1948,3 @@ too many users, please read the release notes carefully!
1892
1948
  * clicking links where the image's alt attribute contains the text is now possible
1893
1949
  * within_fieldset and within_table work when the default selector is CSS
1894
1950
  * boolean attributes work the same across drivers (return true/false)
1895
-
data/README.md CHANGED
@@ -74,7 +74,7 @@ GitHub): http://groups.google.com/group/ruby-capybara
74
74
 
75
75
  ## <a name="setup"></a>Setup
76
76
 
77
- Capybara requires Ruby 2.5.0 or later. To install, add this line to your
77
+ Capybara requires Ruby 2.7.0 or later. To install, add this line to your
78
78
  `Gemfile` and run `bundle install`:
79
79
 
80
80
  ```ruby
@@ -151,6 +151,8 @@ RSpec](https://relishapp.com/rspec/rspec-rails/v/4-0/docs/directory-structure))
151
151
  and if you have your Capybara specs in a different directory, then tag the
152
152
  example groups with `type: :feature` or `type: :system` depending on which type of test you're writing.
153
153
 
154
+ If you are using Rails system specs please see [their documentation](https://relishapp.com/rspec/rspec-rails/docs/system-specs/system-spec#system-specs-driven-by-selenium-chrome-headless) for selecting the driver you wish to use.
155
+
154
156
  If you are not using Rails, tag all the example groups in which you want to use
155
157
  Capybara with `type: :feature`.
156
158
 
@@ -258,6 +260,8 @@ end
258
260
 
259
261
  ## <a name="using-capybara-with-minitest"></a>Using Capybara with Minitest
260
262
 
263
+ * If you are using Rails system tests please see their documentation for information on selecting the driver you wish to use.
264
+
261
265
  * If you are using Rails, but not using Rails system tests, add the following code in your `test_helper.rb`
262
266
  file to make Capybara available in all test cases deriving from
263
267
  `ActionDispatch::IntegrationTest`:
@@ -7,10 +7,12 @@ module Capybara
7
7
  class Config
8
8
  extend Forwardable
9
9
 
10
- OPTIONS = %i[app reuse_server threadsafe server default_driver javascript_driver allow_gumbo].freeze
10
+ OPTIONS = %i[
11
+ app reuse_server threadsafe server default_driver javascript_driver use_html5_parsing allow_gumbo
12
+ ].freeze
11
13
 
12
- attr_accessor :app, :allow_gumbo
13
- attr_reader :reuse_server, :threadsafe, :session_options
14
+ attr_accessor :app, :use_html5_parsing
15
+ attr_reader :reuse_server, :threadsafe, :session_options # rubocop:disable Style/BisectedAttrAccessor
14
16
  attr_writer :default_driver, :javascript_driver
15
17
 
16
18
  SessionConfig::OPTIONS.each do |method|
@@ -22,7 +24,7 @@ module Capybara
22
24
  @javascript_driver = nil
23
25
  end
24
26
 
25
- attr_writer :reuse_server
27
+ attr_writer :reuse_server # rubocop:disable Style/BisectedAttrAccessor
26
28
 
27
29
  def threadsafe=(bool)
28
30
  if (bool != threadsafe) && Session.instance_created?
@@ -88,5 +90,15 @@ module Capybara
88
90
  end
89
91
  @deprecation_notified[method] = true
90
92
  end
93
+
94
+ def allow_gumbo=(val)
95
+ deprecate('allow_gumbo=', 'use_html5_parsing=')
96
+ self.use_html5_parsing = val
97
+ end
98
+
99
+ def allow_gumbo
100
+ deprecate('allow_gumbo', 'use_html5_parsing')
101
+ use_html5_parsing
102
+ end
91
103
  end
92
104
  end
@@ -63,6 +63,10 @@ class Capybara::Driver::Base
63
63
  raise Capybara::NotSupportedByDriverError, 'Capybara::Driver::Base#send_keys'
64
64
  end
65
65
 
66
+ def active_element
67
+ raise Capybara::NotSupportedByDriverError, 'Capybara::Driver::Base#active_element'
68
+ end
69
+
66
70
  ##
67
71
  #
68
72
  # @param frame [Capybara::Node::Element, :parent, :top] The iframe element to switch to
@@ -125,6 +125,10 @@ module Capybara
125
125
  raise NotSupportedByDriverError, 'Capybara::Driver::Node#trigger'
126
126
  end
127
127
 
128
+ def shadow_root
129
+ raise NotSupportedByDriverError, 'Capybara::Driver::Node#shadow_root'
130
+ end
131
+
128
132
  def inspect
129
133
  %(#<#{self.class} tag="#{tag_name}" path="#{path}">)
130
134
  rescue NotSupportedByDriverError
@@ -132,7 +136,7 @@ module Capybara
132
136
  end
133
137
 
134
138
  def ==(other)
135
- raise NotSupportedByDriverError, 'Capybara::Driver::Node#=='
139
+ eql?(other) || (other.respond_to?(:native) && native == other.native)
136
140
  end
137
141
  end
138
142
  end
data/lib/capybara/dsl.rb CHANGED
@@ -47,17 +47,11 @@ module Capybara
47
47
  end
48
48
 
49
49
  Session::DSL_METHODS.each do |method|
50
- if RUBY_VERSION >= '2.7'
51
- class_eval <<~METHOD, __FILE__, __LINE__ + 1
52
- def #{method}(...)
53
- page.method("#{method}").call(...)
54
- end
55
- METHOD
56
- else
57
- define_method method do |*args, &block|
58
- page.send method, *args, &block
50
+ class_eval <<~METHOD, __FILE__, __LINE__ + 1
51
+ def #{method}(...)
52
+ page.method("#{method}").call(...)
59
53
  end
60
- end
54
+ METHOD
61
55
  end
62
56
  end
63
57
 
@@ -75,23 +75,14 @@ module Capybara
75
75
 
76
76
  filter = %r{lib/capybara/|lib/rspec/|lib/minitest/}
77
77
  new_trace = trace.take_while { |line| line !~ filter }
78
- new_trace = trace.reject { |line| line =~ filter } if new_trace.empty?
78
+ new_trace = trace.grep_v(filter) if new_trace.empty?
79
79
  new_trace = trace.dup if new_trace.empty?
80
80
 
81
81
  new_trace.first.split(/:in /, 2).first
82
82
  end
83
83
 
84
84
  def warn(message, uplevel: 1)
85
- return Kernel.warn(message, uplevel: uplevel) if RUBY_VERSION >= '2.6'
86
-
87
- # TODO: Remove when we drop support for Ruby 2.5
88
- # Workaround for emulating `warn '...', uplevel: n` in Ruby 2.5 or lower.
89
- if (match = /^(?<file>.+?):(?<line>\d+)(?::in `.*')?/.match(caller[uplevel]))
90
- location = [match[:file], match[:line]].join(':')
91
- Kernel.warn "#{location}: #{message}"
92
- else
93
- Kernel.warn message
94
- end
85
+ Kernel.warn(message, uplevel: uplevel)
95
86
  end
96
87
 
97
88
  if defined?(Process::CLOCK_MONOTONIC)
@@ -246,9 +246,9 @@ module Capybara
246
246
 
247
247
  ##
248
248
  # @deprecated
249
- def must_have_style(*args, **kw_args, &block)
249
+ def must_have_style(...)
250
250
  warn 'must_have_style is deprecated, please use must_match_style'
251
- must_match_style(*args, **kw_args, &block)
251
+ must_match_style(...)
252
252
  end
253
253
  end
254
254
  end
@@ -92,8 +92,9 @@ module Capybara
92
92
  end
93
93
 
94
94
  # @!macro label_click
95
- # @option options [Boolean] allow_label_click
95
+ # @option options [Boolean, Hash] allow_label_click
96
96
  # Attempt to click the label to toggle state if element is non-visible. Defaults to {Capybara.configure automatic_label_click}.
97
+ # If set to a Hash it is passed as options to the `click` on the label
97
98
 
98
99
  ##
99
100
  #
@@ -277,7 +278,7 @@ module Capybara
277
278
  # @return [Capybara::Node::Element] The file field element
278
279
  def attach_file(locator = nil, paths, make_visible: nil, **options) # rubocop:disable Style/OptionalArguments
279
280
  if locator && block_given?
280
- raise ArgumentError, '``#attach_file` does not support passing both a locator and a block'
281
+ raise ArgumentError, '`#attach_file` does not support passing both a locator and a block'
281
282
  end
282
283
 
283
284
  Array(paths).each do |path|
@@ -314,7 +315,7 @@ module Capybara
314
315
 
315
316
  begin
316
317
  find(:datalist_input, from, **options)
317
- rescue Capybara::ElementNotFound => dlinput_error # rubocop:disable Naming/RescuedExceptionsVariableName
318
+ rescue Capybara::ElementNotFound => dlinput_error
318
319
  raise Capybara::ElementNotFound, "#{select_error.message} and #{dlinput_error.message}"
319
320
  end
320
321
  end
@@ -371,7 +372,11 @@ module Capybara
371
372
 
372
373
  begin
373
374
  el ||= find(selector, locator, **options.merge(visible: :all))
374
- el.session.find(:label, for: el, visible: true, match: :first).click unless el.checked? == checked
375
+ unless el.checked? == checked
376
+ el.session
377
+ .find(:label, for: el, visible: true, match: :first)
378
+ .click(**(Hash.try_convert(allow_label_click) || {}))
379
+ end
375
380
  rescue StandardError # swallow extra errors - raise original
376
381
  raise e
377
382
  end
@@ -408,7 +413,7 @@ module Capybara
408
413
  this.removeEventListener('click', file_catcher);
409
414
  e.preventDefault();
410
415
  }
411
- })
416
+ }, {capture: true})
412
417
  JS
413
418
  end
414
419
  end
@@ -40,8 +40,8 @@ module Capybara
40
40
  find(:xpath, '/html').evaluate_script(*args)
41
41
  end
42
42
 
43
- def scroll_to(*args, **options)
44
- find(:xpath, '//body').scroll_to(*args, **options)
43
+ def scroll_to(*args, quirks: false, **options)
44
+ find(:xpath, quirks ? '//body' : '/html').scroll_to(*args, **options)
45
45
  end
46
46
  end
47
47
  end
@@ -115,7 +115,7 @@ module Capybara
115
115
  #
116
116
  # @return [Capybara::Node::Element] The element
117
117
  def set(value, **options)
118
- if ENV['CAPYBARA_THOROUGH'] && readonly?
118
+ if ENV.fetch('CAPYBARA_THOROUGH', nil) && readonly?
119
119
  raise Capybara::ReadOnlyElementError, "Attempt to set readonly element with value: #{value}"
120
120
  end
121
121
 
@@ -472,6 +472,17 @@ module Capybara
472
472
  self
473
473
  end
474
474
 
475
+ ##
476
+ #
477
+ # Return the shadow_root for the current element
478
+ #
479
+ # @return [Capybara::Node::Element] The shadow root
480
+
481
+ def shadow_root
482
+ root = synchronize { base.shadow_root }
483
+ root && Capybara::Node::Element.new(session, root, nil, nil)
484
+ end
485
+
475
486
  ##
476
487
  #
477
488
  # Execute the given JS in the context of the element not returning a result. This is useful for scripts that return
@@ -550,7 +561,7 @@ module Capybara
550
561
  return self unless @allow_reload
551
562
 
552
563
  begin
553
- reloaded = @query.resolve_for(query_scope.reload)[@query_idx.to_i]
564
+ reloaded = @query.resolve_for(query_scope ? query_scope.reload : session)[@query_idx.to_i]
554
565
  @base = reloaded.base if reloaded
555
566
  rescue StandardError => e
556
567
  raise e unless catch_error?(e)
@@ -18,11 +18,11 @@ module Capybara
18
18
  #
19
19
  # @!macro system_filters
20
20
  # @option options [String, Regexp] text Only find elements which contain this text or match this regexp
21
- # @option options [String, Boolean] exact_text
21
+ # @option options [String, Regexp, String] exact_text
22
22
  # When String the elements contained text must match exactly, when Boolean controls whether the `text` option must match exactly.
23
23
  # Defaults to {Capybara.configure exact_text}.
24
24
  # @option options [Boolean] normalize_ws
25
- # Whether the `text`/`exact_text` options are compared against elment text with whitespace normalized or as returned by the driver.
25
+ # Whether the `text`/`exact_text` options are compared against element text with whitespace normalized or as returned by the driver.
26
26
  # Defaults to {Capybara.configure default_normalize_ws}.
27
27
  # @option options [Boolean, Symbol] visible
28
28
  # Only find elements with the specified visibility. Defaults to behavior indicated by {Capybara.configure ignore_hidden_elements}.
@@ -110,7 +110,7 @@ module Capybara
110
110
  # No need for an xpath if only checking the current element
111
111
  !(native.key?('hidden') ||
112
112
  /display:\s?none/.match?(native[:style] || '') ||
113
- %w[script head].include?(tag_name))
113
+ %w[script head style].include?(tag_name))
114
114
  end
115
115
  end
116
116
 
@@ -191,6 +191,10 @@ module Capybara
191
191
  {}
192
192
  end
193
193
 
194
+ def ==(other)
195
+ eql?(other) || (other.respond_to?(:native) && native == other.native)
196
+ end
197
+
194
198
  private
195
199
 
196
200
  def option_value(option)
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capybara
4
+ # @api private
5
+ module Queries
6
+ class ActiveElementQuery < BaseQuery
7
+ def initialize(**options)
8
+ @options = options
9
+ super(@options)
10
+ end
11
+
12
+ def resolve_for(session)
13
+ node = session.driver.active_element
14
+ [Capybara::Node::Element.new(session, node, nil, self)]
15
+ end
16
+ end
17
+ end
18
+ end
@@ -8,7 +8,8 @@ module Capybara
8
8
  @child_node = node
9
9
 
10
10
  node.synchronize do
11
- match_results = super(node.session.current_scope, exact)
11
+ scope = node.respond_to?(:session) ? node.session.current_scope : node.find(:xpath, '/*')
12
+ match_results = super(scope, exact)
12
13
  ancestors = node.find_xpath(XPath.ancestor.to_s)
13
14
  .map(&method(:to_element))
14
15
  .select { |el| match_results.include?(el) }
@@ -19,7 +19,7 @@ module Capybara
19
19
 
20
20
  def resolves_for?(session)
21
21
  uri = ::Addressable::URI.parse(session.current_url)
22
- @actual_path = (options[:ignore_query] ? uri&.omit(:query) : uri).yield_self do |u|
22
+ @actual_path = (options[:ignore_query] ? uri&.omit(:query) : uri).then do |u|
23
23
  options[:url] ? u&.to_s : u&.request_uri
24
24
  end
25
25
 
@@ -9,7 +9,7 @@ module Capybara
9
9
 
10
10
  SPATIAL_KEYS = %i[above below left_of right_of near].freeze
11
11
  VALID_KEYS = SPATIAL_KEYS + COUNT_KEYS +
12
- %i[text id class style visible obscured exact exact_text normalize_ws match wait filter_set]
12
+ %i[text id class style visible obscured exact exact_text normalize_ws match wait filter_set focused]
13
13
  VALID_MATCH = %i[first smart prefer_exact one].freeze
14
14
 
15
15
  def initialize(*args,
@@ -27,6 +27,12 @@ module Capybara
27
27
  @order = order
28
28
  @filter_cache = Hash.new { |hsh, key| hsh[key] = {} }
29
29
 
30
+ if @options[:text].is_a?(Regexp) && [true, false].include?(@options[:exact_text])
31
+ Capybara::Helpers.warn(
32
+ "Boolean 'exact_text' option is not supported when 'text' option is a Regexp - ignoring"
33
+ )
34
+ end
35
+
30
36
  super(@options)
31
37
  self.session_options = session_options
32
38
 
@@ -73,6 +79,8 @@ module Capybara
73
79
 
74
80
  desc << " with id #{options[:id]}" if options[:id]
75
81
  desc << " with classes [#{Array(options[:class]).join(',')}]" if options[:class]
82
+ desc << ' that is focused' if options[:focused]
83
+ desc << ' that is not focused' if options[:focused] == false
76
84
 
77
85
  desc << case options[:style]
78
86
  when String
@@ -371,6 +379,10 @@ module Capybara
371
379
  options.key?(:style) && !custom_keys.include?(:style)
372
380
  end
373
381
 
382
+ def use_default_focused_filter?
383
+ options.key?(:focused) && !custom_keys.include?(:focused)
384
+ end
385
+
374
386
  def use_spatial_filter?
375
387
  options.values_at(*SPATIAL_KEYS).compact.any?
376
388
  end
@@ -435,6 +447,7 @@ module Capybara
435
447
  matches_id_filter?(node) &&
436
448
  matches_class_filter?(node) &&
437
449
  matches_style_filter?(node) &&
450
+ matches_focused_filter?(node) &&
438
451
  matches_text_filter?(node) &&
439
452
  matches_exact_text_filter?(node)
440
453
  end
@@ -494,6 +507,12 @@ module Capybara
494
507
  end
495
508
  end
496
509
 
510
+ def matches_focused_filter?(node)
511
+ return true unless use_default_focused_filter?
512
+
513
+ (node == node.session.active_element) == options[:focused]
514
+ end
515
+
497
516
  def need_to_process_classes?
498
517
  case options[:class]
499
518
  when Regexp then true
@@ -528,16 +547,19 @@ module Capybara
528
547
  def matches_text_filter?(node)
529
548
  value = options[:text]
530
549
  return true unless value
531
- return matches_text_exactly?(node, value) if exact_text == true
550
+ return matches_text_exactly?(node, value) if exact_text == true && !value.is_a?(Regexp)
532
551
 
533
552
  regexp = value.is_a?(Regexp) ? value : Regexp.escape(value.to_s)
534
553
  matches_text_regexp?(node, regexp)
535
554
  end
536
555
 
537
556
  def matches_exact_text_filter?(node)
538
- return true unless exact_text.is_a?(String)
539
-
540
- matches_text_exactly?(node, exact_text)
557
+ case exact_text
558
+ when String, Regexp
559
+ matches_text_exactly?(node, exact_text)
560
+ else
561
+ true
562
+ end
541
563
  end
542
564
 
543
565
  def matches_visibility_filters?(node)
@@ -565,17 +587,21 @@ module Capybara
565
587
 
566
588
  def matches_text_exactly?(node, value)
567
589
  regexp = value.is_a?(Regexp) ? value : /\A#{Regexp.escape(value.to_s)}\z/
568
- matches_text_regexp?(node, regexp)
590
+ matches_text_regexp(node, regexp).then { |m| m&.pre_match == '' && m&.post_match == '' }
569
591
  end
570
592
 
571
593
  def normalize_ws
572
594
  options.fetch(:normalize_ws, session_options.default_normalize_ws)
573
595
  end
574
596
 
575
- def matches_text_regexp?(node, regexp)
597
+ def matches_text_regexp(node, regexp)
576
598
  text_visible = visible
577
599
  text_visible = :all if text_visible == :hidden
578
- node.text(text_visible, normalize_ws: normalize_ws).match?(regexp)
600
+ node.text(text_visible, normalize_ws: normalize_ws).match(regexp)
601
+ end
602
+
603
+ def matches_text_regexp?(node, regexp)
604
+ !matches_text_regexp(node, regexp).nil?
579
605
  end
580
606
 
581
607
  def default_visibility
@@ -7,7 +7,8 @@ module Capybara
7
7
  def resolve_for(node, exact = nil)
8
8
  @sibling_node = node
9
9
  node.synchronize do
10
- match_results = super(node.session.current_scope, exact)
10
+ scope = node.respond_to?(:session) ? node.session.current_scope : node.find(:xpath, '/*')
11
+ match_results = super(scope, exact)
11
12
  siblings = node.find_xpath((XPath.preceding_sibling + XPath.following_sibling).to_s)
12
13
  .map(&method(:to_element))
13
14
  .select { |el| match_results.include?(el) }
@@ -33,13 +33,13 @@ class Capybara::RackTest::Browser
33
33
  path = request_path if path.nil? || path.empty?
34
34
  uri = build_uri(path)
35
35
  uri.query = '' if method.to_s.casecmp('get').zero?
36
- process_and_follow_redirects(method, uri.to_s, attributes, 'HTTP_REFERER' => current_url)
36
+ process_and_follow_redirects(method, uri.to_s, attributes, 'HTTP_REFERER' => referer_url)
37
37
  end
38
38
 
39
39
  def follow(method, path, **attributes)
40
40
  return if fragment_or_script?(path)
41
41
 
42
- process_and_follow_redirects(method, path, attributes, 'HTTP_REFERER' => current_url)
42
+ process_and_follow_redirects(method, path, attributes, 'HTTP_REFERER' => referer_url)
43
43
  end
44
44
 
45
45
  def process_and_follow_redirects(method, path, attributes = {}, env = {})
@@ -73,14 +73,18 @@ class Capybara::RackTest::Browser
73
73
  end
74
74
 
75
75
  def build_uri(path)
76
- URI.parse(path).tap do |uri|
77
- uri.path = request_path if path.empty? || path.start_with?('?')
78
- uri.path = '/' if uri.path.empty?
79
- uri.path = request_path.sub(%r{/[^/]*$}, '/') + uri.path unless uri.path.start_with?('/')
76
+ uri = URI.parse(path)
77
+ base_uri = base_relative_uri_for(uri)
80
78
 
79
+ uri.path = base_uri.path + uri.path unless uri.absolute? || uri.path.start_with?('/')
80
+
81
+ if base_uri.absolute?
82
+ base_uri.merge(uri)
83
+ else
81
84
  uri.scheme ||= @current_scheme
82
85
  uri.host ||= @current_host
83
86
  uri.port ||= @current_port unless uri.default_port == @current_port
87
+ uri
84
88
  end
85
89
  end
86
90
 
@@ -125,6 +129,25 @@ class Capybara::RackTest::Browser
125
129
 
126
130
  protected
127
131
 
132
+ def base_href
133
+ find(:css, 'head > base').first&.[](:href).to_s
134
+ end
135
+
136
+ def base_relative_uri_for(uri)
137
+ base_uri = URI.parse(base_href)
138
+ current_uri = URI.parse(safe_last_request&.url.to_s).tap do |c|
139
+ c.path.sub!(%r{/[^/]*$}, '/') unless uri.path.empty?
140
+ c.path = '/' if c.path.empty?
141
+ end
142
+
143
+ if [current_uri, base_uri].any?(&:absolute?)
144
+ current_uri.merge(base_uri)
145
+ else
146
+ base_uri.path = current_uri.path if base_uri.path.empty?
147
+ base_uri
148
+ end
149
+ end
150
+
128
151
  def build_rack_mock_session
129
152
  reset_host! unless current_host
130
153
  Rack::MockSession.new(app, current_host)
@@ -136,9 +159,21 @@ protected
136
159
  '/'
137
160
  end
138
161
 
162
+ def safe_last_request
163
+ last_request
164
+ rescue Rack::Test::Error
165
+ nil
166
+ end
167
+
139
168
  private
140
169
 
141
170
  def fragment_or_script?(path)
142
171
  path.gsub(/^#{Regexp.escape(request_path)}/, '').start_with?('#') || path.downcase.start_with?('javascript:')
143
172
  end
173
+
174
+ def referer_url
175
+ build_uri(last_request.url).to_s
176
+ rescue Rack::Test::Error
177
+ ''
178
+ end
144
179
  end
@@ -98,10 +98,10 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
98
98
  @browser = nil
99
99
  end
100
100
 
101
- def get(*args, &block); browser.get(*args, &block); end
102
- def post(*args, &block); browser.post(*args, &block); end
103
- def put(*args, &block); browser.put(*args, &block); end
104
- def delete(*args, &block); browser.delete(*args, &block); end
101
+ def get(...); browser.get(...); end
102
+ def post(...); browser.post(...); end
103
+ def put(...); browser.put(...); end
104
+ def delete(...); browser.delete(...); end
105
105
  def header(key, value); browser.header(key, value); end
106
106
 
107
107
  def invalid_element_errors