capybara 3.35.0 → 3.40.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 +168 -5
- data/README.md +199 -39
- data/lib/capybara/config.rb +16 -4
- data/lib/capybara/driver/base.rb +4 -0
- data/lib/capybara/driver/node.rb +5 -1
- data/lib/capybara/dsl.rb +4 -10
- data/lib/capybara/helpers.rb +9 -14
- data/lib/capybara/minitest/spec.rb +18 -6
- data/lib/capybara/minitest.rb +14 -1
- data/lib/capybara/node/actions.rb +14 -9
- data/lib/capybara/node/base.rb +2 -1
- data/lib/capybara/node/document.rb +2 -2
- data/lib/capybara/node/element.rb +13 -2
- data/lib/capybara/node/finders.rb +11 -2
- data/lib/capybara/node/matchers.rb +25 -0
- data/lib/capybara/node/simple.rb +5 -1
- data/lib/capybara/node/whitespace_normalizer.rb +81 -0
- data/lib/capybara/queries/active_element_query.rb +18 -0
- data/lib/capybara/queries/ancestor_query.rb +2 -1
- data/lib/capybara/queries/base_query.rb +2 -2
- data/lib/capybara/queries/current_path_query.rb +1 -1
- data/lib/capybara/queries/selector_query.rb +40 -11
- data/lib/capybara/queries/sibling_query.rb +2 -1
- data/lib/capybara/queries/text_query.rb +1 -1
- data/lib/capybara/rack_test/browser.rb +64 -8
- data/lib/capybara/rack_test/driver.rb +4 -4
- data/lib/capybara/rack_test/form.rb +29 -7
- data/lib/capybara/rack_test/node.rb +32 -33
- data/lib/capybara/registration_container.rb +2 -5
- data/lib/capybara/registrations/drivers.rb +7 -7
- data/lib/capybara/registrations/servers.rb +37 -16
- data/lib/capybara/result.rb +2 -2
- data/lib/capybara/rspec/matcher_proxies.rb +6 -6
- data/lib/capybara/rspec/matchers/base.rb +8 -6
- data/lib/capybara/rspec/matchers/compound.rb +1 -1
- data/lib/capybara/rspec/matchers/have_selector.rb +9 -17
- data/lib/capybara/rspec/matchers.rb +21 -16
- data/lib/capybara/selector/builders/css_builder.rb +1 -1
- data/lib/capybara/selector/builders/xpath_builder.rb +1 -1
- data/lib/capybara/selector/css.rb +6 -6
- data/lib/capybara/selector/definition/button.rb +10 -5
- data/lib/capybara/selector/definition/checkbox.rb +1 -1
- data/lib/capybara/selector/definition/file_field.rb +1 -1
- data/lib/capybara/selector/definition/fillable_field.rb +1 -1
- data/lib/capybara/selector/definition/link.rb +2 -1
- data/lib/capybara/selector/definition/radio_button.rb +1 -1
- data/lib/capybara/selector/definition/table.rb +1 -1
- data/lib/capybara/selector/definition/table_row.rb +2 -2
- data/lib/capybara/selector/definition.rb +4 -2
- data/lib/capybara/selector/filter_set.rb +4 -7
- data/lib/capybara/selector/regexp_disassembler.rb +2 -5
- data/lib/capybara/selector/selector.rb +5 -1
- data/lib/capybara/selector.rb +252 -0
- data/lib/capybara/selenium/driver.rb +31 -54
- data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +1 -1
- data/lib/capybara/selenium/driver_specializations/edge_driver.rb +9 -5
- data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +2 -7
- data/lib/capybara/selenium/extensions/html5_drag.rb +5 -4
- data/lib/capybara/selenium/node.rb +60 -38
- data/lib/capybara/selenium/nodes/chrome_node.rb +4 -16
- data/lib/capybara/selenium/nodes/edge_node.rb +19 -13
- data/lib/capybara/selenium/nodes/firefox_node.rb +3 -3
- data/lib/capybara/selenium/nodes/safari_node.rb +4 -4
- data/lib/capybara/selenium/patches/atoms.rb +1 -1
- data/lib/capybara/selenium/patches/pause_duration_fix.rb +1 -1
- data/lib/capybara/server/animation_disabler.rb +40 -23
- data/lib/capybara/server/middleware.rb +1 -1
- data/lib/capybara/server.rb +1 -1
- data/lib/capybara/session/config.rb +4 -2
- data/lib/capybara/session.rb +34 -34
- data/lib/capybara/spec/public/test.js +4 -0
- data/lib/capybara/spec/session/active_element_spec.rb +31 -0
- data/lib/capybara/spec/session/all_spec.rb +11 -15
- data/lib/capybara/spec/session/assert_text_spec.rb +17 -17
- data/lib/capybara/spec/session/attach_file_spec.rb +6 -0
- data/lib/capybara/spec/session/check_spec.rb +10 -0
- data/lib/capybara/spec/session/choose_spec.rb +6 -0
- data/lib/capybara/spec/session/click_link_or_button_spec.rb +9 -0
- data/lib/capybara/spec/session/click_link_spec.rb +12 -1
- data/lib/capybara/spec/session/current_scope_spec.rb +1 -1
- data/lib/capybara/spec/session/fill_in_spec.rb +6 -0
- data/lib/capybara/spec/session/find_link_spec.rb +10 -0
- data/lib/capybara/spec/session/find_spec.rb +15 -1
- data/lib/capybara/spec/session/first_spec.rb +1 -1
- data/lib/capybara/spec/session/frame/within_frame_spec.rb +2 -0
- data/lib/capybara/spec/session/has_all_selectors_spec.rb +5 -5
- data/lib/capybara/spec/session/has_ancestor_spec.rb +2 -2
- data/lib/capybara/spec/session/has_any_selectors_spec.rb +6 -2
- data/lib/capybara/spec/session/has_button_spec.rb +30 -0
- data/lib/capybara/spec/session/has_current_path_spec.rb +3 -3
- data/lib/capybara/spec/session/has_element_spec.rb +47 -0
- data/lib/capybara/spec/session/has_field_spec.rb +25 -1
- data/lib/capybara/spec/session/has_link_spec.rb +40 -0
- data/lib/capybara/spec/session/has_none_selectors_spec.rb +7 -7
- data/lib/capybara/spec/session/has_select_spec.rb +10 -4
- data/lib/capybara/spec/session/has_selector_spec.rb +15 -0
- data/lib/capybara/spec/session/has_table_spec.rb +13 -2
- data/lib/capybara/spec/session/has_text_spec.rb +6 -14
- data/lib/capybara/spec/session/matches_style_spec.rb +2 -0
- data/lib/capybara/spec/session/node_spec.rb +88 -1
- data/lib/capybara/spec/session/node_wrapper_spec.rb +1 -1
- data/lib/capybara/spec/session/reset_session_spec.rb +13 -0
- data/lib/capybara/spec/session/scroll_spec.rb +7 -5
- data/lib/capybara/spec/session/uncheck_spec.rb +1 -1
- data/lib/capybara/spec/session/visit_spec.rb +20 -0
- data/lib/capybara/spec/session/window/window_spec.rb +1 -1
- data/lib/capybara/spec/session/window/windows_spec.rb +1 -1
- data/lib/capybara/spec/session/within_spec.rb +13 -0
- data/lib/capybara/spec/spec_helper.rb +12 -5
- data/lib/capybara/spec/test_app.rb +91 -14
- data/lib/capybara/spec/views/animated.erb +1 -1
- data/lib/capybara/spec/views/form.erb +34 -4
- data/lib/capybara/spec/views/frame_child.erb +1 -1
- data/lib/capybara/spec/views/frame_one.erb +1 -1
- data/lib/capybara/spec/views/frame_parent.erb +1 -1
- data/lib/capybara/spec/views/frame_two.erb +1 -1
- data/lib/capybara/spec/views/initial_alert.erb +2 -1
- data/lib/capybara/spec/views/layout.erb +10 -0
- data/lib/capybara/spec/views/obscured.erb +1 -1
- data/lib/capybara/spec/views/offset.erb +2 -1
- data/lib/capybara/spec/views/path.erb +2 -2
- data/lib/capybara/spec/views/popup_one.erb +1 -1
- data/lib/capybara/spec/views/popup_two.erb +1 -1
- data/lib/capybara/spec/views/react.erb +2 -2
- data/lib/capybara/spec/views/scroll.erb +2 -1
- data/lib/capybara/spec/views/spatial.erb +1 -1
- data/lib/capybara/spec/views/with_animation.erb +2 -3
- data/lib/capybara/spec/views/with_base_tag.erb +2 -2
- data/lib/capybara/spec/views/with_dragula.erb +2 -2
- data/lib/capybara/spec/views/with_fixed_header_footer.erb +2 -1
- data/lib/capybara/spec/views/with_hover.erb +2 -2
- data/lib/capybara/spec/views/with_html.erb +5 -3
- data/lib/capybara/spec/views/with_jquery_animation.erb +1 -1
- data/lib/capybara/spec/views/with_js.erb +2 -3
- data/lib/capybara/spec/views/with_jstree.erb +1 -1
- data/lib/capybara/spec/views/with_namespace.erb +1 -0
- data/lib/capybara/spec/views/with_scope.erb +2 -2
- data/lib/capybara/spec/views/with_shadow.erb +31 -0
- data/lib/capybara/spec/views/with_slow_unload.erb +2 -1
- data/lib/capybara/spec/views/with_sortable_js.erb +2 -2
- data/lib/capybara/spec/views/with_unload_alert.erb +1 -0
- data/lib/capybara/spec/views/with_windows.erb +1 -1
- data/lib/capybara/spec/views/within_frames.erb +1 -1
- data/lib/capybara/version.rb +1 -1
- data/lib/capybara/window.rb +1 -1
- data/lib/capybara.rb +30 -30
- data/spec/basic_node_spec.rb +16 -3
- data/spec/capybara_spec.rb +12 -0
- data/spec/counter_spec.rb +35 -0
- data/spec/css_builder_spec.rb +1 -1
- data/spec/css_splitter_spec.rb +1 -1
- data/spec/dsl_spec.rb +5 -3
- data/spec/fixtures/selenium_driver_rspec_failure.rb +2 -2
- data/spec/fixtures/selenium_driver_rspec_success.rb +2 -2
- data/spec/minitest_spec.rb +12 -1
- data/spec/minitest_spec_spec.rb +4 -0
- data/spec/per_session_config_spec.rb +1 -1
- data/spec/rack_test_spec.rb +30 -12
- data/spec/result_spec.rb +41 -35
- data/spec/rspec/features_spec.rb +3 -3
- data/spec/rspec/scenarios_spec.rb +2 -2
- data/spec/rspec/shared_spec_matchers.rb +27 -3
- data/spec/rspec_matchers_spec.rb +25 -0
- data/spec/rspec_spec.rb +3 -3
- data/spec/sauce_spec_chrome.rb +5 -5
- data/spec/selector_spec.rb +4 -4
- data/spec/selenium_spec_chrome.rb +20 -18
- data/spec/selenium_spec_chrome_remote.rb +15 -19
- data/spec/selenium_spec_edge.rb +19 -6
- data/spec/selenium_spec_firefox.rb +26 -8
- data/spec/selenium_spec_firefox_remote.rb +18 -4
- data/spec/selenium_spec_ie.rb +7 -8
- data/spec/selenium_spec_safari.rb +34 -20
- data/spec/server_spec.rb +19 -7
- data/spec/shared_selenium_node.rb +0 -4
- data/spec/shared_selenium_session.rb +22 -14
- data/spec/spec_helper.rb +36 -3
- data/spec/whitespace_normalizer_spec.rb +54 -0
- data/spec/xpath_builder_spec.rb +1 -1
- metadata +49 -30
- data/lib/capybara/selenium/logger_suppressor.rb +0 -34
- data/lib/capybara/selenium/patches/action_pauser.rb +0 -26
- data/lib/capybara/spec/views/with_title.erb +0 -5
data/lib/capybara/dsl.rb
CHANGED
@@ -47,17 +47,11 @@ module Capybara
|
|
47
47
|
end
|
48
48
|
|
49
49
|
Session::DSL_METHODS.each do |method|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
page.method("#{method}").call(...)
|
54
|
-
end
|
55
|
-
METHOD
|
56
|
-
else
|
57
|
-
define_method method do |*args, &block|
|
58
|
-
page.send method, *args, &block
|
50
|
+
class_eval <<~METHOD, __FILE__, __LINE__ + 1
|
51
|
+
def #{method}(...)
|
52
|
+
page.method("#{method}").call(...)
|
59
53
|
end
|
60
|
-
|
54
|
+
METHOD
|
61
55
|
end
|
62
56
|
end
|
63
57
|
|
data/lib/capybara/helpers.rb
CHANGED
@@ -73,28 +73,23 @@ module Capybara
|
|
73
73
|
def filter_backtrace(trace)
|
74
74
|
return 'No backtrace' unless trace
|
75
75
|
|
76
|
-
filter = %r{lib/capybara/|lib/rspec/|lib/minitest
|
76
|
+
filter = %r{lib/capybara/|lib/rspec/|lib/minitest/|delegate.rb}
|
77
77
|
new_trace = trace.take_while { |line| line !~ filter }
|
78
|
-
new_trace = trace.
|
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(
|
81
|
+
new_trace.first.split(':in ', 2).first
|
82
82
|
end
|
83
83
|
|
84
84
|
def warn(message, uplevel: 1)
|
85
|
-
|
86
|
-
|
87
|
-
# TODO: Remove when we drop support for Ruby 2.5
|
88
|
-
# Workaround for emulating `warn '...', uplevel: n` in Ruby 2.5 or lower.
|
89
|
-
if (match = /^(?<file>.+?):(?<line>\d+)(?::in `.*')?/.match(caller[uplevel]))
|
90
|
-
location = [match[:file], match[:line]].join(':')
|
91
|
-
Kernel.warn "#{location}: #{message}"
|
92
|
-
else
|
93
|
-
Kernel.warn message
|
94
|
-
end
|
85
|
+
Kernel.warn(message, uplevel: uplevel)
|
95
86
|
end
|
96
87
|
|
97
|
-
if defined?(Process::
|
88
|
+
if defined?(Process::CLOCK_MONOTONIC_RAW)
|
89
|
+
def monotonic_time; Process.clock_gettime Process::CLOCK_MONOTONIC_RAW; end
|
90
|
+
elsif defined?(Process::CLOCK_MONOTONIC_PRECISE)
|
91
|
+
def monotonic_time; Process.clock_gettime Process::CLOCK_MONOTONIC_PRECISE; end
|
92
|
+
elsif defined?(Process::CLOCK_MONOTONIC)
|
98
93
|
def monotonic_time; Process.clock_gettime Process::CLOCK_MONOTONIC; end
|
99
94
|
else
|
100
95
|
def monotonic_time; Time.now.to_f; end
|
@@ -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}
|
234
|
-
::Minitest::Expectation.new(self, ::Minitest::Spec.current).#{new_name}(
|
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}
|
251
|
+
def #{new_name}(...)
|
240
252
|
raise "Calling ##{new_name} outside of test." unless ctx
|
241
|
-
ctx.#{meth}(target,
|
253
|
+
ctx.#{meth}(target, ...)
|
242
254
|
end
|
243
255
|
ASSERTION
|
244
256
|
end
|
@@ -246,9 +258,9 @@ module Capybara
|
|
246
258
|
|
247
259
|
##
|
248
260
|
# @deprecated
|
249
|
-
def must_have_style(
|
261
|
+
def must_have_style(...)
|
250
262
|
warn 'must_have_style is deprecated, please use must_match_style'
|
251
|
-
must_match_style(
|
263
|
+
must_match_style(...)
|
252
264
|
end
|
253
265
|
end
|
254
266
|
end
|
data/lib/capybara/minitest.rb
CHANGED
@@ -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)
|
@@ -92,8 +92,9 @@ module Capybara
|
|
92
92
|
end
|
93
93
|
|
94
94
|
# @!macro label_click
|
95
|
-
# @option options [Boolean] allow_label_click
|
95
|
+
# @option options [Boolean, Hash] allow_label_click
|
96
96
|
# Attempt to click the label to toggle state if element is non-visible. Defaults to {Capybara.configure automatic_label_click}.
|
97
|
+
# If set to a Hash it is passed as options to the `click` on the label
|
97
98
|
|
98
99
|
##
|
99
100
|
#
|
@@ -277,7 +278,7 @@ module Capybara
|
|
277
278
|
# @return [Capybara::Node::Element] The file field element
|
278
279
|
def attach_file(locator = nil, paths, make_visible: nil, **options) # rubocop:disable Style/OptionalArguments
|
279
280
|
if locator && block_given?
|
280
|
-
raise ArgumentError, '
|
281
|
+
raise ArgumentError, '`#attach_file` does not support passing both a locator and a block'
|
281
282
|
end
|
282
283
|
|
283
284
|
Array(paths).each do |path|
|
@@ -314,7 +315,7 @@ module Capybara
|
|
314
315
|
|
315
316
|
begin
|
316
317
|
find(:datalist_input, from, **options)
|
317
|
-
rescue Capybara::ElementNotFound => dlinput_error
|
318
|
+
rescue Capybara::ElementNotFound => dlinput_error
|
318
319
|
raise Capybara::ElementNotFound, "#{select_error.message} and #{dlinput_error.message}"
|
319
320
|
end
|
320
321
|
end
|
@@ -371,14 +372,18 @@ module Capybara
|
|
371
372
|
|
372
373
|
begin
|
373
374
|
el ||= find(selector, locator, **options.merge(visible: :all))
|
374
|
-
|
375
|
+
unless el.checked? == checked
|
376
|
+
el.session
|
377
|
+
.find(:label, for: el, visible: true, match: :first)
|
378
|
+
.click(**(Hash.try_convert(allow_label_click) || {}))
|
379
|
+
end
|
375
380
|
rescue StandardError # swallow extra errors - raise original
|
376
381
|
raise e
|
377
382
|
end
|
378
383
|
end
|
379
384
|
end
|
380
385
|
|
381
|
-
UPDATE_STYLE_SCRIPT = <<~
|
386
|
+
UPDATE_STYLE_SCRIPT = <<~JS
|
382
387
|
this.capybara_style_cache = this.style.cssText;
|
383
388
|
var css = arguments[0];
|
384
389
|
for (var prop in css){
|
@@ -388,27 +393,27 @@ module Capybara
|
|
388
393
|
}
|
389
394
|
JS
|
390
395
|
|
391
|
-
RESET_STYLE_SCRIPT = <<~
|
396
|
+
RESET_STYLE_SCRIPT = <<~JS
|
392
397
|
if (this.hasOwnProperty('capybara_style_cache')) {
|
393
398
|
this.style.cssText = this.capybara_style_cache;
|
394
399
|
delete this.capybara_style_cache;
|
395
400
|
}
|
396
401
|
JS
|
397
402
|
|
398
|
-
DATALIST_OPTIONS_SCRIPT = <<~
|
403
|
+
DATALIST_OPTIONS_SCRIPT = <<~JS
|
399
404
|
Array.prototype.slice.call((this.list||{}).options || []).
|
400
405
|
filter(function(el){ return !el.disabled }).
|
401
406
|
map(function(el){ return { "value": el.value, "label": el.label} })
|
402
407
|
JS
|
403
408
|
|
404
|
-
CAPTURE_FILE_ELEMENT_SCRIPT = <<~
|
409
|
+
CAPTURE_FILE_ELEMENT_SCRIPT = <<~JS
|
405
410
|
document.addEventListener('click', function file_catcher(e){
|
406
411
|
if (e.target.matches("input[type='file']")) {
|
407
412
|
window._capybara_clicked_file_input = e.target;
|
408
413
|
this.removeEventListener('click', file_catcher);
|
409
414
|
e.preventDefault();
|
410
415
|
}
|
411
|
-
})
|
416
|
+
}, {capture: true})
|
412
417
|
JS
|
413
418
|
end
|
414
419
|
end
|
data/lib/capybara/node/base.rb
CHANGED
@@ -77,6 +77,7 @@ module Capybara
|
|
77
77
|
return yield if session.synchronized
|
78
78
|
|
79
79
|
seconds = session_options.default_max_wait_time if [nil, true].include? seconds
|
80
|
+
interval = session_options.default_retry_interval
|
80
81
|
session.synchronized = true
|
81
82
|
timer = Capybara::Helpers.timer(expire_in: seconds)
|
82
83
|
begin
|
@@ -88,7 +89,7 @@ module Capybara
|
|
88
89
|
if driver.wait?
|
89
90
|
raise e if timer.expired?
|
90
91
|
|
91
|
-
sleep
|
92
|
+
sleep interval
|
92
93
|
reload if session_options.automatic_reload
|
93
94
|
else
|
94
95
|
old_base = @base
|
@@ -40,8 +40,8 @@ module Capybara
|
|
40
40
|
find(:xpath, '/html').evaluate_script(*args)
|
41
41
|
end
|
42
42
|
|
43
|
-
def scroll_to(*args, **options)
|
44
|
-
find(:xpath, '//body').scroll_to(*args, **options)
|
43
|
+
def scroll_to(*args, quirks: false, **options)
|
44
|
+
find(:xpath, quirks ? '//body' : '/html').scroll_to(*args, **options)
|
45
45
|
end
|
46
46
|
end
|
47
47
|
end
|
@@ -115,7 +115,7 @@ module Capybara
|
|
115
115
|
#
|
116
116
|
# @return [Capybara::Node::Element] The element
|
117
117
|
def set(value, **options)
|
118
|
-
if ENV
|
118
|
+
if ENV.fetch('CAPYBARA_THOROUGH', nil) && readonly?
|
119
119
|
raise Capybara::ReadOnlyElementError, "Attempt to set readonly element with value: #{value}"
|
120
120
|
end
|
121
121
|
|
@@ -472,6 +472,17 @@ module Capybara
|
|
472
472
|
self
|
473
473
|
end
|
474
474
|
|
475
|
+
##
|
476
|
+
#
|
477
|
+
# Return the shadow_root for the current element
|
478
|
+
#
|
479
|
+
# @return [Capybara::Node::Element] The shadow root
|
480
|
+
|
481
|
+
def shadow_root
|
482
|
+
root = synchronize { base.shadow_root }
|
483
|
+
root && Capybara::Node::Element.new(session, root, nil, nil)
|
484
|
+
end
|
485
|
+
|
475
486
|
##
|
476
487
|
#
|
477
488
|
# Execute the given JS in the context of the element not returning a result. This is useful for scripts that return
|
@@ -550,7 +561,7 @@ module Capybara
|
|
550
561
|
return self unless @allow_reload
|
551
562
|
|
552
563
|
begin
|
553
|
-
reloaded = @query.resolve_for(query_scope.reload)[@query_idx.to_i]
|
564
|
+
reloaded = @query.resolve_for(query_scope ? query_scope.reload : session)[@query_idx.to_i]
|
554
565
|
@base = reloaded.base if reloaded
|
555
566
|
rescue StandardError => e
|
556
567
|
raise e unless catch_error?(e)
|
@@ -18,11 +18,11 @@ module Capybara
|
|
18
18
|
#
|
19
19
|
# @!macro system_filters
|
20
20
|
# @option options [String, Regexp] text Only find elements which contain this text or match this regexp
|
21
|
-
# @option options [String,
|
21
|
+
# @option options [String, Regexp, String] exact_text
|
22
22
|
# When String the elements contained text must match exactly, when Boolean controls whether the `text` option must match exactly.
|
23
23
|
# Defaults to {Capybara.configure exact_text}.
|
24
24
|
# @option options [Boolean] normalize_ws
|
25
|
-
# Whether the `text`/`exact_text` options are compared against
|
25
|
+
# Whether the `text`/`exact_text` options are compared against element text with whitespace normalized or as returned by the driver.
|
26
26
|
# Defaults to {Capybara.configure default_normalize_ws}.
|
27
27
|
# @option options [Boolean, Symbol] visible
|
28
28
|
# Only find elements with the specified visibility. Defaults to behavior indicated by {Capybara.configure ignore_hidden_elements}.
|
@@ -50,6 +50,13 @@ module Capybara
|
|
50
50
|
#
|
51
51
|
def find(*args, **options, &optional_filter_block)
|
52
52
|
options[:session_options] = session_options
|
53
|
+
count_options = options.slice(*Capybara::Queries::BaseQuery::COUNT_KEYS)
|
54
|
+
unless count_options.empty?
|
55
|
+
Capybara::Helpers.warn(
|
56
|
+
"'find' does not support count options (#{count_options}) ignoring. " \
|
57
|
+
"Called from: #{Capybara::Helpers.filter_backtrace(caller)}"
|
58
|
+
)
|
59
|
+
end
|
53
60
|
synced_resolve Capybara::Queries::SelectorQuery.new(*args, **options, &optional_filter_block)
|
54
61
|
end
|
55
62
|
|
@@ -142,6 +149,8 @@ module Capybara
|
|
142
149
|
# @option options [String, Regexp] id Match links with the id provided
|
143
150
|
# @option options [String] title Match links with the title provided
|
144
151
|
# @option options [String] alt Match links with a contained img element whose alt matches
|
152
|
+
# @option options [String, Boolean] download Match links with the download provided
|
153
|
+
# @option options [String] target Match links with the target provided
|
145
154
|
# @option options [String, Array<String>, Regexp] class Match links that match the class(es) provided
|
146
155
|
# @return [Capybara::Node::Element] The found element
|
147
156
|
#
|
@@ -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
|
data/lib/capybara/node/simple.rb
CHANGED
@@ -110,7 +110,7 @@ module Capybara
|
|
110
110
|
# No need for an xpath if only checking the current element
|
111
111
|
!(native.key?('hidden') ||
|
112
112
|
/display:\s?none/.match?(native[:style] || '') ||
|
113
|
-
%w[script head].include?(tag_name))
|
113
|
+
%w[script head style].include?(tag_name))
|
114
114
|
end
|
115
115
|
end
|
116
116
|
|
@@ -191,6 +191,10 @@ module Capybara
|
|
191
191
|
{}
|
192
192
|
end
|
193
193
|
|
194
|
+
def ==(other)
|
195
|
+
eql?(other) || (other.respond_to?(:native) && native == other.native)
|
196
|
+
end
|
197
|
+
|
194
198
|
private
|
195
199
|
|
196
200
|
def option_value(option)
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Capybara
|
4
|
+
module Node
|
5
|
+
##
|
6
|
+
#
|
7
|
+
# {Capybara::Node::WhitespaceNormalizer} provides methods that
|
8
|
+
# help to normalize the spacing of text content inside of
|
9
|
+
# {Capybara::Node::Element}s by removing various unicode
|
10
|
+
# spacing and directional markings.
|
11
|
+
#
|
12
|
+
module WhitespaceNormalizer
|
13
|
+
# Unicode for NBSP, or
|
14
|
+
NON_BREAKING_SPACE = "\u00a0"
|
15
|
+
LINE_SEPERATOR = "\u2028"
|
16
|
+
PARAGRAPH_SEPERATOR = "\u2029"
|
17
|
+
|
18
|
+
# All spaces except for NBSP
|
19
|
+
BREAKING_SPACES = "[[:space:]&&[^#{NON_BREAKING_SPACE}]]".freeze
|
20
|
+
|
21
|
+
# Whitespace we want to substitute with plain spaces
|
22
|
+
SQUEEZED_SPACES = " \n\f\t\v#{LINE_SEPERATOR}#{PARAGRAPH_SEPERATOR}".freeze
|
23
|
+
|
24
|
+
# Any whitespace at the front of text
|
25
|
+
LEADING_SPACES = /\A#{BREAKING_SPACES}+/
|
26
|
+
|
27
|
+
# Any whitespace at the end of text
|
28
|
+
TRAILING_SPACES = /#{BREAKING_SPACES}+\z/
|
29
|
+
|
30
|
+
# "Invisible" space character
|
31
|
+
ZERO_WIDTH_SPACE = "\u200b"
|
32
|
+
|
33
|
+
# Signifies text is read left to right
|
34
|
+
LEFT_TO_RIGHT_MARK = "\u200e"
|
35
|
+
|
36
|
+
# Signifies text is read right to left
|
37
|
+
RIGHT_TO_LEFT_MARK = "\u200f"
|
38
|
+
|
39
|
+
# Characters we want to truncate from text
|
40
|
+
REMOVED_CHARACTERS = [ZERO_WIDTH_SPACE, LEFT_TO_RIGHT_MARK, RIGHT_TO_LEFT_MARK].join
|
41
|
+
|
42
|
+
# Matches multiple empty lines
|
43
|
+
EMPTY_LINES = /[\ \n]*\n[\ \n]*/
|
44
|
+
|
45
|
+
##
|
46
|
+
#
|
47
|
+
# Normalizes the spacing of a node's text to be similar to
|
48
|
+
# what matchers might expect.
|
49
|
+
#
|
50
|
+
# @param text [String]
|
51
|
+
# @return [String]
|
52
|
+
#
|
53
|
+
def normalize_spacing(text)
|
54
|
+
text
|
55
|
+
.delete(REMOVED_CHARACTERS)
|
56
|
+
.tr(SQUEEZED_SPACES, ' ')
|
57
|
+
.squeeze(' ')
|
58
|
+
.sub(LEADING_SPACES, '')
|
59
|
+
.sub(TRAILING_SPACES, '')
|
60
|
+
.tr(NON_BREAKING_SPACE, ' ')
|
61
|
+
end
|
62
|
+
|
63
|
+
##
|
64
|
+
#
|
65
|
+
# Variant on {Capybara::Node::Normalizer#normalize_spacing} that
|
66
|
+
# targets the whitespace of visible elements only.
|
67
|
+
#
|
68
|
+
# @param text [String]
|
69
|
+
# @return [String]
|
70
|
+
#
|
71
|
+
def normalize_visible_spacing(text)
|
72
|
+
text
|
73
|
+
.squeeze(' ')
|
74
|
+
.gsub(EMPTY_LINES, "\n")
|
75
|
+
.sub(LEADING_SPACES, '')
|
76
|
+
.sub(TRAILING_SPACES, '')
|
77
|
+
.tr(NON_BREAKING_SPACE, ' ')
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Capybara
|
4
|
+
# @api private
|
5
|
+
module Queries
|
6
|
+
class ActiveElementQuery < BaseQuery
|
7
|
+
def initialize(**options)
|
8
|
+
@options = options
|
9
|
+
super(@options)
|
10
|
+
end
|
11
|
+
|
12
|
+
def resolve_for(session)
|
13
|
+
node = session.driver.active_element
|
14
|
+
[Capybara::Node::Element.new(session, node, nil, self)]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -8,7 +8,8 @@ module Capybara
|
|
8
8
|
@child_node = node
|
9
9
|
|
10
10
|
node.synchronize do
|
11
|
-
|
11
|
+
scope = node.respond_to?(:session) ? node.session.current_scope : node.find(:xpath, '/*')
|
12
|
+
match_results = super(scope, exact)
|
12
13
|
ancestors = node.find_xpath(XPath.ancestor.to_s)
|
13
14
|
.map(&method(:to_element))
|
14
15
|
.select { |el| match_results.include?(el) }
|
@@ -79,8 +79,8 @@ module Capybara
|
|
79
79
|
if count
|
80
80
|
message << " #{occurrences count}"
|
81
81
|
elsif between
|
82
|
-
message << " between #{between.begin ? between.first : 1} and" \
|
83
|
-
"
|
82
|
+
message << " between #{between.begin ? between.first : 1} and " \
|
83
|
+
"#{between.end ? between.last : 'infinite'} times"
|
84
84
|
elsif maximum
|
85
85
|
message << " at most #{occurrences maximum}"
|
86
86
|
elsif minimum
|
@@ -19,7 +19,7 @@ module Capybara
|
|
19
19
|
|
20
20
|
def resolves_for?(session)
|
21
21
|
uri = ::Addressable::URI.parse(session.current_url)
|
22
|
-
@actual_path = (options[:ignore_query] ? uri&.omit(:query) : uri).
|
22
|
+
@actual_path = (options[:ignore_query] ? uri&.omit(:query) : uri).then do |u|
|
23
23
|
options[:url] ? u&.to_s : u&.request_uri
|
24
24
|
end
|
25
25
|
|
@@ -9,7 +9,7 @@ module Capybara
|
|
9
9
|
|
10
10
|
SPATIAL_KEYS = %i[above below left_of right_of near].freeze
|
11
11
|
VALID_KEYS = SPATIAL_KEYS + COUNT_KEYS +
|
12
|
-
%i[text id class style visible obscured exact exact_text normalize_ws match wait filter_set]
|
12
|
+
%i[text id class style visible obscured exact exact_text normalize_ws match wait filter_set focused]
|
13
13
|
VALID_MATCH = %i[first smart prefer_exact one].freeze
|
14
14
|
|
15
15
|
def initialize(*args,
|
@@ -27,6 +27,12 @@ module Capybara
|
|
27
27
|
@order = order
|
28
28
|
@filter_cache = Hash.new { |hsh, key| hsh[key] = {} }
|
29
29
|
|
30
|
+
if @options[:text].is_a?(Regexp) && [true, false].include?(@options[:exact_text])
|
31
|
+
Capybara::Helpers.warn(
|
32
|
+
"Boolean 'exact_text' option is not supported when 'text' option is a Regexp - ignoring"
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
30
36
|
super(@options)
|
31
37
|
self.session_options = session_options
|
32
38
|
|
@@ -64,7 +70,8 @@ module Capybara
|
|
64
70
|
desc << 'non-visible ' if visible == :hidden
|
65
71
|
end
|
66
72
|
|
67
|
-
desc <<
|
73
|
+
desc << label.to_s
|
74
|
+
desc << " #{locator.inspect}" unless locator.nil?
|
68
75
|
|
69
76
|
if show_for[:any]
|
70
77
|
desc << " with#{' exact' if exact_text == true} text #{options[:text].inspect}" if options[:text]
|
@@ -73,6 +80,8 @@ module Capybara
|
|
73
80
|
|
74
81
|
desc << " with id #{options[:id]}" if options[:id]
|
75
82
|
desc << " with classes [#{Array(options[:class]).join(',')}]" if options[:class]
|
83
|
+
desc << ' that is focused' if options[:focused]
|
84
|
+
desc << ' that is not focused' if options[:focused] == false
|
76
85
|
|
77
86
|
desc << case options[:style]
|
78
87
|
when String
|
@@ -264,7 +273,7 @@ module Capybara
|
|
264
273
|
end
|
265
274
|
|
266
275
|
def valid_keys
|
267
|
-
VALID_KEYS + custom_keys
|
276
|
+
(VALID_KEYS + custom_keys).uniq
|
268
277
|
end
|
269
278
|
|
270
279
|
def matches_node_filters?(node, errors)
|
@@ -371,6 +380,10 @@ module Capybara
|
|
371
380
|
options.key?(:style) && !custom_keys.include?(:style)
|
372
381
|
end
|
373
382
|
|
383
|
+
def use_default_focused_filter?
|
384
|
+
options.key?(:focused) && !custom_keys.include?(:focused)
|
385
|
+
end
|
386
|
+
|
374
387
|
def use_spatial_filter?
|
375
388
|
options.values_at(*SPATIAL_KEYS).compact.any?
|
376
389
|
end
|
@@ -435,6 +448,7 @@ module Capybara
|
|
435
448
|
matches_id_filter?(node) &&
|
436
449
|
matches_class_filter?(node) &&
|
437
450
|
matches_style_filter?(node) &&
|
451
|
+
matches_focused_filter?(node) &&
|
438
452
|
matches_text_filter?(node) &&
|
439
453
|
matches_exact_text_filter?(node)
|
440
454
|
end
|
@@ -494,6 +508,12 @@ module Capybara
|
|
494
508
|
end
|
495
509
|
end
|
496
510
|
|
511
|
+
def matches_focused_filter?(node)
|
512
|
+
return true unless use_default_focused_filter?
|
513
|
+
|
514
|
+
(node == node.session.active_element) == options[:focused]
|
515
|
+
end
|
516
|
+
|
497
517
|
def need_to_process_classes?
|
498
518
|
case options[:class]
|
499
519
|
when Regexp then true
|
@@ -528,16 +548,19 @@ module Capybara
|
|
528
548
|
def matches_text_filter?(node)
|
529
549
|
value = options[:text]
|
530
550
|
return true unless value
|
531
|
-
return matches_text_exactly?(node, value) if exact_text == true
|
551
|
+
return matches_text_exactly?(node, value) if exact_text == true && !value.is_a?(Regexp)
|
532
552
|
|
533
553
|
regexp = value.is_a?(Regexp) ? value : Regexp.escape(value.to_s)
|
534
554
|
matches_text_regexp?(node, regexp)
|
535
555
|
end
|
536
556
|
|
537
557
|
def matches_exact_text_filter?(node)
|
538
|
-
|
539
|
-
|
540
|
-
|
558
|
+
case exact_text
|
559
|
+
when String, Regexp
|
560
|
+
matches_text_exactly?(node, exact_text)
|
561
|
+
else
|
562
|
+
true
|
563
|
+
end
|
541
564
|
end
|
542
565
|
|
543
566
|
def matches_visibility_filters?(node)
|
@@ -548,7 +571,9 @@ module Capybara
|
|
548
571
|
when :visible
|
549
572
|
node.initial_cache[:visible] || (node.initial_cache[:visible].nil? && node.visible?)
|
550
573
|
when :hidden
|
551
|
-
|
574
|
+
# TODO: check why the 'visbile' cache spelling mistake wasn't caught in a test
|
575
|
+
# (node.initial_cache[:visible] == false) || (node.initial_cache[:visbile].nil? && !node.visible?)
|
576
|
+
(node.initial_cache[:visible] == false) || (node.initial_cache[:visible].nil? && !node.visible?)
|
552
577
|
else
|
553
578
|
true
|
554
579
|
end
|
@@ -565,17 +590,21 @@ module Capybara
|
|
565
590
|
|
566
591
|
def matches_text_exactly?(node, value)
|
567
592
|
regexp = value.is_a?(Regexp) ? value : /\A#{Regexp.escape(value.to_s)}\z/
|
568
|
-
matches_text_regexp
|
593
|
+
matches_text_regexp(node, regexp).then { |m| m&.pre_match == '' && m&.post_match == '' }
|
569
594
|
end
|
570
595
|
|
571
596
|
def normalize_ws
|
572
597
|
options.fetch(:normalize_ws, session_options.default_normalize_ws)
|
573
598
|
end
|
574
599
|
|
575
|
-
def matches_text_regexp
|
600
|
+
def matches_text_regexp(node, regexp)
|
576
601
|
text_visible = visible
|
577
602
|
text_visible = :all if text_visible == :hidden
|
578
|
-
node.text(text_visible, normalize_ws: normalize_ws).match
|
603
|
+
node.text(text_visible, normalize_ws: normalize_ws).match(regexp)
|
604
|
+
end
|
605
|
+
|
606
|
+
def matches_text_regexp?(node, regexp)
|
607
|
+
!matches_text_regexp(node, regexp).nil?
|
579
608
|
end
|
580
609
|
|
581
610
|
def default_visibility
|
@@ -7,7 +7,8 @@ module Capybara
|
|
7
7
|
def resolve_for(node, exact = nil)
|
8
8
|
@sibling_node = node
|
9
9
|
node.synchronize do
|
10
|
-
|
10
|
+
scope = node.respond_to?(:session) ? node.session.current_scope : node.find(:xpath, '/*')
|
11
|
+
match_results = super(scope, exact)
|
11
12
|
siblings = node.find_xpath((XPath.preceding_sibling + XPath.following_sibling).to_s)
|
12
13
|
.map(&method(:to_element))
|
13
14
|
.select { |el| match_results.include?(el) }
|
@@ -13,7 +13,7 @@ module Capybara
|
|
13
13
|
self.session_options = session_options
|
14
14
|
|
15
15
|
if expected_text.nil? && !exact?
|
16
|
-
warn 'Checking for expected text of nil is confusing and/or pointless since it will always match. '\
|
16
|
+
warn 'Checking for expected text of nil is confusing and/or pointless since it will always match. ' \
|
17
17
|
"Please specify a string or regexp instead. #{Capybara::Helpers.filter_backtrace(caller)}"
|
18
18
|
end
|
19
19
|
|