capybara 3.29.0 → 3.31.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 +40 -1
- data/README.md +1 -1
- data/lib/capybara/config.rb +7 -3
- data/lib/capybara/dsl.rb +10 -2
- data/lib/capybara/helpers.rb +3 -1
- data/lib/capybara/minitest.rb +18 -4
- data/lib/capybara/node/actions.rb +23 -19
- data/lib/capybara/node/document.rb +2 -2
- data/lib/capybara/node/document_matchers.rb +3 -3
- data/lib/capybara/node/element.rb +21 -16
- data/lib/capybara/node/finders.rb +17 -11
- data/lib/capybara/node/matchers.rb +60 -45
- data/lib/capybara/node/simple.rb +4 -2
- data/lib/capybara/queries/ancestor_query.rb +1 -1
- data/lib/capybara/queries/base_query.rb +2 -1
- data/lib/capybara/queries/selector_query.rb +17 -4
- data/lib/capybara/queries/sibling_query.rb +1 -1
- data/lib/capybara/rack_test/browser.rb +4 -1
- data/lib/capybara/rack_test/driver.rb +1 -1
- data/lib/capybara/rack_test/form.rb +1 -1
- data/lib/capybara/rack_test/node.rb +34 -9
- data/lib/capybara/result.rb +24 -4
- data/lib/capybara/rspec/matchers.rb +27 -27
- data/lib/capybara/rspec/matchers/base.rb +12 -6
- data/lib/capybara/rspec/matchers/count_sugar.rb +2 -1
- data/lib/capybara/rspec/matchers/have_ancestor.rb +4 -3
- data/lib/capybara/rspec/matchers/have_current_path.rb +2 -2
- data/lib/capybara/rspec/matchers/have_selector.rb +15 -7
- data/lib/capybara/rspec/matchers/have_sibling.rb +3 -3
- data/lib/capybara/rspec/matchers/have_text.rb +2 -2
- data/lib/capybara/rspec/matchers/have_title.rb +2 -2
- data/lib/capybara/rspec/matchers/match_selector.rb +3 -3
- data/lib/capybara/rspec/matchers/match_style.rb +2 -2
- data/lib/capybara/rspec/matchers/spatial_sugar.rb +2 -1
- data/lib/capybara/selector.rb +24 -16
- data/lib/capybara/selector/css.rb +1 -1
- data/lib/capybara/selector/definition.rb +2 -2
- data/lib/capybara/selector/definition/button.rb +7 -2
- data/lib/capybara/selector/definition/checkbox.rb +2 -2
- data/lib/capybara/selector/definition/css.rb +3 -1
- data/lib/capybara/selector/definition/datalist_input.rb +1 -1
- data/lib/capybara/selector/definition/datalist_option.rb +1 -1
- data/lib/capybara/selector/definition/element.rb +1 -1
- data/lib/capybara/selector/definition/field.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/label.rb +4 -2
- data/lib/capybara/selector/definition/radio_button.rb +2 -2
- data/lib/capybara/selector/definition/select.rb +32 -13
- data/lib/capybara/selector/definition/table.rb +5 -2
- data/lib/capybara/selector/filter_set.rb +11 -9
- data/lib/capybara/selector/filters/base.rb +6 -1
- data/lib/capybara/selector/filters/locator_filter.rb +1 -1
- data/lib/capybara/selector/selector.rb +4 -2
- data/lib/capybara/selenium/driver.rb +19 -11
- data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +1 -1
- data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +2 -2
- data/lib/capybara/selenium/extensions/html5_drag.rb +30 -13
- data/lib/capybara/selenium/node.rb +29 -10
- data/lib/capybara/selenium/nodes/chrome_node.rb +11 -5
- data/lib/capybara/selenium/nodes/edge_node.rb +4 -2
- data/lib/capybara/selenium/nodes/firefox_node.rb +2 -2
- data/lib/capybara/server.rb +15 -3
- data/lib/capybara/server/checker.rb +1 -1
- data/lib/capybara/server/middleware.rb +20 -10
- data/lib/capybara/session.rb +40 -23
- data/lib/capybara/session/config.rb +6 -2
- data/lib/capybara/session/matchers.rb +6 -6
- data/lib/capybara/spec/public/test.js +51 -6
- data/lib/capybara/spec/session/all_spec.rb +60 -5
- data/lib/capybara/spec/session/ancestor_spec.rb +5 -0
- data/lib/capybara/spec/session/assert_text_spec.rb +9 -5
- data/lib/capybara/spec/session/click_button_spec.rb +5 -0
- data/lib/capybara/spec/session/fill_in_spec.rb +20 -0
- data/lib/capybara/spec/session/find_spec.rb +20 -0
- data/lib/capybara/spec/session/has_css_spec.rb +3 -3
- data/lib/capybara/spec/session/has_select_spec.rb +28 -0
- data/lib/capybara/spec/session/has_table_spec.rb +51 -5
- data/lib/capybara/spec/session/has_text_spec.rb +35 -0
- data/lib/capybara/spec/session/node_spec.rb +106 -2
- data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +2 -2
- data/lib/capybara/spec/session/save_screenshot_spec.rb +4 -4
- data/lib/capybara/spec/session/selectors_spec.rb +15 -2
- data/lib/capybara/spec/views/form.erb +11 -1
- data/lib/capybara/version.rb +1 -1
- data/spec/dsl_spec.rb +2 -2
- data/spec/minitest_spec_spec.rb +46 -46
- data/spec/rack_test_spec.rb +0 -1
- data/spec/regexp_dissassembler_spec.rb +45 -37
- data/spec/result_spec.rb +7 -3
- data/spec/rspec/features_spec.rb +1 -0
- data/spec/rspec/shared_spec_matchers.rb +3 -3
- data/spec/rspec_spec.rb +4 -4
- data/spec/selenium_spec_chrome.rb +5 -4
- data/spec/selenium_spec_firefox.rb +7 -2
- data/spec/server_spec.rb +42 -0
- data/spec/session_spec.rb +1 -1
- data/spec/shared_selenium_node.rb +3 -3
- data/spec/shared_selenium_session.rb +8 -7
- metadata +3 -3
@@ -6,7 +6,7 @@ Capybara.add_selector(:fillable_field, locator_type: [String, Symbol]) do
|
|
6
6
|
xpath = XPath.axis(allow_self ? :"descendant-or-self" : :descendant, :input, :textarea)[
|
7
7
|
!XPath.attr(:type).one_of('submit', 'image', 'radio', 'checkbox', 'hidden', 'file')
|
8
8
|
]
|
9
|
-
locate_field(xpath, locator, options)
|
9
|
+
locate_field(xpath, locator, **options)
|
10
10
|
end
|
11
11
|
|
12
12
|
expression_filter(:type) do |expr, type|
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
Capybara.add_selector(:label, locator_type: [String, Symbol]) do
|
4
4
|
label 'label'
|
5
|
-
xpath(:for) do |locator, options|
|
5
|
+
xpath(:for) do |locator, **options|
|
6
6
|
xpath = XPath.descendant(:label)
|
7
7
|
unless locator.nil?
|
8
8
|
locator_matchers = XPath.string.n.is(locator.to_s) | (XPath.attr(:id) == locator.to_s)
|
@@ -10,7 +10,9 @@ Capybara.add_selector(:label, locator_type: [String, Symbol]) do
|
|
10
10
|
xpath = xpath[locator_matchers]
|
11
11
|
end
|
12
12
|
if options.key?(:for)
|
13
|
-
|
13
|
+
for_option = options[:for]
|
14
|
+
for_option = for_option[:id] if for_option.is_a?(Capybara::Node::Element)
|
15
|
+
if for_option && (for_option != '')
|
14
16
|
with_attr = builder(XPath.self).add_attribute_conditions(for: for_option)
|
15
17
|
wrapped = !XPath.attr(:for) &
|
16
18
|
builder(XPath.self.descendant(*labelable_elements)).add_attribute_conditions(id: for_option)
|
@@ -6,7 +6,7 @@ Capybara.add_selector(:radio_button, locator_type: [String, Symbol]) do
|
|
6
6
|
xpath = XPath.axis(allow_self ? :"descendant-or-self" : :descendant, :input)[
|
7
7
|
XPath.attr(:type) == 'radio'
|
8
8
|
]
|
9
|
-
locate_field(xpath, locator, options)
|
9
|
+
locate_field(xpath, locator, **options)
|
10
10
|
end
|
11
11
|
|
12
12
|
filter_set(:_field, %i[checked unchecked disabled name])
|
@@ -21,7 +21,7 @@ Capybara.add_selector(:radio_button, locator_type: [String, Symbol]) do
|
|
21
21
|
describe_node_filters do |option: nil, with: nil, **|
|
22
22
|
desc = +''
|
23
23
|
desc << " with value #{option.inspect}" if option
|
24
|
-
desc << " with value #{with.
|
24
|
+
desc << " with value #{with.inspect}" if with
|
25
25
|
desc
|
26
26
|
end
|
27
27
|
end
|
@@ -5,22 +5,32 @@ Capybara.add_selector(:select, locator_type: [String, Symbol]) do
|
|
5
5
|
|
6
6
|
xpath do |locator, **options|
|
7
7
|
xpath = XPath.descendant(:select)
|
8
|
-
locate_field(xpath, locator, options)
|
8
|
+
locate_field(xpath, locator, **options)
|
9
9
|
end
|
10
10
|
|
11
11
|
filter_set(:_field, %i[disabled multiple name placeholder])
|
12
12
|
|
13
13
|
node_filter(:options) do |node, options|
|
14
|
-
actual =
|
15
|
-
node.all(:xpath, './/option', wait: false).map(&:text)
|
16
|
-
else
|
17
|
-
node.all(:xpath, './/option', visible: false, wait: false).map { |option| option.text(:all) }
|
18
|
-
end
|
14
|
+
actual = options_text(node)
|
19
15
|
(options.sort == actual.sort).tap do |res|
|
20
16
|
add_error("Expected options #{options.inspect} found #{actual.inspect}") unless res
|
21
17
|
end
|
22
18
|
end
|
23
19
|
|
20
|
+
node_filter(:enabled_options) do |node, options|
|
21
|
+
actual = options_text(node) { |o| !o.disabled? }
|
22
|
+
(options.sort == actual.sort).tap do |res|
|
23
|
+
add_error("Expected enabled options #{options.inspect} found #{actual.inspect}") unless res
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
node_filter(:disabled_options) do |node, options|
|
28
|
+
actual = options_text(node, &:disabled?)
|
29
|
+
(options.sort == actual.sort).tap do |res|
|
30
|
+
add_error("Expected disabled options #{options.inspect} found #{actual.inspect}") unless res
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
24
34
|
expression_filter(:with_options) do |expr, options|
|
25
35
|
options.inject(expr) do |xpath, option|
|
26
36
|
xpath[expression_for(:option, option)]
|
@@ -28,18 +38,14 @@ Capybara.add_selector(:select, locator_type: [String, Symbol]) do
|
|
28
38
|
end
|
29
39
|
|
30
40
|
node_filter(:selected) do |node, selected|
|
31
|
-
actual = node
|
32
|
-
.select(&:selected?)
|
33
|
-
.map { |option| option.text(:all) }
|
41
|
+
actual = options_text(node, visible: false, &:selected?)
|
34
42
|
(Array(selected).sort == actual.sort).tap do |res|
|
35
43
|
add_error("Expected #{selected.inspect} to be selected found #{actual.inspect}") unless res
|
36
44
|
end
|
37
45
|
end
|
38
46
|
|
39
47
|
node_filter(:with_selected) do |node, selected|
|
40
|
-
actual = node
|
41
|
-
.select(&:selected?)
|
42
|
-
.map { |option| option.text(:all) }
|
48
|
+
actual = options_text(node, visible: false, &:selected?)
|
43
49
|
(Array(selected) - actual).empty?.tap do |res|
|
44
50
|
add_error("Expected at least #{selected.inspect} to be selected found #{actual.inspect}") unless res
|
45
51
|
end
|
@@ -51,12 +57,25 @@ Capybara.add_selector(:select, locator_type: [String, Symbol]) do
|
|
51
57
|
desc
|
52
58
|
end
|
53
59
|
|
54
|
-
describe_node_filters do |
|
60
|
+
describe_node_filters do |
|
61
|
+
options: nil, disabled_options: nil, enabled_options: nil,
|
62
|
+
selected: nil, with_selected: nil,
|
63
|
+
disabled: nil, **|
|
55
64
|
desc = +''
|
56
65
|
desc << " with options #{options.inspect}" if options
|
66
|
+
desc << " with disabled options #{disabled_options.inspect}}" if disabled_options
|
67
|
+
desc << " with enabled options #{enabled_options.inspect}" if enabled_options
|
57
68
|
desc << " with #{selected.inspect} selected" if selected
|
58
69
|
desc << " with at least #{with_selected.inspect} selected" if with_selected
|
59
70
|
desc << ' which is disabled' if disabled
|
60
71
|
desc
|
61
72
|
end
|
73
|
+
|
74
|
+
def options_text(node, **opts, &filter_block)
|
75
|
+
opts[:wait] = false
|
76
|
+
opts[:visible] = false unless node.visible?
|
77
|
+
node.all(:xpath, './/option', **opts, &filter_block).map do |o|
|
78
|
+
o.text((:all if opts[:visible] == false))
|
79
|
+
end
|
80
|
+
end
|
62
81
|
end
|
@@ -19,7 +19,10 @@ Capybara.add_selector(:table, locator_type: [String, Symbol]) do
|
|
19
19
|
header = XPath.descendant(:th)[XPath.string.n.is(header)]
|
20
20
|
td = XPath.descendant(:tr)[header].descendant(:td)
|
21
21
|
cell_condition = XPath.string.n.is(cell_str)
|
22
|
-
|
22
|
+
if xp
|
23
|
+
prev_cell = XPath.ancestor(:table)[1].join(xp)
|
24
|
+
cell_condition &= (prev_cell & prev_col_position?(prev_cell))
|
25
|
+
end
|
23
26
|
td[cell_condition]
|
24
27
|
end
|
25
28
|
else
|
@@ -28,7 +31,7 @@ Capybara.add_selector(:table, locator_type: [String, Symbol]) do
|
|
28
31
|
|
29
32
|
if prev_cell
|
30
33
|
prev_cell = XPath.ancestor(:tr)[1].preceding_sibling(:tr).join(prev_cell)
|
31
|
-
cell_condition &= prev_col_position?(prev_cell)
|
34
|
+
cell_condition &= (prev_cell & prev_col_position?(prev_cell))
|
32
35
|
end
|
33
36
|
|
34
37
|
XPath.descendant(:td)[cell_condition]
|
@@ -15,15 +15,15 @@ module Capybara
|
|
15
15
|
instance_eval(&block)
|
16
16
|
end
|
17
17
|
|
18
|
-
def node_filter(names, *
|
18
|
+
def node_filter(names, *types, **options, &block)
|
19
19
|
Array(names).each do |name|
|
20
|
-
add_filter(name, Filters::NodeFilter, *
|
20
|
+
add_filter(name, Filters::NodeFilter, *types, **options, &block)
|
21
21
|
end
|
22
22
|
end
|
23
23
|
alias_method :filter, :node_filter
|
24
24
|
|
25
|
-
def expression_filter(name, *
|
26
|
-
add_filter(name, Filters::ExpressionFilter, *
|
25
|
+
def expression_filter(name, *types, **options, &block)
|
26
|
+
add_filter(name, Filters::ExpressionFilter, *types, **options, &block)
|
27
27
|
end
|
28
28
|
|
29
29
|
def describe(what = nil, &block)
|
@@ -42,9 +42,9 @@ module Capybara
|
|
42
42
|
def description(node_filters: true, expression_filters: true, **options)
|
43
43
|
opts = options_with_defaults(options)
|
44
44
|
description = +''
|
45
|
-
description << undeclared_descriptions.map { |desc| desc.call(opts).to_s }.join
|
46
|
-
description << expression_filter_descriptions.map { |desc| desc.call(opts).to_s }.join if expression_filters
|
47
|
-
description << node_filter_descriptions.map { |desc| desc.call(opts).to_s }.join if node_filters
|
45
|
+
description << undeclared_descriptions.map { |desc| desc.call(**opts).to_s }.join
|
46
|
+
description << expression_filter_descriptions.map { |desc| desc.call(**opts).to_s }.join if expression_filters
|
47
|
+
description << node_filter_descriptions.map { |desc| desc.call(**opts).to_s }.join if node_filters
|
48
48
|
description
|
49
49
|
end
|
50
50
|
|
@@ -112,9 +112,11 @@ module Capybara
|
|
112
112
|
|
113
113
|
def add_filter(name, filter_class, *types, matcher: nil, **options, &block)
|
114
114
|
types.each { |type| options[type] = true }
|
115
|
-
|
115
|
+
if matcher && options[:default]
|
116
|
+
raise 'ArgumentError', ':default option is not supported for filters with a :matcher option'
|
117
|
+
end
|
116
118
|
|
117
|
-
filter = filter_class.new(name, matcher, block, options)
|
119
|
+
filter = filter_class.new(name, matcher, block, **options)
|
118
120
|
(filter_class <= Filters::ExpressionFilter ? @expression_filters : @node_filters)[name] = filter
|
119
121
|
end
|
120
122
|
end
|
@@ -48,7 +48,12 @@ module Capybara
|
|
48
48
|
|
49
49
|
def apply(subject, name, value, skip_value, ctx)
|
50
50
|
return skip_value if skip?(value)
|
51
|
-
|
51
|
+
|
52
|
+
unless valid_value?(value)
|
53
|
+
raise ArgumentError,
|
54
|
+
"Invalid value #{value.inspect} passed to #{self.class.name.split('::').last} #{name}" \
|
55
|
+
"#{" : #{name}" if @name.is_a?(Regexp)}"
|
56
|
+
end
|
52
57
|
|
53
58
|
if @block.arity == 2
|
54
59
|
filter_context(ctx).instance_exec(subject, value, &@block)
|
@@ -56,12 +56,14 @@ module Capybara
|
|
56
56
|
if format
|
57
57
|
raise ArgumentError, "Selector #{@name} does not support #{format}" unless expressions.key?(format)
|
58
58
|
|
59
|
-
instance_exec(locator, options, &expressions[format])
|
59
|
+
instance_exec(locator, **options, &expressions[format])
|
60
60
|
else
|
61
61
|
warn 'Selector has no format'
|
62
62
|
end
|
63
63
|
ensure
|
64
|
-
|
64
|
+
unless locator_valid?(locator)
|
65
|
+
warn "Locator #{locator.class}:#{locator.inspect} for selector #{name.inspect} must #{locator_description}. This will raise an error in a future version of Capybara."
|
66
|
+
end
|
65
67
|
end
|
66
68
|
|
67
69
|
def add_error(error_msg)
|
@@ -20,7 +20,9 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
20
20
|
require 'capybara/selenium/logger_suppressor'
|
21
21
|
require 'capybara/selenium/patches/atoms'
|
22
22
|
require 'capybara/selenium/patches/is_displayed'
|
23
|
-
|
23
|
+
if Gem.loaded_specs['selenium-webdriver'].version < Gem::Version.new('3.5.0')
|
24
|
+
warn "Warning: You're using an unsupported version of selenium-webdriver, please upgrade."
|
25
|
+
end
|
24
26
|
rescue LoadError => e
|
25
27
|
raise e unless e.message.match?(/selenium-webdriver/)
|
26
28
|
|
@@ -143,7 +145,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
143
145
|
|
144
146
|
switch_to_frame(:parent)
|
145
147
|
begin
|
146
|
-
|
148
|
+
frame.base.obscured?(x: x, y: y)
|
147
149
|
ensure
|
148
150
|
switch_to_frame(frame)
|
149
151
|
end
|
@@ -219,7 +221,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
219
221
|
|
220
222
|
def accept_modal(_type, **options)
|
221
223
|
yield if block_given?
|
222
|
-
modal = find_modal(options)
|
224
|
+
modal = find_modal(**options)
|
223
225
|
|
224
226
|
modal.send_keys options[:with] if options[:with]
|
225
227
|
|
@@ -230,7 +232,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
230
232
|
|
231
233
|
def dismiss_modal(_type, **options)
|
232
234
|
yield if block_given?
|
233
|
-
modal = find_modal(options)
|
235
|
+
modal = find_modal(**options)
|
234
236
|
message = modal.text
|
235
237
|
modal.dismiss
|
236
238
|
message
|
@@ -238,7 +240,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
238
240
|
|
239
241
|
def quit
|
240
242
|
@browser&.quit
|
241
|
-
rescue Selenium::WebDriver::Error::SessionNotCreatedError, Errno::ECONNREFUSED # rubocop:disable Lint/
|
243
|
+
rescue Selenium::WebDriver::Error::SessionNotCreatedError, Errno::ECONNREFUSED # rubocop:disable Lint/SuppressedException
|
242
244
|
# Browser must have already gone
|
243
245
|
rescue Selenium::WebDriver::Error::UnknownError => e
|
244
246
|
unless silenced_unknown_error_message?(e.message) # Most likely already gone
|
@@ -290,7 +292,7 @@ private
|
|
290
292
|
def clear_browser_state
|
291
293
|
delete_all_cookies
|
292
294
|
clear_storage
|
293
|
-
rescue *clear_browser_state_errors # rubocop:disable Lint/
|
295
|
+
rescue *clear_browser_state_errors # rubocop:disable Lint/SuppressedException
|
294
296
|
# delete_all_cookies fails when we've previously gone
|
295
297
|
# to about:blank, so we rescue this error and do nothing
|
296
298
|
# instead.
|
@@ -314,7 +316,7 @@ private
|
|
314
316
|
def clear_storage
|
315
317
|
clear_session_storage unless options[:clear_session_storage] == false
|
316
318
|
clear_local_storage unless options[:clear_local_storage] == false
|
317
|
-
rescue Selenium::WebDriver::Error::JavascriptError # rubocop:disable Lint/
|
319
|
+
rescue Selenium::WebDriver::Error::JavascriptError # rubocop:disable Lint/SuppressedException
|
318
320
|
# session/local storage may not be available if on non-http pages (e.g. about:blank)
|
319
321
|
end
|
320
322
|
|
@@ -325,7 +327,9 @@ private
|
|
325
327
|
begin
|
326
328
|
@browser&.execute_script('window.sessionStorage.clear()')
|
327
329
|
rescue # rubocop:disable Style/RescueStandardError
|
328
|
-
|
330
|
+
unless options[:clear_session_storage].nil?
|
331
|
+
warn 'sessionStorage clear requested but is not supported by this driver'
|
332
|
+
end
|
329
333
|
end
|
330
334
|
end
|
331
335
|
end
|
@@ -337,7 +341,9 @@ private
|
|
337
341
|
begin
|
338
342
|
@browser&.execute_script('window.localStorage.clear()')
|
339
343
|
rescue # rubocop:disable Style/RescueStandardError
|
340
|
-
|
344
|
+
unless options[:clear_local_storage].nil?
|
345
|
+
warn 'localStorage clear requested but is not supported by this driver'
|
346
|
+
end
|
341
347
|
end
|
342
348
|
end
|
343
349
|
end
|
@@ -346,7 +352,7 @@ private
|
|
346
352
|
@browser.navigate.to(url)
|
347
353
|
sleep 0.1 # slight wait for alert
|
348
354
|
@browser.switch_to.alert.accept
|
349
|
-
rescue modal_error # rubocop:disable Lint/
|
355
|
+
rescue modal_error # rubocop:disable Lint/SuppressedException
|
350
356
|
# alert now gone, should mean navigation happened
|
351
357
|
end
|
352
358
|
|
@@ -378,7 +384,9 @@ private
|
|
378
384
|
alert = @browser.switch_to.alert
|
379
385
|
regexp = text.is_a?(Regexp) ? text : Regexp.new(Regexp.escape(text.to_s))
|
380
386
|
matched = alert.text.match?(regexp)
|
381
|
-
|
387
|
+
unless matched
|
388
|
+
raise Capybara::ModalNotFound, "Unable to find modal dialog with #{text} - found '#{alert.text}' instead."
|
389
|
+
end
|
382
390
|
|
383
391
|
alert
|
384
392
|
end
|
@@ -96,7 +96,7 @@ private
|
|
96
96
|
|
97
97
|
def execute_cdp(cmd, params = {})
|
98
98
|
if browser.respond_to? :execute_cdp
|
99
|
-
browser.execute_cdp(cmd, params)
|
99
|
+
browser.execute_cdp(cmd, **params)
|
100
100
|
else
|
101
101
|
args = { cmd: cmd, params: params }
|
102
102
|
result = bridge.http.call(:post, "session/#{bridge.session_id}/goog/cdp/execute", args)
|
@@ -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/
|
49
|
+
rescue Selenium::WebDriver::Error::NoSuchAlertError # rubocop:disable Lint/SuppressedException
|
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/
|
64
|
+
rescue Capybara::ModalNotFound # rubocop:disable Lint/SuppressedException
|
65
65
|
# No modal was opened - page has refreshed - ignore
|
66
66
|
end
|
67
67
|
|
@@ -4,25 +4,32 @@ class Capybara::Selenium::Node
|
|
4
4
|
module Html5Drag
|
5
5
|
# Implement methods to emulate HTML5 drag and drop
|
6
6
|
|
7
|
-
def drag_to(element, html5: nil, delay: 0.05)
|
7
|
+
def drag_to(element, html5: nil, delay: 0.05, drop_modifiers: [])
|
8
|
+
drop_modifiers = Array(drop_modifiers)
|
9
|
+
|
8
10
|
driver.execute_script MOUSEDOWN_TRACKER
|
9
11
|
scroll_if_needed { browser_action.click_and_hold(native).perform }
|
10
12
|
html5 = !driver.evaluate_script(LEGACY_DRAG_CHECK, self) if html5.nil?
|
11
13
|
if html5
|
12
|
-
perform_html5_drag(element, delay)
|
14
|
+
perform_html5_drag(element, delay, drop_modifiers)
|
13
15
|
else
|
14
|
-
perform_legacy_drag(element)
|
16
|
+
perform_legacy_drag(element, drop_modifiers)
|
15
17
|
end
|
16
18
|
end
|
17
19
|
|
18
20
|
private
|
19
21
|
|
20
|
-
def perform_legacy_drag(element)
|
21
|
-
element.scroll_if_needed
|
22
|
+
def perform_legacy_drag(element, drop_modifiers)
|
23
|
+
element.scroll_if_needed do
|
24
|
+
# browser_action.move_to(element.native).release.perform
|
25
|
+
keys_down = modifiers_down(browser_action, drop_modifiers)
|
26
|
+
keys_up = modifiers_up(keys_down.move_to(element.native).release, drop_modifiers)
|
27
|
+
keys_up.perform
|
28
|
+
end
|
22
29
|
end
|
23
30
|
|
24
|
-
def perform_html5_drag(element, delay)
|
25
|
-
driver.evaluate_async_script HTML5_DRAG_DROP_SCRIPT, self, element, delay * 1000
|
31
|
+
def perform_html5_drag(element, delay, drop_modifiers)
|
32
|
+
driver.evaluate_async_script HTML5_DRAG_DROP_SCRIPT, self, element, delay * 1000, normalize_keys(drop_modifiers)
|
26
33
|
browser_action.release.perform
|
27
34
|
end
|
28
35
|
|
@@ -153,6 +160,14 @@ class Capybara::Selenium::Node
|
|
153
160
|
var targetRect = target.getBoundingClientRect();
|
154
161
|
var sourceCenter = rectCenter(source.getBoundingClientRect());
|
155
162
|
|
163
|
+
for (var i = 0; i < drop_modifier_keys.length; i++) {
|
164
|
+
key = drop_modifier_keys[i];
|
165
|
+
if (key == "control"){
|
166
|
+
key = "ctrl"
|
167
|
+
}
|
168
|
+
opts[key + 'Key'] = true;
|
169
|
+
}
|
170
|
+
|
156
171
|
// fire 2 dragover events to simulate dragging with a direction
|
157
172
|
var entryPoint = pointOnRect(sourceCenter, targetRect)
|
158
173
|
var dragOverOpts = Object.assign({clientX: entryPoint.x, clientY: entryPoint.y}, opts);
|
@@ -166,17 +181,18 @@ class Capybara::Selenium::Node
|
|
166
181
|
var dragOverOpts = Object.assign({clientX: targetCenter.x, clientY: targetCenter.y}, opts);
|
167
182
|
var dragOverEvent = new DragEvent('dragover', dragOverOpts);
|
168
183
|
target.dispatchEvent(dragOverEvent);
|
169
|
-
window.setTimeout(dragLeave, step_delay, dragOverEvent.defaultPrevented);
|
184
|
+
window.setTimeout(dragLeave, step_delay, dragOverEvent.defaultPrevented, dragOverOpts);
|
170
185
|
}
|
171
186
|
|
172
|
-
function dragLeave(drop) {
|
173
|
-
var
|
187
|
+
function dragLeave(drop, dragOverOpts) {
|
188
|
+
var dragLeaveOptions = Object.assign({}, opts, dragOverOpts);
|
189
|
+
var dragLeaveEvent = new DragEvent('dragleave', dragLeaveOptions);
|
174
190
|
target.dispatchEvent(dragLeaveEvent);
|
175
191
|
if (drop) {
|
176
|
-
var dropEvent = new DragEvent('drop',
|
192
|
+
var dropEvent = new DragEvent('drop', dragLeaveOptions);
|
177
193
|
target.dispatchEvent(dropEvent);
|
178
194
|
}
|
179
|
-
var dragEndEvent = new DragEvent('dragend',
|
195
|
+
var dragEndEvent = new DragEvent('dragend', dragLeaveOptions);
|
180
196
|
source.dispatchEvent(dragEndEvent);
|
181
197
|
callback.call(true);
|
182
198
|
}
|
@@ -184,7 +200,8 @@ class Capybara::Selenium::Node
|
|
184
200
|
var source = arguments[0],
|
185
201
|
target = arguments[1],
|
186
202
|
step_delay = arguments[2],
|
187
|
-
|
203
|
+
drop_modifier_keys = arguments[3],
|
204
|
+
callback = arguments[4];
|
188
205
|
|
189
206
|
var dt = new DataTransfer();
|
190
207
|
var opts = { cancelable: true, bubbles: true, dataTransfer: dt };
|