capybara 3.36.0 → 3.39.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (121) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +94 -4
  3. data/README.md +24 -12
  4. data/lib/capybara/driver/node.rb +4 -0
  5. data/lib/capybara/dsl.rb +4 -10
  6. data/lib/capybara/helpers.rb +6 -2
  7. data/lib/capybara/minitest/spec.rb +2 -2
  8. data/lib/capybara/node/actions.rb +4 -4
  9. data/lib/capybara/node/base.rb +2 -1
  10. data/lib/capybara/node/element.rb +12 -1
  11. data/lib/capybara/node/finders.rb +10 -1
  12. data/lib/capybara/node/whitespace_normalizer.rb +81 -0
  13. data/lib/capybara/queries/base_query.rb +2 -2
  14. data/lib/capybara/queries/selector_query.rb +24 -9
  15. data/lib/capybara/queries/text_query.rb +1 -1
  16. data/lib/capybara/rack_test/browser.rb +63 -8
  17. data/lib/capybara/rack_test/driver.rb +4 -4
  18. data/lib/capybara/rack_test/form.rb +29 -7
  19. data/lib/capybara/rack_test/node.rb +19 -16
  20. data/lib/capybara/registration_container.rb +0 -3
  21. data/lib/capybara/registrations/drivers.rb +3 -3
  22. data/lib/capybara/registrations/servers.rb +30 -10
  23. data/lib/capybara/rspec/matcher_proxies.rb +3 -3
  24. data/lib/capybara/rspec/matchers/base.rb +8 -6
  25. data/lib/capybara/rspec/matchers/compound.rb +1 -1
  26. data/lib/capybara/rspec/matchers/have_selector.rb +4 -4
  27. data/lib/capybara/rspec/matchers.rb +14 -14
  28. data/lib/capybara/selector/definition/link.rb +2 -1
  29. data/lib/capybara/selector/definition.rb +3 -2
  30. data/lib/capybara/selector/filter_set.rb +4 -5
  31. data/lib/capybara/selector/regexp_disassembler.rb +2 -5
  32. data/lib/capybara/selector/selector.rb +5 -1
  33. data/lib/capybara/selenium/driver.rb +11 -4
  34. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +8 -4
  35. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +1 -1
  36. data/lib/capybara/selenium/extensions/html5_drag.rb +5 -4
  37. data/lib/capybara/selenium/logger_suppressor.rb +6 -2
  38. data/lib/capybara/selenium/node.rb +72 -25
  39. data/lib/capybara/selenium/nodes/chrome_node.rb +5 -1
  40. data/lib/capybara/selenium/nodes/edge_node.rb +24 -2
  41. data/lib/capybara/selenium/nodes/firefox_node.rb +2 -2
  42. data/lib/capybara/selenium/nodes/safari_node.rb +2 -2
  43. data/lib/capybara/selenium/patches/action_pauser.rb +3 -3
  44. data/lib/capybara/selenium/patches/atoms.rb +1 -1
  45. data/lib/capybara/selenium/patches/pause_duration_fix.rb +1 -1
  46. data/lib/capybara/server/animation_disabler.rb +36 -25
  47. data/lib/capybara/server/middleware.rb +1 -1
  48. data/lib/capybara/session/config.rb +4 -2
  49. data/lib/capybara/session.rb +19 -30
  50. data/lib/capybara/spec/public/test.js +4 -0
  51. data/lib/capybara/spec/session/all_spec.rb +6 -8
  52. data/lib/capybara/spec/session/assert_text_spec.rb +17 -17
  53. data/lib/capybara/spec/session/attach_file_spec.rb +6 -0
  54. data/lib/capybara/spec/session/check_spec.rb +1 -0
  55. data/lib/capybara/spec/session/click_link_spec.rb +11 -0
  56. data/lib/capybara/spec/session/current_scope_spec.rb +1 -1
  57. data/lib/capybara/spec/session/fill_in_spec.rb +6 -0
  58. data/lib/capybara/spec/session/find_link_spec.rb +10 -0
  59. data/lib/capybara/spec/session/find_spec.rb +7 -1
  60. data/lib/capybara/spec/session/first_spec.rb +1 -1
  61. data/lib/capybara/spec/session/frame/within_frame_spec.rb +2 -0
  62. data/lib/capybara/spec/session/has_all_selectors_spec.rb +5 -5
  63. data/lib/capybara/spec/session/has_ancestor_spec.rb +2 -2
  64. data/lib/capybara/spec/session/has_any_selectors_spec.rb +2 -2
  65. data/lib/capybara/spec/session/has_button_spec.rb +6 -0
  66. data/lib/capybara/spec/session/has_current_path_spec.rb +3 -3
  67. data/lib/capybara/spec/session/has_field_spec.rb +1 -1
  68. data/lib/capybara/spec/session/has_link_spec.rb +16 -0
  69. data/lib/capybara/spec/session/has_none_selectors_spec.rb +7 -7
  70. data/lib/capybara/spec/session/has_select_spec.rb +10 -4
  71. data/lib/capybara/spec/session/has_selector_spec.rb +15 -0
  72. data/lib/capybara/spec/session/has_text_spec.rb +5 -13
  73. data/lib/capybara/spec/session/matches_style_spec.rb +2 -0
  74. data/lib/capybara/spec/session/node_spec.rb +81 -0
  75. data/lib/capybara/spec/session/reset_session_spec.rb +13 -0
  76. data/lib/capybara/spec/session/scroll_spec.rb +3 -1
  77. data/lib/capybara/spec/session/visit_spec.rb +20 -0
  78. data/lib/capybara/spec/session/window/window_spec.rb +1 -1
  79. data/lib/capybara/spec/session/window/windows_spec.rb +1 -1
  80. data/lib/capybara/spec/session/within_spec.rb +13 -0
  81. data/lib/capybara/spec/spec_helper.rb +8 -2
  82. data/lib/capybara/spec/test_app.rb +74 -6
  83. data/lib/capybara/spec/views/form.erb +17 -0
  84. data/lib/capybara/spec/views/with_html.erb +3 -3
  85. data/lib/capybara/spec/views/with_scope.erb +2 -2
  86. data/lib/capybara/spec/views/with_shadow.erb +31 -0
  87. data/lib/capybara/version.rb +1 -1
  88. data/lib/capybara/window.rb +1 -1
  89. data/lib/capybara.rb +5 -2
  90. data/spec/capybara_spec.rb +12 -0
  91. data/spec/counter_spec.rb +35 -0
  92. data/spec/css_builder_spec.rb +1 -1
  93. data/spec/css_splitter_spec.rb +1 -1
  94. data/spec/dsl_spec.rb +4 -2
  95. data/spec/fixtures/selenium_driver_rspec_failure.rb +2 -2
  96. data/spec/fixtures/selenium_driver_rspec_success.rb +2 -2
  97. data/spec/minitest_spec.rb +4 -0
  98. data/spec/minitest_spec_spec.rb +4 -0
  99. data/spec/per_session_config_spec.rb +1 -1
  100. data/spec/rack_test_spec.rb +16 -2
  101. data/spec/result_spec.rb +27 -29
  102. data/spec/rspec/features_spec.rb +2 -2
  103. data/spec/rspec/scenarios_spec.rb +2 -2
  104. data/spec/rspec/shared_spec_matchers.rb +1 -1
  105. data/spec/rspec_matchers_spec.rb +25 -0
  106. data/spec/rspec_spec.rb +2 -2
  107. data/spec/sauce_spec_chrome.rb +4 -4
  108. data/spec/selector_spec.rb +4 -4
  109. data/spec/selenium_spec_chrome.rb +8 -7
  110. data/spec/selenium_spec_chrome_remote.rb +9 -9
  111. data/spec/selenium_spec_edge.rb +12 -6
  112. data/spec/selenium_spec_firefox.rb +21 -5
  113. data/spec/selenium_spec_firefox_remote.rb +19 -4
  114. data/spec/selenium_spec_ie.rb +4 -2
  115. data/spec/selenium_spec_safari.rb +8 -2
  116. data/spec/server_spec.rb +7 -7
  117. data/spec/shared_selenium_session.rb +13 -6
  118. data/spec/spec_helper.rb +35 -2
  119. data/spec/whitespace_normalizer_spec.rb +54 -0
  120. data/spec/xpath_builder_spec.rb +1 -1
  121. metadata +9 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fd7f81974b21d47d35c99dff02f878607128c99c80b18b1fef54680797978898
4
- data.tar.gz: ee6659925b7ddaaa0220ad0af81ae65a34e96094583308c160902470e7019128
3
+ metadata.gz: '0945d8fedaab75fa31a90d12105de7318d78c20a46955a1b78ae88dc791f6a5a'
4
+ data.tar.gz: 9a59ca68ff9d44ef28597603ed275ebef28c79c8b6fb76ffdc26daaee68ab383
5
5
  SHA512:
6
- metadata.gz: ad7d2ea4d2612770e801443e61b23a45c0b9936fa4387170ffbbf051706dbc8aee5111b8eefb2d6fabb3042b479dc19d26248923d84da3e78d12dc090aca1e2d
7
- data.tar.gz: 8868469dc6ddcb6c802f4a7b76df9455ff561d00ad78e3cab75e5f02e1b71b74402700c081864368d446304acb6fac2fed5cb79df117d309d4a0a278c95e35d8
6
+ metadata.gz: dbb58a338e2408140ce5e6a44bc141fc44833952c10f611996f5a217c9a0ad00f8d6344a1c16b0bf53000801f9e1d45ef3848cb0fa886dabe68c6d766a42372a
7
+ data.tar.gz: db985c0c947ff348c80008fd4c90232a0ad4dcd38dbe8694b157ecfb512c099d6e9bc08177d8a115e1db17cca35c68903e10662da0fdd14c4f1a2ab671932b16
data/History.md CHANGED
@@ -1,3 +1,93 @@
1
+ # Version 3.39.2
2
+ Release date: 2023-06-10
3
+
4
+ ### Fixed
5
+
6
+ * Fix Selenium version comparison [aki77]
7
+
8
+ # Version 3.39.1
9
+ Release date: 2023-05-12
10
+
11
+ ### Fixed
12
+
13
+ * Fix usage of Selenium logger
14
+
15
+ # Version 3.39.0
16
+ Release date: 2023-04-02
17
+
18
+ ### Added
19
+
20
+ * Support `:target` filter option on `:link` selector [Yudai Takada]
21
+ * Experimental Rack 3 support
22
+ * Text normalization performance improvements [Brandon Weaver]
23
+
24
+ ### Fixed
25
+
26
+ * MS Edge button click [Brian J. Bayer]
27
+ * Options/Capabilities choosing based on Selenium versions
28
+ * Support for base versions [Matijs van Zuijlen]
29
+ * ExpectedError not defined in Selenium 4+
30
+ * Filter block forwarding to a number of matchers [Christophe Bliard]
31
+ ### Changed
32
+
33
+ * Dropped support for rack 1.x
34
+
35
+ # Version 3.38.0
36
+ Release date: 2022-11-03
37
+
38
+ ### Changed
39
+
40
+ * Capybara.w3c_click_offset now defaults to true. If you need click offsets to be from the elements top left corner set it to false in your config
41
+
42
+ ### Added
43
+
44
+ * Support Selenium 4.3 changes to click offset calculations
45
+ * `click`, `double_click`, `right_click` can now be called on the session to click the currently scoped element (or document)
46
+ * `Session#within` now passes the scoped element to the block
47
+ * Support rack-test 2+
48
+ * Retry interval is now configurable [Masahiro NOMOTO]
49
+ * Support Puma 6 - Issue #2590
50
+ * Selenium: DetachedShadowRootError is treated as an invalid element error [Perryn Fowler]
51
+ * Selenium: When inspected shadow roots will have a tag name of "ShadowRoot"
52
+ * `evaluate_async_script` added to Session::DSL_METHODS [Henry Blyth]
53
+
54
+ ### Fixed
55
+
56
+ * Use higher precision clock in Capybara::Helpers::Timer if available
57
+ * rack-test driver behavior with \r\n - Issue #2547 [Stefan Hoffmann]
58
+ * Updated for deprecation of positional parameters in Selenium::WebDriver::ActionBuilder#pause
59
+ * Explicitly set cause on server raised errors
60
+ * Options no longer duplicated in have_xxx invalid option error message [Yudai Takada]
61
+ * Animation disabler is now threadsafe [Daniel Sheppard]
62
+ * Server connection count tracking [Oleksandr K.]
63
+ * Ensure scopes are reset when session is [Henry Blyth]
64
+
65
+ # Version 3.37.1
66
+ Release date: 2022-05-09
67
+
68
+ ### Fixed
69
+
70
+ * Regression in rack-test visit - Issue #2548
71
+
72
+ # Version 3.37.0
73
+ Release date: 2022-05-07
74
+
75
+ ### Changed
76
+
77
+ * Ruby 2.7.0+ is now required
78
+
79
+ ### Added
80
+
81
+ * [Beta] CSP nonces inserted into animation disabler additions - Issue #2542
82
+ * Support `<base>` element in rack-test driver - ISsue #2544
83
+ * [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.
84
+ * Regexp now supported for `exact_text` finder option
85
+
86
+ ### Fixed
87
+
88
+ * Fragments in referer headers in rack-test driver - Issue #2525
89
+ * Selenium v4.1 deprecation notice
90
+
1
91
  # Version 3.36.0
2
92
  Release date: 2021-10-24
3
93
 
@@ -10,7 +100,7 @@ Release date: 2021-10-24
10
100
 
11
101
  * Support for selenium-webdriver 4.x
12
102
  * `allow_label_click` accepts click options to be used when clicking an associated label
13
- * Deprecated `allow_gumbo=` in favor of `use_html5_parsing=` to enable use of Nokogiri::HTL5 when available
103
+ * Deprecated `allow_gumbo=` in favor of `use_html5_parsing=` to enable use of Nokogiri::HTML5 when available
14
104
  * `Session#active_element` returns the element with focus - Not supported by the `RackTest` driver [Sean Doyle]
15
105
  * Support `focused:` filter for finding interactive elements - Not supported by the `RackTest` driver [Sean Doyle]
16
106
 
@@ -784,7 +874,7 @@ Release date: 2018-03-23
784
874
 
785
875
  ### Changed
786
876
 
787
- * Visibile text whitespace is no longer fully normalized in favor of being more in line with the WebDriver spec for visible text
877
+ * Visible text whitespace is no longer fully normalized in favor of being more in line with the WebDriver spec for visible text
788
878
  * Drivers are expected to close extra windows when resetting the session
789
879
  * Selenium driver supports Date/Time when filling in date/time/datetime-local inputs
790
880
  * `current_url` returns the url for the top level browsing context
@@ -1190,7 +1280,7 @@ Release date: 2016-01-27
1190
1280
 
1191
1281
  # Version 2.6.0
1192
1282
 
1193
- Relase date: 2016-01-17
1283
+ Release date: 2016-01-17
1194
1284
 
1195
1285
  ### Fixed
1196
1286
 
@@ -1254,7 +1344,7 @@ Release date: 2014-10-13
1254
1344
 
1255
1345
  # Version 2.4.3
1256
1346
 
1257
- Relase date: 2014-09-21
1347
+ Release date: 2014-09-21
1258
1348
 
1259
1349
  ### Fixed
1260
1350
 
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.6.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
@@ -161,7 +161,7 @@ You can now write your specs like so:
161
161
  ```ruby
162
162
  describe "the signin process", type: :feature do
163
163
  before :each do
164
- User.make(email: 'user@example.com', password: 'password')
164
+ User.create(email: 'user@example.com', password: 'password')
165
165
  end
166
166
 
167
167
  it "signs me in" do
@@ -192,7 +192,7 @@ Capybara also comes with a built in DSL for creating descriptive acceptance test
192
192
  ```ruby
193
193
  feature "Signing in" do
194
194
  background do
195
- User.make(email: 'user@example.com', password: 'caplin')
195
+ User.create(email: 'user@example.com', password: 'caplin')
196
196
  end
197
197
 
198
198
  scenario "Signing in with correct credentials" do
@@ -205,7 +205,7 @@ feature "Signing in" do
205
205
  expect(page).to have_content 'Success'
206
206
  end
207
207
 
208
- given(:other_user) { User.make(email: 'other@example.com', password: 'rous') }
208
+ given(:other_user) { User.create(email: 'other@example.com', password: 'rous') }
209
209
 
210
210
  scenario "Signing in as another user" do
211
211
  visit '/sessions/new'
@@ -336,7 +336,7 @@ By default, Capybara uses the `:rack_test` driver, which is fast but limited: it
336
336
  does not support JavaScript, nor is it able to access HTTP resources outside of
337
337
  your Rack application, such as remote APIs and OAuth services. To get around
338
338
  these limitations, you can set up a different default driver for your features.
339
- For example if you'd prefer to run everything in Selenium, you could do:
339
+ For example, if you'd prefer to run everything in Selenium, you could do:
340
340
 
341
341
  ```ruby
342
342
  Capybara.default_driver = :selenium # :selenium_chrome and :selenium_chrome_headless are also registered
@@ -630,7 +630,7 @@ JS
630
630
 
631
631
  ### <a name="modals"></a>Modals
632
632
 
633
- In drivers which support it, you can accept, dismiss and respond to alerts, confirms and prompts.
633
+ In drivers which support it, you can accept, dismiss and respond to alerts, confirms, and prompts.
634
634
 
635
635
  You can accept or dismiss alert messages by wrapping the code that produces an alert in a block:
636
636
 
@@ -781,7 +781,7 @@ expect(page).to have_content('baz')
781
781
  If clicking on the *foo* link triggers an asynchronous process, such as
782
782
  an Ajax request, which, when complete will add the *bar* link to the page,
783
783
  clicking on the *bar* link would be expected to fail, since that link doesn't
784
- exist yet. However Capybara is smart enough to retry finding the link for a
784
+ exist yet. However, Capybara is smart enough to retry finding the link for a
785
785
  brief period of time before giving up and throwing an error. The same is true of
786
786
  the next line, which looks for the content *baz* on the page; it will retry
787
787
  looking for that content for a brief time. You can adjust how long this period
@@ -795,13 +795,25 @@ Be aware that because of this behaviour, the following two statements are **not*
795
795
  equivalent, and you should **always** use the latter!
796
796
 
797
797
  ```ruby
798
- !page.has_xpath?('a')
799
- page.has_no_xpath?('a')
798
+ # Given use of a driver where the page is loaded when visit returns
799
+ # and that Capybara.predicates_wait is `true`
800
+ # consider a page where the `a` tag is removed through AJAX after 1s
801
+ visit(some_path)
802
+ !page.has_xpath?('a') # is false
803
+ page.has_no_xpath?('a') # is true
800
804
  ```
801
805
 
802
- The former would immediately fail because the content has not yet been removed.
803
- Only the latter would wait for the asynchronous process to remove the content
804
- from the page.
806
+ First expression:
807
+ - `has_xpath?('a')` is called right after `visit` returns. It is `true` because the link has not yet been removed
808
+ - Capybara does not wait upon successful predicates/assertions, therefore **has_xpath? returns `true` immediately**
809
+ - The expression returns `false` (because it is negated with the leading `!`)
810
+
811
+ Second expression:
812
+ - `has_no_xpath?('a')` is called right after `visit` returns. It is `false` because the link has not yet been removed.
813
+ - Capybara waits upon failed predicates/assertions, therefore **has_no_xpath? does not return `false` immediately**
814
+ - Capybara will periodically re-check the predicate/assertion up to the `default_max_wait_time` defined
815
+ - after 1s, the predicate becomes `true` (because the link has been removed)
816
+ - The expression returns `true`
805
817
 
806
818
  Capybara's RSpec matchers, however, are smart enough to handle either form.
807
819
  The two following statements are functionally equivalent:
@@ -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
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
 
@@ -73,7 +73,7 @@ module Capybara
73
73
  def filter_backtrace(trace)
74
74
  return 'No backtrace' unless trace
75
75
 
76
- filter = %r{lib/capybara/|lib/rspec/|lib/minitest/}
76
+ filter = %r{lib/capybara/|lib/rspec/|lib/minitest/|delegate.rb}
77
77
  new_trace = trace.take_while { |line| line !~ filter }
78
78
  new_trace = trace.grep_v(filter) if new_trace.empty?
79
79
  new_trace = trace.dup if new_trace.empty?
@@ -85,7 +85,11 @@ module Capybara
85
85
  Kernel.warn(message, uplevel: uplevel)
86
86
  end
87
87
 
88
- if defined?(Process::CLOCK_MONOTONIC)
88
+ if defined?(Process::CLOCK_MONOTONIC_RAW)
89
+ def monotonic_time; Process.clock_gettime Process::CLOCK_MONOTONIC_RAW; end
90
+ elsif defined?(Process::CLOCK_MONOTONIC_PRECISE)
91
+ def monotonic_time; Process.clock_gettime Process::CLOCK_MONOTONIC_PRECISE; end
92
+ elsif defined?(Process::CLOCK_MONOTONIC)
89
93
  def monotonic_time; Process.clock_gettime Process::CLOCK_MONOTONIC; end
90
94
  else
91
95
  def monotonic_time; Time.now.to_f; end
@@ -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
@@ -383,7 +383,7 @@ module Capybara
383
383
  end
384
384
  end
385
385
 
386
- UPDATE_STYLE_SCRIPT = <<~'JS'
386
+ UPDATE_STYLE_SCRIPT = <<~JS
387
387
  this.capybara_style_cache = this.style.cssText;
388
388
  var css = arguments[0];
389
389
  for (var prop in css){
@@ -393,20 +393,20 @@ module Capybara
393
393
  }
394
394
  JS
395
395
 
396
- RESET_STYLE_SCRIPT = <<~'JS'
396
+ RESET_STYLE_SCRIPT = <<~JS
397
397
  if (this.hasOwnProperty('capybara_style_cache')) {
398
398
  this.style.cssText = this.capybara_style_cache;
399
399
  delete this.capybara_style_cache;
400
400
  }
401
401
  JS
402
402
 
403
- DATALIST_OPTIONS_SCRIPT = <<~'JS'
403
+ DATALIST_OPTIONS_SCRIPT = <<~JS
404
404
  Array.prototype.slice.call((this.list||{}).options || []).
405
405
  filter(function(el){ return !el.disabled }).
406
406
  map(function(el){ return { "value": el.value, "label": el.label} })
407
407
  JS
408
408
 
409
- CAPTURE_FILE_ELEMENT_SCRIPT = <<~'JS'
409
+ CAPTURE_FILE_ELEMENT_SCRIPT = <<~JS
410
410
  document.addEventListener('click', function file_catcher(e){
411
411
  if (e.target.matches("input[type='file']")) {
412
412
  window._capybara_clicked_file_input = e.target;
@@ -77,6 +77,7 @@ module Capybara
77
77
  return yield if session.synchronized
78
78
 
79
79
  seconds = session_options.default_max_wait_time if [nil, true].include? seconds
80
+ interval = session_options.default_retry_interval
80
81
  session.synchronized = true
81
82
  timer = Capybara::Helpers.timer(expire_in: seconds)
82
83
  begin
@@ -88,7 +89,7 @@ module Capybara
88
89
  if driver.wait?
89
90
  raise e if timer.expired?
90
91
 
91
- sleep(0.01)
92
+ sleep interval
92
93
  reload if session_options.automatic_reload
93
94
  else
94
95
  old_base = @base
@@ -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
@@ -18,7 +18,7 @@ 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
@@ -50,6 +50,13 @@ module Capybara
50
50
  #
51
51
  def find(*args, **options, &optional_filter_block)
52
52
  options[:session_options] = session_options
53
+ count_options = options.slice(*Capybara::Queries::BaseQuery::COUNT_KEYS)
54
+ unless count_options.empty?
55
+ Capybara::Helpers.warn(
56
+ "'find' does not support count options (#{count_options}) ignoring. " \
57
+ "Called from: #{Capybara::Helpers.filter_backtrace(caller)}"
58
+ )
59
+ end
53
60
  synced_resolve Capybara::Queries::SelectorQuery.new(*args, **options, &optional_filter_block)
54
61
  end
55
62
 
@@ -142,6 +149,8 @@ module Capybara
142
149
  # @option options [String, Regexp] id Match links with the id provided
143
150
  # @option options [String] title Match links with the title provided
144
151
  # @option options [String] alt Match links with a contained img element whose alt matches
152
+ # @option options [String, Boolean] download Match links with the download provided
153
+ # @option options [String] target Match links with the target provided
145
154
  # @option options [String, Array<String>, Regexp] class Match links that match the class(es) provided
146
155
  # @return [Capybara::Node::Element] The found element
147
156
  #
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capybara
4
+ module Node
5
+ ##
6
+ #
7
+ # {Capybara::Node::WhitespaceNormalizer} provides methods that
8
+ # help to normalize the spacing of text content inside of
9
+ # {Capybara::Node::Element}s by removing various unicode
10
+ # spacing and directional markings.
11
+ #
12
+ module WhitespaceNormalizer
13
+ # Unicode for NBSP, or &nbsp;
14
+ NON_BREAKING_SPACE = "\u00a0"
15
+ LINE_SEPERATOR = "\u2028"
16
+ PARAGRAPH_SEPERATOR = "\u2029"
17
+
18
+ # All spaces except for NBSP
19
+ BREAKING_SPACES = "[[:space:]&&[^#{NON_BREAKING_SPACE}]]"
20
+
21
+ # Whitespace we want to substitute with plain spaces
22
+ SQUEEZED_SPACES = " \n\f\t\v#{LINE_SEPERATOR}#{PARAGRAPH_SEPERATOR}"
23
+
24
+ # Any whitespace at the front of text
25
+ LEADING_SPACES = /\A#{BREAKING_SPACES}+/.freeze
26
+
27
+ # Any whitespace at the end of text
28
+ TRAILING_SPACES = /#{BREAKING_SPACES}+\z/.freeze
29
+
30
+ # "Invisible" space character
31
+ ZERO_WIDTH_SPACE = "\u200b"
32
+
33
+ # Signifies text is read left to right
34
+ LEFT_TO_RIGHT_MARK = "\u200e"
35
+
36
+ # Signifies text is read right to left
37
+ RIGHT_TO_LEFT_MARK = "\u200f"
38
+
39
+ # Characters we want to truncate from text
40
+ REMOVED_CHARACTERS = [ZERO_WIDTH_SPACE, LEFT_TO_RIGHT_MARK, RIGHT_TO_LEFT_MARK].join
41
+
42
+ # Matches multiple empty lines
43
+ EMPTY_LINES = /[\ \n]*\n[\ \n]*/.freeze
44
+
45
+ ##
46
+ #
47
+ # Normalizes the spacing of a node's text to be similar to
48
+ # what matchers might expect.
49
+ #
50
+ # @param text [String]
51
+ # @return [String]
52
+ #
53
+ def normalize_spacing(text)
54
+ text
55
+ .delete(REMOVED_CHARACTERS)
56
+ .tr(SQUEEZED_SPACES, ' ')
57
+ .squeeze(' ')
58
+ .sub(LEADING_SPACES, '')
59
+ .sub(TRAILING_SPACES, '')
60
+ .tr(NON_BREAKING_SPACE, ' ')
61
+ end
62
+
63
+ ##
64
+ #
65
+ # Variant on {Capybara::Node::Normalizer#normalize_spacing} that
66
+ # targets the whitespace of visible elements only.
67
+ #
68
+ # @param text [String]
69
+ # @return [String]
70
+ #
71
+ def normalize_visible_spacing(text)
72
+ text
73
+ .squeeze(' ')
74
+ .gsub(EMPTY_LINES, "\n")
75
+ .sub(LEADING_SPACES, '')
76
+ .sub(TRAILING_SPACES, '')
77
+ .tr(NON_BREAKING_SPACE, ' ')
78
+ end
79
+ end
80
+ end
81
+ end
@@ -79,8 +79,8 @@ module Capybara
79
79
  if count
80
80
  message << " #{occurrences count}"
81
81
  elsif between
82
- message << " between #{between.begin ? between.first : 1} and" \
83
- " #{between.end ? between.last : 'infinite'} times"
82
+ message << " between #{between.begin ? between.first : 1} and " \
83
+ "#{between.end ? between.last : 'infinite'} times"
84
84
  elsif maximum
85
85
  message << " at most #{occurrences maximum}"
86
86
  elsif minimum
@@ -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
 
@@ -266,7 +272,7 @@ module Capybara
266
272
  end
267
273
 
268
274
  def valid_keys
269
- VALID_KEYS + custom_keys
275
+ (VALID_KEYS + custom_keys).uniq
270
276
  end
271
277
 
272
278
  def matches_node_filters?(node, errors)
@@ -541,16 +547,19 @@ module Capybara
541
547
  def matches_text_filter?(node)
542
548
  value = options[:text]
543
549
  return true unless value
544
- 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)
545
551
 
546
552
  regexp = value.is_a?(Regexp) ? value : Regexp.escape(value.to_s)
547
553
  matches_text_regexp?(node, regexp)
548
554
  end
549
555
 
550
556
  def matches_exact_text_filter?(node)
551
- return true unless exact_text.is_a?(String)
552
-
553
- 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
554
563
  end
555
564
 
556
565
  def matches_visibility_filters?(node)
@@ -561,7 +570,9 @@ module Capybara
561
570
  when :visible
562
571
  node.initial_cache[:visible] || (node.initial_cache[:visible].nil? && node.visible?)
563
572
  when :hidden
564
- (node.initial_cache[:visible] == false) || (node.initial_cache[:visbile].nil? && !node.visible?)
573
+ # TODO: check why the 'visbile' cache spelling mistake wasn't caught in a test
574
+ # (node.initial_cache[:visible] == false) || (node.initial_cache[:visbile].nil? && !node.visible?)
575
+ (node.initial_cache[:visible] == false) || (node.initial_cache[:visible].nil? && !node.visible?)
565
576
  else
566
577
  true
567
578
  end
@@ -578,17 +589,21 @@ module Capybara
578
589
 
579
590
  def matches_text_exactly?(node, value)
580
591
  regexp = value.is_a?(Regexp) ? value : /\A#{Regexp.escape(value.to_s)}\z/
581
- matches_text_regexp?(node, regexp)
592
+ matches_text_regexp(node, regexp).then { |m| m&.pre_match == '' && m&.post_match == '' }
582
593
  end
583
594
 
584
595
  def normalize_ws
585
596
  options.fetch(:normalize_ws, session_options.default_normalize_ws)
586
597
  end
587
598
 
588
- def matches_text_regexp?(node, regexp)
599
+ def matches_text_regexp(node, regexp)
589
600
  text_visible = visible
590
601
  text_visible = :all if text_visible == :hidden
591
- node.text(text_visible, normalize_ws: normalize_ws).match?(regexp)
602
+ node.text(text_visible, normalize_ws: normalize_ws).match(regexp)
603
+ end
604
+
605
+ def matches_text_regexp?(node, regexp)
606
+ !matches_text_regexp(node, regexp).nil?
592
607
  end
593
608
 
594
609
  def default_visibility
@@ -13,7 +13,7 @@ module Capybara
13
13
  self.session_options = session_options
14
14
 
15
15
  if expected_text.nil? && !exact?
16
- warn 'Checking for expected text of nil is confusing and/or pointless since it will always match. '\
16
+ warn 'Checking for expected text of nil is confusing and/or pointless since it will always match. ' \
17
17
  "Please specify a string or regexp instead. #{Capybara::Helpers.filter_backtrace(caller)}"
18
18
  end
19
19