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
@@ -1,13 +1,30 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
class Capybara::Selenium::Node < Capybara::Driver::Node
|
4
|
+
SET_FORMATS = Hash.new(date: '%Y-%m-%d', time: '%H:%M', datetime: "%m%d%Y\t%I%M%P").merge(
|
5
|
+
firefox: {
|
6
|
+
date: '%Y-%m-%d',
|
7
|
+
time: '%H:%M',
|
8
|
+
datetime: "%m%d%Y\t%I%M%P"
|
9
|
+
},
|
10
|
+
chrome: {
|
11
|
+
date: '%m%d%Y',
|
12
|
+
time: '%I%M%P',
|
13
|
+
datetime: "%m%d%Y\t%I%M%P"
|
14
|
+
}
|
15
|
+
)
|
16
|
+
|
3
17
|
def visible_text
|
4
|
-
|
5
|
-
Capybara::Helpers.normalize_whitespace(native.text)
|
18
|
+
native.text
|
6
19
|
end
|
7
20
|
|
8
21
|
def all_text
|
9
22
|
text = driver.execute_script("return arguments[0].textContent", self)
|
10
|
-
|
23
|
+
text.gsub(/[\u200b\u200e\u200f]/, '')
|
24
|
+
.gsub(/[\ \n\f\t\v\u2028\u2029]+/, ' ')
|
25
|
+
.gsub(/\A[[:space:]&&[^\u00a0]]+/, "")
|
26
|
+
.gsub(/[[:space:]&&[^\u00a0]]+\z/, "")
|
27
|
+
.tr("\u00a0", ' ')
|
11
28
|
end
|
12
29
|
|
13
30
|
def [](name)
|
@@ -35,57 +52,31 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
35
52
|
# :none => append the new value to the existing value <br/>
|
36
53
|
# :backspace => send backspace keystrokes to clear the field <br/>
|
37
54
|
# Array => an array of keys to send before the value being set, e.g. [[:command, 'a'], :backspace]
|
38
|
-
def set(value, options
|
39
|
-
|
40
|
-
type = self[:type]
|
41
|
-
|
42
|
-
if (Array === value) && !multiple?
|
43
|
-
raise ArgumentError.new "Value cannot be an Array when 'multiple' attribute is not present. Not a #{value.class}"
|
44
|
-
end
|
55
|
+
def set(value, **options)
|
56
|
+
raise ArgumentError, "Value cannot be an Array when 'multiple' attribute is not present. Not a #{value.class}" if value.is_a?(Array) && !multiple?
|
45
57
|
|
46
58
|
case tag_name
|
47
59
|
when 'input'
|
48
|
-
case type
|
60
|
+
case self[:type]
|
49
61
|
when 'radio'
|
50
62
|
click
|
51
63
|
when 'checkbox'
|
52
|
-
click if value ^
|
64
|
+
click if value ^ checked?
|
53
65
|
when 'file'
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
66
|
+
set_file(value)
|
67
|
+
when 'date'
|
68
|
+
set_date(value)
|
69
|
+
when 'time'
|
70
|
+
set_time(value)
|
71
|
+
when 'datetime-local'
|
72
|
+
set_datetime_local(value)
|
60
73
|
else
|
61
74
|
set_text(value, options)
|
62
75
|
end
|
63
76
|
when 'textarea'
|
64
77
|
set_text(value, options)
|
65
78
|
else
|
66
|
-
if content_editable?
|
67
|
-
#ensure we are focused on the element
|
68
|
-
click
|
69
|
-
|
70
|
-
script = <<-JS
|
71
|
-
var range = document.createRange();
|
72
|
-
var sel = window.getSelection();
|
73
|
-
arguments[0].focus();
|
74
|
-
range.selectNodeContents(arguments[0]);
|
75
|
-
sel.removeAllRanges();
|
76
|
-
sel.addRange(range);
|
77
|
-
JS
|
78
|
-
driver.execute_script script, self
|
79
|
-
|
80
|
-
if (driver.chrome?) || (driver.firefox? && !driver.marionette?)
|
81
|
-
# chromedriver raises a can't focus element for child elements if we use native.send_keys
|
82
|
-
# we've already focused it so just use action api
|
83
|
-
driver.browser.action.send_keys(value.to_s).perform
|
84
|
-
else
|
85
|
-
# action api is really slow here just use native.send_keys
|
86
|
-
native.send_keys(value.to_s)
|
87
|
-
end
|
88
|
-
end
|
79
|
+
set_content_editable(value) if content_editable?
|
89
80
|
end
|
90
81
|
end
|
91
82
|
|
@@ -94,20 +85,57 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
94
85
|
end
|
95
86
|
|
96
87
|
def unselect_option
|
97
|
-
raise Capybara::UnselectNotAllowed, "Cannot unselect option from single select box."
|
88
|
+
raise Capybara::UnselectNotAllowed, "Cannot unselect option from single select box." unless select_node.multiple?
|
98
89
|
native.click if selected?
|
99
90
|
end
|
100
91
|
|
101
|
-
def click
|
102
|
-
|
92
|
+
def click(keys = [], options = {})
|
93
|
+
if keys.empty? && !(options[:x] && options[:y])
|
94
|
+
native.click
|
95
|
+
else
|
96
|
+
scroll_if_needed do
|
97
|
+
action_with_modifiers(keys, options) do |a|
|
98
|
+
if options[:x] && options[:y]
|
99
|
+
a.click
|
100
|
+
else
|
101
|
+
a.click(native)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
rescue => e
|
107
|
+
if e.is_a?(::Selenium::WebDriver::Error::ElementClickInterceptedError) ||
|
108
|
+
e.message =~ /Other element would receive the click/
|
109
|
+
begin
|
110
|
+
driver.execute_script("arguments[0].scrollIntoView({behavior: 'instant', block: 'center', inline: 'center'})", self)
|
111
|
+
rescue # Swallow error if scrollIntoView with options isn't supported
|
112
|
+
end
|
113
|
+
end
|
114
|
+
raise e
|
103
115
|
end
|
104
116
|
|
105
|
-
def right_click
|
106
|
-
|
117
|
+
def right_click(keys = [], options = {})
|
118
|
+
scroll_if_needed do
|
119
|
+
action_with_modifiers(keys, options) do |a|
|
120
|
+
if options[:x] && options[:y]
|
121
|
+
a.context_click
|
122
|
+
else
|
123
|
+
a.context_click(native)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
107
127
|
end
|
108
128
|
|
109
|
-
def double_click
|
110
|
-
|
129
|
+
def double_click(keys = [], options = {})
|
130
|
+
scroll_if_needed do
|
131
|
+
action_with_modifiers(keys, options) do |a|
|
132
|
+
if options[:x] && options[:y]
|
133
|
+
a.double_click
|
134
|
+
else
|
135
|
+
a.double_click(native)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
111
139
|
end
|
112
140
|
|
113
141
|
def send_keys(*args)
|
@@ -115,51 +143,38 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
115
143
|
end
|
116
144
|
|
117
145
|
def hover
|
118
|
-
driver.browser.action.move_to(native).perform
|
146
|
+
scroll_if_needed { driver.browser.action.move_to(native).perform }
|
119
147
|
end
|
120
148
|
|
121
149
|
def drag_to(element)
|
122
|
-
driver.browser.action.drag_and_drop(native, element.native).perform
|
150
|
+
scroll_if_needed { driver.browser.action.drag_and_drop(native, element.native).perform }
|
123
151
|
end
|
124
152
|
|
125
153
|
def tag_name
|
126
154
|
native.tag_name.downcase
|
127
155
|
end
|
128
156
|
|
129
|
-
def visible?
|
130
|
-
|
131
|
-
|
132
|
-
end
|
133
|
-
|
134
|
-
def selected?
|
135
|
-
selected = native.selected?
|
136
|
-
selected and selected != "false"
|
137
|
-
end
|
157
|
+
def visible?; boolean_attr(native.displayed?); end
|
158
|
+
def readonly?; boolean_attr(self[:readonly]); end
|
159
|
+
def multiple?; boolean_attr(self[:multiple]); end
|
160
|
+
def selected?; boolean_attr(native.selected?); end
|
138
161
|
alias :checked? :selected?
|
139
162
|
|
140
163
|
def disabled?
|
164
|
+
return true unless native.enabled?
|
165
|
+
|
141
166
|
# workaround for selenium-webdriver/geckodriver reporting elements as enabled when they are nested in disabling elements
|
142
167
|
if driver.marionette?
|
143
|
-
if %w
|
144
|
-
|
168
|
+
if %w[option optgroup].include? tag_name
|
169
|
+
find_xpath("parent::*[self::optgroup or self::select]")[0].disabled?
|
145
170
|
else
|
146
|
-
!
|
171
|
+
!find_xpath("parent::fieldset[@disabled] | ancestor::*[not(self::legend) or preceding-sibling::legend][parent::fieldset[@disabled]]").empty?
|
147
172
|
end
|
148
173
|
else
|
149
|
-
|
174
|
+
false
|
150
175
|
end
|
151
176
|
end
|
152
177
|
|
153
|
-
def readonly?
|
154
|
-
readonly = self[:readonly]
|
155
|
-
readonly and readonly != "false"
|
156
|
-
end
|
157
|
-
|
158
|
-
def multiple?
|
159
|
-
multiple = self[:multiple]
|
160
|
-
multiple and multiple != "false"
|
161
|
-
end
|
162
|
-
|
163
178
|
def content_editable?
|
164
179
|
native.attribute('isContentEditable')
|
165
180
|
end
|
@@ -177,56 +192,155 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
177
192
|
end
|
178
193
|
|
179
194
|
def path
|
180
|
-
path = find_xpath(
|
181
|
-
path.unshift self
|
195
|
+
path = find_xpath(XPath.ancestor_or_self).reverse
|
182
196
|
|
183
197
|
result = []
|
184
|
-
while node = path.shift
|
198
|
+
while (node = path.shift)
|
185
199
|
parent = path.first
|
186
|
-
|
200
|
+
selector = node.tag_name
|
187
201
|
if parent
|
188
202
|
siblings = parent.find_xpath(node.tag_name)
|
189
|
-
|
190
|
-
result.unshift node.tag_name
|
191
|
-
else
|
192
|
-
index = siblings.index(node)
|
193
|
-
result.unshift "#{node.tag_name}[#{index+1}]"
|
194
|
-
end
|
195
|
-
else
|
196
|
-
result.unshift node.tag_name
|
203
|
+
selector += "[#{siblings.index(node) + 1}]" unless siblings.size == 1
|
197
204
|
end
|
205
|
+
result.push selector
|
198
206
|
end
|
199
207
|
|
200
|
-
'/' + result.join('/')
|
208
|
+
'/' + result.reverse.join('/')
|
201
209
|
end
|
202
210
|
|
203
211
|
private
|
212
|
+
|
213
|
+
def boolean_attr(val)
|
214
|
+
val and val != "false"
|
215
|
+
end
|
216
|
+
|
204
217
|
# a reference to the select node if this is an option node
|
205
218
|
def select_node
|
206
|
-
find_xpath(
|
219
|
+
find_xpath(XPath.ancestor(:select)[1]).first
|
220
|
+
end
|
221
|
+
|
222
|
+
def set_text(value, clear: nil, **_unused)
|
223
|
+
if value.to_s.empty? && clear.nil?
|
224
|
+
native.clear
|
225
|
+
elsif clear == :backspace
|
226
|
+
# Clear field by sending the correct number of backspace keys.
|
227
|
+
backspaces = [:backspace] * self.value.to_s.length
|
228
|
+
native.send_keys(*(backspaces + [value.to_s]))
|
229
|
+
elsif clear == :none
|
230
|
+
native.send_keys(value.to_s)
|
231
|
+
elsif clear.is_a? Array
|
232
|
+
native.send_keys(*clear, value.to_s)
|
233
|
+
else
|
234
|
+
# Clear field by JavaScript assignment of the value property.
|
235
|
+
# Script can change a readonly element which user input cannot, so
|
236
|
+
# don't execute if readonly.
|
237
|
+
driver.execute_script "arguments[0].value = ''", self
|
238
|
+
native.send_keys(value.to_s)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
def scroll_if_needed
|
243
|
+
yield
|
244
|
+
rescue ::Selenium::WebDriver::Error::MoveTargetOutOfBoundsError
|
245
|
+
script = <<-'JS'
|
246
|
+
try {
|
247
|
+
arguments[0].scrollIntoView({behavior: 'instant', block: 'center', inline: 'center'});
|
248
|
+
} catch(e) {
|
249
|
+
arguments[0].scrollIntoView(true);
|
250
|
+
}
|
251
|
+
JS
|
252
|
+
driver.execute_script(script, self)
|
253
|
+
yield
|
254
|
+
end
|
255
|
+
|
256
|
+
def set_date(value) # rubocop:disable Naming/AccessorMethodName
|
257
|
+
if value.respond_to?(:to_date)
|
258
|
+
set_text(value.to_date.strftime(SET_FORMATS[driver.options[:browser].to_sym][:date]))
|
259
|
+
else
|
260
|
+
set_text(value)
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
def set_time(value) # rubocop:disable Naming/AccessorMethodName
|
265
|
+
if value.respond_to?(:to_time)
|
266
|
+
set_text(value.to_time.strftime(SET_FORMATS[driver.options[:browser].to_sym][:time]))
|
267
|
+
else
|
268
|
+
set_text(value)
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
def set_datetime_local(value) # rubocop:disable Naming/AccessorMethodName
|
273
|
+
if value.respond_to?(:to_time)
|
274
|
+
set_text(value.to_time.strftime(SET_FORMATS[driver.options[:browser].to_sym][:datetime]))
|
275
|
+
else
|
276
|
+
set_text(value)
|
277
|
+
end
|
207
278
|
end
|
208
279
|
|
209
|
-
def
|
210
|
-
|
211
|
-
|
212
|
-
elsif value.to_s.empty?
|
280
|
+
def set_file(value) # rubocop:disable Naming/AccessorMethodName
|
281
|
+
path_names = value.to_s.empty? ? [] : value
|
282
|
+
if driver.marionette?
|
213
283
|
native.clear
|
284
|
+
Array(path_names).each { |p| native.send_keys(p) }
|
214
285
|
else
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
286
|
+
native.send_keys(Array(path_names).join("\n"))
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
def set_content_editable(value) # rubocop:disable Naming/AccessorMethodName
|
291
|
+
# Ensure we are focused on the element
|
292
|
+
click
|
293
|
+
|
294
|
+
script = <<-JS
|
295
|
+
var range = document.createRange();
|
296
|
+
var sel = window.getSelection();
|
297
|
+
arguments[0].focus();
|
298
|
+
range.selectNodeContents(arguments[0]);
|
299
|
+
sel.removeAllRanges();
|
300
|
+
sel.addRange(range);
|
301
|
+
JS
|
302
|
+
driver.execute_script script, self
|
303
|
+
|
304
|
+
# The action api has a speed problem but both chrome and firefox 58 raise errors
|
305
|
+
# if we use the faster direct send_keys. For now just send_keys to the element
|
306
|
+
# we've already focused.
|
307
|
+
# native.send_keys(value.to_s)
|
308
|
+
driver.browser.action.send_keys(value.to_s).perform
|
309
|
+
end
|
310
|
+
|
311
|
+
def action_with_modifiers(keys, x: nil, y: nil)
|
312
|
+
actions = driver.browser.action
|
313
|
+
actions.move_to(native, x, y)
|
314
|
+
modifiers_down(actions, keys)
|
315
|
+
yield actions
|
316
|
+
modifiers_up(actions, keys)
|
317
|
+
actions.perform
|
318
|
+
ensure
|
319
|
+
a = driver.browser.action
|
320
|
+
a.release_actions if a.respond_to?(:release_actions)
|
321
|
+
end
|
322
|
+
|
323
|
+
def modifiers_down(actions, keys)
|
324
|
+
keys.each do |key|
|
325
|
+
key = case key
|
326
|
+
when :ctrl then :control
|
327
|
+
when :command, :cmd then :meta
|
328
|
+
else
|
329
|
+
key
|
330
|
+
end
|
331
|
+
actions.key_down(key)
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
def modifiers_up(actions, keys)
|
336
|
+
keys.each do |key|
|
337
|
+
key = case key
|
338
|
+
when :ctrl then :control
|
339
|
+
when :command, :cmd then :meta
|
223
340
|
else
|
224
|
-
|
225
|
-
# Script can change a readonly element which user input cannot, so
|
226
|
-
# don't execute if readonly.
|
227
|
-
driver.execute_script "arguments[0].value = ''", self
|
228
|
-
native.send_keys(value.to_s)
|
341
|
+
key
|
229
342
|
end
|
343
|
+
actions.key_up(key)
|
230
344
|
end
|
231
345
|
end
|
232
346
|
end
|
data/lib/capybara/server.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'uri'
|
3
4
|
require 'net/http'
|
4
5
|
require 'rack'
|
@@ -43,7 +44,7 @@ module Capybara
|
|
43
44
|
begin
|
44
45
|
@app.call(env)
|
45
46
|
rescue *@server_errors => e
|
46
|
-
@error
|
47
|
+
@error ||= e
|
47
48
|
raise e
|
48
49
|
ensure
|
49
50
|
@counter.decrement
|
@@ -60,7 +61,7 @@ module Capybara
|
|
60
61
|
|
61
62
|
attr_reader :app, :port, :host
|
62
63
|
|
63
|
-
def initialize(app, port=Capybara.server_port, host=Capybara.server_host, server_errors=Capybara.server_errors)
|
64
|
+
def initialize(app, port = Capybara.server_port, host = Capybara.server_host, server_errors = Capybara.server_errors)
|
64
65
|
@app = app
|
65
66
|
@server_thread = nil # suppress warnings
|
66
67
|
@host, @port, @server_errors = host, port, server_errors
|
@@ -1,60 +1,55 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'delegate'
|
3
4
|
|
4
5
|
module Capybara
|
5
6
|
class SessionConfig
|
6
|
-
OPTIONS = [
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
OPTIONS = %i[always_include_port run_server default_selector default_max_wait_time ignore_hidden_elements
|
8
|
+
automatic_reload match exact exact_text raise_server_errors visible_text_only
|
9
|
+
automatic_label_click enable_aria_label save_path asset_host default_host app_host
|
10
|
+
server_host server_port server_errors].freeze
|
10
11
|
|
11
12
|
attr_accessor(*OPTIONS)
|
12
13
|
|
13
14
|
##
|
14
|
-
|
15
|
-
#
|
16
|
-
|
17
|
-
#
|
18
|
-
|
19
|
-
#
|
20
|
-
|
21
|
-
#
|
22
|
-
|
23
|
-
#
|
24
|
-
|
25
|
-
#
|
26
|
-
|
27
|
-
#
|
28
|
-
|
29
|
-
#
|
30
|
-
|
31
|
-
#
|
32
|
-
|
33
|
-
#
|
34
|
-
|
35
|
-
#
|
36
|
-
|
37
|
-
#
|
38
|
-
|
39
|
-
#
|
40
|
-
|
41
|
-
#
|
42
|
-
|
43
|
-
#
|
44
|
-
|
45
|
-
#
|
46
|
-
|
47
|
-
#
|
48
|
-
|
49
|
-
#
|
50
|
-
|
51
|
-
#
|
52
|
-
#@!method server_host
|
53
|
-
# See {Capybara.configure}
|
54
|
-
#@!method server_port
|
55
|
-
# See {Capybara.configure}
|
56
|
-
#@!method server_errors
|
57
|
-
# See {Capybara.configure}
|
15
|
+
# @!method always_include_port
|
16
|
+
# See {Capybara.configure}
|
17
|
+
# @!method run_server
|
18
|
+
# See {Capybara.configure}
|
19
|
+
# @!method default_selector
|
20
|
+
# See {Capybara.configure}
|
21
|
+
# @!method default_max_wait_time
|
22
|
+
# See {Capybara.configure}
|
23
|
+
# @!method ignore_hidden_elements
|
24
|
+
# See {Capybara.configure}
|
25
|
+
# @!method automatic_reload
|
26
|
+
# See {Capybara.configure}
|
27
|
+
# @!method match
|
28
|
+
# See {Capybara.configure}
|
29
|
+
# @!method exact
|
30
|
+
# See {Capybara.configure}
|
31
|
+
# @!method raise_server_errors
|
32
|
+
# See {Capybara.configure}
|
33
|
+
# @!method visible_text_only
|
34
|
+
# See {Capybara.configure}
|
35
|
+
# @!method automatic_label_click
|
36
|
+
# See {Capybara.configure}
|
37
|
+
# @!method enable_aria_label
|
38
|
+
# See {Capybara.configure}
|
39
|
+
# @!method save_path
|
40
|
+
# See {Capybara.configure}
|
41
|
+
# @!method asset_host
|
42
|
+
# See {Capybara.configure}
|
43
|
+
# @!method default_host
|
44
|
+
# See {Capybara.configure}
|
45
|
+
# @!method app_host
|
46
|
+
# See {Capybara.configure}
|
47
|
+
# @!method server_host
|
48
|
+
# See {Capybara.configure}
|
49
|
+
# @!method server_port
|
50
|
+
# See {Capybara.configure}
|
51
|
+
# @!method server_errors
|
52
|
+
# See {Capybara.configure}
|
58
53
|
|
59
54
|
remove_method :server_host
|
60
55
|
|
@@ -73,23 +68,16 @@ module Capybara
|
|
73
68
|
|
74
69
|
remove_method :app_host=
|
75
70
|
def app_host=(url)
|
76
|
-
raise ArgumentError
|
71
|
+
raise ArgumentError, "Capybara.app_host should be set to a url (http://www.example.com). Attempted to set #{url.inspect}." if url && url !~ URI::DEFAULT_PARSER.make_regexp
|
77
72
|
@app_host = url
|
78
73
|
end
|
79
74
|
|
80
75
|
remove_method :default_host=
|
81
76
|
def default_host=(url)
|
82
|
-
raise ArgumentError
|
77
|
+
raise ArgumentError, "Capybara.default_host should be set to a url (http://www.example.com). Attempted to set #{url.inspect}." if url && url !~ URI::DEFAULT_PARSER.make_regexp
|
83
78
|
@default_host = url
|
84
79
|
end
|
85
80
|
|
86
|
-
remove_method :save_and_open_page_path=
|
87
|
-
def save_and_open_page_path=(path)
|
88
|
-
warn "DEPRECATED: #save_and_open_page_path is deprecated, please use #save_path instead. \n"\
|
89
|
-
"Note: Behavior is slightly different with relative paths - see documentation" unless path.nil?
|
90
|
-
@save_and_open_page_path = path
|
91
|
-
end
|
92
|
-
|
93
81
|
def initialize_copy(other)
|
94
82
|
super
|
95
83
|
@server_errors = @server_errors.dup
|
@@ -98,9 +86,9 @@ module Capybara
|
|
98
86
|
|
99
87
|
class ReadOnlySessionConfig < SimpleDelegator
|
100
88
|
SessionConfig::OPTIONS.each do |m|
|
101
|
-
define_method "#{m}=" do |
|
89
|
+
define_method "#{m}=" do |_|
|
102
90
|
raise "Per session settings are only supported when Capybara.threadsafe == true"
|
103
91
|
end
|
104
92
|
end
|
105
93
|
end
|
106
|
-
end
|
94
|
+
end
|
@@ -1,45 +1,52 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Capybara
|
3
4
|
module SessionMatchers
|
4
5
|
##
|
5
6
|
# Asserts that the page has the given path.
|
6
|
-
# By default this will compare against the
|
7
|
+
# By default, if passed a full url this will compare against the full url,
|
8
|
+
# if passed a path only the path+query portion will be compared, if passed a regexp
|
9
|
+
# the comparison will depend on the :url option
|
7
10
|
#
|
8
11
|
# @!macro current_path_query_params
|
9
12
|
# @overload $0(string, options = {})
|
10
13
|
# @param string [String] The string that the current 'path' should equal
|
11
14
|
# @overload $0(regexp, options = {})
|
12
15
|
# @param regexp [Regexp] The regexp that the current 'path' should match to
|
13
|
-
# @option options [
|
14
|
-
# @option options [Boolean] :
|
15
|
-
# @option options [
|
16
|
+
# @option options [Boolean] :url (true if `string` ia a full url, otherwise false) Whether the compare should be done against the full current url or just the path
|
17
|
+
# @option options [Boolean] :ignore_query (false) Whether the query portion of the current url/path should be ignored
|
18
|
+
# @option options [Numeric] :wait (Capybara.default_max_wait_time) Maximum time that Capybara will wait for the current url/path to eq/match given string/regexp argument
|
16
19
|
# @raise [Capybara::ExpectationNotMet] if the assertion hasn't succeeded during wait time
|
17
20
|
# @return [true]
|
18
21
|
#
|
19
|
-
def assert_current_path(path, options
|
20
|
-
_verify_current_path(path,options) { |query| raise Capybara::ExpectationNotMet, query.failure_message unless query.resolves_for?(self) }
|
22
|
+
def assert_current_path(path, **options)
|
23
|
+
_verify_current_path(path, options) { |query| raise Capybara::ExpectationNotMet, query.failure_message unless query.resolves_for?(self) }
|
21
24
|
end
|
22
25
|
|
23
26
|
##
|
24
27
|
# Asserts that the page doesn't have the given path.
|
25
|
-
# By default this will compare against the
|
28
|
+
# By default, if passed a full url this will compare against the full url,
|
29
|
+
# if passed a path only the path+query portion will be compared, if passed a regexp
|
30
|
+
# the comparison will depend on the :url option
|
26
31
|
#
|
27
32
|
# @macro current_path_query_params
|
28
33
|
# @raise [Capybara::ExpectationNotMet] if the assertion hasn't succeeded during wait time
|
29
34
|
# @return [true]
|
30
35
|
#
|
31
|
-
def assert_no_current_path(path, options
|
32
|
-
_verify_current_path(path,options) { |query| raise Capybara::ExpectationNotMet, query.negative_failure_message if query.resolves_for?(self) }
|
36
|
+
def assert_no_current_path(path, **options)
|
37
|
+
_verify_current_path(path, options) { |query| raise Capybara::ExpectationNotMet, query.negative_failure_message if query.resolves_for?(self) }
|
33
38
|
end
|
34
39
|
|
35
40
|
##
|
36
41
|
# Checks if the page has the given path.
|
37
|
-
# By default this will compare against the
|
42
|
+
# By default, if passed a full url this will compare against the full url,
|
43
|
+
# if passed a path only the path+query portion will be compared, if passed a regexp
|
44
|
+
# the comparison will depend on the :url option
|
38
45
|
#
|
39
46
|
# @macro current_path_query_params
|
40
47
|
# @return [Boolean]
|
41
48
|
#
|
42
|
-
def has_current_path?(path, options
|
49
|
+
def has_current_path?(path, **options)
|
43
50
|
assert_current_path(path, options)
|
44
51
|
rescue Capybara::ExpectationNotMet
|
45
52
|
return false
|
@@ -47,18 +54,20 @@ module Capybara
|
|
47
54
|
|
48
55
|
##
|
49
56
|
# Checks if the page doesn't have the given path.
|
50
|
-
# By default this will compare against the
|
57
|
+
# By default, if passed a full url this will compare against the full url,
|
58
|
+
# if passed a path only the path+query portion will be compared, if passed a regexp
|
59
|
+
# the comparison will depend on the :url option
|
51
60
|
#
|
52
61
|
# @macro current_path_query_params
|
53
62
|
# @return [Boolean]
|
54
63
|
#
|
55
|
-
def has_no_current_path?(path, options
|
64
|
+
def has_no_current_path?(path, **options)
|
56
65
|
assert_no_current_path(path, options)
|
57
66
|
rescue Capybara::ExpectationNotMet
|
58
67
|
return false
|
59
68
|
end
|
60
69
|
|
61
|
-
|
70
|
+
private
|
62
71
|
|
63
72
|
def _verify_current_path(path, options)
|
64
73
|
query = Capybara::Queries::CurrentPathQuery.new(path, options)
|