capybara 3.39.2 → 3.40.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +22 -0
  3. data/README.md +173 -29
  4. data/lib/capybara/helpers.rb +1 -1
  5. data/lib/capybara/minitest/spec.rb +16 -4
  6. data/lib/capybara/minitest.rb +14 -1
  7. data/lib/capybara/node/matchers.rb +25 -0
  8. data/lib/capybara/node/whitespace_normalizer.rb +5 -5
  9. data/lib/capybara/queries/selector_query.rb +2 -1
  10. data/lib/capybara/rack_test/browser.rb +3 -2
  11. data/lib/capybara/rack_test/node.rb +5 -12
  12. data/lib/capybara/registration_container.rb +2 -2
  13. data/lib/capybara/registrations/drivers.rb +1 -1
  14. data/lib/capybara/registrations/servers.rb +8 -7
  15. data/lib/capybara/result.rb +2 -2
  16. data/lib/capybara/rspec/matchers/have_selector.rb +4 -12
  17. data/lib/capybara/rspec/matchers.rb +7 -2
  18. data/lib/capybara/selector/css.rb +5 -5
  19. data/lib/capybara/selector/definition/table.rb +1 -1
  20. data/lib/capybara/selector/definition/table_row.rb +2 -2
  21. data/lib/capybara/selector.rb +251 -0
  22. data/lib/capybara/selenium/driver.rb +11 -51
  23. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +1 -6
  24. data/lib/capybara/selenium/node.rb +4 -31
  25. data/lib/capybara/selenium/nodes/chrome_node.rb +3 -19
  26. data/lib/capybara/selenium/nodes/edge_node.rb +0 -16
  27. data/lib/capybara/server/animation_disabler.rb +1 -1
  28. data/lib/capybara/server.rb +1 -1
  29. data/lib/capybara/session.rb +3 -2
  30. data/lib/capybara/spec/session/all_spec.rb +1 -1
  31. data/lib/capybara/spec/session/click_link_spec.rb +1 -1
  32. data/lib/capybara/spec/session/find_spec.rb +8 -0
  33. data/lib/capybara/spec/session/has_element_spec.rb +47 -0
  34. data/lib/capybara/spec/session/has_table_spec.rb +13 -2
  35. data/lib/capybara/spec/session/node_spec.rb +6 -0
  36. data/lib/capybara/spec/session/node_wrapper_spec.rb +1 -1
  37. data/lib/capybara/spec/session/uncheck_spec.rb +1 -1
  38. data/lib/capybara/spec/spec_helper.rb +2 -2
  39. data/lib/capybara/spec/test_app.rb +1 -1
  40. data/lib/capybara/spec/views/form.erb +5 -0
  41. data/lib/capybara/spec/views/with_html.erb +2 -0
  42. data/lib/capybara/version.rb +1 -1
  43. data/lib/capybara.rb +7 -6
  44. data/spec/minitest_spec.rb +8 -1
  45. data/spec/result_spec.rb +9 -0
  46. data/spec/rspec/shared_spec_matchers.rb +26 -2
  47. data/spec/rspec_spec.rb +1 -1
  48. data/spec/sauce_spec_chrome.rb +1 -1
  49. data/spec/selector_spec.rb +1 -1
  50. data/spec/selenium_spec_chrome.rb +7 -5
  51. data/spec/selenium_spec_chrome_remote.rb +0 -5
  52. data/spec/selenium_spec_edge.rb +11 -4
  53. data/spec/selenium_spec_firefox.rb +4 -3
  54. data/spec/selenium_spec_firefox_remote.rb +2 -3
  55. data/spec/server_spec.rb +12 -0
  56. data/spec/shared_selenium_session.rb +0 -2
  57. data/spec/spec_helper.rb +2 -2
  58. metadata +25 -32
  59. data/lib/capybara/selenium/logger_suppressor.rb +0 -44
  60. data/lib/capybara/selenium/patches/action_pauser.rb +0 -26
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '0945d8fedaab75fa31a90d12105de7318d78c20a46955a1b78ae88dc791f6a5a'
4
- data.tar.gz: 9a59ca68ff9d44ef28597603ed275ebef28c79c8b6fb76ffdc26daaee68ab383
3
+ metadata.gz: e0537a83e6809595a8e15b9c450f4f4b0976c5ebf1327cc0611ae20a05b3574b
4
+ data.tar.gz: c3035e7a05874cc93ab27c6aea26e529dea4c3b63bd5d3281deaaf34c45a91b0
5
5
  SHA512:
6
- metadata.gz: dbb58a338e2408140ce5e6a44bc141fc44833952c10f611996f5a217c9a0ad00f8d6344a1c16b0bf53000801f9e1d45ef3848cb0fa886dabe68c6d766a42372a
7
- data.tar.gz: db985c0c947ff348c80008fd4c90232a0ad4dcd38dbe8694b157ecfb512c099d6e9bc08177d8a115e1db17cca35c68903e10662da0fdd14c4f1a2ab671932b16
6
+ metadata.gz: 48c7bc5cbfc8e3324ea09d937a207299ad91ffc2e255dbbb7ed13265e4541c3672271ccb2f19b3902588d5c649cb6f561b7c483e7b815b824ccf37df8bbb5075
7
+ data.tar.gz: 5b3a9cc9a6ba6ed67a54ef381c00ed9cc94027d26bdfc98ba1c509a097c9dc123d39bd72a6a5728e5b5b4e503202e475889d22cf47c07ce88eb7afedf604ed83
data/History.md CHANGED
@@ -1,3 +1,25 @@
1
+ #Version 3.40.0
2
+ Release date: 2024-01-26
3
+
4
+ ### Changned
5
+
6
+ * Dropped support for Ruby 2.7, 3.0+ is now required
7
+ * Dropped support for Selenium < 4.8
8
+ * Use the new headless option on chromedriver with registered selenium driver [Neil Carvalho]
9
+
10
+ ### Added
11
+
12
+ * `Capybara::Result#to_ary` to support multiple assignment [Sean Doyle]
13
+ * `has_element?` and related matchers [Sean Doyle]
14
+ * Rack 3 support
15
+
16
+ ### Fixed
17
+
18
+ * Forward save_screenshot options to selenium - Issue 2738
19
+ * Rack test - don't auto submit forms with multiple inputs [Mitchell Henke]
20
+ * Table row selector matches cell values in order - Issue 2686 [Jeff Parr]
21
+ * Table row selector fixes for first column - Issue 2685 [Jeff Par]
22
+
1
23
  # Version 3.39.2
2
24
  Release date: 2023-06-10
3
25
 
data/README.md CHANGED
@@ -1,11 +1,8 @@
1
1
  # Capybara
2
2
 
3
- [![Build Status](https://secure.travis-ci.org/teamcapybara/capybara.svg)](https://travis-ci.org/teamcapybara/capybara)
4
- [![Build Status](https://ci.appveyor.com/api/projects/status/github/teamcapybara/capybara?svg=true)](https://ci.appveyor.com/api/projects/github/teamcapybara/capybara)
3
+ [![Build Status](https://github.com/teamcapybara/capybara/actions/workflows/build.yml/badge.svg)](https://github.com/teamcapybara/capybara/actions/workflows/build.yml)
5
4
  [![Code Climate](https://codeclimate.com/github/teamcapybara/capybara.svg)](https://codeclimate.com/github/teamcapybara/capybara)
6
5
  [![Coverage Status](https://coveralls.io/repos/github/teamcapybara/capybara/badge.svg?branch=master)](https://coveralls.io/github/teamcapybara/capybara?branch=master)
7
- [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/jnicklas/capybara?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
8
- [![SemVer](https://api.dependabot.com/badges/compatibility_score?dependency-name=capybara&package-manager=bundler&version-scheme=semver)](https://dependabot.com/compatibility-score.html?dependency-name=capybara&package-manager=bundler&version-scheme=semver)
9
6
 
10
7
  Capybara helps you test web applications by simulating how a real user would
11
8
  interact with your app. It is agnostic about the driver running your tests and
@@ -18,8 +15,7 @@ If you and/or your company find value in Capybara and would like to contribute f
18
15
  <a href="https://www.patreon.com/capybara">Patreon</a>
19
16
 
20
17
 
21
- **Need help?** Ask on the mailing list (please do not open an issue on
22
- GitHub): http://groups.google.com/group/ruby-capybara
18
+ **Need help?** Ask on the discussions (please do not open an issue): https://github.com/orgs/teamcapybara/discussions/categories/q-a
23
19
 
24
20
  ## Table of contents
25
21
 
@@ -34,7 +30,6 @@ GitHub): http://groups.google.com/group/ruby-capybara
34
30
  - [Selecting the Driver](#selecting-the-driver)
35
31
  - [RackTest](#racktest)
36
32
  - [Selenium](#selenium)
37
- - [Apparition](#apparition)
38
33
  - [The DSL](#the-dsl)
39
34
  - [Navigating](#navigating)
40
35
  - [Clicking links and buttons](#clicking-links-and-buttons)
@@ -46,6 +41,10 @@ GitHub): http://groups.google.com/group/ruby-capybara
46
41
  - [Scripting](#scripting)
47
42
  - [Modals](#modals)
48
43
  - [Debugging](#debugging)
44
+ - [Selectors](#selectors)
45
+ - [Name](#selectors-name)
46
+ - [Locator](#selectors-locator)
47
+ - [Filters](#selectors-filters)
49
48
  - [Matching](#matching)
50
49
  - [Exactness](#exactness)
51
50
  - [Strategy](#strategy)
@@ -74,7 +73,7 @@ GitHub): http://groups.google.com/group/ruby-capybara
74
73
 
75
74
  ## <a name="setup"></a>Setup
76
75
 
77
- Capybara requires Ruby 2.7.0 or later. To install, add this line to your
76
+ Capybara requires Ruby 3.0.0 or later. To install, add this line to your
78
77
  `Gemfile` and run `bundle install`:
79
78
 
80
79
  ```ruby
@@ -145,13 +144,13 @@ Load RSpec 3.5+ support by adding the following line (typically to your
145
144
  require 'capybara/rspec'
146
145
  ```
147
146
 
148
- If you are using Rails, put your Capybara specs in `spec/features` or `spec/system` (only works
149
- if [you have it configured in
150
- RSpec](https://relishapp.com/rspec/rspec-rails/v/4-0/docs/directory-structure))
151
- and if you have your Capybara specs in a different directory, then tag the
152
- example groups with `type: :feature` or `type: :system` depending on which type of test you're writing.
147
+ If you are using Rails, put your Capybara specs in `spec/features` or `spec/system` (only works if
148
+ [you have it configured in RSpec](https://rspec.info/features/6-0/rspec-rails/directory-structure/))
149
+ and if you have your Capybara specs in a different directory, then tag the example groups with
150
+ `type: :feature` or `type: :system` depending on which type of test you're writing.
153
151
 
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.
152
+ If you are using Rails system specs please see [their documentation](https://rspec.info/features/6-0/rspec-rails/system-specs/system-specs)
153
+ for selecting the driver you wish to use.
155
154
 
156
155
  If you are not using Rails, tag all the example groups in which you want to use
157
156
  Capybara with `type: :feature`.
@@ -183,7 +182,7 @@ to one specific driver. For example:
183
182
  ```ruby
184
183
  describe 'some stuff which requires js', js: true do
185
184
  it 'will use the default js driver'
186
- it 'will switch to one specific driver', driver: :apparition
185
+ it 'will switch to one specific driver', driver: :selenium
187
186
  end
188
187
  ```
189
188
 
@@ -353,7 +352,7 @@ You can also change the driver temporarily (typically in the Before/setup and
353
352
  After/teardown blocks):
354
353
 
355
354
  ```ruby
356
- Capybara.current_driver = :apparition # temporarily select different driver
355
+ Capybara.current_driver = :selenium # temporarily select different driver
357
356
  # tests here
358
357
  Capybara.use_default_driver # switch back to default driver
359
358
  ```
@@ -409,15 +408,6 @@ to the browsers. See the section on adding and configuring drivers.
409
408
  same transaction as your tests, causing data not to be shared between your test
410
409
  and test server, see [Transactions and database setup](#transactions-and-database-setup) below.
411
410
 
412
- ### <a name="apparition"></a>Apparition
413
-
414
- The [apparition driver](https://github.com/twalpole/apparition) is a new driver that allows you to run tests using Chrome in a headless
415
- or headed configuration. It attempts to provide backwards compatibility with the [Poltergeist driver API](https://github.com/teampoltergeist/poltergeist)
416
- and [capybara-webkit API](https://github.com/thoughtbot/capybara-webkit) while allowing for the use of modern JS/CSS. It
417
- uses CDP to communicate with Chrome, thereby obviating the need for chromedriver. This driver is being developed by the
418
- current developer of Capybara and will attempt to keep up to date with new Capybara releases. It will probably be moved into the
419
- teamcapybara repo once it reaches v1.0.
420
-
421
411
  ## <a name="the-dsl"></a>The DSL
422
412
 
423
413
  *A complete reference is available at
@@ -632,10 +622,10 @@ JS
632
622
 
633
623
  In drivers which support it, you can accept, dismiss and respond to alerts, confirms, and prompts.
634
624
 
635
- You can accept or dismiss alert messages by wrapping the code that produces an alert in a block:
625
+ You can accept alert messages by wrapping the code that produces an alert in a block:
636
626
 
637
627
  ```ruby
638
- accept_alert do
628
+ accept_alert 'optional text or regex' do
639
629
  click_link('Show Alert')
640
630
  end
641
631
  ```
@@ -643,7 +633,13 @@ end
643
633
  You can accept or dismiss a confirmation by wrapping it in a block, as well:
644
634
 
645
635
  ```ruby
646
- dismiss_confirm do
636
+ accept_confirm 'optional text' do
637
+ click_link('Show Confirm')
638
+ end
639
+ ```
640
+
641
+ ```ruby
642
+ dismiss_confirm 'optional text' do
647
643
  click_link('Show Confirm')
648
644
  end
649
645
  ```
@@ -651,7 +647,13 @@ end
651
647
  You can accept or dismiss prompts as well, and also provide text to fill in for the response:
652
648
 
653
649
  ```ruby
654
- accept_prompt(with: 'Linus Torvalds') do
650
+ accept_prompt('optional text', with: 'Linus Torvalds') do
651
+ click_link('Show Prompt About Linux')
652
+ end
653
+ ```
654
+
655
+ ```ruby
656
+ dismiss_prompt('optional text') do
655
657
  click_link('Show Prompt About Linux')
656
658
  end
657
659
  ```
@@ -701,6 +703,148 @@ Screenshots are saved to `Capybara.save_path`, relative to the app directory.
701
703
  If you have required `capybara/rails`, `Capybara.save_path` will default to
702
704
  `tmp/capybara`.
703
705
 
706
+ ## <a name="selectors"></a>Selectors
707
+
708
+ Helpers and matchers that accept Selectors share a common method signature that
709
+ includes:
710
+
711
+ 1. a positional Name argument
712
+ 2. a positional Locator argument
713
+ 3. keyword Filter arguments
714
+ 4. a predicate Filter block argument
715
+
716
+ These arguments are usually optional in one way or another.
717
+
718
+ ### <a name="selectors-name"></a>Name
719
+
720
+ The name argument determines the Selector to use. The argument is optional when
721
+ a helper explicitly conveys the selector name (for example, [`find_field`][]
722
+ uses `:field`, [`find_link`][] uses `:link`, etc):
723
+
724
+ ```ruby
725
+ page.html # => '<a href="/">Home</a>'
726
+
727
+ page.find(:link) == page.find_link
728
+
729
+ page.html # => '<input>'
730
+
731
+ page.find(:field) == page.find_field
732
+ ```
733
+
734
+ ### <a name="selectors-locator"></a>Locator
735
+
736
+ The locator argument usually represents information that can most meaningfully
737
+ distinguish an element that matches the selector from an element that does not:
738
+
739
+ ```ruby
740
+ page.html # => '<div id="greeting">Hello world</div>'
741
+
742
+ page.find(:css, 'div').text # => 'Hello world'
743
+ page.find(:xpath, './/div').text # => 'Hello world'
744
+ ```
745
+
746
+ General purpose finder methods like [`find`][] and [`all`][] can accept the
747
+ locator as their first positional argument when the method can infer the default
748
+ value from the [`Capybara.default_selector`][] configuration:
749
+
750
+ ```ruby
751
+ page.html # => '<div id="greeting">Hello world</div>'
752
+
753
+ Capybara.default_selector = :css
754
+
755
+ page.find('div').text # => 'Hello world'
756
+
757
+ Capybara.default_selector = :xpath
758
+
759
+ page.find('.//div').text # => 'Hello world'
760
+ ```
761
+
762
+ The locator argument's semantics are context-specific, and depend on the
763
+ selector. The types of arguments are varied. Some selectors support `String` or
764
+ `Regexp` arguments, while others like `:table_row` support `Array<String>` and
765
+ `Hash<String, String>`:
766
+
767
+ ```ruby
768
+ page.html # => '<label for="greeting">Greeting</label>
769
+ <input id="greeting" name="content">'
770
+
771
+ # find by the <input> element's [id] attribute
772
+ page.find(:id, 'greeting') == page.find_by_id('greeting') # => true
773
+
774
+ # find by the <input> element's [id] attribute
775
+ page.find(:field, 'greeting') == page.find_field('greeting') # => true
776
+
777
+ # find by the <input> element's [name] attribute
778
+ page.find(:field, 'content') == page.find_field('content') # => true
779
+
780
+ # find by the <label> element's text
781
+ page.find(:field, 'Greeting') == page.find_field('Greeting') # => true
782
+
783
+ page.html # => '<table>
784
+ <tr>
785
+ <th>A</th>
786
+ <th>B</th>
787
+ </tr>
788
+ <tr>
789
+ <td>1</td>
790
+ <td>2</td>
791
+ </tr>
792
+ </table>'
793
+
794
+ # find by <td> content
795
+ page.find(:table_row, ['1', '2']) == page.find(:css, 'tr:last-of-type') # => true
796
+
797
+ # find by <th> content paired with corresponding <td> content
798
+ page.find(:table_row, 'A' => '1') == page.find(:table_row, 'B' => '2') # => true
799
+ ```
800
+
801
+ ### <a name="selectors-filters"></a> Filters
802
+
803
+ All filters are optional. The supported set of keys is a mixture of both global
804
+ and context-specific filters.The supported types of values depend on the
805
+ context:
806
+
807
+ ```ruby
808
+ page.html # => '<a href="/">Home</a>'
809
+
810
+ # find by the [href] attribute
811
+ page.find_link(href: '/') == page.find_link(text: 'Home') # => true
812
+
813
+ page.html # => '<div id="element" data-attribute="value">Content</div>'
814
+
815
+ # find by the [id] attribute
816
+ page.find(id: 'element') == page.find(text: 'Content') # => true
817
+
818
+ # find by the [data-attribute] attribute
819
+ page.find(:element, 'data-attribute': /value/) == page.find(text: 'Content') # => true
820
+
821
+ page.html # => '<input type="checkbox">'
822
+
823
+ # find by the absence of the [checked] attribute
824
+ page.find_field(checked: false) == page.find_field(unchecked: true) # => true
825
+ ```
826
+
827
+ The predicate block is always optional. When there are results for a selector
828
+ query, the block is called with each item in the result set. When the block
829
+ evaluates to true, the item is included from the result set. Otherwise, the item
830
+ is excluded:
831
+
832
+ ```ruby
833
+ page.html # => '<input role="switch" type="checkbox" checked>'
834
+
835
+ switch = page.find_field { |input| input["role"] == "switch" }
836
+ field = page.find_field(checked: true)
837
+
838
+ switch == field # => true
839
+ ```
840
+
841
+ [`find`]: https://rubydoc.info/github/teamcapybara/capybara/master/Capybara/Node/Finders:find
842
+ [`all`]: https://rubydoc.info/github/teamcapybara/capybara/master/Capybara/Node/Finders:all
843
+ [`Capybara.default_selector`]: https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2Econfigure
844
+ [`find_by_id`]: https://rubydoc.info/github/teamcapybara/capybara/master/Capybara/Node/Finders:find_by_id
845
+ [`find_field`]: https://rubydoc.info/github/teamcapybara/capybara/master/Capybara/Node/Finders:find_field
846
+ [`find_link`]: https://rubydoc.info/github/teamcapybara/capybara/master/Capybara/Node/Finders:find_link
847
+
704
848
  ## <a name="matching"></a>Matching
705
849
 
706
850
  It is possible to customize how Capybara finds elements. At your disposal
@@ -78,7 +78,7 @@ module Capybara
78
78
  new_trace = trace.grep_v(filter) if new_trace.empty?
79
79
  new_trace = trace.dup if new_trace.empty?
80
80
 
81
- new_trace.first.split(/:in /, 2).first
81
+ new_trace.first.split(':in ', 2).first
82
82
  end
83
83
 
84
84
  def warn(message, uplevel: 1)
@@ -95,6 +95,18 @@ module Capybara
95
95
  # @!method wont_have_field
96
96
  # See {Capybara::Node::Matchers#has_no_field?}
97
97
 
98
+ ##
99
+ # Expectation that there is element
100
+ #
101
+ # @!method must_have_element
102
+ # See {Capybara::Node::Matchers#has_element?}
103
+
104
+ ##
105
+ # Expectation that there is no element
106
+ #
107
+ # @!method wont_have_element
108
+ # See {Capybara::Node::Matchers#has_no_element?}
109
+
98
110
  ##
99
111
  # Expectation that there is link
100
112
  #
@@ -230,15 +242,15 @@ module Capybara
230
242
  %W[refute_matches_#{assertion} wont_match_#{assertion}]]
231
243
  end).each do |(meth, new_name)|
232
244
  class_eval <<-ASSERTION, __FILE__, __LINE__ + 1
233
- def #{new_name} *args, **kw_args, &block
234
- ::Minitest::Expectation.new(self, ::Minitest::Spec.current).#{new_name}(*args, **kw_args, &block)
245
+ def #{new_name}(...)
246
+ ::Minitest::Expectation.new(self, ::Minitest::Spec.current).#{new_name}(...)
235
247
  end
236
248
  ASSERTION
237
249
 
238
250
  ::Minitest::Expectation.class_eval <<-ASSERTION, __FILE__, __LINE__ + 1
239
- def #{new_name} *args, **kw_args, &block
251
+ def #{new_name}(...)
240
252
  raise "Calling ##{new_name} outside of test." unless ctx
241
- ctx.#{meth}(target, *args, **kw_args, &block)
253
+ ctx.#{meth}(target, ...)
242
254
  end
243
255
  ASSERTION
244
256
  end
@@ -190,6 +190,19 @@ module Capybara
190
190
  # @!method assert_no_css
191
191
  # See {Capybara::Node::Matchers#has_no_css?}
192
192
 
193
+ ##
194
+ # Assert that provided element exists
195
+ #
196
+ # @!method assert_element
197
+ # See {Capybara::Node::Matchers#has_element?}
198
+
199
+ ##
200
+ # Assert that provided element does not exist
201
+ #
202
+ # @!method assert_no_element
203
+ # @!method refute_element
204
+ # See {Capybara::Node::Matchers#has_no_element?}
205
+
193
206
  ##
194
207
  # Assert that provided link exists
195
208
  #
@@ -281,7 +294,7 @@ module Capybara
281
294
  # @!method assert_no_table
282
295
  # See {Capybara::Node::Matchers#has_no_table?}
283
296
 
284
- %w[xpath css link button field select table].each do |selector_type|
297
+ %w[xpath css element link button field select table].each do |selector_type|
285
298
  define_method "assert_#{selector_type}" do |*args, &optional_filter_block|
286
299
  subject, args = determine_subject(args)
287
300
  locator, options = extract_locator(args)
@@ -322,6 +322,31 @@ module Capybara
322
322
  has_no_selector?(:css, path, **options, &optional_filter_block)
323
323
  end
324
324
 
325
+ ##
326
+ #
327
+ # Checks if the page or current node has a element with the given
328
+ # local name.
329
+ #
330
+ # @param [String] locator The local name of a element to check for
331
+ # @option options [String, Regexp] The attributes values of matching elements
332
+ # @return [Boolean] Whether it exists
333
+ #
334
+ def has_element?(locator = nil, **options, &optional_filter_block)
335
+ has_selector?(:element, locator, **options, &optional_filter_block)
336
+ end
337
+
338
+ ##
339
+ #
340
+ # Checks if the page or current node has no element with the given
341
+ # local name.
342
+ #
343
+ # @param (see #has_element?)
344
+ # @return [Boolean] Whether it doesn't exist
345
+ #
346
+ def has_no_element?(locator = nil, **options, &optional_filter_block)
347
+ has_no_selector?(:element, locator, **options, &optional_filter_block)
348
+ end
349
+
325
350
  ##
326
351
  #
327
352
  # Checks if the page or current node has a link with the given
@@ -16,16 +16,16 @@ module Capybara
16
16
  PARAGRAPH_SEPERATOR = "\u2029"
17
17
 
18
18
  # All spaces except for NBSP
19
- BREAKING_SPACES = "[[:space:]&&[^#{NON_BREAKING_SPACE}]]"
19
+ BREAKING_SPACES = "[[:space:]&&[^#{NON_BREAKING_SPACE}]]".freeze
20
20
 
21
21
  # Whitespace we want to substitute with plain spaces
22
- SQUEEZED_SPACES = " \n\f\t\v#{LINE_SEPERATOR}#{PARAGRAPH_SEPERATOR}"
22
+ SQUEEZED_SPACES = " \n\f\t\v#{LINE_SEPERATOR}#{PARAGRAPH_SEPERATOR}".freeze
23
23
 
24
24
  # Any whitespace at the front of text
25
- LEADING_SPACES = /\A#{BREAKING_SPACES}+/.freeze
25
+ LEADING_SPACES = /\A#{BREAKING_SPACES}+/
26
26
 
27
27
  # Any whitespace at the end of text
28
- TRAILING_SPACES = /#{BREAKING_SPACES}+\z/.freeze
28
+ TRAILING_SPACES = /#{BREAKING_SPACES}+\z/
29
29
 
30
30
  # "Invisible" space character
31
31
  ZERO_WIDTH_SPACE = "\u200b"
@@ -40,7 +40,7 @@ module Capybara
40
40
  REMOVED_CHARACTERS = [ZERO_WIDTH_SPACE, LEFT_TO_RIGHT_MARK, RIGHT_TO_LEFT_MARK].join
41
41
 
42
42
  # Matches multiple empty lines
43
- EMPTY_LINES = /[\ \n]*\n[\ \n]*/.freeze
43
+ EMPTY_LINES = /[\ \n]*\n[\ \n]*/
44
44
 
45
45
  ##
46
46
  #
@@ -70,7 +70,8 @@ module Capybara
70
70
  desc << 'non-visible ' if visible == :hidden
71
71
  end
72
72
 
73
- desc << "#{label} #{locator.inspect}"
73
+ desc << label.to_s
74
+ desc << " #{locator.inspect}" unless locator.nil?
74
75
 
75
76
  if show_for[:any]
76
77
  desc << " with#{' exact' if exact_text == true} text #{options[:text].inspect}" if options[:text]
@@ -35,12 +35,13 @@ class Capybara::RackTest::Browser
35
35
  path = request_path if path.nil? || path.empty?
36
36
  uri = build_uri(path)
37
37
  uri.query = '' if method.to_s.casecmp('get').zero?
38
+ env = { 'HTTP_REFERER' => referer_url }
39
+ env['CONTENT_TYPE'] = content_type if content_type
38
40
  process_and_follow_redirects(
39
41
  method,
40
42
  uri.to_s,
41
43
  attributes,
42
- 'HTTP_REFERER' => referer_url,
43
- 'CONTENT_TYPE' => content_type
44
+ env
44
45
  )
45
46
  end
46
47
 
@@ -125,19 +125,12 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
125
125
  alias_method "unchecked_#{meth_name}", meth_name
126
126
  private "unchecked_#{meth_name}" # rubocop:disable Style/AccessModifierDeclarations
127
127
 
128
- if RUBY_VERSION >= '2.7'
129
- class_eval <<~METHOD, __FILE__, __LINE__ + 1
130
- def #{meth_name}(...)
131
- stale_check
132
- method(:"unchecked_#{meth_name}").call(...)
133
- end
134
- METHOD
135
- else
136
- define_method meth_name do |*args|
128
+ class_eval <<~METHOD, __FILE__, __LINE__ + 1
129
+ def #{meth_name}(...)
137
130
  stale_check
138
- send("unchecked_#{meth_name}", *args)
131
+ method(:"unchecked_#{meth_name}").call(...)
139
132
  end
140
- end
133
+ METHOD
141
134
  end
142
135
 
143
136
  protected
@@ -232,7 +225,7 @@ private
232
225
  native.remove
233
226
  else
234
227
  value.to_s.tap do |set_value|
235
- if set_value.end_with?("\n") && form&.css('input, textarea')&.count
228
+ if set_value.end_with?("\n") && form&.css('input, textarea')&.count == 1
236
229
  native['value'] = set_value.to_s.chop
237
230
  Capybara::RackTest::Form.new(driver, form).submit(self)
238
231
  else
@@ -16,10 +16,10 @@ module Capybara
16
16
  @registered[name] = value
17
17
  end
18
18
 
19
- def method_missing(method_name, *args, **options, &block)
19
+ def method_missing(method_name, ...)
20
20
  if @registered.respond_to?(method_name)
21
21
  Capybara::Helpers.warn "DEPRECATED: Calling '#{method_name}' on the drivers/servers container is deprecated without replacement"
22
- return @registered.public_send(method_name, *args, **options, &block)
22
+ return @registered.public_send(method_name, ...)
23
23
  end
24
24
  super
25
25
  end
@@ -32,7 +32,7 @@ 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
34
  browser_options = Selenium::WebDriver::Chrome::Options.new.tap do |opts|
35
- opts.add_argument('--headless')
35
+ opts.add_argument('--headless=new')
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
38
38
  opts.add_argument('--disable-site-isolation-trials')
@@ -27,19 +27,20 @@ Capybara.register_server :puma do |app, port, host, **options| # rubocop:disable
27
27
  require 'rack/handler/puma'
28
28
  rescue LoadError
29
29
  raise LoadError, 'Capybara is unable to load `puma` for its server, please add `puma` to your project or specify a different server via something like `Capybara.server = :webrick`.'
30
- else
31
- unless Rack::Handler::Puma.respond_to?(:config)
32
- raise LoadError, 'Capybara requires `puma` version 3.8.0 or higher, please upgrade `puma` or register and specify your own server block'
33
- end
30
+ end
31
+ puma_rack_handler = defined?(Rackup::Handler::Puma) ? Rackup::Handler::Puma : Rack::Handler::Puma
32
+
33
+ unless puma_rack_handler.respond_to?(:config)
34
+ raise LoadError, 'Capybara requires `puma` version 3.8.0 or higher, please upgrade `puma` or register and specify your own server block'
34
35
  end
35
36
 
36
37
  # If we just run the Puma Rack handler it installs signal handlers which prevent us from being able to interrupt tests.
37
38
  # Therefore construct and run the Server instance ourselves.
38
- # Rack::Handler::Puma.run(app, { Host: host, Port: port, Threads: "0:4", workers: 0, daemon: false }.merge(options))
39
+ # puma_rack_handler.run(app, { Host: host, Port: port, Threads: "0:4", workers: 0, daemon: false }.merge(options))
39
40
  default_options = { Host: host, Port: port, Threads: '0:4', workers: 0, daemon: false }
40
41
  options = default_options.merge(options)
41
42
 
42
- conf = Rack::Handler::Puma.config(app, options)
43
+ conf = puma_rack_handler.config(app, options)
43
44
  conf.clamp
44
45
 
45
46
  puma_ver = Gem::Version.new(Puma::Const::PUMA_VERSION)
@@ -51,7 +52,7 @@ Capybara.register_server :puma do |app, port, host, **options| # rubocop:disable
51
52
  conf.options[:log_writer] = logger
52
53
 
53
54
  logger.log 'Capybara starting Puma...'
54
- logger.log "* Version #{Puma::Const::PUMA_VERSION} , codename: #{Puma::Const::CODE_NAME}"
55
+ logger.log "* Version #{Puma::Const::PUMA_VERSION}, codename: #{Puma::Const::CODE_NAME}"
55
56
  logger.log "* Min threads: #{conf.options[:min_threads]}, max threads: #{conf.options[:max_threads]}"
56
57
 
57
58
  Puma::Server.new(
@@ -34,7 +34,7 @@ module Capybara
34
34
  @allow_reload = false
35
35
  end
36
36
 
37
- def_delegators :full_results, :size, :length, :last, :values_at, :inspect, :sample
37
+ def_delegators :full_results, :size, :length, :last, :values_at, :inspect, :sample, :to_ary
38
38
 
39
39
  alias index find_index
40
40
 
@@ -116,7 +116,7 @@ module Capybara
116
116
  message << ' but there were no matches'
117
117
  else
118
118
  message << ", found #{count} #{Capybara::Helpers.declension('match', 'matches', count)}: " \
119
- << full_results.map(&:text).map(&:inspect).join(', ')
119
+ << full_results.map { |r| r.text.inspect }.join(', ')
120
120
  end
121
121
  unless rest.empty?
122
122
  elements = rest.map { |el| el.text rescue '<<ERROR>>' }.map(&:inspect).join(', ') # rubocop:disable Style/RescueModifier
@@ -22,9 +22,7 @@ module Capybara
22
22
  el.assert_no_selector(*@args, **session_query_options, &@filter_block)
23
23
  end
24
24
 
25
- def description
26
- "have #{query.description}"
27
- end
25
+ def description = "have #{query.description}"
28
26
 
29
27
  def query
30
28
  @query ||= Capybara::Queries::SelectorQuery.new(*session_query_args, **session_query_options, &@filter_block)
@@ -40,9 +38,7 @@ module Capybara
40
38
  raise ArgumentError, 'The have_all_selectors matcher does not support use with not_to/should_not'
41
39
  end
42
40
 
43
- def description
44
- 'have all selectors'
45
- end
41
+ def description = 'have all selectors'
46
42
  end
47
43
 
48
44
  class HaveNoSelectors < WrappedElementMatcher
@@ -54,9 +50,7 @@ module Capybara
54
50
  raise ArgumentError, 'The have_none_of_selectors matcher does not support use with not_to/should_not'
55
51
  end
56
52
 
57
- def description
58
- 'have no selectors'
59
- end
53
+ def description = 'have no selectors'
60
54
  end
61
55
 
62
56
  class HaveAnySelectors < WrappedElementMatcher
@@ -68,9 +62,7 @@ module Capybara
68
62
  el.assert_none_of_selectors(*@args, **session_query_options, &@filter_block)
69
63
  end
70
64
 
71
- def description
72
- 'have any selectors'
73
- end
65
+ def description = 'have any selectors'
74
66
  end
75
67
  end
76
68
  end