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.
- checksums.yaml +4 -4
- data/History.md +38 -1
- data/README.md +10 -3
- data/lib/capybara/cucumber.rb +1 -1
- data/lib/capybara/minitest.rb +2 -3
- data/lib/capybara/node/actions.rb +16 -20
- data/lib/capybara/node/matchers.rb +4 -6
- data/lib/capybara/queries/selector_query.rb +8 -1
- data/lib/capybara/queries/style_query.rb +1 -1
- data/lib/capybara/queries/text_query.rb +6 -0
- data/lib/capybara/registration_container.rb +44 -0
- data/lib/capybara/registrations/servers.rb +1 -1
- data/lib/capybara/result.rb +4 -1
- data/lib/capybara/rspec/matchers/have_text.rb +1 -1
- data/lib/capybara/selector/definition/button.rb +1 -0
- data/lib/capybara/selector/definition/fillable_field.rb +1 -1
- data/lib/capybara/selector/definition/link.rb +8 -0
- data/lib/capybara/selector/definition/table.rb +1 -1
- data/lib/capybara/selector/definition.rb +5 -4
- data/lib/capybara/selector/selector.rb +4 -0
- data/lib/capybara/selector.rb +10 -1
- data/lib/capybara/selenium/driver.rb +6 -4
- data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +7 -9
- data/lib/capybara/selenium/driver_specializations/edge_driver.rb +7 -9
- data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +2 -2
- data/lib/capybara/selenium/node.rb +17 -3
- data/lib/capybara/selenium/nodes/firefox_node.rb +1 -1
- data/lib/capybara/selenium/patches/action_pauser.rb +4 -1
- data/lib/capybara/selenium/patches/logs.rb +3 -5
- data/lib/capybara/session/config.rb +3 -1
- data/lib/capybara/session.rb +3 -3
- data/lib/capybara/spec/public/test.js +7 -0
- data/lib/capybara/spec/session/click_button_spec.rb +11 -0
- data/lib/capybara/spec/session/has_button_spec.rb +16 -0
- data/lib/capybara/spec/session/has_current_path_spec.rb +2 -2
- data/lib/capybara/spec/session/has_field_spec.rb +16 -0
- data/lib/capybara/spec/session/has_select_spec.rb +4 -4
- data/lib/capybara/spec/session/has_selector_spec.rb +4 -4
- data/lib/capybara/spec/session/node_spec.rb +6 -6
- data/lib/capybara/spec/spec_helper.rb +1 -0
- data/lib/capybara/spec/test_app.rb +14 -18
- data/lib/capybara/spec/views/form.erb +6 -1
- data/lib/capybara/spec/views/with_dragula.erb +3 -1
- data/lib/capybara/spec/views/with_js.erb +1 -0
- data/lib/capybara/version.rb +1 -1
- data/lib/capybara.rb +17 -7
- data/spec/capybara_spec.rb +1 -1
- data/spec/dsl_spec.rb +14 -1
- data/spec/minitest_spec.rb +1 -1
- data/spec/rack_test_spec.rb +1 -0
- data/spec/result_spec.rb +1 -17
- data/spec/rspec/shared_spec_matchers.rb +65 -53
- data/spec/selector_spec.rb +1 -1
- data/spec/selenium_spec_chrome.rb +0 -2
- data/spec/server_spec.rb +41 -49
- data/spec/shared_selenium_node.rb +10 -0
- data/spec/shared_selenium_session.rb +25 -7
- data/spec/spec_helper.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ab64d9d47c6374c4f203945be70b868fb9e4c48d3fdca087ba4e66498e40b91c
|
|
4
|
+
data.tar.gz: 451c97390c148b5b0fad1b06107a4f0de87f17f7b8cef7156fb76ad2ee503f76
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
39
|
+
Release date: 2020-03-29
|
|
3
40
|
|
|
4
41
|
### Added
|
|
5
42
|
|
data/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
[](https://gitter.im/jnicklas/capybara?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
|
8
8
|
[](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.
|
|
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.
|
|
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://
|
|
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
|
data/lib/capybara/cucumber.rb
CHANGED
|
@@ -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
|
|
25
|
+
Capybara.current_driver = driver_name if Capybara.drivers[driver_name]
|
|
26
26
|
end
|
|
27
27
|
end
|
data/lib/capybara/minitest.rb
CHANGED
|
@@ -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}
|
|
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(:
|
|
313
|
-
rescue Capybara::ElementNotFound =>
|
|
314
|
-
raise
|
|
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
|
|
372
|
-
el.
|
|
373
|
-
rescue StandardError
|
|
374
|
-
raise
|
|
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
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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: {
|
|
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']
|
|
@@ -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|
|
data/lib/capybara/result.rb
CHANGED
|
@@ -171,10 +171,13 @@ module Capybara
|
|
|
171
171
|
@rest ||= @elements - full_results
|
|
172
172
|
end
|
|
173
173
|
|
|
174
|
-
if
|
|
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
|
|
@@ -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?
|
|
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
|
-
|
|
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
|
|
255
|
-
|
|
256
|
-
filter.handles_option?(
|
|
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
|
|
data/lib/capybara/selector.rb
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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?
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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?
|
|
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
|
|
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
|
|
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
|
|
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 >
|
|
282
|
+
if rapid == true || ((value.length > auto_rapid_set_length) && rapid != false)
|
|
283
283
|
send_keys(value[0..3])
|
|
284
|
-
driver.execute_script
|
|
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
|