capybara 3.32.0 → 3.33.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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +38 -1
  3. data/README.md +10 -3
  4. data/lib/capybara/cucumber.rb +1 -1
  5. data/lib/capybara/minitest.rb +2 -3
  6. data/lib/capybara/node/actions.rb +16 -20
  7. data/lib/capybara/node/matchers.rb +4 -6
  8. data/lib/capybara/queries/selector_query.rb +8 -1
  9. data/lib/capybara/queries/style_query.rb +1 -1
  10. data/lib/capybara/queries/text_query.rb +6 -0
  11. data/lib/capybara/registration_container.rb +44 -0
  12. data/lib/capybara/registrations/servers.rb +1 -1
  13. data/lib/capybara/result.rb +4 -1
  14. data/lib/capybara/rspec/matchers/have_text.rb +1 -1
  15. data/lib/capybara/selector/definition/button.rb +1 -0
  16. data/lib/capybara/selector/definition/fillable_field.rb +1 -1
  17. data/lib/capybara/selector/definition/link.rb +8 -0
  18. data/lib/capybara/selector/definition/table.rb +1 -1
  19. data/lib/capybara/selector/definition.rb +5 -4
  20. data/lib/capybara/selector/selector.rb +4 -0
  21. data/lib/capybara/selector.rb +10 -1
  22. data/lib/capybara/selenium/driver.rb +6 -4
  23. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +7 -9
  24. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +7 -9
  25. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +2 -2
  26. data/lib/capybara/selenium/node.rb +17 -3
  27. data/lib/capybara/selenium/nodes/firefox_node.rb +1 -1
  28. data/lib/capybara/selenium/patches/action_pauser.rb +4 -1
  29. data/lib/capybara/selenium/patches/logs.rb +3 -5
  30. data/lib/capybara/session/config.rb +3 -1
  31. data/lib/capybara/session.rb +3 -3
  32. data/lib/capybara/spec/public/test.js +7 -0
  33. data/lib/capybara/spec/session/click_button_spec.rb +11 -0
  34. data/lib/capybara/spec/session/has_button_spec.rb +16 -0
  35. data/lib/capybara/spec/session/has_current_path_spec.rb +2 -2
  36. data/lib/capybara/spec/session/has_field_spec.rb +16 -0
  37. data/lib/capybara/spec/session/has_select_spec.rb +4 -4
  38. data/lib/capybara/spec/session/has_selector_spec.rb +4 -4
  39. data/lib/capybara/spec/session/node_spec.rb +6 -6
  40. data/lib/capybara/spec/spec_helper.rb +1 -0
  41. data/lib/capybara/spec/test_app.rb +14 -18
  42. data/lib/capybara/spec/views/form.erb +6 -1
  43. data/lib/capybara/spec/views/with_dragula.erb +3 -1
  44. data/lib/capybara/spec/views/with_js.erb +1 -0
  45. data/lib/capybara/version.rb +1 -1
  46. data/lib/capybara.rb +17 -7
  47. data/spec/capybara_spec.rb +1 -1
  48. data/spec/dsl_spec.rb +14 -1
  49. data/spec/minitest_spec.rb +1 -1
  50. data/spec/rack_test_spec.rb +1 -0
  51. data/spec/result_spec.rb +1 -17
  52. data/spec/rspec/shared_spec_matchers.rb +65 -53
  53. data/spec/selector_spec.rb +1 -1
  54. data/spec/selenium_spec_chrome.rb +0 -2
  55. data/spec/server_spec.rb +41 -49
  56. data/spec/shared_selenium_node.rb +10 -0
  57. data/spec/shared_selenium_session.rb +25 -7
  58. data/spec/spec_helper.rb +1 -1
  59. metadata +4 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0f60918220b590a4a64f3bb1a42b7e8c5dac86dddcec48d46c0f92c1ac6c491f
4
- data.tar.gz: aab5f09c11d94dc1becdb514d753011cdfad7f2a43f0e59b074ff6e69aa49847
3
+ metadata.gz: ab64d9d47c6374c4f203945be70b868fb9e4c48d3fdca087ba4e66498e40b91c
4
+ data.tar.gz: 451c97390c148b5b0fad1b06107a4f0de87f17f7b8cef7156fb76ad2ee503f76
5
5
  SHA512:
6
- metadata.gz: 7adcf8fbe5fe95d42c3c7fe513f62236cf93dd0f95bbccb20d31c1022a5e87bd64514d2fd6f57a263d31b96945a2e1c7a300d6b16db018651b929abd137e8290
7
- data.tar.gz: d28874576cd8613f16b1d455785e4e6fba9cb387b8580ba0bdc0c46f13255661f07e47a51f5d7f2c28afa6691a4c330e094ff9625a89bb4f6f5d9532a3123063
6
+ metadata.gz: b0ca3fd8a4535e1df696ddc7f547e066b873d3f4be2b54655fdbb0b6588c5086a87b93500e241e0da031b586c0b6a30cdd8522f209309cf6729c683226bfcf7f
7
+ data.tar.gz: d295dcd733856816a1c89810f4d54304a52c251d585afcc62b986a66d63e080e03ddc81bdc46cf51a044e29d2849ccce9bb3cb307a0757746e662394f648a717
data/History.md CHANGED
@@ -1,5 +1,42 @@
1
+ # Version 3.33.0
2
+ Release date: 2020-06-21
3
+
4
+ ### Added
5
+
6
+ * Block passed to `within_session` now receives the new and old session
7
+ * Support for aria-role button when enabled [Seiei Miyagi]
8
+ * Support for aria-role link when enabled
9
+ * Support for `validation_message` filter with :field and :fillable_field selectors
10
+
11
+ ### Changed
12
+
13
+ * Ruby 2.5.0+ is now required
14
+ * Deprecated direct manupulation of the driver and server registries
15
+
16
+ ### Fixed
17
+
18
+ * Ruby 2.7 warning in minitest `assert_text` [Eileen M. Uchitelle]
19
+
20
+
21
+ # Version 3.32.2
22
+ Release date: 2020-05-16
23
+
24
+ ### Fixed
25
+
26
+ * Don't use lazy enumerator with JRuby due to leaking threads
27
+ * Ruby 2.7 deprecation warning when registering Webrick [Jon Zeppieri]
28
+ * `have_text` description [Juan Pablo Rinaldi]
29
+
30
+ # Version 3.32.1
31
+ Release date: 2020-04-05
32
+
33
+ ### Fixed
34
+
35
+ * Rapid set now respects field maxlength (Issue #2332)
36
+ * Only patch pause into legacy actions in Selenium < 4 (Issue #2334)
37
+
1
38
  # Version 3.32.0
2
- Relesae date: 2020-03-29
39
+ Release date: 2020-03-29
3
40
 
4
41
  ### Added
5
42
 
data/README.md CHANGED
@@ -7,7 +7,7 @@
7
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
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
9
 
10
- **Note** You are viewing the README for the 3.32.x version of Capybara.
10
+ **Note** You are viewing the README for the 3.33.x release of Capybara.
11
11
 
12
12
  Capybara helps you test web applications by simulating how a real user would
13
13
  interact with your app. It is agnostic about the driver running your tests and
@@ -76,7 +76,7 @@ GitHub): http://groups.google.com/group/ruby-capybara
76
76
 
77
77
  ## <a name="setup"></a>Setup
78
78
 
79
- Capybara requires Ruby 2.4.0 or later. To install, add this line to your
79
+ Capybara requires Ruby 2.5.0 or later. To install, add this line to your
80
80
  `Gemfile` and run `bundle install`:
81
81
 
82
82
  ```ruby
@@ -149,7 +149,7 @@ require 'capybara/rspec'
149
149
 
150
150
  If you are using Rails, put your Capybara specs in `spec/features` or `spec/system` (only works
151
151
  if [you have it configured in
152
- RSpec](https://www.relishapp.com/rspec/rspec-rails/docs/upgrade#file-type-inference-disabled))
152
+ RSpec](https://relishapp.com/rspec/rspec-rails/v/4-0/docs/directory-structure))
153
153
  and if you have your Capybara specs in a different directory, then tag the
154
154
  example groups with `type: :feature` or `type: :system` depending on which type of test you're writing.
155
155
 
@@ -1055,6 +1055,13 @@ additional info about how the underlying driver can be configured.
1055
1055
  are testing for specific server errors and using multiple sessions make sure to test for the
1056
1056
  errors using the initial session (usually :default)
1057
1057
 
1058
+ * If WebMock is enabled, you may encounter a "Too many open files"
1059
+ error. A simple `page.find` call may cause thousands of HTTP requests
1060
+ until the timeout occurs. By default, WebMock will cause each of these
1061
+ requests to spawn a new connection. To work around this problem, you
1062
+ may need to [enable WebMock's `net_http_connect_on_start: true`
1063
+ parameter](https://github.com/bblimke/webmock/blob/master/README.md#connecting-on-nethttpstart).
1064
+
1058
1065
  ## <a name="threadsafe"></a>"Threadsafe" mode
1059
1066
 
1060
1067
  In normal mode most of Capybara's configuration options are global settings which can cause issues
@@ -22,6 +22,6 @@ end
22
22
  Before do |scenario|
23
23
  scenario.source_tag_names.each do |tag|
24
24
  driver_name = tag.sub(/^@/, '').to_sym
25
- Capybara.current_driver = driver_name if Capybara.drivers.key?(driver_name)
25
+ Capybara.current_driver = driver_name if Capybara.drivers[driver_name]
26
26
  end
27
27
  end
@@ -50,15 +50,14 @@ module Capybara
50
50
 
51
51
  %w[text no_text title no_title current_path no_current_path].each do |assertion_name|
52
52
  class_eval <<-ASSERTION, __FILE__, __LINE__ + 1
53
- def assert_#{assertion_name} *args
53
+ def assert_#{assertion_name}(*args, **kwargs)
54
54
  self.assertions +=1
55
55
  subject, args = determine_subject(args)
56
- subject.assert_#{assertion_name}(*args)
56
+ subject.assert_#{assertion_name}(*args, **kwargs)
57
57
  rescue Capybara::ExpectationNotMet => e
58
58
  raise ::Minitest::Assertion, e.message
59
59
  end
60
60
  ASSERTION
61
- ruby2_keywords "assert_#{assertion_name}" if respond_to?(:ruby2_keywords)
62
61
  end
63
62
 
64
63
  alias_method :refute_title, :assert_no_title
@@ -308,16 +308,14 @@ module Capybara
308
308
 
309
309
  def find_select_or_datalist_input(from, options)
310
310
  synchronize(Capybara::Queries::BaseQuery.wait(options, session_options.default_max_wait_time)) do
311
+ find(:select, from, **options)
312
+ rescue Capybara::ElementNotFound => select_error # rubocop:disable Naming/RescuedExceptionsVariableName
313
+ raise if %i[selected with_selected multiple].any? { |option| options.key?(option) }
314
+
311
315
  begin
312
- find(:select, from, **options)
313
- rescue Capybara::ElementNotFound => select_error # rubocop:disable Naming/RescuedExceptionsVariableName
314
- raise if %i[selected with_selected multiple].any? { |option| options.key?(option) }
315
-
316
- begin
317
- find(:datalist_input, from, **options)
318
- rescue Capybara::ElementNotFound => dlinput_error # rubocop:disable Naming/RescuedExceptionsVariableName
319
- raise Capybara::ElementNotFound, "#{select_error.message} and #{dlinput_error.message}"
320
- end
316
+ find(:datalist_input, from, **options)
317
+ rescue Capybara::ElementNotFound => dlinput_error # rubocop:disable Naming/RescuedExceptionsVariableName
318
+ raise Capybara::ElementNotFound, "#{select_error.message} and #{dlinput_error.message}"
321
319
  end
322
320
  end
323
321
  end
@@ -367,18 +365,16 @@ module Capybara
367
365
  options[:allow_self] = true if locator.nil?
368
366
 
369
367
  synchronize(Capybara::Queries::BaseQuery.wait(options, session_options.default_max_wait_time)) do
368
+ el = find(selector, locator, **options)
369
+ el.set(checked)
370
+ rescue StandardError => e
371
+ raise unless allow_label_click && catch_error?(e)
372
+
370
373
  begin
371
- el = find(selector, locator, **options)
372
- el.set(checked)
373
- rescue StandardError => e
374
- raise unless allow_label_click && catch_error?(e)
375
-
376
- begin
377
- el ||= find(selector, locator, **options.merge(visible: :all))
378
- el.session.find(:label, for: el, visible: true).click unless el.checked? == checked
379
- rescue StandardError # swallow extra errors - raise original
380
- raise e
381
- end
374
+ el ||= find(selector, locator, **options.merge(visible: :all))
375
+ el.session.find(:label, for: el, visible: true).click unless el.checked? == checked
376
+ rescue StandardError # swallow extra errors - raise original
377
+ raise e
382
378
  end
383
379
  end
384
380
  end
@@ -201,12 +201,10 @@ module Capybara
201
201
  selector = extract_selector(args)
202
202
  synchronize(wait) do
203
203
  res = args.map do |locator|
204
- begin
205
- assert_selector(selector, locator, options, &optional_filter_block)
206
- break nil
207
- rescue Capybara::ExpectationNotMet => e
208
- e.message
209
- end
204
+ assert_selector(selector, locator, options, &optional_filter_block)
205
+ break nil
206
+ rescue Capybara::ExpectationNotMet => e
207
+ e.message
210
208
  end
211
209
  raise Capybara::ExpectationNotMet, res.join(' or ') if res
212
210
 
@@ -6,6 +6,7 @@ module Capybara
6
6
  module Queries
7
7
  class SelectorQuery < Queries::BaseQuery
8
8
  attr_reader :expression, :selector, :locator, :options
9
+
9
10
  SPATIAL_KEYS = %i[above below left_of right_of near].freeze
10
11
  VALID_KEYS = SPATIAL_KEYS + COUNT_KEYS +
11
12
  %i[text id class style visible obscured exact exact_text normalize_ws match wait filter_set]
@@ -14,6 +15,7 @@ module Capybara
14
15
  def initialize(*args,
15
16
  session_options:,
16
17
  enable_aria_label: session_options.enable_aria_label,
18
+ enable_aria_role: session_options.enable_aria_role,
17
19
  test_id: session_options.test_id,
18
20
  selector_format: nil,
19
21
  order: nil,
@@ -30,7 +32,11 @@ module Capybara
30
32
 
31
33
  @selector = Selector.new(
32
34
  find_selector(args[0].is_a?(Symbol) ? args.shift : args[0]),
33
- config: { enable_aria_label: enable_aria_label, test_id: test_id },
35
+ config: {
36
+ enable_aria_label: enable_aria_label,
37
+ enable_aria_role: enable_aria_role,
38
+ test_id: test_id
39
+ },
34
40
  format: selector_format
35
41
  )
36
42
 
@@ -575,6 +581,7 @@ module Capybara
575
581
 
576
582
  class Rectangle
577
583
  attr_reader :top, :bottom, :left, :right
584
+
578
585
  def initialize(position)
579
586
  # rubocop:disable Style/RescueModifier
580
587
  @top = position['top'] rescue position['y']
@@ -34,7 +34,7 @@ module Capybara
34
34
  private
35
35
 
36
36
  def stringify_keys(hsh)
37
- hsh.each_with_object({}) { |(k, v), str_keys| str_keys[k.to_s] = v }
37
+ hsh.transform_keys(&:to_s)
38
38
  end
39
39
 
40
40
  def valid_keys
@@ -6,6 +6,8 @@ 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
@@ -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
@@ -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
+ 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
+ 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_private = false)
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
@@ -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|
@@ -171,10 +171,13 @@ module Capybara
171
171
  @rest ||= @elements - full_results
172
172
  end
173
173
 
174
- if (RUBY_PLATFORM == 'java') && (Gem::Version.new(JRUBY_VERSION) < Gem::Version.new('9.2.8.0'))
174
+ if RUBY_PLATFORM == 'java'
175
175
  # JRuby < 9.2.8.0 has an issue with lazy enumerators which
176
176
  # causes a concurrency issue with network requests here
177
177
  # https://github.com/jruby/jruby/issues/4212
178
+ # while JRuby >= 9.2.8.0 leaks threads when using lazy enumerators
179
+ # https://github.com/teamcapybara/capybara/issues/2349
180
+ # so disable the use and JRuby users will need to pay a performance penalty
178
181
  def lazy_select_elements(&block)
179
182
  @elements.select(&block).to_enum # non-lazy evaluation
180
183
  end
@@ -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)
@@ -4,6 +4,7 @@ Capybara.add_selector(:button, locator_type: [String, Symbol]) do
4
4
  xpath(:value, :title, :type, :name) do |locator, **options|
5
5
  input_btn_xpath = XPath.descendant(:input)[XPath.attr(:type).one_of('submit', 'reset', 'image', 'button')]
6
6
  btn_xpath = XPath.descendant(:button)
7
+ btn_xpath += XPath.descendant[XPath.attr(:role).equals('button')] if enable_aria_role
7
8
  image_btn_xpath = XPath.descendant(:input)[XPath.attr(:type) == 'image']
8
9
 
9
10
  unless locator.nil?
@@ -18,7 +18,7 @@ Capybara.add_selector(:fillable_field, locator_type: [String, Symbol]) do
18
18
  end
19
19
  end
20
20
 
21
- filter_set(:_field, %i[disabled multiple name placeholder valid])
21
+ filter_set(:_field, %i[disabled multiple name placeholder valid validation_message])
22
22
 
23
23
  node_filter(:with) do |node, with|
24
24
  val = node.value
@@ -5,6 +5,13 @@ Capybara.add_selector(:link, locator_type: [String, Symbol]) do
5
5
  xpath = XPath.descendant(:a)
6
6
  xpath = builder(xpath).add_attribute_conditions(href: href) unless href == false
7
7
 
8
+ if enable_aria_role
9
+ role_path = XPath.descendant[XPath.attr(:role).equals('link')]
10
+ role_path = builder(role_path).add_attribute_conditions(href: href) unless [true, false].include? href
11
+
12
+ xpath += role_path
13
+ end
14
+
8
15
  unless locator.nil?
9
16
  locator = locator.to_s
10
17
  matchers = [XPath.attr(:id) == locator,
@@ -18,6 +25,7 @@ Capybara.add_selector(:link, locator_type: [String, Symbol]) do
18
25
 
19
26
  xpath = xpath[find_by_attr(:title, title)]
20
27
  xpath = xpath[XPath.descendant(:img)[XPath.attr(:alt) == alt]] if alt
28
+
21
29
  xpath
22
30
  end
23
31
 
@@ -43,7 +43,7 @@ Capybara.add_selector(:table, locator_type: [String, Symbol]) do
43
43
  end
44
44
 
45
45
  expression_filter(:cols, valid_values: [Array]) do |xpath, cols|
46
- raise ArgumentError, ':cols must be an Array of Arrays' unless cols.all? { |col| col.is_a? Array }
46
+ raise ArgumentError, ':cols must be an Array of Arrays' unless cols.all?(Array)
47
47
 
48
48
  rows = cols.transpose
49
49
  col_conditions = rows.map { |row| match_row(row, match_size: true) }.reduce(:&)
@@ -10,6 +10,7 @@ module Capybara
10
10
  class Selector
11
11
  class Definition
12
12
  attr_reader :name, :expressions
13
+
13
14
  extend Forwardable
14
15
 
15
16
  def initialize(name, locator_type: nil, raw_locator: false, supports_exact: nil, &block)
@@ -189,7 +190,7 @@ module Capybara
189
190
  def describe_all_expression_filters(**opts)
190
191
  expression_filters.map do |ef_name, ef|
191
192
  if ef.matcher?
192
- handled_custom_keys(ef, opts.keys).map { |key| " with #{ef_name}[#{key} => #{opts[key]}]" }.join
193
+ handled_custom_options(ef, opts).map { |option, value| " with #{ef_name}[#{option} => #{value}]" }.join
193
194
  elsif opts.key?(ef_name)
194
195
  " with #{ef_name} #{opts[ef_name]}"
195
196
  end
@@ -251,9 +252,9 @@ module Capybara
251
252
 
252
253
  private
253
254
 
254
- def handled_custom_keys(filter, keys)
255
- keys.select do |key|
256
- filter.handles_option?(key) && !::Capybara::Queries::SelectorQuery::VALID_KEYS.include?(key)
255
+ def handled_custom_options(filter, options)
256
+ options.select do |option, _|
257
+ filter.handles_option?(option) && !::Capybara::Queries::SelectorQuery::VALID_KEYS.include?(option)
257
258
  end
258
259
  end
259
260
 
@@ -48,6 +48,10 @@ module Capybara
48
48
  @config[:enable_aria_label]
49
49
  end
50
50
 
51
+ def enable_aria_role
52
+ @config[:enable_aria_role]
53
+ end
54
+
51
55
  def test_id
52
56
  @config[:test_id]
53
57
  end
@@ -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
@@ -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
@@ -85,6 +85,8 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
85
85
 
86
86
  def html
87
87
  browser.page_source
88
+ rescue Selenium::WebDriver::Error::JavascriptError => e
89
+ raise unless e.message.match?(/documentElement is null/)
88
90
  end
89
91
 
90
92
  def title
@@ -241,7 +243,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
241
243
 
242
244
  def quit
243
245
  @browser&.quit
244
- rescue Selenium::WebDriver::Error::SessionNotCreatedError, Errno::ECONNREFUSED # rubocop:disable Lint/SuppressedException
246
+ rescue Selenium::WebDriver::Error::SessionNotCreatedError, Errno::ECONNREFUSED
245
247
  # Browser must have already gone
246
248
  rescue Selenium::WebDriver::Error::UnknownError => e
247
249
  unless silenced_unknown_error_message?(e.message) # Most likely already gone
@@ -293,7 +295,7 @@ private
293
295
  def clear_browser_state
294
296
  delete_all_cookies
295
297
  clear_storage
296
- rescue *clear_browser_state_errors # rubocop:disable Lint/SuppressedException
298
+ rescue *clear_browser_state_errors
297
299
  # delete_all_cookies fails when we've previously gone
298
300
  # to about:blank, so we rescue this error and do nothing
299
301
  # instead.
@@ -317,7 +319,7 @@ private
317
319
  def clear_storage
318
320
  clear_session_storage unless options[:clear_session_storage] == false
319
321
  clear_local_storage unless options[:clear_local_storage] == false
320
- rescue Selenium::WebDriver::Error::JavascriptError # rubocop:disable Lint/SuppressedException
322
+ rescue Selenium::WebDriver::Error::JavascriptError
321
323
  # session/local storage may not be available if on non-http pages (e.g. about:blank)
322
324
  end
323
325
 
@@ -353,7 +355,7 @@ private
353
355
  @browser.navigate.to(url)
354
356
  sleep 0.1 # slight wait for alert
355
357
  @browser.switch_to.alert.accept
356
- rescue modal_error # rubocop:disable Lint/SuppressedException
358
+ rescue modal_error
357
359
  # alert now gone, should mean navigation happened
358
360
  end
359
361
 
@@ -13,14 +13,12 @@ module Capybara::Selenium::Driver::ChromeDriver
13
13
 
14
14
  def fullscreen_window(handle)
15
15
  within_given_window(handle) do
16
- begin
17
- super
18
- rescue NoMethodError => e
19
- raise unless e.message.match?(/full_screen_window/)
20
-
21
- result = bridge.http.call(:post, "session/#{bridge.session_id}/window/fullscreen", {})
22
- result['value']
23
- end
16
+ super
17
+ rescue NoMethodError => e
18
+ raise unless e.message.match?(/full_screen_window/)
19
+
20
+ result = bridge.http.call(:post, "session/#{bridge.session_id}/window/fullscreen", {})
21
+ result['value']
24
22
  end
25
23
  end
26
24
 
@@ -65,7 +63,7 @@ private
65
63
  end
66
64
 
67
65
  def clear_all_storage?
68
- storage_clears.none? { |s| s == false }
66
+ storage_clears.none? false
69
67
  end
70
68
 
71
69
  def uniform_storage_clear?
@@ -13,14 +13,12 @@ module Capybara::Selenium::Driver::EdgeDriver
13
13
  return super if edgedriver_version < 75
14
14
 
15
15
  within_given_window(handle) do
16
- begin
17
- super
18
- rescue NoMethodError => e
19
- raise unless e.message.match?(/full_screen_window/)
20
-
21
- result = bridge.http.call(:post, "session/#{bridge.session_id}/window/fullscreen", {})
22
- result['value']
23
- end
16
+ super
17
+ rescue NoMethodError => e
18
+ raise unless e.message.match?(/full_screen_window/)
19
+
20
+ result = bridge.http.call(:post, "session/#{bridge.session_id}/window/fullscreen", {})
21
+ result['value']
24
22
  end
25
23
  end
26
24
 
@@ -74,7 +72,7 @@ private
74
72
  end
75
73
 
76
74
  def clear_all_storage?
77
- storage_clears.none? { |s| s == false }
75
+ storage_clears.none? false
78
76
  end
79
77
 
80
78
  def uniform_storage_clear?
@@ -46,7 +46,7 @@ module Capybara::Selenium::Driver::W3CFirefoxDriver
46
46
  begin
47
47
  # Firefox 68 hangs if we try to switch windows while a modal is visible
48
48
  browser.switch_to.alert&.dismiss
49
- rescue Selenium::WebDriver::Error::NoSuchAlertError # rubocop:disable Lint/SuppressedException
49
+ rescue Selenium::WebDriver::Error::NoSuchAlertError
50
50
  # Swallow
51
51
  end
52
52
  end
@@ -61,7 +61,7 @@ module Capybara::Selenium::Driver::W3CFirefoxDriver
61
61
  accept_modal :confirm, wait: 0.1 do
62
62
  super
63
63
  end
64
- rescue Capybara::ModalNotFound # rubocop:disable Lint/SuppressedException
64
+ rescue Capybara::ModalNotFound
65
65
  # No modal was opened - page has refreshed - ignore
66
66
  end
67
67
 
@@ -237,7 +237,7 @@ protected
237
237
  JS
238
238
  begin
239
239
  driver.execute_script(script, self)
240
- rescue StandardError # rubocop:disable Lint/SuppressedException
240
+ rescue StandardError
241
241
  # Swallow error if scrollIntoView with options isn't supported
242
242
  end
243
243
  end
@@ -279,9 +279,9 @@ private
279
279
  send_keys(*clear, value)
280
280
  else
281
281
  driver.execute_script 'arguments[0].select()', self unless clear == :none
282
- if rapid == true || (value.length > 30 && rapid != false)
282
+ if rapid == true || ((value.length > auto_rapid_set_length) && rapid != false)
283
283
  send_keys(value[0..3])
284
- driver.execute_script 'arguments[0].value += arguments[1]', self, value[4...-3]
284
+ driver.execute_script RAPID_APPEND_TEXT, self, value[4...-3]
285
285
  send_keys(value[-3..-1])
286
286
  else
287
287
  send_keys(value)
@@ -289,6 +289,10 @@ private
289
289
  end
290
290
  end
291
291
 
292
+ def auto_rapid_set_length
293
+ 30
294
+ end
295
+
292
296
  def perform_with_options(click_options, &block)
293
297
  raise ArgumentError, 'A block must be provided' unless block
294
298
 
@@ -530,6 +534,16 @@ private
530
534
  })(arguments[0], arguments[1], arguments[2])
531
535
  JS
532
536
 
537
+ RAPID_APPEND_TEXT = <<~'JS'
538
+ (function(el, value) {
539
+ value = el.value + value;
540
+ if (el.maxLength && el.maxLength != -1){
541
+ value = value.slice(0, el.maxLength);
542
+ }
543
+ el.value = value;
544
+ })(arguments[0], arguments[1])
545
+ JS
546
+
533
547
  # SettableValue encapsulates time/date field formatting
534
548
  class SettableValue
535
549
  attr_reader :value