capybara 3.32.0 → 3.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +99 -15
  3. data/README.md +9 -4
  4. data/lib/capybara.rb +18 -8
  5. data/lib/capybara/config.rb +4 -6
  6. data/lib/capybara/cucumber.rb +1 -1
  7. data/lib/capybara/driver/base.rb +4 -0
  8. data/lib/capybara/helpers.rb +25 -1
  9. data/lib/capybara/minitest.rb +2 -3
  10. data/lib/capybara/minitest/spec.rb +14 -11
  11. data/lib/capybara/node/actions.rb +16 -21
  12. data/lib/capybara/node/base.rb +6 -6
  13. data/lib/capybara/node/element.rb +1 -5
  14. data/lib/capybara/node/finders.rb +7 -6
  15. data/lib/capybara/node/matchers.rb +12 -12
  16. data/lib/capybara/node/simple.rb +5 -1
  17. data/lib/capybara/queries/ancestor_query.rb +1 -1
  18. data/lib/capybara/queries/current_path_query.rb +14 -4
  19. data/lib/capybara/queries/selector_query.rb +40 -18
  20. data/lib/capybara/queries/sibling_query.rb +1 -1
  21. data/lib/capybara/queries/style_query.rb +1 -1
  22. data/lib/capybara/queries/text_query.rb +7 -1
  23. data/lib/capybara/rack_test/browser.rb +7 -3
  24. data/lib/capybara/rack_test/driver.rb +1 -0
  25. data/lib/capybara/rack_test/form.rb +1 -1
  26. data/lib/capybara/rack_test/node.rb +1 -1
  27. data/lib/capybara/registration_container.rb +44 -0
  28. data/lib/capybara/registrations/drivers.rb +18 -12
  29. data/lib/capybara/registrations/patches/puma_ssl.rb +3 -1
  30. data/lib/capybara/registrations/servers.rb +3 -2
  31. data/lib/capybara/result.rb +10 -11
  32. data/lib/capybara/rspec.rb +2 -0
  33. data/lib/capybara/rspec/matcher_proxies.rb +1 -1
  34. data/lib/capybara/rspec/matchers.rb +7 -6
  35. data/lib/capybara/rspec/matchers/have_current_path.rb +2 -2
  36. data/lib/capybara/rspec/matchers/have_text.rb +1 -1
  37. data/lib/capybara/rspec/matchers/match_style.rb +5 -0
  38. data/lib/capybara/selector.rb +12 -3
  39. data/lib/capybara/selector/builders/css_builder.rb +1 -1
  40. data/lib/capybara/selector/builders/xpath_builder.rb +3 -1
  41. data/lib/capybara/selector/definition.rb +11 -9
  42. data/lib/capybara/selector/definition/button.rb +26 -14
  43. data/lib/capybara/selector/definition/css.rb +1 -1
  44. data/lib/capybara/selector/definition/datalist_input.rb +1 -1
  45. data/lib/capybara/selector/definition/element.rb +2 -1
  46. data/lib/capybara/selector/definition/fillable_field.rb +1 -1
  47. data/lib/capybara/selector/definition/label.rb +1 -1
  48. data/lib/capybara/selector/definition/link.rb +8 -0
  49. data/lib/capybara/selector/definition/select.rb +1 -1
  50. data/lib/capybara/selector/definition/table.rb +1 -1
  51. data/lib/capybara/selector/definition/table_row.rb +2 -2
  52. data/lib/capybara/selector/filter_set.rb +2 -2
  53. data/lib/capybara/selector/selector.rb +9 -1
  54. data/lib/capybara/selenium/atoms/src/isDisplayed.js +1 -1
  55. data/lib/capybara/selenium/driver.rb +51 -7
  56. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +9 -11
  57. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +9 -11
  58. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +3 -3
  59. data/lib/capybara/selenium/extensions/find.rb +4 -4
  60. data/lib/capybara/selenium/extensions/scroll.rb +8 -10
  61. data/lib/capybara/selenium/logger_suppressor.rb +1 -1
  62. data/lib/capybara/selenium/node.rb +23 -6
  63. data/lib/capybara/selenium/nodes/chrome_node.rb +23 -5
  64. data/lib/capybara/selenium/nodes/firefox_node.rb +7 -2
  65. data/lib/capybara/selenium/nodes/safari_node.rb +1 -1
  66. data/lib/capybara/selenium/patches/action_pauser.rb +4 -1
  67. data/lib/capybara/selenium/patches/atoms.rb +4 -4
  68. data/lib/capybara/selenium/patches/logs.rb +7 -9
  69. data/lib/capybara/server/animation_disabler.rb +8 -3
  70. data/lib/capybara/server/middleware.rb +4 -2
  71. data/lib/capybara/session.rb +23 -14
  72. data/lib/capybara/session/config.rb +3 -1
  73. data/lib/capybara/session/matchers.rb +11 -11
  74. data/lib/capybara/spec/public/test.js +13 -1
  75. data/lib/capybara/spec/session/accept_alert_spec.rb +1 -1
  76. data/lib/capybara/spec/session/check_spec.rb +6 -0
  77. data/lib/capybara/spec/session/click_button_spec.rb +11 -0
  78. data/lib/capybara/spec/session/current_url_spec.rb +11 -1
  79. data/lib/capybara/spec/session/has_button_spec.rb +51 -0
  80. data/lib/capybara/spec/session/has_css_spec.rb +2 -1
  81. data/lib/capybara/spec/session/has_current_path_spec.rb +15 -2
  82. data/lib/capybara/spec/session/has_field_spec.rb +16 -0
  83. data/lib/capybara/spec/session/has_select_spec.rb +4 -4
  84. data/lib/capybara/spec/session/has_selector_spec.rb +4 -4
  85. data/lib/capybara/spec/session/has_text_spec.rb +0 -11
  86. data/lib/capybara/spec/session/html_spec.rb +1 -1
  87. data/lib/capybara/spec/session/matches_style_spec.rb +2 -2
  88. data/lib/capybara/spec/session/node_spec.rb +29 -9
  89. data/lib/capybara/spec/session/refresh_spec.rb +2 -1
  90. data/lib/capybara/spec/session/save_page_spec.rb +4 -4
  91. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +1 -1
  92. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +1 -1
  93. data/lib/capybara/spec/session/window/window_spec.rb +1 -1
  94. data/lib/capybara/spec/session/window/windows_spec.rb +1 -1
  95. data/lib/capybara/spec/spec_helper.rb +12 -12
  96. data/lib/capybara/spec/test_app.rb +23 -21
  97. data/lib/capybara/spec/views/form.erb +28 -1
  98. data/lib/capybara/spec/views/with_animation.erb +8 -0
  99. data/lib/capybara/spec/views/with_dragula.erb +3 -1
  100. data/lib/capybara/spec/views/with_jquery_animation.erb +24 -0
  101. data/lib/capybara/spec/views/with_js.erb +3 -0
  102. data/lib/capybara/spec/views/with_sortable_js.erb +1 -1
  103. data/lib/capybara/version.rb +1 -1
  104. data/lib/capybara/window.rb +3 -7
  105. data/spec/basic_node_spec.rb +9 -8
  106. data/spec/capybara_spec.rb +1 -1
  107. data/spec/dsl_spec.rb +14 -1
  108. data/spec/fixtures/selenium_driver_rspec_success.rb +1 -1
  109. data/spec/minitest_spec.rb +3 -2
  110. data/spec/rack_test_spec.rb +16 -5
  111. data/spec/result_spec.rb +1 -17
  112. data/spec/rspec/features_spec.rb +3 -1
  113. data/spec/rspec/scenarios_spec.rb +4 -0
  114. data/spec/rspec/shared_spec_matchers.rb +63 -51
  115. data/spec/rspec_spec.rb +4 -0
  116. data/spec/selector_spec.rb +17 -2
  117. data/spec/selenium_spec_chrome.rb +39 -20
  118. data/spec/selenium_spec_chrome_remote.rb +5 -1
  119. data/spec/selenium_spec_firefox.rb +15 -13
  120. data/spec/server_spec.rb +60 -49
  121. data/spec/shared_selenium_node.rb +10 -0
  122. data/spec/shared_selenium_session.rb +98 -7
  123. data/spec/spec_helper.rb +1 -1
  124. metadata +50 -15
  125. data/lib/capybara/spec/session/source_spec.rb +0 -0
@@ -6,13 +6,15 @@ module Capybara
6
6
  class TextQuery < BaseQuery
7
7
  def initialize(type = nil, expected_text, session_options:, **options) # rubocop:disable Style/OptionalArguments
8
8
  @type = type.nil? ? default_type : type
9
+ raise ArgumentError, "#{@type} is not a valid type for a text query" unless valid_types.include?(@type)
10
+
9
11
  @options = options
10
12
  super(@options)
11
13
  self.session_options = session_options
12
14
 
13
15
  if expected_text.nil? && !exact?
14
16
  warn 'Checking for expected text of nil is confusing and/or pointless since it will always match. '\
15
- 'Please specify a string or regexp instead.'
17
+ "Please specify a string or regexp instead. #{Capybara::Helpers.filter_backtrace(caller)}"
16
18
  end
17
19
 
18
20
  @expected_text = expected_text.is_a?(Regexp) ? expected_text : expected_text.to_s
@@ -89,6 +91,10 @@ module Capybara
89
91
  COUNT_KEYS + %i[wait exact normalize_ws]
90
92
  end
91
93
 
94
+ def valid_types
95
+ %i[all visible]
96
+ end
97
+
92
98
  def check_visible_text?
93
99
  @type == :visible
94
100
  end
@@ -8,6 +8,7 @@ class Capybara::RackTest::Browser
8
8
 
9
9
  def initialize(driver)
10
10
  @driver = driver
11
+ @current_fragment = nil
11
12
  end
12
13
 
13
14
  def app
@@ -31,7 +32,7 @@ class Capybara::RackTest::Browser
31
32
  def submit(method, path, attributes)
32
33
  path = request_path if path.nil? || path.empty?
33
34
  uri = build_uri(path)
34
- uri.query = '' if method&.to_s&.downcase == 'get'
35
+ uri.query = '' if method.to_s.casecmp('get').zero?
35
36
  process_and_follow_redirects(method, uri.to_s, attributes, 'HTTP_REFERER' => current_url)
36
37
  end
37
38
 
@@ -42,6 +43,7 @@ class Capybara::RackTest::Browser
42
43
  end
43
44
 
44
45
  def process_and_follow_redirects(method, path, attributes = {}, env = {})
46
+ @current_fragment = build_uri(path).fragment
45
47
  process(method, path, attributes, env)
46
48
 
47
49
  return unless driver.follow_redirects?
@@ -65,7 +67,7 @@ class Capybara::RackTest::Browser
65
67
  method = method.downcase
66
68
  new_uri = build_uri(path)
67
69
  @current_scheme, @current_host, @current_port = new_uri.select(:scheme, :host, :port)
68
-
70
+ @current_fragment = new_uri.fragment || @current_fragment
69
71
  reset_cache!
70
72
  send(method, new_uri.to_s, attributes, env.merge(options[:headers] || {}))
71
73
  end
@@ -83,7 +85,9 @@ class Capybara::RackTest::Browser
83
85
  end
84
86
 
85
87
  def current_url
86
- last_request.url
88
+ uri = build_uri(last_request.url)
89
+ uri.fragment = @current_fragment if @current_fragment
90
+ uri.to_s
87
91
  rescue Rack::Test::Error
88
92
  ''
89
93
  end
@@ -17,6 +17,7 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
17
17
  def initialize(app, **options)
18
18
  raise ArgumentError, 'rack-test requires a rack application, but none was given' unless app
19
19
 
20
+ super()
20
21
  @app = app
21
22
  @options = DEFAULT_OPTIONS.merge(options)
22
23
  end
@@ -6,7 +6,7 @@ class Capybara::RackTest::Form < Capybara::RackTest::Node
6
6
  # That check should be based solely on the form element's 'enctype' attribute value,
7
7
  # which should probably be provided to Rack::Test in its non-GET request methods.
8
8
  class NilUploadedFile < Rack::Test::UploadedFile
9
- def initialize
9
+ def initialize # rubocop:disable Lint/MissingSuper
10
10
  @empty_file = Tempfile.new('nil_uploaded_file')
11
11
  @empty_file.close
12
12
  end
@@ -15,7 +15,7 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
15
15
  end
16
16
 
17
17
  def visible_text
18
- displayed_text.gsub(/\ +/, ' ')
18
+ displayed_text.squeeze(' ')
19
19
  .gsub(/[\ \n]*\n[\ \n]*/, "\n")
20
20
  .gsub(/\A[[:space:]&&[^\u00a0]]+/, '')
21
21
  .gsub(/[[:space:]&&[^\u00a0]]+\z/, '')
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capybara
4
+ # @api private
5
+ class RegistrationContainer
6
+ def names
7
+ @registered.keys
8
+ end
9
+
10
+ def [](name)
11
+ @registered[name]
12
+ end
13
+
14
+ def []=(name, value)
15
+ Capybara::Helpers.warn 'DEPRECATED: Directly setting drivers/servers is deprecated, please use Capybara.register_driver/register_server instead'
16
+ @registered[name] = value
17
+ end
18
+
19
+ def method_missing(method_name, *args, **options, &block)
20
+ if @registered.respond_to?(method_name)
21
+ Capybara::Helpers.warn "DEPRECATED: Calling '#{method_name}' on the drivers/servers container is deprecated without replacement"
22
+ # RUBY 2.6 will send an empty hash rather than nothing with **options so fix that
23
+ return @registered.public_send(method_name, *args, &block) if options.empty?
24
+
25
+ return @registered.public_send(method_name, *args, **options, &block)
26
+ end
27
+ super
28
+ end
29
+
30
+ def respond_to_missing?(method_name, include_all)
31
+ @registered.respond_to?(method_name) || super
32
+ end
33
+
34
+ private
35
+
36
+ def initialize
37
+ @registered = {}
38
+ end
39
+
40
+ def register(name, block)
41
+ @registered[name] = block
42
+ end
43
+ end
44
+ end
@@ -9,28 +9,34 @@ Capybara.register_driver :selenium do |app|
9
9
  end
10
10
 
11
11
  Capybara.register_driver :selenium_headless do |app|
12
- Capybara::Selenium::Driver.load_selenium
13
- browser_options = ::Selenium::WebDriver::Firefox::Options.new
14
- browser_options.args << '-headless'
15
- Capybara::Selenium::Driver.new(app, browser: :firefox, options: browser_options)
12
+ version = Capybara::Selenium::Driver.load_selenium
13
+ options_key = Capybara::Selenium::Driver::CAPS_VERSION.satisfied_by?(version) ? :capabilities : :options
14
+ browser_options = ::Selenium::WebDriver::Firefox::Options.new.tap do |opts|
15
+ opts.add_argument '-headless'
16
+ end
17
+ Capybara::Selenium::Driver.new(app, **Hash[:browser => :firefox, options_key => browser_options])
16
18
  end
17
19
 
18
20
  Capybara.register_driver :selenium_chrome do |app|
19
- Capybara::Selenium::Driver.load_selenium
21
+ version = Capybara::Selenium::Driver.load_selenium
22
+ options_key = Capybara::Selenium::Driver::CAPS_VERSION.satisfied_by?(version) ? :capabilities : :options
20
23
  browser_options = ::Selenium::WebDriver::Chrome::Options.new.tap do |opts|
21
24
  # Workaround https://bugs.chromium.org/p/chromedriver/issues/detail?id=2650&q=load&sort=-id&colspec=ID%20Status%20Pri%20Owner%20Summary
22
- opts.args << '--disable-site-isolation-trials'
25
+ opts.add_argument('--disable-site-isolation-trials')
23
26
  end
24
- Capybara::Selenium::Driver.new(app, browser: :chrome, options: browser_options)
27
+
28
+ Capybara::Selenium::Driver.new(app, **Hash[:browser => :firefox, options_key => browser_options])
25
29
  end
26
30
 
27
31
  Capybara.register_driver :selenium_chrome_headless do |app|
28
- Capybara::Selenium::Driver.load_selenium
32
+ version = Capybara::Selenium::Driver.load_selenium
33
+ options_key = Capybara::Selenium::Driver::CAPS_VERSION.satisfied_by?(version) ? :capabilities : :options
29
34
  browser_options = ::Selenium::WebDriver::Chrome::Options.new.tap do |opts|
30
- opts.args << '--headless'
31
- opts.args << '--disable-gpu' if Gem.win_platform?
35
+ opts.add_argument('--headless')
36
+ opts.add_argument('--disable-gpu') if Gem.win_platform?
32
37
  # Workaround https://bugs.chromium.org/p/chromedriver/issues/detail?id=2650&q=load&sort=-id&colspec=ID%20Status%20Pri%20Owner%20Summary
33
- opts.args << '--disable-site-isolation-trials'
38
+ opts.add_argument('--disable-site-isolation-trials')
34
39
  end
35
- Capybara::Selenium::Driver.new(app, browser: :chrome, options: browser_options)
40
+
41
+ Capybara::Selenium::Driver.new(app, **Hash[:browser => :firefox, options_key => browser_options])
36
42
  end
@@ -4,12 +4,14 @@ module Puma
4
4
  module MiniSSL
5
5
  class Socket
6
6
  def read_nonblock(size, *_)
7
+ wait_states = %i[wait_readable wait_writable]
8
+
7
9
  loop do
8
10
  output = engine_read_all
9
11
  return output if output
10
12
 
11
13
  data = @socket.read_nonblock(size, exception: false)
12
- raise IO::EAGAINWaitReadable if %i[wait_readable wait_writable].include? data
14
+ raise IO::EAGAINWaitReadable if wait_states.include? data
13
15
  return nil if data.nil?
14
16
 
15
17
  @engine.inject(data)
@@ -7,7 +7,7 @@ end
7
7
  Capybara.register_server :webrick do |app, port, host, **options|
8
8
  require 'rack/handler/webrick'
9
9
  options = { Host: host, Port: port, AccessLog: [], Logger: WEBrick::Log.new(nil, 0) }.merge(options)
10
- Rack::Handler::WEBrick.run(app, options)
10
+ Rack::Handler::WEBrick.run(app, **options)
11
11
  end
12
12
 
13
13
  Capybara.register_server :puma do |app, port, host, **options|
@@ -28,10 +28,11 @@ Capybara.register_server :puma do |app, port, host, **options|
28
28
  options = default_options.merge(options)
29
29
 
30
30
  conf = Rack::Handler::Puma.config(app, options)
31
+ conf.clamp
31
32
  events = conf.options[:Silent] ? ::Puma::Events.strings : ::Puma::Events.stdio
32
33
 
33
34
  puma_ver = Gem::Version.new(Puma::Const::PUMA_VERSION)
34
- require_relative 'patches/puma_ssl' if (Gem::Version.new('4.0.0')...Gem::Version.new('4.1.0')).cover? puma_ver
35
+ require_relative 'patches/puma_ssl' if Gem::Requirement.new('>=4.0.0', '< 4.1.0').satisfied_by?(puma_ver)
35
36
 
36
37
  events.log 'Capybara starting Puma...'
37
38
  events.log "* Version #{Puma::Const::PUMA_VERSION} , codename: #{Puma::Const::CODE_NAME}"
@@ -39,7 +39,7 @@ module Capybara
39
39
  alias index find_index
40
40
 
41
41
  def each(&block)
42
- return enum_for(:each) unless block_given?
42
+ return enum_for(:each) unless block
43
43
 
44
44
  @result_cache.each(&block)
45
45
  loop do
@@ -54,10 +54,10 @@ module Capybara
54
54
  idx, length = args
55
55
  max_idx = case idx
56
56
  when Integer
57
- if !idx.negative?
58
- length.nil? ? idx : idx + length - 1
59
- else
57
+ if idx.negative?
60
58
  nil
59
+ else
60
+ length.nil? ? idx : idx + length - 1
61
61
  end
62
62
  when Range
63
63
  # idx.max is broken with beginless ranges
@@ -91,13 +91,9 @@ module Capybara
91
91
  return load_up_to(count + 1) <=> count
92
92
  end
93
93
 
94
- if min && (min = Integer(min))
95
- return -1 if load_up_to(min) < min
96
- end
94
+ return -1 if min && (min = Integer(min)) && (load_up_to(min) < min)
97
95
 
98
- if max && (max = Integer(max))
99
- return 1 if load_up_to(max + 1) > max
100
- end
96
+ return 1 if max && (max = Integer(max)) && (load_up_to(max + 1) > max)
101
97
 
102
98
  if between
103
99
  min, max = (between.begin && between.min) || 1, between.end
@@ -171,10 +167,13 @@ module Capybara
171
167
  @rest ||= @elements - full_results
172
168
  end
173
169
 
174
- if (RUBY_PLATFORM == 'java') && (Gem::Version.new(JRUBY_VERSION) < Gem::Version.new('9.2.8.0'))
170
+ if RUBY_PLATFORM == 'java'
175
171
  # JRuby < 9.2.8.0 has an issue with lazy enumerators which
176
172
  # causes a concurrency issue with network requests here
177
173
  # https://github.com/jruby/jruby/issues/4212
174
+ # while JRuby >= 9.2.8.0 leaks threads when using lazy enumerators
175
+ # https://github.com/teamcapybara/capybara/issues/2349
176
+ # so disable the use and JRuby users will need to pay a performance penalty
178
177
  def lazy_select_elements(&block)
179
178
  @elements.select(&block).to_enum # non-lazy evaluation
180
179
  end
@@ -9,6 +9,8 @@ require 'capybara/rspec/matcher_proxies'
9
9
  RSpec.configure do |config|
10
10
  config.include Capybara::DSL, type: :feature
11
11
  config.include Capybara::RSpecMatchers, type: :feature
12
+ config.include Capybara::DSL, type: :system
13
+ config.include Capybara::RSpecMatchers, type: :system
12
14
  config.include Capybara::RSpecMatchers, type: :view
13
15
 
14
16
  # The before and after blocks must run instantaneously, because Capybara
@@ -11,7 +11,7 @@ module Capybara
11
11
  end
12
12
 
13
13
  def within(*args, **kwargs, &block)
14
- if block_given?
14
+ if block
15
15
  within_element(*args, **kwargs, &block)
16
16
  else
17
17
  be_within(*args)
@@ -94,7 +94,7 @@ module Capybara
94
94
  # @see Capybara::Node::Matchers#has_button?
95
95
 
96
96
  # @!method have_field(locator = nil, **options, &optional_filter_block)
97
- # RSpec matcher for links.
97
+ # RSpec matcher for form fields.
98
98
  #
99
99
  # @see Capybara::Node::Matchers#has_field?
100
100
 
@@ -139,22 +139,23 @@ module Capybara
139
139
  # RSpec matcher for the current path.
140
140
  #
141
141
  # @see Capybara::SessionMatchers#assert_current_path
142
- def have_current_path(path, **options)
143
- Matchers::HaveCurrentPath.new(path, **options)
142
+ def have_current_path(path, **options, &optional_filter_block)
143
+ Matchers::HaveCurrentPath.new(path, **options, &optional_filter_block)
144
144
  end
145
145
 
146
146
  # RSpec matcher for element style.
147
147
  #
148
148
  # @see Capybara::Node::Matchers#matches_style?
149
- def match_style(styles, **options)
149
+ def match_style(styles = nil, **options)
150
+ styles, options = options, {} if styles.nil?
150
151
  Matchers::MatchStyle.new(styles, **options)
151
152
  end
152
153
 
153
154
  ##
154
155
  # @deprecated
155
156
  #
156
- def have_style(styles, **options)
157
- warn 'DEPRECATED: have_style is deprecated, please use match_style'
157
+ def have_style(styles = nil, **options)
158
+ Capybara::Helpers.warn "DEPRECATED: have_style is deprecated, please use match_style : #{Capybara::Helpers.filter_backtrace(caller)}"
158
159
  match_style(styles, **options)
159
160
  end
160
161
 
@@ -7,11 +7,11 @@ module Capybara
7
7
  module Matchers
8
8
  class HaveCurrentPath < WrappedElementMatcher
9
9
  def element_matches?(el)
10
- el.assert_current_path(current_path, **@kw_args)
10
+ el.assert_current_path(current_path, **@kw_args, &@filter_block)
11
11
  end
12
12
 
13
13
  def element_does_not_match?(el)
14
- el.assert_no_current_path(current_path, **@kw_args)
14
+ el.assert_no_current_path(current_path, **@kw_args, &@filter_block)
15
15
  end
16
16
 
17
17
  def description
@@ -15,7 +15,7 @@ module Capybara
15
15
  end
16
16
 
17
17
  def description
18
- "text #{format(text)}"
18
+ "have text #{format(text)}"
19
19
  end
20
20
 
21
21
  def format(content)
@@ -6,6 +6,11 @@ module Capybara
6
6
  module RSpecMatchers
7
7
  module Matchers
8
8
  class MatchStyle < WrappedElementMatcher
9
+ def initialize(styles = nil, **kw_args, &filter_block)
10
+ styles, kw_args = kw_args, {} if styles.nil?
11
+ super(styles, **kw_args, &filter_block)
12
+ end
13
+
9
14
  def element_matches?(el)
10
15
  el.assert_matches_style(*@args, **@kw_args)
11
16
  end
@@ -7,7 +7,7 @@ require 'capybara/selector/definition'
7
7
  #
8
8
  # All Selectors below support the listed selector specific filters in addition to the following system-wide filters
9
9
  # * :id (String, Regexp, XPath::Expression) - Matches the id attribute
10
- # * :class (String, Array<String>, Regexp, XPath::Expression) - Matches the class(es) provided
10
+ # * :class (String, Array<String | Regexp>, Regexp, XPath::Expression) - Matches the class(es) provided
11
11
  # * :style (String, Regexp, Hash<String, String>) - Match on elements style
12
12
  # * :above (Element) - Match elements above the passed element on the page
13
13
  # * :below (Element) - Match elements below the passed element on the page
@@ -40,6 +40,7 @@ require 'capybara/selector/definition'
40
40
  # * :disabled (Boolean, :all) - Match disabled field? (Default: false)
41
41
  # * :multiple (Boolean) - Match fields that accept multiple values
42
42
  # * :valid (Boolean) - Match fields that are valid/invalid according to HTML5 form validation
43
+ # * :validation_message (String, Regexp) - Matches the elements current validationMessage
43
44
  #
44
45
  # * **:fieldset** - Select fieldset elements
45
46
  # * Locator: Matches id, {Capybara.configure test_id}, or contents of wrapped legend
@@ -79,6 +80,7 @@ require 'capybara/selector/definition'
79
80
  # * :disabled (Boolean, :all) - Match disabled field? (Default: false)
80
81
  # * :multiple (Boolean) - Match fields that accept multiple values
81
82
  # * :valid (Boolean) - Match fields that are valid/invalid according to HTML5 form validation
83
+ # * :validation_message (String, Regexp) - Matches the elements current validationMessage
82
84
  #
83
85
  # * **:radio_button** - Find radio buttons
84
86
  # * Locator: Match id, {Capybara.configure test_id} attribute, name, or associated label text
@@ -169,7 +171,7 @@ require 'capybara/selector/definition'
169
171
  # * Filters:
170
172
  # * :\<any> (String, Regexp) - Match on any specified element attribute
171
173
  #
172
- class Capybara::Selector; end
174
+ class Capybara::Selector; end # rubocop:disable Lint/EmptyClass
173
175
 
174
176
  Capybara::Selector::FilterSet.add(:_field) do
175
177
  node_filter(:checked, :boolean) { |node, value| !(value ^ node.checked?) }
@@ -178,6 +180,12 @@ Capybara::Selector::FilterSet.add(:_field) do
178
180
  node_filter(:valid, :boolean) { |node, value| node.evaluate_script('this.validity.valid') == value }
179
181
  node_filter(:name) { |node, value| !value.is_a?(Regexp) || value.match?(node[:name]) }
180
182
  node_filter(:placeholder) { |node, value| !value.is_a?(Regexp) || value.match?(node[:placeholder]) }
183
+ node_filter(:validation_message) do |node, msg|
184
+ vm = node[:validationMessage]
185
+ (msg.is_a?(Regexp) ? msg.match?(vm) : vm == msg.to_s).tap do |res|
186
+ add_error("Expected validation message to be #{msg.inspect} but was #{vm}") unless res
187
+ end
188
+ end
181
189
 
182
190
  expression_filter(:name) do |xpath, val|
183
191
  builder(xpath).add_attribute_conditions(name: val)
@@ -198,7 +206,7 @@ Capybara::Selector::FilterSet.add(:_field) do
198
206
  desc
199
207
  end
200
208
 
201
- describe(:node_filters) do |checked: nil, unchecked: nil, disabled: nil, valid: nil, **|
209
+ describe(:node_filters) do |checked: nil, unchecked: nil, disabled: nil, valid: nil, validation_message: nil, **|
202
210
  desc, states = +'', []
203
211
  states << 'checked' if checked || (unchecked == false)
204
212
  states << 'not checked' if unchecked || (checked == false)
@@ -206,6 +214,7 @@ Capybara::Selector::FilterSet.add(:_field) do
206
214
  desc << " that is #{states.join(' and ')}" unless states.empty?
207
215
  desc << ' that is valid' if valid == true
208
216
  desc << ' that is invalid' if valid == false
217
+ desc << " with validation message #{validation_message.to_s.inspect}" if validation_message
209
218
  desc
210
219
  end
211
220
  end
@@ -74,7 +74,7 @@ module Capybara
74
74
  end.join
75
75
  end
76
76
  else
77
- cls = Array(classes).group_by { |cl| cl.match?(/^!(?!!!)/) }
77
+ cls = Array(classes).reject { |c| c.is_a? Regexp }.group_by { |cl| cl.match?(/^!(?!!!)/) }
78
78
  [(cls[false].to_a.map { |cl| ".#{Capybara::Selector::CSS.escape(cl.sub(/^!!/, ''))}" } +
79
79
  cls[true].to_a.map { |cl| ":not(.#{Capybara::Selector::CSS.escape(cl.slice(1..-1))})" }).join]
80
80
  end