capybara 3.37.0 → 3.39.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +74 -3
  3. data/README.md +23 -11
  4. data/lib/capybara/helpers.rb +6 -2
  5. data/lib/capybara/node/actions.rb +4 -4
  6. data/lib/capybara/node/base.rb +2 -1
  7. data/lib/capybara/node/finders.rb +9 -0
  8. data/lib/capybara/node/whitespace_normalizer.rb +81 -0
  9. data/lib/capybara/queries/base_query.rb +2 -2
  10. data/lib/capybara/queries/selector_query.rb +4 -2
  11. data/lib/capybara/queries/text_query.rb +1 -1
  12. data/lib/capybara/rack_test/browser.rb +23 -3
  13. data/lib/capybara/rack_test/form.rb +29 -7
  14. data/lib/capybara/rack_test/node.rb +18 -15
  15. data/lib/capybara/registrations/drivers.rb +3 -3
  16. data/lib/capybara/registrations/servers.rb +30 -10
  17. data/lib/capybara/rspec/matcher_proxies.rb +3 -3
  18. data/lib/capybara/rspec/matchers/base.rb +8 -6
  19. data/lib/capybara/rspec/matchers/compound.rb +1 -1
  20. data/lib/capybara/selector/definition/link.rb +2 -1
  21. data/lib/capybara/selector/definition.rb +1 -1
  22. data/lib/capybara/selector/filter_set.rb +4 -5
  23. data/lib/capybara/selector/regexp_disassembler.rb +2 -5
  24. data/lib/capybara/selector/selector.rb +5 -1
  25. data/lib/capybara/selenium/driver.rb +6 -3
  26. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +8 -4
  27. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +1 -1
  28. data/lib/capybara/selenium/extensions/html5_drag.rb +5 -4
  29. data/lib/capybara/selenium/logger_suppressor.rb +6 -2
  30. data/lib/capybara/selenium/node.rb +61 -26
  31. data/lib/capybara/selenium/nodes/chrome_node.rb +5 -1
  32. data/lib/capybara/selenium/nodes/edge_node.rb +24 -2
  33. data/lib/capybara/selenium/nodes/firefox_node.rb +2 -2
  34. data/lib/capybara/selenium/nodes/safari_node.rb +2 -2
  35. data/lib/capybara/selenium/patches/action_pauser.rb +3 -3
  36. data/lib/capybara/selenium/patches/atoms.rb +1 -1
  37. data/lib/capybara/selenium/patches/pause_duration_fix.rb +1 -1
  38. data/lib/capybara/server/animation_disabler.rb +21 -22
  39. data/lib/capybara/server/middleware.rb +1 -1
  40. data/lib/capybara/session/config.rb +3 -1
  41. data/lib/capybara/session.rb +11 -9
  42. data/lib/capybara/spec/public/test.js +4 -0
  43. data/lib/capybara/spec/session/all_spec.rb +1 -1
  44. data/lib/capybara/spec/session/attach_file_spec.rb +6 -0
  45. data/lib/capybara/spec/session/check_spec.rb +1 -0
  46. data/lib/capybara/spec/session/click_link_spec.rb +11 -0
  47. data/lib/capybara/spec/session/current_scope_spec.rb +1 -1
  48. data/lib/capybara/spec/session/fill_in_spec.rb +6 -0
  49. data/lib/capybara/spec/session/find_link_spec.rb +10 -0
  50. data/lib/capybara/spec/session/find_spec.rb +7 -1
  51. data/lib/capybara/spec/session/first_spec.rb +1 -1
  52. data/lib/capybara/spec/session/frame/within_frame_spec.rb +2 -0
  53. data/lib/capybara/spec/session/has_all_selectors_spec.rb +5 -5
  54. data/lib/capybara/spec/session/has_ancestor_spec.rb +2 -2
  55. data/lib/capybara/spec/session/has_any_selectors_spec.rb +2 -2
  56. data/lib/capybara/spec/session/has_button_spec.rb +6 -0
  57. data/lib/capybara/spec/session/has_current_path_spec.rb +1 -1
  58. data/lib/capybara/spec/session/has_link_spec.rb +16 -0
  59. data/lib/capybara/spec/session/has_none_selectors_spec.rb +7 -7
  60. data/lib/capybara/spec/session/has_select_spec.rb +6 -0
  61. data/lib/capybara/spec/session/has_text_spec.rb +4 -8
  62. data/lib/capybara/spec/session/matches_style_spec.rb +2 -0
  63. data/lib/capybara/spec/session/node_spec.rb +40 -1
  64. data/lib/capybara/spec/session/reset_session_spec.rb +13 -0
  65. data/lib/capybara/spec/session/scroll_spec.rb +3 -1
  66. data/lib/capybara/spec/session/visit_spec.rb +6 -0
  67. data/lib/capybara/spec/session/window/windows_spec.rb +1 -1
  68. data/lib/capybara/spec/session/within_spec.rb +13 -0
  69. data/lib/capybara/spec/spec_helper.rb +8 -2
  70. data/lib/capybara/spec/test_app.rb +41 -6
  71. data/lib/capybara/spec/views/form.erb +17 -0
  72. data/lib/capybara/spec/views/with_html.erb +3 -3
  73. data/lib/capybara/spec/views/with_scope.erb +2 -2
  74. data/lib/capybara/version.rb +1 -1
  75. data/lib/capybara.rb +4 -2
  76. data/spec/capybara_spec.rb +12 -0
  77. data/spec/counter_spec.rb +35 -0
  78. data/spec/css_builder_spec.rb +1 -1
  79. data/spec/css_splitter_spec.rb +1 -1
  80. data/spec/dsl_spec.rb +2 -0
  81. data/spec/minitest_spec.rb +4 -0
  82. data/spec/minitest_spec_spec.rb +4 -0
  83. data/spec/per_session_config_spec.rb +1 -1
  84. data/spec/rack_test_spec.rb +10 -2
  85. data/spec/rspec/scenarios_spec.rb +1 -1
  86. data/spec/rspec/shared_spec_matchers.rb +1 -1
  87. data/spec/rspec_matchers_spec.rb +25 -0
  88. data/spec/rspec_spec.rb +2 -2
  89. data/spec/sauce_spec_chrome.rb +1 -1
  90. data/spec/selector_spec.rb +3 -3
  91. data/spec/selenium_spec_chrome.rb +7 -6
  92. data/spec/selenium_spec_chrome_remote.rb +9 -9
  93. data/spec/selenium_spec_edge.rb +12 -6
  94. data/spec/selenium_spec_firefox.rb +20 -8
  95. data/spec/selenium_spec_firefox_remote.rb +19 -4
  96. data/spec/selenium_spec_ie.rb +4 -2
  97. data/spec/selenium_spec_safari.rb +3 -1
  98. data/spec/server_spec.rb +2 -2
  99. data/spec/shared_selenium_session.rb +5 -5
  100. data/spec/spec_helper.rb +34 -1
  101. data/spec/whitespace_normalizer_spec.rb +54 -0
  102. data/spec/xpath_builder_spec.rb +1 -1
  103. metadata +5 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 125837a4db6ae6c9b12ab2339f5daa673397ef9cdb9054689fb1d155a5d6e62f
4
- data.tar.gz: 445b064e8ae642678457cbf2739a1a6124b7d35dd6aaf295d3d9edce4f6ee8f3
3
+ metadata.gz: '0945d8fedaab75fa31a90d12105de7318d78c20a46955a1b78ae88dc791f6a5a'
4
+ data.tar.gz: 9a59ca68ff9d44ef28597603ed275ebef28c79c8b6fb76ffdc26daaee68ab383
5
5
  SHA512:
6
- metadata.gz: 95154994ad904eae4c9eeb73605bcc273daef240a0cbf241afa8cf1bcfef1ca2de52ecd159fe551ee2d863a0a9a07a2fb9a3beab659f334ec5782f50c5af4ba1
7
- data.tar.gz: 600e6610c5c48ac5619d1c4cf2ddfc83b86d5315ea259c7d8a6476863a14d4a209bcec0c6334dab5f3ebefac5eaeb1ccaa8a263114a5838f7a49a8483fb44538
6
+ metadata.gz: dbb58a338e2408140ce5e6a44bc141fc44833952c10f611996f5a217c9a0ad00f8d6344a1c16b0bf53000801f9e1d45ef3848cb0fa886dabe68c6d766a42372a
7
+ data.tar.gz: db985c0c947ff348c80008fd4c90232a0ad4dcd38dbe8694b157ecfb512c099d6e9bc08177d8a115e1db17cca35c68903e10662da0fdd14c4f1a2ab671932b16
data/History.md CHANGED
@@ -1,3 +1,74 @@
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
+
1
72
  # Version 3.37.0
2
73
  Release date: 2022-05-07
3
74
 
@@ -803,7 +874,7 @@ Release date: 2018-03-23
803
874
 
804
875
  ### Changed
805
876
 
806
- * 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
807
878
  * Drivers are expected to close extra windows when resetting the session
808
879
  * Selenium driver supports Date/Time when filling in date/time/datetime-local inputs
809
880
  * `current_url` returns the url for the top level browsing context
@@ -1209,7 +1280,7 @@ Release date: 2016-01-27
1209
1280
 
1210
1281
  # Version 2.6.0
1211
1282
 
1212
- Relase date: 2016-01-17
1283
+ Release date: 2016-01-17
1213
1284
 
1214
1285
  ### Fixed
1215
1286
 
@@ -1273,7 +1344,7 @@ Release date: 2014-10-13
1273
1344
 
1274
1345
  # Version 2.4.3
1275
1346
 
1276
- Relase date: 2014-09-21
1347
+ Release date: 2014-09-21
1277
1348
 
1278
1349
  ### Fixed
1279
1350
 
data/README.md CHANGED
@@ -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:
@@ -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
@@ -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
@@ -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
@@ -272,7 +272,7 @@ module Capybara
272
272
  end
273
273
 
274
274
  def valid_keys
275
- VALID_KEYS + custom_keys
275
+ (VALID_KEYS + custom_keys).uniq
276
276
  end
277
277
 
278
278
  def matches_node_filters?(node, errors)
@@ -570,7 +570,9 @@ module Capybara
570
570
  when :visible
571
571
  node.initial_cache[:visible] || (node.initial_cache[:visible].nil? && node.visible?)
572
572
  when :hidden
573
- (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?)
574
576
  else
575
577
  true
576
578
  end
@@ -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
 
@@ -20,6 +20,8 @@ class Capybara::RackTest::Browser
20
20
  end
21
21
 
22
22
  def visit(path, **attributes)
23
+ @new_visit_request = true
24
+ reset_cache!
23
25
  reset_host!
24
26
  process_and_follow_redirects(:get, path, attributes)
25
27
  end
@@ -29,11 +31,17 @@ class Capybara::RackTest::Browser
29
31
  request(last_request.fullpath, last_request.env)
30
32
  end
31
33
 
32
- def submit(method, path, attributes)
34
+ def submit(method, path, attributes, content_type: nil)
33
35
  path = request_path if path.nil? || path.empty?
34
36
  uri = build_uri(path)
35
37
  uri.query = '' if method.to_s.casecmp('get').zero?
36
- process_and_follow_redirects(method, uri.to_s, attributes, 'HTTP_REFERER' => referer_url)
38
+ process_and_follow_redirects(
39
+ method,
40
+ uri.to_s,
41
+ attributes,
42
+ 'HTTP_REFERER' => referer_url,
43
+ 'CONTENT_TYPE' => content_type
44
+ )
37
45
  end
38
46
 
39
47
  def follow(method, path, **attributes)
@@ -45,7 +53,6 @@ class Capybara::RackTest::Browser
45
53
  def process_and_follow_redirects(method, path, attributes = {}, env = {})
46
54
  @current_fragment = build_uri(path).fragment
47
55
  process(method, path, attributes, env)
48
-
49
56
  return unless driver.follow_redirects?
50
57
 
51
58
  driver.redirect_limit.times do
@@ -69,6 +76,7 @@ class Capybara::RackTest::Browser
69
76
  @current_scheme, @current_host, @current_port = new_uri.select(:scheme, :host, :port)
70
77
  @current_fragment = new_uri.fragment || @current_fragment
71
78
  reset_cache!
79
+ @new_visit_request = false
72
80
  send(method, new_uri.to_s, attributes, env.merge(options[:headers] || {}))
73
81
  end
74
82
 
@@ -127,6 +135,18 @@ class Capybara::RackTest::Browser
127
135
  dom.title
128
136
  end
129
137
 
138
+ def last_request
139
+ raise Rack::Test::Error if @new_visit_request
140
+
141
+ super
142
+ end
143
+
144
+ def last_response
145
+ raise Rack::Test::Error if @new_visit_request
146
+
147
+ super
148
+ end
149
+
130
150
  protected
131
151
 
132
152
  def base_href
@@ -16,6 +16,8 @@ class Capybara::RackTest::Form < Capybara::RackTest::Node
16
16
  def path; @empty_file.path; end
17
17
  def size; 0; end
18
18
  def read; ''; end
19
+ def append_to(_); end
20
+ def set_encoding(_); end # rubocop:disable Naming/AccessorMethodName
19
21
  end
20
22
 
21
23
  def params(button)
@@ -28,19 +30,31 @@ class Capybara::RackTest::Form < Capybara::RackTest::Node
28
30
 
29
31
  form_elements = native.xpath(form_elements_xpath).reject { |el| submitter?(el) && (el != button.native) }
30
32
 
31
- form_elements.each_with_object(make_params) do |field, params|
33
+ form_params = form_elements.each_with_object({}.compare_by_identity) do |field, params|
32
34
  case field.name
33
35
  when 'input', 'button' then add_input_param(field, params)
34
36
  when 'select' then add_select_param(field, params)
35
37
  when 'textarea' then add_textarea_param(field, params)
36
38
  end
39
+ end
40
+
41
+ form_params.each_with_object(make_params) do |(name, value), params|
42
+ merge_param!(params, name, value)
37
43
  end.to_params_hash
44
+
45
+ # form_elements.each_with_object(make_params) do |field, params|
46
+ # case field.name
47
+ # when 'input', 'button' then add_input_param(field, params)
48
+ # when 'select' then add_select_param(field, params)
49
+ # when 'textarea' then add_textarea_param(field, params)
50
+ # end
51
+ # end.to_params_hash
38
52
  end
39
53
 
40
54
  def submit(button)
41
55
  action = button&.[]('formaction') || native['action']
42
56
  method = button&.[]('formmethod') || request_method
43
- driver.submit(method, action.to_s, params(button))
57
+ driver.submit(method, action.to_s, params(button), content_type: native['enctype'])
44
58
  end
45
59
 
46
60
  def multipart?
@@ -86,6 +100,8 @@ private
86
100
 
87
101
  Capybara::RackTest::Node.new(driver, field).value.to_s
88
102
  when 'file'
103
+ return if value.empty? && params.keys.include?(name) && Rack::Test::VERSION.to_f >= 2.0 # rubocop:disable Performance/InefficientHashSearch
104
+
89
105
  if multipart?
90
106
  file_to_upload(value)
91
107
  else
@@ -94,7 +110,8 @@ private
94
110
  else
95
111
  value
96
112
  end
97
- merge_param!(params, name, value)
113
+ # merge_param!(params, name, value)
114
+ params[name] = value
98
115
  end
99
116
 
100
117
  def file_to_upload(filename)
@@ -107,18 +124,23 @@ private
107
124
  end
108
125
 
109
126
  def add_select_param(field, params)
127
+ name = field['name']
110
128
  if field.has_attribute?('multiple')
111
- field.xpath('.//option[@selected]').each do |option|
112
- merge_param!(params, field['name'], (option['value'] || option.text).to_s)
129
+ value = field.xpath('.//option[@selected]').map do |option|
130
+ # merge_param!(params, field['name'], (option['value'] || option.text).to_s)
131
+ (option['value'] || option.text).to_s
113
132
  end
133
+ params[name] = value unless value.empty?
114
134
  else
115
135
  option = field.xpath('.//option[@selected]').first || field.xpath('.//option').first
116
- merge_param!(params, field['name'], (option['value'] || option.text).to_s) if option
136
+ # merge_param!(params, field['name'], (option['value'] || option.text).to_s) if option
137
+ params[name] = (option['value'] || option.text).to_s if option
117
138
  end
118
139
  end
119
140
 
120
141
  def add_textarea_param(field, params)
121
- merge_param!(params, field['name'], field['_capybara_raw_value'].to_s.gsub(/\n/, "\r\n"))
142
+ # merge_param!(params, field['name'], field['_capybara_raw_value'].to_s.gsub(/\r?\n/, "\r\n"))
143
+ params[field['name']] = field['_capybara_raw_value'].to_s.gsub(/\r?\n/, "\r\n")
122
144
  end
123
145
 
124
146
  def submitter?(el)
@@ -1,25 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'capybara/rack_test/errors'
4
+ require 'capybara/node/whitespace_normalizer'
4
5
 
5
6
  class Capybara::RackTest::Node < Capybara::Driver::Node
7
+ include Capybara::Node::WhitespaceNormalizer
8
+
6
9
  BLOCK_ELEMENTS = %w[p h1 h2 h3 h4 h5 h6 ol ul pre address blockquote dl div fieldset form hr noscript table].freeze
7
10
 
8
11
  def all_text
9
- native.text
10
- .gsub(/[\u200b\u200e\u200f]/, '')
11
- .gsub(/[\ \n\f\t\v\u2028\u2029]+/, ' ')
12
- .gsub(/\A[[:space:]&&[^\u00a0]]+/, '')
13
- .gsub(/[[:space:]&&[^\u00a0]]+\z/, '')
14
- .tr("\u00a0", ' ')
12
+ normalize_spacing(native.text)
15
13
  end
16
14
 
17
15
  def visible_text
18
- displayed_text.squeeze(' ')
19
- .gsub(/[\ \n]*\n[\ \n]*/, "\n")
20
- .gsub(/\A[[:space:]&&[^\u00a0]]+/, '')
21
- .gsub(/[[:space:]&&[^\u00a0]]+\z/, '')
22
- .tr("\u00a0", ' ')
16
+ normalize_visible_spacing(displayed_text)
23
17
  end
24
18
 
25
19
  def [](name)
@@ -153,9 +147,11 @@ protected
153
147
  if !string_node.visible?(check_ancestor)
154
148
  ''
155
149
  elsif native.text?
156
- native.text
157
- .gsub(/[\u200b\u200e\u200f]/, '')
158
- .gsub(/[\ \n\f\t\v\u2028\u2029]+/, ' ')
150
+ native
151
+ .text
152
+ .delete(REMOVED_CHARACTERS)
153
+ .tr(SQUEEZED_SPACES, ' ')
154
+ .squeeze(' ')
159
155
  elsif native.element?
160
156
  text = native.children.map do |child|
161
157
  Capybara::RackTest::Node.new(driver, child).displayed_text(check_ancestor: false)
@@ -235,7 +231,14 @@ private
235
231
  end
236
232
  native.remove
237
233
  else
238
- native['value'] = value.to_s
234
+ value.to_s.tap do |set_value|
235
+ if set_value.end_with?("\n") && form&.css('input, textarea')&.count
236
+ native['value'] = set_value.to_s.chop
237
+ Capybara::RackTest::Form.new(driver, form).submit(self)
238
+ else
239
+ native['value'] = set_value
240
+ end
241
+ end
239
242
  end
240
243
  end
241
244
 
@@ -11,7 +11,7 @@ end
11
11
  Capybara.register_driver :selenium_headless do |app|
12
12
  version = Capybara::Selenium::Driver.load_selenium
13
13
  options_key = Capybara::Selenium::Driver::CAPS_VERSION.satisfied_by?(version) ? :capabilities : :options
14
- browser_options = ::Selenium::WebDriver::Firefox::Options.new.tap do |opts|
14
+ browser_options = Selenium::WebDriver::Firefox::Options.new.tap do |opts|
15
15
  opts.add_argument '-headless'
16
16
  end
17
17
  Capybara::Selenium::Driver.new(app, **{ :browser => :firefox, options_key => browser_options })
@@ -20,7 +20,7 @@ end
20
20
  Capybara.register_driver :selenium_chrome do |app|
21
21
  version = Capybara::Selenium::Driver.load_selenium
22
22
  options_key = Capybara::Selenium::Driver::CAPS_VERSION.satisfied_by?(version) ? :capabilities : :options
23
- browser_options = ::Selenium::WebDriver::Chrome::Options.new.tap do |opts|
23
+ browser_options = Selenium::WebDriver::Chrome::Options.new.tap do |opts|
24
24
  # Workaround https://bugs.chromium.org/p/chromedriver/issues/detail?id=2650&q=load&sort=-id&colspec=ID%20Status%20Pri%20Owner%20Summary
25
25
  opts.add_argument('--disable-site-isolation-trials')
26
26
  end
@@ -31,7 +31,7 @@ end
31
31
  Capybara.register_driver :selenium_chrome_headless do |app|
32
32
  version = Capybara::Selenium::Driver.load_selenium
33
33
  options_key = Capybara::Selenium::Driver::CAPS_VERSION.satisfied_by?(version) ? :capabilities : :options
34
- browser_options = ::Selenium::WebDriver::Chrome::Options.new.tap do |opts|
34
+ browser_options = Selenium::WebDriver::Chrome::Options.new.tap do |opts|
35
35
  opts.add_argument('--headless')
36
36
  opts.add_argument('--disable-gpu') if Gem.win_platform?
37
37
  # Workaround https://bugs.chromium.org/p/chromedriver/issues/detail?id=2650&q=load&sort=-id&colspec=ID%20Status%20Pri%20Owner%20Summary