capybara 3.32.0 → 3.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/History.md +99 -15
- data/README.md +9 -4
- data/lib/capybara.rb +18 -8
- data/lib/capybara/config.rb +4 -6
- data/lib/capybara/cucumber.rb +1 -1
- data/lib/capybara/driver/base.rb +4 -0
- data/lib/capybara/helpers.rb +25 -1
- data/lib/capybara/minitest.rb +2 -3
- data/lib/capybara/minitest/spec.rb +14 -11
- data/lib/capybara/node/actions.rb +16 -21
- data/lib/capybara/node/base.rb +6 -6
- data/lib/capybara/node/element.rb +1 -5
- data/lib/capybara/node/finders.rb +7 -6
- data/lib/capybara/node/matchers.rb +12 -12
- data/lib/capybara/node/simple.rb +5 -1
- data/lib/capybara/queries/ancestor_query.rb +1 -1
- data/lib/capybara/queries/current_path_query.rb +14 -4
- data/lib/capybara/queries/selector_query.rb +40 -18
- data/lib/capybara/queries/sibling_query.rb +1 -1
- data/lib/capybara/queries/style_query.rb +1 -1
- data/lib/capybara/queries/text_query.rb +7 -1
- data/lib/capybara/rack_test/browser.rb +7 -3
- data/lib/capybara/rack_test/driver.rb +1 -0
- data/lib/capybara/rack_test/form.rb +1 -1
- data/lib/capybara/rack_test/node.rb +1 -1
- data/lib/capybara/registration_container.rb +44 -0
- data/lib/capybara/registrations/drivers.rb +18 -12
- data/lib/capybara/registrations/patches/puma_ssl.rb +3 -1
- data/lib/capybara/registrations/servers.rb +3 -2
- data/lib/capybara/result.rb +10 -11
- data/lib/capybara/rspec.rb +2 -0
- data/lib/capybara/rspec/matcher_proxies.rb +1 -1
- data/lib/capybara/rspec/matchers.rb +7 -6
- data/lib/capybara/rspec/matchers/have_current_path.rb +2 -2
- data/lib/capybara/rspec/matchers/have_text.rb +1 -1
- data/lib/capybara/rspec/matchers/match_style.rb +5 -0
- data/lib/capybara/selector.rb +12 -3
- data/lib/capybara/selector/builders/css_builder.rb +1 -1
- data/lib/capybara/selector/builders/xpath_builder.rb +3 -1
- data/lib/capybara/selector/definition.rb +11 -9
- data/lib/capybara/selector/definition/button.rb +26 -14
- data/lib/capybara/selector/definition/css.rb +1 -1
- data/lib/capybara/selector/definition/datalist_input.rb +1 -1
- data/lib/capybara/selector/definition/element.rb +2 -1
- data/lib/capybara/selector/definition/fillable_field.rb +1 -1
- data/lib/capybara/selector/definition/label.rb +1 -1
- data/lib/capybara/selector/definition/link.rb +8 -0
- data/lib/capybara/selector/definition/select.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/filter_set.rb +2 -2
- data/lib/capybara/selector/selector.rb +9 -1
- data/lib/capybara/selenium/atoms/src/isDisplayed.js +1 -1
- data/lib/capybara/selenium/driver.rb +51 -7
- data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +9 -11
- data/lib/capybara/selenium/driver_specializations/edge_driver.rb +9 -11
- data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +3 -3
- data/lib/capybara/selenium/extensions/find.rb +4 -4
- data/lib/capybara/selenium/extensions/scroll.rb +8 -10
- data/lib/capybara/selenium/logger_suppressor.rb +1 -1
- data/lib/capybara/selenium/node.rb +23 -6
- data/lib/capybara/selenium/nodes/chrome_node.rb +23 -5
- data/lib/capybara/selenium/nodes/firefox_node.rb +7 -2
- data/lib/capybara/selenium/nodes/safari_node.rb +1 -1
- data/lib/capybara/selenium/patches/action_pauser.rb +4 -1
- data/lib/capybara/selenium/patches/atoms.rb +4 -4
- data/lib/capybara/selenium/patches/logs.rb +7 -9
- data/lib/capybara/server/animation_disabler.rb +8 -3
- data/lib/capybara/server/middleware.rb +4 -2
- data/lib/capybara/session.rb +23 -14
- data/lib/capybara/session/config.rb +3 -1
- data/lib/capybara/session/matchers.rb +11 -11
- data/lib/capybara/spec/public/test.js +13 -1
- data/lib/capybara/spec/session/accept_alert_spec.rb +1 -1
- data/lib/capybara/spec/session/check_spec.rb +6 -0
- data/lib/capybara/spec/session/click_button_spec.rb +11 -0
- data/lib/capybara/spec/session/current_url_spec.rb +11 -1
- data/lib/capybara/spec/session/has_button_spec.rb +51 -0
- data/lib/capybara/spec/session/has_css_spec.rb +2 -1
- data/lib/capybara/spec/session/has_current_path_spec.rb +15 -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/has_text_spec.rb +0 -11
- data/lib/capybara/spec/session/html_spec.rb +1 -1
- data/lib/capybara/spec/session/matches_style_spec.rb +2 -2
- data/lib/capybara/spec/session/node_spec.rb +29 -9
- data/lib/capybara/spec/session/refresh_spec.rb +2 -1
- data/lib/capybara/spec/session/save_page_spec.rb +4 -4
- data/lib/capybara/spec/session/window/switch_to_window_spec.rb +1 -1
- data/lib/capybara/spec/session/window/window_opened_by_spec.rb +1 -1
- 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/spec_helper.rb +12 -12
- data/lib/capybara/spec/test_app.rb +23 -21
- data/lib/capybara/spec/views/form.erb +28 -1
- data/lib/capybara/spec/views/with_animation.erb +8 -0
- data/lib/capybara/spec/views/with_dragula.erb +3 -1
- data/lib/capybara/spec/views/with_jquery_animation.erb +24 -0
- data/lib/capybara/spec/views/with_js.erb +3 -0
- data/lib/capybara/spec/views/with_sortable_js.erb +1 -1
- data/lib/capybara/version.rb +1 -1
- data/lib/capybara/window.rb +3 -7
- data/spec/basic_node_spec.rb +9 -8
- data/spec/capybara_spec.rb +1 -1
- data/spec/dsl_spec.rb +14 -1
- data/spec/fixtures/selenium_driver_rspec_success.rb +1 -1
- data/spec/minitest_spec.rb +3 -2
- data/spec/rack_test_spec.rb +16 -5
- data/spec/result_spec.rb +1 -17
- data/spec/rspec/features_spec.rb +3 -1
- data/spec/rspec/scenarios_spec.rb +4 -0
- data/spec/rspec/shared_spec_matchers.rb +63 -51
- data/spec/rspec_spec.rb +4 -0
- data/spec/selector_spec.rb +17 -2
- data/spec/selenium_spec_chrome.rb +39 -20
- data/spec/selenium_spec_chrome_remote.rb +5 -1
- data/spec/selenium_spec_firefox.rb +15 -13
- data/spec/server_spec.rb +60 -49
- data/spec/shared_selenium_node.rb +10 -0
- data/spec/shared_selenium_session.rb +98 -7
- data/spec/spec_helper.rb +1 -1
- metadata +50 -15
- data/lib/capybara/spec/session/source_spec.rb +0 -0
@@ -15,6 +15,8 @@ module Capybara
|
|
15
15
|
def add_attribute_conditions(**conditions)
|
16
16
|
@expression = conditions.inject(expression) do |xp, (name, value)|
|
17
17
|
conditions = name == :class ? class_conditions(value) : attribute_conditions(name => value)
|
18
|
+
return xp if conditions.nil?
|
19
|
+
|
18
20
|
if xp.is_a? XPath::Expression
|
19
21
|
xp[conditions]
|
20
22
|
else
|
@@ -47,7 +49,7 @@ module Capybara
|
|
47
49
|
when XPath::Expression, Regexp
|
48
50
|
attribute_conditions(class: classes)
|
49
51
|
else
|
50
|
-
Array(classes).map do |klass|
|
52
|
+
Array(classes).reject { |c| c.is_a? Regexp }.map do |klass|
|
51
53
|
if klass.match?(/^!(?!!!)/)
|
52
54
|
!XPath.attr(:class).contains_word(klass.slice(1..-1))
|
53
55
|
else
|
@@ -10,11 +10,12 @@ 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)
|
16
17
|
@name = name
|
17
|
-
@filter_set = Capybara::Selector::FilterSet.add(name)
|
18
|
+
@filter_set = Capybara::Selector::FilterSet.add(name)
|
18
19
|
@match = nil
|
19
20
|
@label = nil
|
20
21
|
@failure_message = nil
|
@@ -82,7 +83,7 @@ module Capybara
|
|
82
83
|
# Automatic selector detection
|
83
84
|
#
|
84
85
|
# @yield [locator] This block takes the passed in locator string and returns whether or not it matches the selector
|
85
|
-
# @yieldparam [String], locator The locator string used to
|
86
|
+
# @yieldparam [String], locator The locator string used to determine if it matches the selector
|
86
87
|
# @yieldreturn [Boolean] Whether this selector matches the locator string
|
87
88
|
# @return [#call] The block that will be used to detect selector match
|
88
89
|
#
|
@@ -177,7 +178,7 @@ module Capybara
|
|
177
178
|
def_delegator :@filter_set, :describe
|
178
179
|
|
179
180
|
def describe_expression_filters(&block)
|
180
|
-
if
|
181
|
+
if block
|
181
182
|
describe(:expression_filters, &block)
|
182
183
|
else
|
183
184
|
describe(:expression_filters) do |**options|
|
@@ -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
|
@@ -214,7 +215,7 @@ module Capybara
|
|
214
215
|
end
|
215
216
|
|
216
217
|
def default_visibility(fallback = Capybara.ignore_hidden_elements, options = {})
|
217
|
-
vis = if @default_visibility
|
218
|
+
vis = if @default_visibility.respond_to?(:call)
|
218
219
|
@default_visibility.call(options)
|
219
220
|
else
|
220
221
|
@default_visibility
|
@@ -251,14 +252,15 @@ 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
|
|
260
261
|
def parameter_names(block)
|
261
|
-
|
262
|
+
key_types = %i[key keyreq]
|
263
|
+
block.parameters.select { |(type, _name)| key_types.include? type }.map { |(_type, name)| name }
|
262
264
|
end
|
263
265
|
|
264
266
|
def expression(type, allowed_filters, &block)
|
@@ -4,30 +4,30 @@ 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
|
+
aria_btn_xpath = XPath.descendant[XPath.attr(:role).equals('button')]
|
7
8
|
image_btn_xpath = XPath.descendant(:input)[XPath.attr(:type) == 'image']
|
8
9
|
|
9
10
|
unless locator.nil?
|
10
11
|
locator = locator.to_s
|
11
|
-
locator_matchers =
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
locator_matchers |= XPath.attr(:'aria-label').is(locator) if enable_aria_label
|
16
|
-
locator_matchers |= XPath.attr(test_id) == locator if test_id
|
12
|
+
locator_matchers = combine_locators(locator, config: self)
|
13
|
+
btn_matchers = locator_matchers |
|
14
|
+
XPath.string.n.is(locator) |
|
15
|
+
XPath.descendant(:img)[XPath.attr(:alt).is(locator)]
|
17
16
|
|
18
|
-
input_btn_xpath = input_btn_xpath[locator_matchers]
|
19
|
-
|
20
|
-
|
21
|
-
XPath.string.n.is(locator) |
|
22
|
-
XPath.descendant(:img)[XPath.attr(:alt).is(locator)]]
|
17
|
+
input_btn_xpath = input_btn_xpath[locator_matchers] + locate_label(locator).descendant(input_btn_xpath)
|
18
|
+
btn_xpath = btn_xpath[btn_matchers] + locate_label(locator).descendant(btn_xpath)
|
19
|
+
aria_btn_xpath = aria_btn_xpath[btn_matchers]
|
23
20
|
|
24
21
|
alt_matches = XPath.attr(:alt).is(locator)
|
25
22
|
alt_matches |= XPath.attr(:'aria-label').is(locator) if enable_aria_label
|
26
|
-
image_btn_xpath = image_btn_xpath[alt_matches]
|
23
|
+
image_btn_xpath = image_btn_xpath[alt_matches] + locate_label(locator).descendant(image_btn_xpath)
|
27
24
|
end
|
28
25
|
|
29
|
-
|
30
|
-
|
26
|
+
btn_xpaths = [input_btn_xpath, btn_xpath, image_btn_xpath]
|
27
|
+
btn_xpaths << aria_btn_xpath if enable_aria_role
|
28
|
+
|
29
|
+
%i[value title type].inject(btn_xpaths.inject(&:union)) do |memo, ef|
|
30
|
+
memo.where(find_by_attr(ef, options[ef]))
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
@@ -48,4 +48,16 @@ Capybara.add_selector(:button, locator_type: [String, Symbol]) do
|
|
48
48
|
describe_node_filters do |disabled: nil, **|
|
49
49
|
' that is disabled' if disabled == true
|
50
50
|
end
|
51
|
+
|
52
|
+
def combine_locators(locator, config:)
|
53
|
+
[
|
54
|
+
XPath.attr(:id).equals(locator),
|
55
|
+
XPath.attr(:name).equals(locator),
|
56
|
+
XPath.attr(:value).is(locator),
|
57
|
+
XPath.attr(:title).is(locator),
|
58
|
+
(XPath.attr(:id) == XPath.anywhere(:label)[XPath.string.n.is(locator)].attr(:for)),
|
59
|
+
(XPath.attr(:'aria-label').is(locator) if config.enable_aria_label),
|
60
|
+
(XPath.attr(test_id) == locator if config.test_id)
|
61
|
+
].compact.inject(&:|)
|
62
|
+
end
|
51
63
|
end
|
@@ -3,7 +3,7 @@
|
|
3
3
|
Capybara.add_selector(:css, locator_type: [String, Symbol], raw_locator: true) do
|
4
4
|
css do |css|
|
5
5
|
if css.is_a? Symbol
|
6
|
-
warn "DEPRECATED: Passing a symbol (#{css.inspect}) as the CSS locator is deprecated - please pass a string instead."
|
6
|
+
Capybara::Helpers.warn "DEPRECATED: Passing a symbol (#{css.inspect}) as the CSS locator is deprecated - please pass a string instead : #{Capybara::Helpers.filter_backtrace(caller)}"
|
7
7
|
end
|
8
8
|
css
|
9
9
|
end
|
@@ -19,7 +19,7 @@ Capybara.add_selector(:datalist_input, locator_type: [String, Symbol]) do
|
|
19
19
|
|
20
20
|
expression_filter(:with_options) do |expr, options|
|
21
21
|
options.inject(expr) do |xpath, option|
|
22
|
-
xpath
|
22
|
+
xpath.where(XPath.attr(:list) == XPath.anywhere(:datalist)[expression_for(:datalist_option, option)].attr(:id))
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
@@ -18,7 +18,8 @@ Capybara.add_selector(:element, locator_type: [String, Symbol]) do
|
|
18
18
|
end
|
19
19
|
|
20
20
|
describe_expression_filters do |**options|
|
21
|
-
|
21
|
+
boolean_values = [true, false]
|
22
|
+
booleans, values = options.partition { |_k, v| boolean_values.include? v }.map(&:to_h)
|
22
23
|
desc = describe_all_expression_filters(**values)
|
23
24
|
desc + booleans.map do |k, v|
|
24
25
|
v ? " with #{k} attribute" : "without #{k} attribute"
|
@@ -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
|
@@ -53,7 +53,7 @@ Capybara.add_selector(:label, locator_type: [String, Symbol]) do
|
|
53
53
|
end
|
54
54
|
end
|
55
55
|
describe_node_filters do |**options|
|
56
|
-
" for element #{options[:for]}" if options[:for]
|
56
|
+
" for element #{options[:for]}" if options[:for].is_a?(Capybara::Node::Element)
|
57
57
|
end
|
58
58
|
|
59
59
|
def labelable_elements
|
@@ -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
|
|
@@ -33,7 +33,7 @@ Capybara.add_selector(:select, locator_type: [String, Symbol]) do
|
|
33
33
|
|
34
34
|
expression_filter(:with_options) do |expr, options|
|
35
35
|
options.inject(expr) do |xpath, option|
|
36
|
-
xpath
|
36
|
+
xpath.where(expression_for(:option, option))
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
@@ -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(:&)
|
@@ -9,12 +9,12 @@ Capybara.add_selector(:table_row, locator_type: [Array, Hash]) do
|
|
9
9
|
cell_xp = XPath.descendant(:td)[
|
10
10
|
XPath.string.n.is(cell) & XPath.position.equals(header_xp.preceding_sibling.count.plus(1))
|
11
11
|
]
|
12
|
-
xp
|
12
|
+
xp.where(cell_xp)
|
13
13
|
end
|
14
14
|
else
|
15
15
|
initial_td = XPath.descendant(:td)[XPath.string.n.is(locator.shift)]
|
16
16
|
tds = locator.reverse.map { |cell| XPath.following_sibling(:td)[XPath.string.n.is(cell)] }
|
17
|
-
.reduce { |xp, cell| xp
|
17
|
+
.reduce { |xp, cell| xp.where(cell) }
|
18
18
|
xpath[initial_td[tds]]
|
19
19
|
end
|
20
20
|
end
|
@@ -12,7 +12,7 @@ module Capybara
|
|
12
12
|
@node_filters = {}
|
13
13
|
@expression_filters = {}
|
14
14
|
@descriptions = Hash.new { |hsh, key| hsh[key] = [] }
|
15
|
-
instance_eval(&block)
|
15
|
+
instance_eval(&block) if block
|
16
16
|
end
|
17
17
|
|
18
18
|
def node_filter(names, *types, **options, &block)
|
@@ -49,7 +49,7 @@ module Capybara
|
|
49
49
|
end
|
50
50
|
|
51
51
|
def descriptions
|
52
|
-
warn 'DEPRECATED: FilterSet#descriptions is deprecated without replacement'
|
52
|
+
Capybara::Helpers.warn 'DEPRECATED: FilterSet#descriptions is deprecated without replacement'
|
53
53
|
[undeclared_descriptions, node_filter_descriptions, expression_filter_descriptions].flatten
|
54
54
|
end
|
55
55
|
|
@@ -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
|
@@ -128,7 +132,11 @@ module Capybara
|
|
128
132
|
attr_matchers |= XPath.attr(test_id) == locator if test_id
|
129
133
|
|
130
134
|
locate_xpath = locate_xpath[attr_matchers]
|
131
|
-
locate_xpath +
|
135
|
+
locate_xpath + locate_label(locator).descendant(xpath)
|
136
|
+
end
|
137
|
+
|
138
|
+
def locate_label(locator)
|
139
|
+
XPath.descendant(:label)[XPath.string.n.is(locator)]
|
132
140
|
end
|
133
141
|
|
134
142
|
def find_by_attr(attribute, value)
|
@@ -158,7 +158,7 @@
|
|
158
158
|
// the overflow style of the body, and the body is really overflow:visible.
|
159
159
|
var overflowElem = e;
|
160
160
|
if (htmlOverflowStyle == "visible") {
|
161
|
-
//
|
161
|
+
// NOTE: bodyElem will be null/undefined in SVG documents.
|
162
162
|
if (e == htmlElem && bodyElem) {
|
163
163
|
overflowElem = bodyElem;
|
164
164
|
} else if (e == bodyElem) {
|
@@ -12,20 +12,44 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
12
12
|
clear_session_storage: nil
|
13
13
|
}.freeze
|
14
14
|
SPECIAL_OPTIONS = %i[browser clear_local_storage clear_session_storage timeout native_displayed].freeze
|
15
|
+
CAPS_VERSION = Gem::Requirement.new('~> 4.0.0.alpha6')
|
16
|
+
|
15
17
|
attr_reader :app, :options
|
16
18
|
|
17
19
|
class << self
|
20
|
+
attr_reader :selenium_webdriver_version
|
21
|
+
|
18
22
|
def load_selenium
|
19
23
|
require 'selenium-webdriver'
|
20
24
|
require 'capybara/selenium/logger_suppressor'
|
21
25
|
require 'capybara/selenium/patches/atoms'
|
22
26
|
require 'capybara/selenium/patches/is_displayed'
|
23
27
|
require 'capybara/selenium/patches/action_pauser'
|
24
|
-
|
28
|
+
|
29
|
+
# Look up the version of `selenium-webdriver` to
|
30
|
+
# see if it's a version we support.
|
31
|
+
#
|
32
|
+
# By default, we use Gem.loaded_specs to determine
|
33
|
+
# the version number. However, in some cases, such
|
34
|
+
# as when loading `selenium-webdriver` outside of
|
35
|
+
# Rubygems, we fall back to referencing
|
36
|
+
# Selenium::WebDriver::VERSION. Ideally we'd
|
37
|
+
# use the constant in all cases, but earlier versions
|
38
|
+
# of `selenium-webdriver` didn't provide the constant.
|
39
|
+
@selenium_webdriver_version =
|
40
|
+
if Gem.loaded_specs['selenium-webdriver']
|
41
|
+
Gem.loaded_specs['selenium-webdriver'].version
|
42
|
+
else
|
43
|
+
Gem::Version.new(Selenium::WebDriver::VERSION)
|
44
|
+
end
|
45
|
+
|
46
|
+
unless Gem::Requirement.new('>= 3.5.0').satisfied_by? @selenium_webdriver_version
|
25
47
|
warn "Warning: You're using an unsupported version of selenium-webdriver, please upgrade."
|
26
48
|
end
|
49
|
+
|
50
|
+
@selenium_webdriver_version
|
27
51
|
rescue LoadError => e
|
28
|
-
raise e unless e.message.
|
52
|
+
raise e unless e.message.include?('selenium-webdriver')
|
29
53
|
|
30
54
|
raise LoadError, "Capybara's selenium driver is unable to load `selenium-webdriver`, please install the gem and add `gem 'selenium-webdriver'` to your Gemfile if you are using bundler."
|
31
55
|
end
|
@@ -49,7 +73,15 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
49
73
|
end
|
50
74
|
end
|
51
75
|
processed_options = options.reject { |key, _val| SPECIAL_OPTIONS.include?(key) }
|
52
|
-
|
76
|
+
|
77
|
+
@browser = if options[:browser] == :firefox &&
|
78
|
+
RUBY_VERSION >= '3.0' &&
|
79
|
+
Capybara::Selenium::Driver.selenium_webdriver_version <= Gem::Version.new('4.0.0.alpha1')
|
80
|
+
# selenium-webdriver 3.x doesn't correctly pass options through for Firefox with Ruby 3 so workaround that
|
81
|
+
Selenium::WebDriver::Firefox::Driver.new(**processed_options)
|
82
|
+
else
|
83
|
+
Selenium::WebDriver.for(options[:browser], processed_options)
|
84
|
+
end
|
53
85
|
|
54
86
|
specialize_driver
|
55
87
|
setup_exit_handler
|
@@ -58,6 +90,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
58
90
|
end
|
59
91
|
|
60
92
|
def initialize(app, **options)
|
93
|
+
super()
|
61
94
|
self.class.load_selenium
|
62
95
|
@app = app
|
63
96
|
@browser = nil
|
@@ -85,6 +118,8 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
85
118
|
|
86
119
|
def html
|
87
120
|
browser.page_source
|
121
|
+
rescue Selenium::WebDriver::Error::JavascriptError => e
|
122
|
+
raise unless e.message.include?('documentElement is null')
|
88
123
|
end
|
89
124
|
|
90
125
|
def title
|
@@ -113,6 +148,10 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
113
148
|
unwrap_script_result(result)
|
114
149
|
end
|
115
150
|
|
151
|
+
def send_keys(*args)
|
152
|
+
active_element.send_keys(*args)
|
153
|
+
end
|
154
|
+
|
116
155
|
def save_screenshot(path, **_options)
|
117
156
|
browser.save_screenshot(path)
|
118
157
|
end
|
@@ -241,7 +280,8 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
241
280
|
|
242
281
|
def quit
|
243
282
|
@browser&.quit
|
244
|
-
rescue Selenium::WebDriver::Error::SessionNotCreatedError, Errno::ECONNREFUSED
|
283
|
+
rescue Selenium::WebDriver::Error::SessionNotCreatedError, Errno::ECONNREFUSED,
|
284
|
+
Selenium::WebDriver::Error::InvalidSessionIdError
|
245
285
|
# Browser must have already gone
|
246
286
|
rescue Selenium::WebDriver::Error::UnknownError => e
|
247
287
|
unless silenced_unknown_error_message?(e.message) # Most likely already gone
|
@@ -293,7 +333,7 @@ private
|
|
293
333
|
def clear_browser_state
|
294
334
|
delete_all_cookies
|
295
335
|
clear_storage
|
296
|
-
rescue *clear_browser_state_errors
|
336
|
+
rescue *clear_browser_state_errors
|
297
337
|
# delete_all_cookies fails when we've previously gone
|
298
338
|
# to about:blank, so we rescue this error and do nothing
|
299
339
|
# instead.
|
@@ -317,7 +357,7 @@ private
|
|
317
357
|
def clear_storage
|
318
358
|
clear_session_storage unless options[:clear_session_storage] == false
|
319
359
|
clear_local_storage unless options[:clear_local_storage] == false
|
320
|
-
rescue Selenium::WebDriver::Error::JavascriptError
|
360
|
+
rescue Selenium::WebDriver::Error::JavascriptError
|
321
361
|
# session/local storage may not be available if on non-http pages (e.g. about:blank)
|
322
362
|
end
|
323
363
|
|
@@ -353,7 +393,7 @@ private
|
|
353
393
|
@browser.navigate.to(url)
|
354
394
|
sleep 0.1 # slight wait for alert
|
355
395
|
@browser.switch_to.alert.accept
|
356
|
-
rescue modal_error
|
396
|
+
rescue modal_error
|
357
397
|
# alert now gone, should mean navigation happened
|
358
398
|
end
|
359
399
|
|
@@ -435,6 +475,10 @@ private
|
|
435
475
|
browser
|
436
476
|
end
|
437
477
|
|
478
|
+
def active_element
|
479
|
+
browser.switch_to.active_element
|
480
|
+
end
|
481
|
+
|
438
482
|
def build_node(native_node, initial_cache = {})
|
439
483
|
::Capybara::Selenium::Node.new(self, native_node, initial_cache)
|
440
484
|
end
|
@@ -7,27 +7,25 @@ module Capybara::Selenium::Driver::ChromeDriver
|
|
7
7
|
def self.extended(base)
|
8
8
|
bridge = base.send(:bridge)
|
9
9
|
bridge.extend Capybara::Selenium::ChromeLogs unless bridge.respond_to?(:log)
|
10
|
-
bridge.extend Capybara::Selenium::IsDisplayed unless bridge.
|
10
|
+
bridge.extend Capybara::Selenium::IsDisplayed unless bridge.send(:commands, :is_element_displayed)
|
11
11
|
base.options[:native_displayed] = false if base.options[:native_displayed].nil?
|
12
12
|
end
|
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.include?('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
|
|
27
25
|
def resize_window_to(handle, width, height)
|
28
26
|
super
|
29
27
|
rescue Selenium::WebDriver::Error::UnknownError => e
|
30
|
-
raise unless e.message.
|
28
|
+
raise unless e.message.include?('failed to change window state')
|
31
29
|
|
32
30
|
# Chromedriver doesn't wait long enough for state to change when coming out of fullscreen
|
33
31
|
# and raises unnecessary error. Wait a bit and try again.
|
@@ -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?
|