capybara 2.15.0 → 3.0.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 +5 -5
- data/History.md +137 -2
- data/README.md +36 -25
- data/lib/capybara/config.rb +11 -57
- data/lib/capybara/cucumber.rb +2 -3
- data/lib/capybara/driver/base.rb +19 -16
- data/lib/capybara/driver/node.rb +5 -4
- data/lib/capybara/dsl.rb +1 -0
- data/lib/capybara/helpers.rb +19 -29
- data/lib/capybara/minitest/spec.rb +16 -13
- data/lib/capybara/minitest.rb +140 -137
- data/lib/capybara/node/actions.rb +68 -89
- data/lib/capybara/node/base.rb +11 -18
- data/lib/capybara/node/document.rb +2 -2
- data/lib/capybara/node/document_matchers.rb +8 -8
- data/lib/capybara/node/element.rb +32 -42
- data/lib/capybara/node/finders.rb +64 -71
- data/lib/capybara/node/matchers.rb +50 -71
- data/lib/capybara/node/simple.rb +11 -17
- data/lib/capybara/queries/ancestor_query.rb +12 -8
- data/lib/capybara/queries/base_query.rb +22 -18
- data/lib/capybara/queries/current_path_query.rb +12 -25
- data/lib/capybara/queries/match_query.rb +3 -7
- data/lib/capybara/queries/selector_query.rb +100 -96
- data/lib/capybara/queries/sibling_query.rb +5 -5
- data/lib/capybara/queries/text_query.rb +35 -35
- data/lib/capybara/queries/title_query.rb +8 -11
- data/lib/capybara/rack_test/browser.rb +15 -18
- data/lib/capybara/rack_test/css_handlers.rb +6 -4
- data/lib/capybara/rack_test/driver.rb +6 -10
- data/lib/capybara/rack_test/form.rb +52 -39
- data/lib/capybara/rack_test/node.rb +93 -63
- data/lib/capybara/rails.rb +2 -6
- data/lib/capybara/result.rb +22 -22
- data/lib/capybara/rspec/compound.rb +5 -10
- data/lib/capybara/rspec/features.rb +17 -48
- data/lib/capybara/rspec/matcher_proxies.rb +31 -15
- data/lib/capybara/rspec/matchers.rb +116 -58
- data/lib/capybara/rspec.rb +5 -10
- data/lib/capybara/selector/css.rb +6 -11
- data/lib/capybara/selector/filter.rb +1 -17
- data/lib/capybara/selector/filter_set.rb +18 -15
- data/lib/capybara/selector/filters/base.rb +7 -6
- data/lib/capybara/selector/filters/expression_filter.rb +6 -23
- data/lib/capybara/selector/filters/node_filter.rb +2 -12
- data/lib/capybara/selector/selector.rb +28 -34
- data/lib/capybara/selector.rb +129 -117
- data/lib/capybara/selenium/driver.rb +172 -163
- data/lib/capybara/selenium/node.rb +218 -104
- data/lib/capybara/server.rb +3 -2
- data/lib/capybara/session/config.rb +47 -59
- data/lib/capybara/session/matchers.rb +23 -14
- data/lib/capybara/session.rb +175 -229
- data/lib/capybara/spec/fixtures/no_extension +1 -0
- data/lib/capybara/spec/public/test.js +38 -6
- data/lib/capybara/spec/session/accept_alert_spec.rb +1 -0
- data/lib/capybara/spec/session/accept_confirm_spec.rb +3 -2
- data/lib/capybara/spec/session/accept_prompt_spec.rb +30 -1
- data/lib/capybara/spec/session/all_spec.rb +31 -18
- data/lib/capybara/spec/session/ancestor_spec.rb +6 -8
- data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +6 -5
- data/lib/capybara/spec/session/assert_current_path.rb +12 -11
- data/lib/capybara/spec/session/assert_selector.rb +1 -0
- data/lib/capybara/spec/session/assert_text.rb +31 -23
- data/lib/capybara/spec/session/assert_title.rb +13 -3
- data/lib/capybara/spec/session/attach_file_spec.rb +57 -29
- data/lib/capybara/spec/session/body_spec.rb +1 -0
- data/lib/capybara/spec/session/check_spec.rb +7 -6
- data/lib/capybara/spec/session/choose_spec.rb +5 -4
- data/lib/capybara/spec/session/click_button_spec.rb +24 -32
- data/lib/capybara/spec/session/click_link_or_button_spec.rb +8 -7
- data/lib/capybara/spec/session/click_link_spec.rb +8 -7
- data/lib/capybara/spec/session/current_scope_spec.rb +4 -3
- data/lib/capybara/spec/session/current_url_spec.rb +19 -8
- data/lib/capybara/spec/session/dismiss_confirm_spec.rb +1 -1
- data/lib/capybara/spec/session/dismiss_prompt_spec.rb +1 -0
- data/lib/capybara/spec/session/element/assert_match_selector.rb +1 -1
- data/lib/capybara/spec/session/element/match_xpath_spec.rb +1 -1
- data/lib/capybara/spec/session/element/matches_selector_spec.rb +5 -5
- data/lib/capybara/spec/session/evaluate_async_script_spec.rb +23 -0
- data/lib/capybara/spec/session/evaluate_script_spec.rb +5 -4
- data/lib/capybara/spec/session/execute_script_spec.rb +4 -3
- data/lib/capybara/spec/session/fill_in_spec.rb +30 -5
- data/lib/capybara/spec/session/find_button_spec.rb +4 -3
- data/lib/capybara/spec/session/find_by_id_spec.rb +2 -1
- data/lib/capybara/spec/session/find_field_spec.rb +9 -15
- data/lib/capybara/spec/session/find_link_spec.rb +6 -5
- data/lib/capybara/spec/session/find_spec.rb +37 -31
- data/lib/capybara/spec/session/first_spec.rb +60 -33
- data/lib/capybara/spec/session/frame/frame_title_spec.rb +23 -0
- data/lib/capybara/spec/session/frame/frame_url_spec.rb +23 -0
- data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +2 -1
- data/lib/capybara/spec/session/frame/within_frame_spec.rb +9 -16
- data/lib/capybara/spec/session/go_back_spec.rb +1 -0
- data/lib/capybara/spec/session/go_forward_spec.rb +1 -0
- data/lib/capybara/spec/session/has_all_selectors_spec.rb +69 -0
- data/lib/capybara/spec/session/has_button_spec.rb +2 -1
- data/lib/capybara/spec/session/has_css_spec.rb +3 -2
- data/lib/capybara/spec/session/has_current_path_spec.rb +49 -22
- data/lib/capybara/spec/session/has_field_spec.rb +4 -3
- data/lib/capybara/spec/session/has_link_spec.rb +5 -4
- data/lib/capybara/spec/session/has_none_selectors_spec.rb +76 -0
- data/lib/capybara/spec/session/has_select_spec.rb +32 -31
- data/lib/capybara/spec/session/has_selector_spec.rb +5 -4
- data/lib/capybara/spec/session/has_table_spec.rb +2 -1
- data/lib/capybara/spec/session/has_text_spec.rb +9 -13
- data/lib/capybara/spec/session/has_title_spec.rb +1 -0
- data/lib/capybara/spec/session/has_xpath_spec.rb +1 -0
- data/lib/capybara/spec/session/headers.rb +2 -1
- data/lib/capybara/spec/session/html_spec.rb +1 -0
- data/lib/capybara/spec/session/node_spec.rb +107 -58
- data/lib/capybara/spec/session/node_wrapper_spec.rb +36 -0
- data/lib/capybara/spec/session/refresh_spec.rb +6 -2
- data/lib/capybara/spec/session/reset_session_spec.rb +19 -0
- data/lib/capybara/spec/session/response_code.rb +1 -0
- data/lib/capybara/spec/session/save_and_open_page_spec.rb +1 -0
- data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +6 -11
- data/lib/capybara/spec/session/save_page_spec.rb +1 -17
- data/lib/capybara/spec/session/save_screenshot_spec.rb +3 -3
- data/lib/capybara/spec/session/select_spec.rb +21 -20
- data/lib/capybara/spec/session/selectors_spec.rb +2 -2
- data/lib/capybara/spec/session/sibling_spec.rb +1 -1
- data/lib/capybara/spec/session/text_spec.rb +17 -3
- data/lib/capybara/spec/session/title_spec.rb +11 -1
- data/lib/capybara/spec/session/uncheck_spec.rb +4 -3
- data/lib/capybara/spec/session/unselect_spec.rb +7 -6
- data/lib/capybara/spec/session/visit_spec.rb +64 -3
- data/lib/capybara/spec/session/window/become_closed_spec.rb +2 -1
- data/lib/capybara/spec/session/window/current_window_spec.rb +1 -0
- data/lib/capybara/spec/session/window/open_new_window_spec.rb +1 -0
- data/lib/capybara/spec/session/window/switch_to_window_spec.rb +2 -1
- data/lib/capybara/spec/session/window/window_opened_by_spec.rb +2 -1
- data/lib/capybara/spec/session/window/window_spec.rb +12 -12
- data/lib/capybara/spec/session/window/windows_spec.rb +2 -3
- data/lib/capybara/spec/session/window/within_window_spec.rb +15 -71
- data/lib/capybara/spec/session/within_spec.rb +1 -0
- data/lib/capybara/spec/spec_helper.rb +36 -18
- data/lib/capybara/spec/test_app.rb +17 -9
- data/lib/capybara/spec/views/form.erb +7 -0
- data/lib/capybara/spec/views/initial_alert.erb +10 -0
- data/lib/capybara/spec/views/with_fixed_header_footer.erb +17 -0
- data/lib/capybara/spec/views/with_hover.erb +5 -0
- data/lib/capybara/spec/views/with_html.erb +27 -1
- data/lib/capybara/spec/views/with_js.erb +11 -0
- data/lib/capybara/spec/views/within_frames.erb +4 -1
- data/lib/capybara/version.rb +2 -1
- data/lib/capybara/window.rb +6 -10
- data/lib/capybara.rb +29 -26
- data/spec/basic_node_spec.rb +1 -0
- data/spec/capybara_spec.rb +16 -69
- data/spec/dsl_spec.rb +5 -13
- data/spec/filter_set_spec.rb +5 -4
- data/spec/fixtures/selenium_driver_rspec_failure.rb +2 -1
- data/spec/fixtures/selenium_driver_rspec_success.rb +3 -2
- data/spec/minitest_spec.rb +13 -4
- data/spec/minitest_spec_spec.rb +12 -3
- data/spec/per_session_config_spec.rb +9 -8
- data/spec/rack_test_spec.rb +21 -20
- data/spec/result_spec.rb +17 -16
- data/spec/rspec/features_spec.rb +17 -14
- data/spec/rspec/scenarios_spec.rb +5 -7
- data/spec/rspec/shared_spec_matchers.rb +96 -99
- data/spec/rspec/views_spec.rb +2 -1
- data/spec/rspec_matchers_spec.rb +18 -2
- data/spec/rspec_spec.rb +11 -15
- data/spec/selector_spec.rb +5 -6
- data/spec/selenium_spec_chrome.rb +20 -11
- data/spec/selenium_spec_edge.rb +27 -0
- data/spec/selenium_spec_ie.rb +31 -0
- data/spec/selenium_spec_marionette.rb +38 -12
- data/spec/server_spec.rb +33 -33
- data/spec/session_spec.rb +2 -1
- data/spec/shared_selenium_session.rb +82 -22
- data/spec/spec_helper.rb +3 -6
- metadata +76 -81
- data/lib/capybara/query.rb +0 -7
- data/spec/selenium_spec_firefox.rb +0 -68
data/lib/capybara/node/simple.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Capybara
|
3
4
|
module Node
|
4
|
-
|
5
5
|
##
|
6
6
|
#
|
7
7
|
# A {Capybara::Node::Simple} is a simpler version of {Capybara::Node::Base} which
|
@@ -28,7 +28,7 @@ module Capybara
|
|
28
28
|
#
|
29
29
|
# @return [String] The text of the element
|
30
30
|
#
|
31
|
-
def text(
|
31
|
+
def text(_type = nil)
|
32
32
|
native.text
|
33
33
|
end
|
34
34
|
|
@@ -45,7 +45,7 @@ module Capybara
|
|
45
45
|
attr_name = name.to_s
|
46
46
|
if attr_name == 'value'
|
47
47
|
value
|
48
|
-
elsif
|
48
|
+
elsif tag_name == 'input' and native[:type] == 'checkbox' and attr_name == 'checked'
|
49
49
|
native['checked'] == 'checked'
|
50
50
|
else
|
51
51
|
native[attr_name]
|
@@ -79,12 +79,12 @@ module Capybara
|
|
79
79
|
native['_capybara_raw_value']
|
80
80
|
elsif tag_name == 'select'
|
81
81
|
if native['multiple'] == 'multiple'
|
82
|
-
native.xpath(".//option[@selected='selected']").map { |option| option[:value] || option.content
|
82
|
+
native.xpath(".//option[@selected='selected']").map { |option| option[:value] || option.content }
|
83
83
|
else
|
84
84
|
option = native.xpath(".//option[@selected='selected']").first || native.xpath(".//option").first
|
85
85
|
option[:value] || option.content if option
|
86
86
|
end
|
87
|
-
elsif tag_name == 'input' && %w
|
87
|
+
elsif tag_name == 'input' && %w[radio checkbox].include?(native[:type])
|
88
88
|
native[:value] || 'on'
|
89
89
|
else
|
90
90
|
native[:value]
|
@@ -100,14 +100,13 @@ module Capybara
|
|
100
100
|
# @return [Boolean] Whether the element is visible
|
101
101
|
#
|
102
102
|
def visible?(check_ancestors = true)
|
103
|
-
return false if (tag_name == 'input') && (native[:type]=="hidden")
|
103
|
+
return false if (tag_name == 'input') && (native[:type] == "hidden")
|
104
104
|
|
105
105
|
if check_ancestors
|
106
|
-
|
107
|
-
native.xpath("./ancestor-or-self::*[contains(@style, 'display:none') or contains(@style, 'display: none') or @hidden or name()='script' or name()='head']").size() == 0
|
106
|
+
!native.xpath("boolean(./ancestor-or-self::*[contains(@style, 'display:none') or contains(@style, 'display: none') or @hidden or name()='script' or name()='head'])")
|
108
107
|
else
|
109
|
-
#
|
110
|
-
!(native.has_attribute?('hidden') || (native[:style] =~ /display:\s?none/) || %w
|
108
|
+
# No need for an xpath if only checking the current element
|
109
|
+
!(native.has_attribute?('hidden') || (native[:style] =~ /display:\s?none/) || %w[script head].include?(tag_name))
|
111
110
|
end
|
112
111
|
end
|
113
112
|
|
@@ -140,7 +139,7 @@ module Capybara
|
|
140
139
|
native.has_attribute?('selected')
|
141
140
|
end
|
142
141
|
|
143
|
-
def synchronize(
|
142
|
+
def synchronize(_seconds = nil)
|
144
143
|
yield # simple nodes don't need to wait
|
145
144
|
end
|
146
145
|
|
@@ -152,12 +151,7 @@ module Capybara
|
|
152
151
|
#
|
153
152
|
# @return [String] The title of the document
|
154
153
|
def title
|
155
|
-
|
156
|
-
native.title
|
157
|
-
else
|
158
|
-
#old versions of nokogiri don't have #title - remove in 3.0
|
159
|
-
native.xpath('/html/head/title | /html/title').first.text
|
160
|
-
end
|
154
|
+
native.title
|
161
155
|
end
|
162
156
|
|
163
157
|
def inspect
|
@@ -1,25 +1,29 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Capybara
|
3
4
|
module Queries
|
4
|
-
class AncestorQuery <
|
5
|
+
class AncestorQuery < Capybara::Queries::SelectorQuery
|
5
6
|
# @api private
|
6
7
|
def resolve_for(node, exact = nil)
|
7
|
-
@
|
8
|
+
@child_node = node
|
8
9
|
node.synchronize do
|
9
10
|
match_results = super(node.session.current_scope, exact)
|
10
|
-
node.all(:xpath, XPath.ancestor)
|
11
|
-
match_results.include?(el)
|
12
|
-
end
|
11
|
+
node.all(:xpath, XPath.ancestor) { |el| match_results.include?(el) }
|
13
12
|
end
|
14
13
|
end
|
15
14
|
|
16
15
|
def description
|
16
|
+
child_query = @child_node && @child_node.instance_variable_get(:@query)
|
17
17
|
desc = super
|
18
|
-
|
19
|
-
desc += " that is an ancestor of #{child_query.description}"
|
20
|
-
end
|
18
|
+
desc += " that is an ancestor of #{child_query.description}" if child_query
|
21
19
|
desc
|
22
20
|
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def valid_keys
|
25
|
+
super - COUNT_KEYS
|
26
|
+
end
|
23
27
|
end
|
24
28
|
end
|
25
29
|
end
|
@@ -1,9 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Capybara
|
3
4
|
# @api private
|
4
5
|
module Queries
|
5
6
|
class BaseQuery
|
6
|
-
COUNT_KEYS = [
|
7
|
+
COUNT_KEYS = %i[count minimum maximum between].freeze
|
7
8
|
|
8
9
|
attr_reader :options
|
9
10
|
attr_writer :session_options
|
@@ -20,8 +21,11 @@ module Capybara
|
|
20
21
|
self.class.wait(options, session_options.default_max_wait_time)
|
21
22
|
end
|
22
23
|
|
23
|
-
def self.wait(options, default=Capybara.default_max_wait_time)
|
24
|
-
|
24
|
+
def self.wait(options, default = Capybara.default_max_wait_time)
|
25
|
+
# if no value or nil for the :wait option is passed it should default to the default
|
26
|
+
w = options.fetch(:wait, nil)
|
27
|
+
w = default if w.nil?
|
28
|
+
w || 0
|
25
29
|
end
|
26
30
|
|
27
31
|
##
|
@@ -30,11 +34,7 @@ module Capybara
|
|
30
34
|
# Returns false if query does not have any count options specified.
|
31
35
|
#
|
32
36
|
def expects_none?
|
33
|
-
|
34
|
-
matches_count?(0)
|
35
|
-
else
|
36
|
-
false
|
37
|
-
end
|
37
|
+
count_specified? ? matches_count?(0) : false
|
38
38
|
end
|
39
39
|
|
40
40
|
##
|
@@ -47,11 +47,11 @@ module Capybara
|
|
47
47
|
# @param [Integer] count The actual number. Should be coercible via Integer()
|
48
48
|
#
|
49
49
|
def matches_count?(count)
|
50
|
-
return (Integer(options[:count]) == count)
|
50
|
+
return (Integer(options[:count]) == count) if options[:count]
|
51
51
|
return false if options[:maximum] && (Integer(options[:maximum]) < count)
|
52
52
|
return false if options[:minimum] && (Integer(options[:minimum]) > count)
|
53
|
-
return false if options[:between] && !
|
54
|
-
|
53
|
+
return false if options[:between] && !options[:between].include?(count)
|
54
|
+
true
|
55
55
|
end
|
56
56
|
|
57
57
|
##
|
@@ -66,10 +66,14 @@ module Capybara
|
|
66
66
|
String.new("expected not to find #{description}") << count_message
|
67
67
|
end
|
68
68
|
|
69
|
-
|
69
|
+
private
|
70
|
+
|
71
|
+
def count_specified?
|
72
|
+
COUNT_KEYS.any? { |k| options.key? k }
|
73
|
+
end
|
70
74
|
|
71
75
|
def count_message
|
72
|
-
message =
|
76
|
+
message = "".dup
|
73
77
|
if options[:count]
|
74
78
|
message << " #{options[:count]} #{Capybara::Helpers.declension('time', 'times', options[:count])}"
|
75
79
|
elsif options[:between]
|
@@ -84,11 +88,11 @@ module Capybara
|
|
84
88
|
|
85
89
|
def assert_valid_keys
|
86
90
|
invalid_keys = @options.keys - valid_keys
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
91
|
+
return if invalid_keys.empty?
|
92
|
+
|
93
|
+
invalid_names = invalid_keys.map(&:inspect).join(", ")
|
94
|
+
valid_names = valid_keys.map(&:inspect).join(", ")
|
95
|
+
raise ArgumentError, "invalid keys #{invalid_names}, should be one of #{valid_names}"
|
92
96
|
end
|
93
97
|
end
|
94
98
|
end
|
@@ -1,34 +1,28 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'addressable/uri'
|
3
4
|
|
4
5
|
module Capybara
|
5
6
|
# @api private
|
6
7
|
module Queries
|
7
8
|
class CurrentPathQuery < BaseQuery
|
8
|
-
def initialize(expected_path, options
|
9
|
+
def initialize(expected_path, **options)
|
9
10
|
super(options)
|
10
11
|
@expected_path = expected_path
|
11
12
|
@options = {
|
12
|
-
url:
|
13
|
-
|
13
|
+
url: !@expected_path.is_a?(Regexp) && !::Addressable::URI.parse(@expected_path || "").hostname.nil?,
|
14
|
+
ignore_query: false
|
15
|
+
}.merge(options)
|
14
16
|
assert_valid_keys
|
15
17
|
end
|
16
18
|
|
17
19
|
def resolves_for?(session)
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
uri = ::Addressable::URI.parse(session.current_url)
|
22
|
-
|
23
|
-
if options[:only_path]
|
24
|
-
uri.path unless uri.nil? # Ensure the parsed url isn't nil.
|
25
|
-
else
|
26
|
-
uri.request_uri unless uri.nil? # Ensure the parsed url isn't nil.
|
27
|
-
end
|
28
|
-
end
|
20
|
+
uri = ::Addressable::URI.parse(session.current_url)
|
21
|
+
uri.query = nil if uri && options[:ignore_query]
|
22
|
+
@actual_path = options[:url] ? uri.to_s : uri && uri.request_uri
|
29
23
|
|
30
24
|
if @expected_path.is_a? Regexp
|
31
|
-
@actual_path.match(@expected_path)
|
25
|
+
@actual_path.to_s.match(@expected_path)
|
32
26
|
else
|
33
27
|
::Addressable::URI.parse(@expected_path) == ::Addressable::URI.parse(@actual_path)
|
34
28
|
end
|
@@ -42,22 +36,15 @@ module Capybara
|
|
42
36
|
failure_message_helper(' not')
|
43
37
|
end
|
44
38
|
|
45
|
-
|
39
|
+
private
|
46
40
|
|
47
41
|
def failure_message_helper(negated = '')
|
48
|
-
verb =
|
42
|
+
verb = @expected_path.is_a?(Regexp) ? 'match' : 'equal'
|
49
43
|
"expected #{@actual_path.inspect}#{negated} to #{verb} #{@expected_path.inspect}"
|
50
44
|
end
|
51
45
|
|
52
46
|
def valid_keys
|
53
|
-
[
|
54
|
-
end
|
55
|
-
|
56
|
-
def assert_valid_keys
|
57
|
-
super
|
58
|
-
if options[:url] && options[:only_path]
|
59
|
-
raise ArgumentError, "the :url and :only_path options cannot both be true"
|
60
|
-
end
|
47
|
+
%i[wait url ignore_query]
|
61
48
|
end
|
62
49
|
end
|
63
50
|
end
|
@@ -2,18 +2,14 @@ module Capybara
|
|
2
2
|
module Queries
|
3
3
|
class MatchQuery < Capybara::Queries::SelectorQuery
|
4
4
|
def visible
|
5
|
-
|
6
|
-
super
|
7
|
-
else
|
8
|
-
:all
|
9
|
-
end
|
5
|
+
options.key?(:visible) ? super : :all
|
10
6
|
end
|
11
7
|
|
12
|
-
|
8
|
+
private
|
13
9
|
|
14
10
|
def valid_keys
|
15
11
|
super - COUNT_KEYS
|
16
12
|
end
|
17
13
|
end
|
18
14
|
end
|
19
|
-
end
|
15
|
+
end
|
@@ -1,36 +1,24 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Capybara
|
3
4
|
module Queries
|
4
5
|
class SelectorQuery < Queries::BaseQuery
|
5
6
|
attr_accessor :selector, :locator, :options, :expression, :find, :negative
|
6
7
|
|
7
|
-
VALID_KEYS = COUNT_KEYS + [
|
8
|
-
VALID_MATCH = [
|
8
|
+
VALID_KEYS = COUNT_KEYS + %i[text id class visible exact exact_text match wait filter_set]
|
9
|
+
VALID_MATCH = %i[first smart prefer_exact one].freeze
|
9
10
|
|
10
|
-
def initialize(*args, &filter_block)
|
11
|
-
@
|
11
|
+
def initialize(*args, session_options:, **options, &filter_block)
|
12
|
+
@resolved_node = nil
|
13
|
+
@options = options.dup
|
12
14
|
super(@options)
|
15
|
+
self.session_options = session_options
|
13
16
|
|
17
|
+
@selector = find_selector(args[0].is_a?(Symbol) ? args.shift : args[0])
|
18
|
+
@locator = args.shift
|
14
19
|
@filter_block = filter_block
|
15
20
|
|
16
|
-
|
17
|
-
@selector = Selector.all.fetch(args.shift) do |selector_type|
|
18
|
-
raise ArgumentError, "Unknown selector type (:#{selector_type})"
|
19
|
-
nil
|
20
|
-
end
|
21
|
-
@locator = args.shift
|
22
|
-
else
|
23
|
-
@selector = Selector.all.values.find { |s| s.match?(args[0]) }
|
24
|
-
@locator = args.shift
|
25
|
-
end
|
26
|
-
@selector ||= Selector.all[session_options.default_selector]
|
27
|
-
|
28
|
-
warn "Unused parameters passed to #{self.class.name} : #{args.to_s}" unless args.empty?
|
29
|
-
|
30
|
-
# for compatibility with Capybara 2.0
|
31
|
-
if session_options.exact_options and @selector == Selector.all[:option]
|
32
|
-
@options[:exact] = true
|
33
|
-
end
|
21
|
+
raise ArgumentError, "Unused parameters passed to #{self.class.name} : #{args}" unless args.empty?
|
34
22
|
|
35
23
|
@expression = @selector.call(@locator, @options.merge(enable_aria_label: session_options.enable_aria_label))
|
36
24
|
|
@@ -40,10 +28,10 @@ module Capybara
|
|
40
28
|
end
|
41
29
|
|
42
30
|
def name; selector.name; end
|
43
|
-
def label; selector.label
|
31
|
+
def label; selector.label || selector.name; end
|
44
32
|
|
45
33
|
def description
|
46
|
-
@description =
|
34
|
+
@description = "".dup
|
47
35
|
@description << "visible " if visible == :visible
|
48
36
|
@description << "non-visible " if visible == :hidden
|
49
37
|
@description << "#{label} #{locator.inspect}"
|
@@ -53,84 +41,44 @@ module Capybara
|
|
53
41
|
@description << " with classes [#{Array(options[:class]).join(',')}]" if options[:class]
|
54
42
|
@description << selector.description(options)
|
55
43
|
@description << " that also matches the custom filter block" if @filter_block
|
44
|
+
@description << " within #{@resolved_node.inspect}" if describe_within?
|
56
45
|
@description
|
57
46
|
end
|
58
47
|
|
59
48
|
def matches_filters?(node)
|
60
|
-
if options[:text]
|
61
|
-
|
62
|
-
options[:text]
|
63
|
-
else
|
64
|
-
if exact_text == true
|
65
|
-
/\A#{Regexp.escape(options[:text].to_s)}\z/
|
66
|
-
else
|
67
|
-
Regexp.escape(options[:text].to_s)
|
68
|
-
end
|
69
|
-
end
|
70
|
-
text_visible = visible
|
71
|
-
text_visible = :all if text_visible == :hidden
|
72
|
-
return false if not node.text(text_visible).match(regexp)
|
73
|
-
end
|
74
|
-
|
75
|
-
if exact_text.is_a?(String)
|
76
|
-
regexp = /\A#{Regexp.escape(options[:exact_text])}\z/
|
77
|
-
text_visible = visible
|
78
|
-
text_visible = :all if text_visible == :hidden
|
79
|
-
return false if not node.text(text_visible).match(regexp)
|
80
|
-
end
|
49
|
+
return false if options[:text] && !matches_text_filter(node, options[:text])
|
50
|
+
return false if exact_text.is_a?(String) && !matches_exact_text_filter(node, exact_text)
|
81
51
|
|
82
52
|
case visible
|
83
|
-
|
84
|
-
|
53
|
+
when :visible then return false unless node.visible?
|
54
|
+
when :hidden then return false if node.visible?
|
85
55
|
end
|
86
56
|
|
87
|
-
|
88
|
-
if options.has_key?(name)
|
89
|
-
filter.matches?(node, options[name])
|
90
|
-
elsif filter.default?
|
91
|
-
filter.matches?(node, filter.default)
|
92
|
-
else
|
93
|
-
true
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
res &&= if node.respond_to?(:session)
|
98
|
-
node.session.using_wait_time(0){ @filter_block.call(node) }
|
99
|
-
else
|
100
|
-
@filter_block.call(node)
|
101
|
-
end unless @filter_block.nil?
|
102
|
-
|
103
|
-
res
|
104
|
-
|
57
|
+
matches_node_filters?(node) && matches_filter_block?(node)
|
105
58
|
rescue *(node.respond_to?(:session) ? node.session.driver.invalid_element_errors : [])
|
106
59
|
return false
|
107
60
|
end
|
108
61
|
|
109
62
|
def visible
|
110
|
-
case (vis = options.fetch(:visible){ @selector.default_visibility(session_options.ignore_hidden_elements) })
|
111
|
-
|
112
|
-
|
113
|
-
|
63
|
+
case (vis = options.fetch(:visible) { @selector.default_visibility(session_options.ignore_hidden_elements) })
|
64
|
+
when true then :visible
|
65
|
+
when false then :all
|
66
|
+
else vis
|
114
67
|
end
|
115
68
|
end
|
116
69
|
|
117
70
|
def exact?
|
118
|
-
|
119
|
-
options.fetch(:exact, session_options.exact)
|
71
|
+
supports_exact? ? options.fetch(:exact, session_options.exact) : false
|
120
72
|
end
|
121
73
|
|
122
74
|
def match
|
123
75
|
options.fetch(:match, session_options.match)
|
124
76
|
end
|
125
77
|
|
126
|
-
def xpath(exact=nil)
|
127
|
-
exact =
|
78
|
+
def xpath(exact = nil)
|
79
|
+
exact = exact? if exact.nil?
|
128
80
|
expr = apply_expression_filters(@expression)
|
129
|
-
expr = if expr.respond_to?(:to_xpath)
|
130
|
-
expr.to_xpath(:exact)
|
131
|
-
else
|
132
|
-
expr.to_s
|
133
|
-
end
|
81
|
+
expr = exact ? expr.to_xpath(:exact) : expr.to_s if expr.respond_to?(:to_xpath)
|
134
82
|
filtered_xpath(expr)
|
135
83
|
end
|
136
84
|
|
@@ -140,11 +88,12 @@ module Capybara
|
|
140
88
|
|
141
89
|
# @api private
|
142
90
|
def resolve_for(node, exact = nil)
|
91
|
+
@resolved_node = node
|
143
92
|
node.synchronize do
|
144
93
|
children = if selector.format == :css
|
145
|
-
node.find_css(
|
94
|
+
node.find_css(css)
|
146
95
|
else
|
147
|
-
node.find_xpath(
|
96
|
+
node.find_xpath(xpath(exact))
|
148
97
|
end.map do |child|
|
149
98
|
if node.is_a?(Capybara::Node::Base)
|
150
99
|
Capybara::Node::Element.new(node.session, child, node, self)
|
@@ -161,14 +110,45 @@ module Capybara
|
|
161
110
|
@expression.respond_to? :to_xpath
|
162
111
|
end
|
163
112
|
|
164
|
-
|
113
|
+
private
|
114
|
+
|
115
|
+
def find_selector(locator)
|
116
|
+
selector = if locator.is_a?(Symbol)
|
117
|
+
Selector.all.fetch(locator) { |sel_type| raise ArgumentError, "Unknown selector type (:#{sel_type})" }
|
118
|
+
else
|
119
|
+
Selector.all.values.find { |s| s.match?(locator) }
|
120
|
+
end
|
121
|
+
selector || Selector.all[session_options.default_selector]
|
122
|
+
end
|
165
123
|
|
166
124
|
def valid_keys
|
167
125
|
VALID_KEYS + custom_keys
|
168
126
|
end
|
169
127
|
|
128
|
+
def matches_node_filters?(node)
|
129
|
+
node_filters.all? do |name, filter|
|
130
|
+
if options.key?(name)
|
131
|
+
filter.matches?(node, options[name])
|
132
|
+
elsif filter.default?
|
133
|
+
filter.matches?(node, filter.default)
|
134
|
+
else
|
135
|
+
true
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def matches_filter_block?(node)
|
141
|
+
return true unless @filter_block
|
142
|
+
|
143
|
+
if node.respond_to?(:session)
|
144
|
+
node.session.using_wait_time(0) { @filter_block.call(node) }
|
145
|
+
else
|
146
|
+
@filter_block.call(node)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
170
150
|
def node_filters
|
171
|
-
if options.
|
151
|
+
if options.key?(:filter_set)
|
172
152
|
::Capybara::Selector::FilterSet.all[options[:filter_set]].node_filters
|
173
153
|
else
|
174
154
|
@selector.node_filters
|
@@ -177,7 +157,7 @@ module Capybara
|
|
177
157
|
|
178
158
|
def expression_filters
|
179
159
|
filters = @selector.expression_filters
|
180
|
-
filters.merge ::Capybara::Selector::FilterSet.all[options[:filter_set]].expression_filters if options.
|
160
|
+
filters.merge ::Capybara::Selector::FilterSet.all[options[:filter_set]].expression_filters if options.key?(:filter_set)
|
181
161
|
filters
|
182
162
|
end
|
183
163
|
|
@@ -193,13 +173,13 @@ module Capybara
|
|
193
173
|
end
|
194
174
|
|
195
175
|
def filtered_xpath(expr)
|
196
|
-
if options.
|
176
|
+
if options.key?(:id) || options.key?(:class)
|
197
177
|
expr = "(#{expr})"
|
198
|
-
expr = "#{expr}[#{XPath.attr(:id) == options[:id]}]" if options.
|
199
|
-
if options.
|
178
|
+
expr = "#{expr}[#{XPath.attr(:id) == options[:id]}]" if options.key?(:id) && !custom_keys.include?(:id)
|
179
|
+
if options.key?(:class) && !custom_keys.include?(:class)
|
200
180
|
class_xpath = Array(options[:class]).map do |klass|
|
201
|
-
|
202
|
-
end.
|
181
|
+
XPath.attr(:class).contains_word(klass)
|
182
|
+
end.reduce(:&)
|
203
183
|
expr = "#{expr}[#{class_xpath}]"
|
204
184
|
end
|
205
185
|
end
|
@@ -207,12 +187,12 @@ module Capybara
|
|
207
187
|
end
|
208
188
|
|
209
189
|
def filtered_css(expr)
|
210
|
-
if options.
|
190
|
+
if options.key?(:id) || options.key?(:class)
|
211
191
|
css_selectors = expr.split(',').map(&:rstrip)
|
212
192
|
expr = css_selectors.map do |sel|
|
213
|
-
|
214
|
-
|
215
|
-
|
193
|
+
sel += "##{Capybara::Selector::CSS.escape(options[:id])}" if options.key?(:id) && !custom_keys.include?(:id)
|
194
|
+
sel += Array(options[:class]).map { |k| ".#{Capybara::Selector::CSS.escape(k)}" }.join if options.key?(:class) && !custom_keys.include?(:class)
|
195
|
+
sel
|
216
196
|
end.join(", ")
|
217
197
|
end
|
218
198
|
expr
|
@@ -220,7 +200,7 @@ module Capybara
|
|
220
200
|
|
221
201
|
def apply_expression_filters(expr)
|
222
202
|
expression_filters.inject(expr) do |memo, (name, ef)|
|
223
|
-
if options.
|
203
|
+
if options.key?(name)
|
224
204
|
ef.apply_filter(memo, options[name])
|
225
205
|
elsif ef.default?
|
226
206
|
ef.apply_filter(memo, ef.default)
|
@@ -231,14 +211,38 @@ module Capybara
|
|
231
211
|
end
|
232
212
|
|
233
213
|
def warn_exact_usage
|
234
|
-
|
235
|
-
|
236
|
-
end
|
214
|
+
return unless options.key?(:exact) && !supports_exact?
|
215
|
+
warn "The :exact option only has an effect on queries using the XPath#is method. Using it with the query \"#{expression}\" has no effect."
|
237
216
|
end
|
238
217
|
|
239
218
|
def exact_text
|
240
219
|
options.fetch(:exact_text, session_options.exact_text)
|
241
220
|
end
|
221
|
+
|
222
|
+
def describe_within?
|
223
|
+
@resolved_node && !(@resolved_node.is_a?(::Capybara::Node::Document) ||
|
224
|
+
(@resolved_node.is_a?(::Capybara::Node::Simple) && @resolved_node.path == '/'))
|
225
|
+
end
|
226
|
+
|
227
|
+
def matches_text_filter(node, text_option)
|
228
|
+
regexp = if text_option.is_a?(Regexp)
|
229
|
+
text_option
|
230
|
+
elsif exact_text == true
|
231
|
+
/\A#{Regexp.escape(text_option.to_s)}\z/
|
232
|
+
else
|
233
|
+
Regexp.escape(text_option.to_s)
|
234
|
+
end
|
235
|
+
text_visible = visible
|
236
|
+
text_visible = :all if text_visible == :hidden
|
237
|
+
node.text(text_visible).match(regexp)
|
238
|
+
end
|
239
|
+
|
240
|
+
def matches_exact_text_filter(node, exact_text_option)
|
241
|
+
regexp = /\A#{Regexp.escape(exact_text_option)}\z/
|
242
|
+
text_visible = visible
|
243
|
+
text_visible = :all if text_visible == :hidden
|
244
|
+
node.text(text_visible).match(regexp)
|
245
|
+
end
|
242
246
|
end
|
243
247
|
end
|
244
248
|
end
|
@@ -1,13 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Capybara
|
3
4
|
module Queries
|
4
5
|
class SiblingQuery < MatchQuery
|
5
6
|
# @api private
|
6
7
|
def resolve_for(node, exact = nil)
|
7
|
-
@
|
8
|
+
@sibling_node = node
|
8
9
|
node.synchronize do
|
9
10
|
match_results = super(node.session.current_scope, exact)
|
10
|
-
node.all(:xpath, XPath.preceding_sibling
|
11
|
+
node.all(:xpath, XPath.preceding_sibling + XPath.following_sibling) do |el|
|
11
12
|
match_results.include?(el)
|
12
13
|
end
|
13
14
|
end
|
@@ -15,9 +16,8 @@ module Capybara
|
|
15
16
|
|
16
17
|
def description
|
17
18
|
desc = super
|
18
|
-
|
19
|
-
|
20
|
-
end
|
19
|
+
sibling_query = @sibling_node && @sibling_node.instance_variable_get(:@query)
|
20
|
+
desc += " that is a sibling of #{sibling_query.description}" if sibling_query
|
21
21
|
desc
|
22
22
|
end
|
23
23
|
end
|