capybara 2.5.0 → 2.18.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/.yard/templates_custom/default/class/html/selectors.erb +38 -0
- data/.yard/templates_custom/default/class/html/setup.rb +17 -0
- data/.yard/yard_extensions.rb +78 -0
- data/.yardopts +1 -0
- data/History.md +413 -10
- data/License.txt +1 -1
- data/README.md +237 -130
- data/lib/capybara/config.rb +132 -0
- data/lib/capybara/cucumber.rb +3 -1
- data/lib/capybara/driver/base.rb +27 -6
- data/lib/capybara/driver/node.rb +14 -5
- data/lib/capybara/dsl.rb +2 -3
- data/lib/capybara/helpers.rb +13 -65
- data/lib/capybara/minitest/spec.rb +177 -0
- data/lib/capybara/minitest.rb +278 -0
- data/lib/capybara/node/actions.rb +180 -24
- data/lib/capybara/node/base.rb +17 -5
- data/lib/capybara/node/document.rb +5 -0
- data/lib/capybara/node/document_matchers.rb +15 -14
- data/lib/capybara/node/element.rb +55 -7
- data/lib/capybara/node/finders.rb +179 -67
- data/lib/capybara/node/matchers.rb +301 -105
- data/lib/capybara/node/simple.rb +15 -4
- data/lib/capybara/queries/ancestor_query.rb +25 -0
- data/lib/capybara/queries/base_query.rb +69 -3
- data/lib/capybara/queries/current_path_query.rb +17 -8
- data/lib/capybara/queries/match_query.rb +19 -0
- data/lib/capybara/queries/selector_query.rb +251 -0
- data/lib/capybara/queries/sibling_query.rb +25 -0
- data/lib/capybara/queries/text_query.rb +67 -16
- data/lib/capybara/queries/title_query.rb +4 -2
- data/lib/capybara/query.rb +3 -131
- data/lib/capybara/rack_test/browser.rb +14 -5
- data/lib/capybara/rack_test/css_handlers.rb +1 -0
- data/lib/capybara/rack_test/driver.rb +15 -8
- data/lib/capybara/rack_test/form.rb +34 -12
- data/lib/capybara/rack_test/node.rb +29 -12
- data/lib/capybara/rails.rb +3 -3
- data/lib/capybara/result.rb +104 -9
- data/lib/capybara/rspec/compound.rb +95 -0
- data/lib/capybara/rspec/features.rb +17 -6
- data/lib/capybara/rspec/matcher_proxies.rb +45 -0
- data/lib/capybara/rspec/matchers.rb +199 -80
- data/lib/capybara/rspec.rb +4 -2
- data/lib/capybara/selector/css.rb +30 -0
- data/lib/capybara/selector/filter.rb +20 -0
- data/lib/capybara/selector/filter_set.rb +74 -0
- data/lib/capybara/selector/filters/base.rb +33 -0
- data/lib/capybara/selector/filters/expression_filter.rb +40 -0
- data/lib/capybara/selector/filters/node_filter.rb +27 -0
- data/lib/capybara/selector/selector.rb +276 -0
- data/lib/capybara/selector.rb +452 -157
- data/lib/capybara/selenium/driver.rb +282 -81
- data/lib/capybara/selenium/node.rb +144 -46
- data/lib/capybara/server.rb +59 -16
- data/lib/capybara/session/config.rb +114 -0
- data/lib/capybara/session/matchers.rb +29 -19
- data/lib/capybara/session.rb +378 -143
- data/lib/capybara/spec/fixtures/no_extension +1 -0
- data/lib/capybara/spec/public/jquery-ui.js +13 -791
- data/lib/capybara/spec/public/jquery.js +4 -9045
- data/lib/capybara/spec/public/test.js +45 -11
- data/lib/capybara/spec/session/accept_alert_spec.rb +30 -7
- data/lib/capybara/spec/session/accept_confirm_spec.rb +14 -2
- data/lib/capybara/spec/session/accept_prompt_spec.rb +35 -6
- data/lib/capybara/spec/session/all_spec.rb +45 -32
- data/lib/capybara/spec/session/ancestor_spec.rb +85 -0
- data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +110 -0
- data/lib/capybara/spec/session/assert_current_path.rb +15 -2
- data/lib/capybara/spec/session/assert_selector.rb +29 -28
- data/lib/capybara/spec/session/assert_text.rb +59 -20
- data/lib/capybara/spec/session/assert_title.rb +25 -11
- data/lib/capybara/spec/session/attach_file_spec.rb +42 -4
- data/lib/capybara/spec/session/body_spec.rb +1 -0
- data/lib/capybara/spec/session/check_spec.rb +90 -14
- data/lib/capybara/spec/session/choose_spec.rb +31 -5
- data/lib/capybara/spec/session/click_button_spec.rb +20 -9
- data/lib/capybara/spec/session/click_link_or_button_spec.rb +15 -9
- data/lib/capybara/spec/session/click_link_spec.rb +39 -15
- data/lib/capybara/spec/session/current_scope_spec.rb +2 -1
- data/lib/capybara/spec/session/current_url_spec.rb +12 -3
- data/lib/capybara/spec/session/dismiss_confirm_spec.rb +6 -5
- data/lib/capybara/spec/session/dismiss_prompt_spec.rb +4 -3
- data/lib/capybara/spec/session/element/assert_match_selector.rb +36 -0
- data/lib/capybara/spec/session/element/match_css_spec.rb +23 -0
- data/lib/capybara/spec/session/element/match_xpath_spec.rb +23 -0
- data/lib/capybara/spec/session/element/matches_selector_spec.rb +106 -0
- data/lib/capybara/spec/session/evaluate_async_script_spec.rb +22 -0
- data/lib/capybara/spec/session/evaluate_script_spec.rb +23 -1
- data/lib/capybara/spec/session/execute_script_spec.rb +22 -3
- data/lib/capybara/spec/session/fill_in_spec.rb +50 -32
- data/lib/capybara/spec/session/find_button_spec.rb +43 -2
- data/lib/capybara/spec/session/find_by_id_spec.rb +3 -2
- data/lib/capybara/spec/session/find_field_spec.rb +42 -6
- data/lib/capybara/spec/session/find_link_spec.rb +22 -3
- data/lib/capybara/spec/session/find_spec.rb +103 -57
- data/lib/capybara/spec/session/first_spec.rb +34 -18
- data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +103 -0
- data/lib/capybara/spec/session/{within_frame_spec.rb → frame/within_frame_spec.rb} +44 -2
- data/lib/capybara/spec/session/go_back_spec.rb +2 -1
- data/lib/capybara/spec/session/go_forward_spec.rb +2 -1
- data/lib/capybara/spec/session/has_all_selectors_spec.rb +69 -0
- data/lib/capybara/spec/session/has_button_spec.rb +17 -8
- data/lib/capybara/spec/session/has_css_spec.rb +85 -73
- data/lib/capybara/spec/session/has_current_path_spec.rb +91 -7
- data/lib/capybara/spec/session/has_field_spec.rb +93 -58
- data/lib/capybara/spec/session/has_link_spec.rb +9 -8
- data/lib/capybara/spec/session/has_none_selectors_spec.rb +76 -0
- data/lib/capybara/spec/session/has_select_spec.rb +159 -59
- data/lib/capybara/spec/session/has_selector_spec.rb +64 -28
- data/lib/capybara/spec/session/has_table_spec.rb +1 -0
- data/lib/capybara/spec/session/has_text_spec.rb +27 -12
- data/lib/capybara/spec/session/has_title_spec.rb +22 -4
- data/lib/capybara/spec/session/has_xpath_spec.rb +32 -29
- data/lib/capybara/spec/session/headers.rb +2 -1
- data/lib/capybara/spec/session/html_spec.rb +4 -3
- data/lib/capybara/spec/session/node_spec.rb +198 -38
- data/lib/capybara/spec/session/refresh_spec.rb +28 -0
- data/lib/capybara/spec/session/reset_session_spec.rb +46 -5
- data/lib/capybara/spec/session/response_code.rb +2 -1
- 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 -5
- data/lib/capybara/spec/session/save_page_spec.rb +34 -2
- data/lib/capybara/spec/session/save_screenshot_spec.rb +31 -1
- data/lib/capybara/spec/session/screenshot_spec.rb +4 -2
- data/lib/capybara/spec/session/select_spec.rb +34 -32
- data/lib/capybara/spec/session/selectors_spec.rb +65 -0
- data/lib/capybara/spec/session/sibling_spec.rb +52 -0
- data/lib/capybara/spec/session/text_spec.rb +4 -4
- data/lib/capybara/spec/session/title_spec.rb +2 -1
- data/lib/capybara/spec/session/uncheck_spec.rb +42 -2
- data/lib/capybara/spec/session/unselect_spec.rb +17 -16
- data/lib/capybara/spec/session/visit_spec.rb +77 -2
- data/lib/capybara/spec/session/window/become_closed_spec.rb +12 -11
- 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 +16 -11
- data/lib/capybara/spec/session/window/window_opened_by_spec.rb +7 -4
- data/lib/capybara/spec/session/window/window_spec.rb +36 -29
- data/lib/capybara/spec/session/window/windows_spec.rb +1 -0
- data/lib/capybara/spec/session/window/within_window_spec.rb +31 -7
- data/lib/capybara/spec/session/within_spec.rb +14 -6
- data/lib/capybara/spec/spec_helper.rb +37 -4
- data/lib/capybara/spec/test_app.rb +15 -3
- data/lib/capybara/spec/views/buttons.erb +1 -0
- data/lib/capybara/spec/views/fieldsets.erb +2 -1
- data/lib/capybara/spec/views/form.erb +169 -9
- data/lib/capybara/spec/views/frame_child.erb +10 -2
- data/lib/capybara/spec/views/frame_one.erb +2 -1
- data/lib/capybara/spec/views/frame_parent.erb +3 -2
- data/lib/capybara/spec/views/frame_two.erb +2 -1
- data/lib/capybara/spec/views/header_links.erb +1 -0
- data/lib/capybara/spec/views/host_links.erb +1 -0
- data/lib/capybara/spec/views/initial_alert.erb +10 -0
- data/lib/capybara/spec/views/path.erb +1 -0
- data/lib/capybara/spec/views/popup_one.erb +1 -0
- data/lib/capybara/spec/views/popup_two.erb +1 -0
- data/lib/capybara/spec/views/postback.erb +2 -1
- data/lib/capybara/spec/views/tables.erb +1 -0
- data/lib/capybara/spec/views/with_base_tag.erb +1 -0
- data/lib/capybara/spec/views/with_count.erb +2 -1
- data/lib/capybara/spec/views/with_fixed_header_footer.erb +17 -0
- data/lib/capybara/spec/views/with_hover.erb +7 -1
- data/lib/capybara/spec/views/with_html.erb +40 -2
- data/lib/capybara/spec/views/with_html_entities.erb +1 -0
- data/lib/capybara/spec/views/with_js.erb +32 -1
- data/lib/capybara/spec/views/with_scope.erb +1 -0
- data/lib/capybara/spec/views/with_simple_html.erb +2 -1
- data/lib/capybara/spec/views/with_slow_unload.erb +17 -0
- data/lib/capybara/spec/views/with_title.erb +2 -1
- data/lib/capybara/spec/views/with_unload_alert.erb +14 -0
- data/lib/capybara/spec/views/with_windows.erb +7 -0
- data/lib/capybara/spec/views/within_frames.erb +3 -2
- data/lib/capybara/version.rb +2 -1
- data/lib/capybara/window.rb +20 -3
- data/lib/capybara.rb +189 -93
- data/spec/basic_node_spec.rb +7 -6
- data/spec/capybara_spec.rb +90 -4
- data/spec/dsl_spec.rb +3 -1
- data/spec/filter_set_spec.rb +28 -0
- data/spec/fixtures/capybara.csv +1 -0
- data/spec/fixtures/selenium_driver_rspec_failure.rb +5 -1
- data/spec/fixtures/selenium_driver_rspec_success.rb +5 -1
- data/spec/minitest_spec.rb +130 -0
- data/spec/minitest_spec_spec.rb +135 -0
- data/spec/per_session_config_spec.rb +67 -0
- data/spec/rack_test_spec.rb +50 -7
- data/spec/result_spec.rb +76 -0
- data/spec/rspec/features_spec.rb +21 -8
- data/spec/rspec/scenarios_spec.rb +21 -0
- data/spec/rspec/{matchers_spec.rb → shared_spec_matchers.rb} +160 -54
- data/spec/rspec/views_spec.rb +5 -0
- data/spec/rspec_matchers_spec.rb +46 -0
- data/spec/rspec_spec.rb +79 -1
- data/spec/selector_spec.rb +199 -0
- data/spec/selenium_spec_chrome.rb +54 -9
- data/spec/selenium_spec_firefox.rb +68 -0
- data/spec/selenium_spec_marionette.rb +127 -0
- data/spec/server_spec.rb +102 -14
- data/spec/session_spec.rb +54 -0
- data/spec/shared_selenium_session.rb +215 -0
- data/spec/spec_helper.rb +7 -0
- metadata +140 -15
- data/spec/selenium_spec.rb +0 -128
data/lib/capybara/node/simple.rb
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
module Capybara
|
|
2
3
|
module Node
|
|
3
4
|
|
|
@@ -75,7 +76,7 @@ module Capybara
|
|
|
75
76
|
#
|
|
76
77
|
def value
|
|
77
78
|
if tag_name == 'textarea'
|
|
78
|
-
native
|
|
79
|
+
native['_capybara_raw_value']
|
|
79
80
|
elsif tag_name == 'select'
|
|
80
81
|
if native['multiple'] == 'multiple'
|
|
81
82
|
native.xpath(".//option[@selected='selected']").map { |option| option[:value] || option.content }
|
|
@@ -99,6 +100,8 @@ module Capybara
|
|
|
99
100
|
# @return [Boolean] Whether the element is visible
|
|
100
101
|
#
|
|
101
102
|
def visible?(check_ancestors = true)
|
|
103
|
+
return false if (tag_name == 'input') && (native[:type]=="hidden")
|
|
104
|
+
|
|
102
105
|
if check_ancestors
|
|
103
106
|
#check size because oldest supported nokogiri doesnt support xpath boolean() function
|
|
104
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
|
|
@@ -115,7 +118,7 @@ module Capybara
|
|
|
115
118
|
# @return [Boolean] Whether the element is checked
|
|
116
119
|
#
|
|
117
120
|
def checked?
|
|
118
|
-
native
|
|
121
|
+
native.has_attribute?('checked')
|
|
119
122
|
end
|
|
120
123
|
|
|
121
124
|
##
|
|
@@ -124,7 +127,7 @@ module Capybara
|
|
|
124
127
|
#
|
|
125
128
|
# @return [Boolean] Whether the element is disabled
|
|
126
129
|
def disabled?
|
|
127
|
-
native
|
|
130
|
+
native.has_attribute?('disabled')
|
|
128
131
|
end
|
|
129
132
|
|
|
130
133
|
##
|
|
@@ -134,7 +137,7 @@ module Capybara
|
|
|
134
137
|
# @return [Boolean] Whether the element is selected
|
|
135
138
|
#
|
|
136
139
|
def selected?
|
|
137
|
-
native
|
|
140
|
+
native.has_attribute?('selected')
|
|
138
141
|
end
|
|
139
142
|
|
|
140
143
|
def synchronize(seconds=nil)
|
|
@@ -145,6 +148,9 @@ module Capybara
|
|
|
145
148
|
# no op
|
|
146
149
|
end
|
|
147
150
|
|
|
151
|
+
##
|
|
152
|
+
#
|
|
153
|
+
# @return [String] The title of the document
|
|
148
154
|
def title
|
|
149
155
|
if native.respond_to? :title
|
|
150
156
|
native.title
|
|
@@ -167,6 +173,11 @@ module Capybara
|
|
|
167
173
|
def find_xpath(xpath)
|
|
168
174
|
native.xpath(xpath)
|
|
169
175
|
end
|
|
176
|
+
|
|
177
|
+
# @api private
|
|
178
|
+
def session_options
|
|
179
|
+
Capybara.session_options
|
|
180
|
+
end
|
|
170
181
|
end
|
|
171
182
|
end
|
|
172
183
|
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Capybara
|
|
3
|
+
module Queries
|
|
4
|
+
class AncestorQuery < MatchQuery
|
|
5
|
+
# @api private
|
|
6
|
+
def resolve_for(node, exact = nil)
|
|
7
|
+
@child_node = node
|
|
8
|
+
node.synchronize do
|
|
9
|
+
match_results = super(node.session.current_scope, exact)
|
|
10
|
+
node.all(:xpath, XPath.ancestor) do |el|
|
|
11
|
+
match_results.include?(el)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def description
|
|
17
|
+
desc = super
|
|
18
|
+
if @child_node && (child_query = @child_node.instance_variable_get(:@query))
|
|
19
|
+
desc += " that is an ancestor of #{child_query.description}"
|
|
20
|
+
end
|
|
21
|
+
desc
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
module Capybara
|
|
2
3
|
# @api private
|
|
3
4
|
module Queries
|
|
@@ -5,17 +6,82 @@ module Capybara
|
|
|
5
6
|
COUNT_KEYS = [:count, :minimum, :maximum, :between]
|
|
6
7
|
|
|
7
8
|
attr_reader :options
|
|
9
|
+
attr_writer :session_options
|
|
10
|
+
|
|
11
|
+
def initialize(options)
|
|
12
|
+
@session_options = options.delete(:session_options)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def session_options
|
|
16
|
+
@session_options || Capybara.session_options
|
|
17
|
+
end
|
|
8
18
|
|
|
9
19
|
def wait
|
|
10
|
-
|
|
11
|
-
|
|
20
|
+
self.class.wait(options, session_options.default_max_wait_time)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.wait(options, default=Capybara.default_max_wait_time)
|
|
24
|
+
options.fetch(:wait, default) || 0
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
##
|
|
28
|
+
#
|
|
29
|
+
# Checks if a count of 0 is valid for the query
|
|
30
|
+
# Returns false if query does not have any count options specified.
|
|
31
|
+
#
|
|
32
|
+
def expects_none?
|
|
33
|
+
if COUNT_KEYS.any? { |k| options.has_key? k }
|
|
34
|
+
matches_count?(0)
|
|
12
35
|
else
|
|
13
|
-
|
|
36
|
+
false
|
|
14
37
|
end
|
|
15
38
|
end
|
|
16
39
|
|
|
40
|
+
##
|
|
41
|
+
#
|
|
42
|
+
# Checks if the given count matches the query count options.
|
|
43
|
+
# Defaults to true if no count options are specified. If multiple
|
|
44
|
+
# count options exist, it tests that all conditions are met;
|
|
45
|
+
# however, if :count is specified, all other options are ignored.
|
|
46
|
+
#
|
|
47
|
+
# @param [Integer] count The actual number. Should be coercible via Integer()
|
|
48
|
+
#
|
|
49
|
+
def matches_count?(count)
|
|
50
|
+
return (Integer(options[:count]) == count) if options[:count]
|
|
51
|
+
return false if options[:maximum] && (Integer(options[:maximum]) < count)
|
|
52
|
+
return false if options[:minimum] && (Integer(options[:minimum]) > count)
|
|
53
|
+
return false if options[:between] && !(options[:between] === count)
|
|
54
|
+
return true
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
##
|
|
58
|
+
#
|
|
59
|
+
# Generates a failure message from the query description and count options.
|
|
60
|
+
#
|
|
61
|
+
def failure_message
|
|
62
|
+
String.new("expected to find #{description}") << count_message
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def negative_failure_message
|
|
66
|
+
String.new("expected not to find #{description}") << count_message
|
|
67
|
+
end
|
|
68
|
+
|
|
17
69
|
private
|
|
18
70
|
|
|
71
|
+
def count_message
|
|
72
|
+
message = String.new()
|
|
73
|
+
if options[:count]
|
|
74
|
+
message << " #{options[:count]} #{Capybara::Helpers.declension('time', 'times', options[:count])}"
|
|
75
|
+
elsif options[:between]
|
|
76
|
+
message << " between #{options[:between].first} and #{options[:between].last} times"
|
|
77
|
+
elsif options[:maximum]
|
|
78
|
+
message << " at most #{options[:maximum]} #{Capybara::Helpers.declension('time', 'times', options[:maximum])}"
|
|
79
|
+
elsif options[:minimum]
|
|
80
|
+
message << " at least #{options[:minimum]} #{Capybara::Helpers.declension('time', 'times', options[:minimum])}"
|
|
81
|
+
end
|
|
82
|
+
message
|
|
83
|
+
end
|
|
84
|
+
|
|
19
85
|
def assert_valid_keys
|
|
20
86
|
invalid_keys = @options.keys - valid_keys
|
|
21
87
|
unless invalid_keys.empty?
|
|
@@ -1,30 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'addressable/uri'
|
|
3
|
+
|
|
1
4
|
module Capybara
|
|
2
5
|
# @api private
|
|
3
6
|
module Queries
|
|
4
7
|
class CurrentPathQuery < BaseQuery
|
|
5
8
|
def initialize(expected_path, options = {})
|
|
9
|
+
super(options)
|
|
6
10
|
@expected_path = expected_path
|
|
11
|
+
warn "DEPRECATED: The :only_path option is deprecated in favor of the :ignore_query option" if options.has_key?(:only_path)
|
|
12
|
+
|
|
7
13
|
@options = {
|
|
8
|
-
url:
|
|
9
|
-
only_path: false
|
|
14
|
+
url: !@expected_path.is_a?(Regexp) && !::Addressable::URI.parse(@expected_path || "").hostname.nil?,
|
|
15
|
+
only_path: false,
|
|
16
|
+
ignore_query: false }.merge(options)
|
|
10
17
|
assert_valid_keys
|
|
11
18
|
end
|
|
12
19
|
|
|
13
20
|
def resolves_for?(session)
|
|
21
|
+
uri = ::Addressable::URI.parse(session.current_url)
|
|
22
|
+
uri.query = nil if uri && options[:ignore_query]
|
|
14
23
|
@actual_path = if options[:url]
|
|
15
|
-
|
|
24
|
+
uri.to_s
|
|
16
25
|
else
|
|
17
26
|
if options[:only_path]
|
|
18
|
-
|
|
27
|
+
uri && uri.path
|
|
19
28
|
else
|
|
20
|
-
|
|
29
|
+
uri && uri.request_uri
|
|
21
30
|
end
|
|
22
31
|
end
|
|
23
32
|
|
|
24
33
|
if @expected_path.is_a? Regexp
|
|
25
|
-
@actual_path.match(@expected_path)
|
|
34
|
+
@actual_path.to_s.match(@expected_path)
|
|
26
35
|
else
|
|
27
|
-
@expected_path == @actual_path
|
|
36
|
+
::Addressable::URI.parse(@expected_path) == ::Addressable::URI.parse(@actual_path)
|
|
28
37
|
end
|
|
29
38
|
end
|
|
30
39
|
|
|
@@ -44,7 +53,7 @@ module Capybara
|
|
|
44
53
|
end
|
|
45
54
|
|
|
46
55
|
def valid_keys
|
|
47
|
-
[:wait, :url, :only_path]
|
|
56
|
+
[:wait, :url, :only_path, :ignore_query]
|
|
48
57
|
end
|
|
49
58
|
|
|
50
59
|
def assert_valid_keys
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Capybara
|
|
2
|
+
module Queries
|
|
3
|
+
class MatchQuery < Capybara::Queries::SelectorQuery
|
|
4
|
+
def visible
|
|
5
|
+
if options.has_key?(:visible)
|
|
6
|
+
super
|
|
7
|
+
else
|
|
8
|
+
:all
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def valid_keys
|
|
15
|
+
super - COUNT_KEYS
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Capybara
|
|
3
|
+
module Queries
|
|
4
|
+
class SelectorQuery < Queries::BaseQuery
|
|
5
|
+
attr_accessor :selector, :locator, :options, :expression, :find, :negative
|
|
6
|
+
|
|
7
|
+
VALID_KEYS = COUNT_KEYS + [:text, :id, :class, :visible, :exact, :exact_text, :match, :wait, :filter_set]
|
|
8
|
+
VALID_MATCH = [:first, :smart, :prefer_exact, :one]
|
|
9
|
+
|
|
10
|
+
def initialize(*args, &filter_block)
|
|
11
|
+
@resolved_node = nil
|
|
12
|
+
@options = if args.last.is_a?(Hash) then args.pop.dup else {} end
|
|
13
|
+
super(@options)
|
|
14
|
+
|
|
15
|
+
@filter_block = filter_block
|
|
16
|
+
|
|
17
|
+
if args[0].is_a?(Symbol)
|
|
18
|
+
@selector = Selector.all.fetch(args.shift) do |selector_type|
|
|
19
|
+
raise ArgumentError, "Unknown selector type (:#{selector_type})"
|
|
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}" 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
|
|
34
|
+
|
|
35
|
+
@expression = @selector.call(@locator, @options.merge(enable_aria_label: session_options.enable_aria_label))
|
|
36
|
+
|
|
37
|
+
warn_exact_usage
|
|
38
|
+
|
|
39
|
+
assert_valid_keys
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def name; selector.name; end
|
|
43
|
+
def label; selector.label or selector.name; end
|
|
44
|
+
|
|
45
|
+
def description
|
|
46
|
+
@description = String.new()
|
|
47
|
+
@description << "visible " if visible == :visible
|
|
48
|
+
@description << "non-visible " if visible == :hidden
|
|
49
|
+
@description << "#{label} #{locator.inspect}"
|
|
50
|
+
@description << " with#{" exact" if exact_text == true} text #{options[:text].inspect}" if options[:text]
|
|
51
|
+
@description << " with exact text #{options[:exact_text]}" if options[:exact_text].is_a?(String)
|
|
52
|
+
@description << " with id #{options[:id]}" if options[:id]
|
|
53
|
+
@description << " with classes [#{Array(options[:class]).join(',')}]" if options[:class]
|
|
54
|
+
@description << selector.description(options)
|
|
55
|
+
@description << " that also matches the custom filter block" if @filter_block
|
|
56
|
+
@description << " within #{@resolved_node.inspect}" if describe_within?
|
|
57
|
+
@description
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def matches_filters?(node)
|
|
61
|
+
if options[:text]
|
|
62
|
+
regexp = if options[:text].is_a?(Regexp)
|
|
63
|
+
options[:text]
|
|
64
|
+
else
|
|
65
|
+
if exact_text == true
|
|
66
|
+
/\A#{Regexp.escape(options[:text].to_s)}\z/
|
|
67
|
+
else
|
|
68
|
+
Regexp.escape(options[:text].to_s)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
text_visible = visible
|
|
72
|
+
text_visible = :all if text_visible == :hidden
|
|
73
|
+
return false if not node.text(text_visible).match(regexp)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
if exact_text.is_a?(String)
|
|
77
|
+
regexp = /\A#{Regexp.escape(options[:exact_text])}\z/
|
|
78
|
+
text_visible = visible
|
|
79
|
+
text_visible = :all if text_visible == :hidden
|
|
80
|
+
return false if not node.text(text_visible).match(regexp)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
case visible
|
|
84
|
+
when :visible then return false unless node.visible?
|
|
85
|
+
when :hidden then return false if node.visible?
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
res = node_filters.all? do |name, filter|
|
|
89
|
+
if options.has_key?(name)
|
|
90
|
+
filter.matches?(node, options[name])
|
|
91
|
+
elsif filter.default?
|
|
92
|
+
filter.matches?(node, filter.default)
|
|
93
|
+
else
|
|
94
|
+
true
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
res &&= if node.respond_to?(:session)
|
|
99
|
+
node.session.using_wait_time(0){ @filter_block.call(node) }
|
|
100
|
+
else
|
|
101
|
+
@filter_block.call(node)
|
|
102
|
+
end unless @filter_block.nil?
|
|
103
|
+
|
|
104
|
+
res
|
|
105
|
+
|
|
106
|
+
rescue *(node.respond_to?(:session) ? node.session.driver.invalid_element_errors : [])
|
|
107
|
+
return false
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def visible
|
|
111
|
+
case (vis = options.fetch(:visible){ @selector.default_visibility(session_options.ignore_hidden_elements) })
|
|
112
|
+
when true then :visible
|
|
113
|
+
when false then :all
|
|
114
|
+
else vis
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def exact?
|
|
119
|
+
return false if !supports_exact?
|
|
120
|
+
options.fetch(:exact, session_options.exact)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def match
|
|
124
|
+
options.fetch(:match, session_options.match)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def xpath(exact=nil)
|
|
128
|
+
exact = self.exact? if exact.nil?
|
|
129
|
+
expr = apply_expression_filters(@expression)
|
|
130
|
+
expr = if expr.respond_to?(:to_xpath) and exact
|
|
131
|
+
expr.to_xpath(:exact)
|
|
132
|
+
else
|
|
133
|
+
expr.to_s
|
|
134
|
+
end
|
|
135
|
+
filtered_xpath(expr)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def css
|
|
139
|
+
filtered_css(apply_expression_filters(@expression))
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# @api private
|
|
143
|
+
def resolve_for(node, exact = nil)
|
|
144
|
+
@resolved_node = node
|
|
145
|
+
node.synchronize do
|
|
146
|
+
children = if selector.format == :css
|
|
147
|
+
node.find_css(self.css)
|
|
148
|
+
else
|
|
149
|
+
node.find_xpath(self.xpath(exact))
|
|
150
|
+
end.map do |child|
|
|
151
|
+
if node.is_a?(Capybara::Node::Base)
|
|
152
|
+
Capybara::Node::Element.new(node.session, child, node, self)
|
|
153
|
+
else
|
|
154
|
+
Capybara::Node::Simple.new(child)
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
Capybara::Result.new(children, self)
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# @api private
|
|
162
|
+
def supports_exact?
|
|
163
|
+
@expression.respond_to? :to_xpath
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
private
|
|
167
|
+
|
|
168
|
+
def valid_keys
|
|
169
|
+
VALID_KEYS + custom_keys
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def node_filters
|
|
173
|
+
if options.has_key?(:filter_set)
|
|
174
|
+
::Capybara::Selector::FilterSet.all[options[:filter_set]].node_filters
|
|
175
|
+
else
|
|
176
|
+
@selector.node_filters
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def expression_filters
|
|
181
|
+
filters = @selector.expression_filters
|
|
182
|
+
filters.merge ::Capybara::Selector::FilterSet.all[options[:filter_set]].expression_filters if options.has_key?(:filter_set)
|
|
183
|
+
filters
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def custom_keys
|
|
187
|
+
@custom_keys ||= node_filters.keys + expression_filters.keys
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def assert_valid_keys
|
|
191
|
+
super
|
|
192
|
+
unless VALID_MATCH.include?(match)
|
|
193
|
+
raise ArgumentError, "invalid option #{match.inspect} for :match, should be one of #{VALID_MATCH.map(&:inspect).join(", ")}"
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def filtered_xpath(expr)
|
|
198
|
+
if options.has_key?(:id) || options.has_key?(:class)
|
|
199
|
+
expr = "(#{expr})"
|
|
200
|
+
expr = "#{expr}[#{XPath.attr(:id) == options[:id]}]" if options.has_key?(:id) && !custom_keys.include?(:id)
|
|
201
|
+
if options.has_key?(:class) && !custom_keys.include?(:class)
|
|
202
|
+
class_xpath = Array(options[:class]).map do |klass|
|
|
203
|
+
"contains(concat(' ',normalize-space(@class),' '),' #{klass} ')"
|
|
204
|
+
end.join(" and ")
|
|
205
|
+
expr = "#{expr}[#{class_xpath}]"
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
expr
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def filtered_css(expr)
|
|
212
|
+
if options.has_key?(:id) || options.has_key?(:class)
|
|
213
|
+
css_selectors = expr.split(',').map(&:rstrip)
|
|
214
|
+
expr = css_selectors.map do |sel|
|
|
215
|
+
sel += "##{Capybara::Selector::CSS.escape(options[:id])}" if options.has_key?(:id) && !custom_keys.include?(:id)
|
|
216
|
+
sel += Array(options[:class]).map { |k| ".#{Capybara::Selector::CSS.escape(k)}"}.join if options.has_key?(:class) && !custom_keys.include?(:class)
|
|
217
|
+
sel
|
|
218
|
+
end.join(", ")
|
|
219
|
+
end
|
|
220
|
+
expr
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def apply_expression_filters(expr)
|
|
224
|
+
expression_filters.inject(expr) do |memo, (name, ef)|
|
|
225
|
+
if options.has_key?(name)
|
|
226
|
+
ef.apply_filter(memo, options[name])
|
|
227
|
+
elsif ef.default?
|
|
228
|
+
ef.apply_filter(memo, ef.default)
|
|
229
|
+
else
|
|
230
|
+
memo
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def warn_exact_usage
|
|
236
|
+
if options.has_key?(:exact) && !supports_exact?
|
|
237
|
+
warn "The :exact option only has an effect on queries using the XPath#is method. Using it with the query \"#{expression}\" has no effect."
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def exact_text
|
|
242
|
+
options.fetch(:exact_text, session_options.exact_text)
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def describe_within?
|
|
246
|
+
@resolved_node && !(@resolved_node.is_a?(::Capybara::Node::Document) ||
|
|
247
|
+
(@resolved_node.is_a?(::Capybara::Node::Simple) && @resolved_node.path == '/'))
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Capybara
|
|
3
|
+
module Queries
|
|
4
|
+
class SiblingQuery < MatchQuery
|
|
5
|
+
# @api private
|
|
6
|
+
def resolve_for(node, exact = nil)
|
|
7
|
+
@sibling_node = node
|
|
8
|
+
node.synchronize do
|
|
9
|
+
match_results = super(node.session.current_scope, exact)
|
|
10
|
+
node.all(:xpath, XPath.preceding_sibling.union(XPath.following_sibling)) do |el|
|
|
11
|
+
match_results.include?(el)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def description
|
|
17
|
+
desc = super
|
|
18
|
+
if @sibling_node && (sibling_query = @sibling_node.instance_variable_get(:@query))
|
|
19
|
+
desc += " that is a sibling of #{sibling_query.description}"
|
|
20
|
+
end
|
|
21
|
+
desc
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -1,46 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
module Capybara
|
|
2
3
|
# @api private
|
|
3
4
|
module Queries
|
|
4
5
|
class TextQuery < BaseQuery
|
|
5
6
|
def initialize(*args)
|
|
6
7
|
@type = (args.first.is_a?(Symbol) || args.first.nil?) ? args.shift : nil
|
|
7
|
-
|
|
8
|
+
# @type = (Capybara.ignore_hidden_elements or Capybara.visible_text_only) ? :visible : :all if @type.nil?
|
|
9
|
+
@options = if args.last.is_a?(Hash) then args.pop.dup else {} end
|
|
10
|
+
super(@options)
|
|
11
|
+
|
|
12
|
+
@type = (session_options.ignore_hidden_elements or session_options.visible_text_only) ? :visible : :all if @type.nil?
|
|
13
|
+
|
|
14
|
+
@expected_text = args.shift
|
|
8
15
|
unless @expected_text.is_a?(Regexp)
|
|
9
16
|
@expected_text = Capybara::Helpers.normalize_whitespace(@expected_text)
|
|
10
17
|
end
|
|
11
|
-
@search_regexp = Capybara::Helpers.to_regexp(@expected_text)
|
|
12
|
-
|
|
18
|
+
@search_regexp = Capybara::Helpers.to_regexp(@expected_text, nil, exact?)
|
|
19
|
+
warn "Unused parameters passed to #{self.class.name} : #{args}" unless args.empty?
|
|
13
20
|
assert_valid_keys
|
|
14
21
|
end
|
|
15
22
|
|
|
16
23
|
def resolve_for(node)
|
|
17
|
-
@
|
|
24
|
+
@node = node
|
|
25
|
+
@actual_text = text(node, @type)
|
|
18
26
|
@count = @actual_text.scan(@search_regexp).size
|
|
19
27
|
end
|
|
20
28
|
|
|
21
29
|
def failure_message
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
30
|
+
super << build_message(true)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def negative_failure_message
|
|
34
|
+
super << build_message(false)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def description
|
|
38
|
+
if @expected_text.is_a?(Regexp)
|
|
39
|
+
"text matching #{@expected_text.inspect}"
|
|
40
|
+
else
|
|
41
|
+
"#{"exact " if exact?}text #{@expected_text.inspect}"
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def exact?
|
|
48
|
+
options.fetch(:exact, session_options.exact_text)
|
|
49
|
+
end
|
|
28
50
|
|
|
29
|
-
|
|
51
|
+
def build_message(report_on_invisible)
|
|
52
|
+
message = String.new()
|
|
30
53
|
unless (COUNT_KEYS & @options.keys).empty?
|
|
31
54
|
message << " but found #{@count} #{Capybara::Helpers.declension('time', 'times', @count)}"
|
|
32
55
|
end
|
|
33
56
|
message << " in #{@actual_text.inspect}"
|
|
57
|
+
|
|
58
|
+
details_message = []
|
|
59
|
+
|
|
60
|
+
if @node and !@expected_text.is_a? Regexp
|
|
61
|
+
insensitive_regexp = Capybara::Helpers.to_regexp(@expected_text, Regexp::IGNORECASE)
|
|
62
|
+
insensitive_count = @actual_text.scan(insensitive_regexp).size
|
|
63
|
+
if insensitive_count != @count
|
|
64
|
+
details_message << "it was found #{insensitive_count} #{Capybara::Helpers.declension("time", "times", insensitive_count)} using a case insensitive search"
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
if @node and check_visible_text? and report_on_invisible
|
|
69
|
+
begin
|
|
70
|
+
invisible_text = text(@node, :all)
|
|
71
|
+
invisible_count = invisible_text.scan(@search_regexp).size
|
|
72
|
+
if invisible_count != @count
|
|
73
|
+
details_message << "it was found #{invisible_count} #{Capybara::Helpers.declension("time", "times", invisible_count)} including non-visible text"
|
|
74
|
+
end
|
|
75
|
+
rescue
|
|
76
|
+
# An error getting the non-visible text (if element goes out of scope) should not affect the response
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
message << ". (However, #{details_message.join(' and ')}.)" unless details_message.empty?
|
|
81
|
+
|
|
82
|
+
message
|
|
34
83
|
end
|
|
35
84
|
|
|
36
|
-
def
|
|
37
|
-
|
|
85
|
+
def valid_keys
|
|
86
|
+
COUNT_KEYS + [:wait, :exact]
|
|
38
87
|
end
|
|
39
88
|
|
|
40
|
-
|
|
89
|
+
def check_visible_text?
|
|
90
|
+
@type == :visible
|
|
91
|
+
end
|
|
41
92
|
|
|
42
|
-
def
|
|
43
|
-
|
|
93
|
+
def text(node, query_type)
|
|
94
|
+
Capybara::Helpers.normalize_whitespace(node.text(query_type))
|
|
44
95
|
end
|
|
45
96
|
end
|
|
46
97
|
end
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
module Capybara
|
|
2
3
|
# @api private
|
|
3
4
|
module Queries
|
|
@@ -5,10 +6,11 @@ module Capybara
|
|
|
5
6
|
def initialize(expected_title, options = {})
|
|
6
7
|
@expected_title = expected_title
|
|
7
8
|
@options = options
|
|
9
|
+
super(@options)
|
|
8
10
|
unless @expected_title.is_a?(Regexp)
|
|
9
11
|
@expected_title = Capybara::Helpers.normalize_whitespace(@expected_title)
|
|
10
12
|
end
|
|
11
|
-
@search_regexp = Capybara::Helpers.to_regexp(@expected_title)
|
|
13
|
+
@search_regexp = Capybara::Helpers.to_regexp(@expected_title, nil, options.fetch(:exact, false))
|
|
12
14
|
assert_valid_keys
|
|
13
15
|
end
|
|
14
16
|
|
|
@@ -33,7 +35,7 @@ module Capybara
|
|
|
33
35
|
end
|
|
34
36
|
|
|
35
37
|
def valid_keys
|
|
36
|
-
[:wait]
|
|
38
|
+
[:wait, :exact]
|
|
37
39
|
end
|
|
38
40
|
end
|
|
39
41
|
end
|