capybara 3.35.3 → 3.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/History.md +31 -1
- data/README.md +5 -1
- data/lib/capybara/config.rb +16 -4
- data/lib/capybara/driver/base.rb +4 -0
- data/lib/capybara/driver/node.rb +1 -1
- data/lib/capybara/helpers.rb +2 -11
- data/lib/capybara/node/actions.rb +10 -5
- data/lib/capybara/node/document.rb +2 -2
- data/lib/capybara/node/element.rb +1 -1
- data/lib/capybara/node/finders.rb +1 -1
- data/lib/capybara/node/simple.rb +5 -1
- data/lib/capybara/queries/active_element_query.rb +18 -0
- data/lib/capybara/queries/ancestor_query.rb +2 -1
- data/lib/capybara/queries/current_path_query.rb +1 -1
- data/lib/capybara/queries/selector_query.rb +14 -1
- data/lib/capybara/queries/sibling_query.rb +2 -1
- data/lib/capybara/rack_test/node.rb +9 -6
- data/lib/capybara/registrations/drivers.rb +3 -3
- data/lib/capybara/rspec/matcher_proxies.rb +3 -3
- data/lib/capybara/rspec/matchers/have_selector.rb +1 -1
- data/lib/capybara/selector/builders/css_builder.rb +1 -1
- data/lib/capybara/selector/builders/xpath_builder.rb +1 -1
- data/lib/capybara/selector/css.rb +1 -1
- data/lib/capybara/selector/definition/button.rb +9 -4
- data/lib/capybara/selector/definition/checkbox.rb +1 -1
- data/lib/capybara/selector/definition/file_field.rb +1 -1
- data/lib/capybara/selector/definition/fillable_field.rb +1 -1
- data/lib/capybara/selector/definition/radio_button.rb +1 -1
- data/lib/capybara/selector/definition.rb +2 -1
- data/lib/capybara/selector/filter_set.rb +4 -6
- data/lib/capybara/selector.rb +1 -0
- data/lib/capybara/selenium/driver.rb +19 -9
- data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +1 -1
- data/lib/capybara/selenium/driver_specializations/edge_driver.rb +1 -1
- data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +1 -1
- data/lib/capybara/selenium/node.rb +10 -8
- data/lib/capybara/selenium/nodes/chrome_node.rb +1 -1
- data/lib/capybara/selenium/nodes/edge_node.rb +1 -1
- data/lib/capybara/selenium/nodes/firefox_node.rb +1 -1
- data/lib/capybara/selenium/nodes/safari_node.rb +2 -2
- data/lib/capybara/server/animation_disabler.rb +15 -9
- data/lib/capybara/session.rb +12 -2
- data/lib/capybara/spec/session/active_element_spec.rb +31 -0
- data/lib/capybara/spec/session/all_spec.rb +4 -6
- data/lib/capybara/spec/session/check_spec.rb +9 -0
- data/lib/capybara/spec/session/choose_spec.rb +6 -0
- data/lib/capybara/spec/session/has_any_selectors_spec.rb +4 -0
- data/lib/capybara/spec/session/has_button_spec.rb +24 -0
- data/lib/capybara/spec/session/has_field_spec.rb +24 -0
- data/lib/capybara/spec/session/has_link_spec.rb +24 -0
- data/lib/capybara/spec/session/has_text_spec.rb +1 -1
- data/lib/capybara/spec/session/node_spec.rb +1 -1
- data/lib/capybara/spec/session/scroll_spec.rb +4 -4
- data/lib/capybara/spec/spec_helper.rb +4 -3
- data/lib/capybara/spec/test_app.rb +17 -8
- data/lib/capybara/spec/views/animated.erb +1 -1
- data/lib/capybara/spec/views/form.erb +11 -3
- data/lib/capybara/spec/views/frame_child.erb +1 -1
- data/lib/capybara/spec/views/frame_one.erb +1 -1
- data/lib/capybara/spec/views/frame_parent.erb +1 -1
- data/lib/capybara/spec/views/frame_two.erb +1 -1
- data/lib/capybara/spec/views/initial_alert.erb +2 -1
- data/lib/capybara/spec/views/layout.erb +10 -0
- data/lib/capybara/spec/views/obscured.erb +1 -1
- data/lib/capybara/spec/views/offset.erb +2 -1
- data/lib/capybara/spec/views/path.erb +2 -2
- data/lib/capybara/spec/views/popup_one.erb +1 -1
- data/lib/capybara/spec/views/popup_two.erb +1 -1
- data/lib/capybara/spec/views/react.erb +2 -2
- data/lib/capybara/spec/views/scroll.erb +2 -1
- data/lib/capybara/spec/views/spatial.erb +1 -1
- data/lib/capybara/spec/views/with_animation.erb +2 -3
- data/lib/capybara/spec/views/with_base_tag.erb +2 -2
- data/lib/capybara/spec/views/with_dragula.erb +2 -2
- data/lib/capybara/spec/views/with_fixed_header_footer.erb +2 -1
- data/lib/capybara/spec/views/with_hover.erb +2 -2
- data/lib/capybara/spec/views/with_html.erb +1 -1
- data/lib/capybara/spec/views/with_jquery_animation.erb +1 -1
- data/lib/capybara/spec/views/with_js.erb +2 -3
- data/lib/capybara/spec/views/with_jstree.erb +1 -1
- data/lib/capybara/spec/views/with_namespace.erb +1 -0
- data/lib/capybara/spec/views/with_slow_unload.erb +2 -1
- data/lib/capybara/spec/views/with_sortable_js.erb +2 -2
- data/lib/capybara/spec/views/with_unload_alert.erb +1 -0
- data/lib/capybara/spec/views/with_windows.erb +1 -1
- data/lib/capybara/spec/views/within_frames.erb +1 -1
- data/lib/capybara/version.rb +1 -1
- data/lib/capybara.rb +18 -22
- data/spec/basic_node_spec.rb +16 -3
- data/spec/dsl_spec.rb +1 -1
- data/spec/fixtures/selenium_driver_rspec_success.rb +1 -1
- data/spec/rack_test_spec.rb +14 -10
- data/spec/result_spec.rb +5 -6
- data/spec/rspec/features_spec.rb +1 -1
- data/spec/rspec/shared_spec_matchers.rb +2 -2
- data/spec/selenium_spec_chrome.rb +8 -9
- data/spec/selenium_spec_chrome_remote.rb +9 -8
- data/spec/selenium_spec_firefox.rb +4 -3
- data/spec/selenium_spec_firefox_remote.rb +2 -2
- data/spec/selenium_spec_ie.rb +3 -6
- data/spec/selenium_spec_safari.rb +27 -19
- data/spec/shared_selenium_node.rb +0 -4
- data/spec/shared_selenium_session.rb +11 -8
- metadata +35 -14
- data/lib/capybara/spec/views/with_title.erb +0 -5
|
@@ -43,7 +43,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
|
43
43
|
Gem::Version.new(Selenium::WebDriver::VERSION)
|
|
44
44
|
end
|
|
45
45
|
|
|
46
|
-
unless Gem::Requirement.new('>= 3.
|
|
46
|
+
unless Gem::Requirement.new('>= 3.142.7').satisfied_by? @selenium_webdriver_version
|
|
47
47
|
warn "Warning: You're using an unsupported version of selenium-webdriver, please upgrade."
|
|
48
48
|
end
|
|
49
49
|
|
|
@@ -148,8 +148,13 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
|
148
148
|
unwrap_script_result(result)
|
|
149
149
|
end
|
|
150
150
|
|
|
151
|
+
def active_element
|
|
152
|
+
build_node(native_active_element)
|
|
153
|
+
end
|
|
154
|
+
|
|
151
155
|
def send_keys(*args)
|
|
152
|
-
|
|
156
|
+
# Should this call the specialized nodes rather than native???
|
|
157
|
+
native_active_element.send_keys(*args)
|
|
153
158
|
end
|
|
154
159
|
|
|
155
160
|
def save_screenshot(path, **_options)
|
|
@@ -249,7 +254,13 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
|
249
254
|
end
|
|
250
255
|
|
|
251
256
|
def open_new_window(kind = :tab)
|
|
252
|
-
browser.
|
|
257
|
+
if browser.switch_to.respond_to?(:new_window)
|
|
258
|
+
handle = current_window_handle
|
|
259
|
+
browser.switch_to.new_window(kind)
|
|
260
|
+
switch_to_window(handle)
|
|
261
|
+
else
|
|
262
|
+
browser.manage.new_window(kind)
|
|
263
|
+
end
|
|
253
264
|
rescue NoMethodError, Selenium::WebDriver::Error::WebDriverError
|
|
254
265
|
# If not supported by the driver or browser default to using JS
|
|
255
266
|
browser.execute_script('window.open();')
|
|
@@ -293,7 +304,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
|
293
304
|
end
|
|
294
305
|
|
|
295
306
|
def invalid_element_errors
|
|
296
|
-
@invalid_element_errors ||=
|
|
307
|
+
@invalid_element_errors ||=
|
|
297
308
|
[
|
|
298
309
|
::Selenium::WebDriver::Error::StaleElementReferenceError,
|
|
299
310
|
::Selenium::WebDriver::Error::ElementNotInteractableError,
|
|
@@ -313,7 +324,6 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
|
313
324
|
end
|
|
314
325
|
end
|
|
315
326
|
end
|
|
316
|
-
end
|
|
317
327
|
end
|
|
318
328
|
|
|
319
329
|
def no_such_window_error
|
|
@@ -330,6 +340,10 @@ private
|
|
|
330
340
|
args.map { |arg| arg.is_a?(Capybara::Selenium::Node) ? arg.native : arg }
|
|
331
341
|
end
|
|
332
342
|
|
|
343
|
+
def native_active_element
|
|
344
|
+
browser.switch_to.active_element
|
|
345
|
+
end
|
|
346
|
+
|
|
333
347
|
def clear_browser_state
|
|
334
348
|
delete_all_cookies
|
|
335
349
|
clear_storage
|
|
@@ -475,10 +489,6 @@ private
|
|
|
475
489
|
browser
|
|
476
490
|
end
|
|
477
491
|
|
|
478
|
-
def active_element
|
|
479
|
-
browser.switch_to.active_element
|
|
480
|
-
end
|
|
481
|
-
|
|
482
492
|
def build_node(native_node, initial_cache = {})
|
|
483
493
|
::Capybara::Selenium::Node.new(self, native_node, initial_cache)
|
|
484
494
|
end
|
|
@@ -38,7 +38,7 @@ module Capybara::Selenium::Driver::ChromeDriver
|
|
|
38
38
|
return unless @browser
|
|
39
39
|
|
|
40
40
|
switch_to_window(window_handles.first)
|
|
41
|
-
window_handles.slice(1
|
|
41
|
+
window_handles.slice(1..).each { |win| close_window(win) }
|
|
42
42
|
return super if chromedriver_version < 73
|
|
43
43
|
|
|
44
44
|
timer = Capybara::Helpers.timer(expire_in: 10)
|
|
@@ -39,7 +39,7 @@ module Capybara::Selenium::Driver::EdgeDriver
|
|
|
39
39
|
return unless @browser
|
|
40
40
|
|
|
41
41
|
switch_to_window(window_handles.first)
|
|
42
|
-
window_handles.slice(1
|
|
42
|
+
window_handles.slice(1..).each { |win| close_window(win) }
|
|
43
43
|
|
|
44
44
|
timer = Capybara::Helpers.timer(expire_in: 10)
|
|
45
45
|
begin
|
|
@@ -14,7 +14,7 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def all_text
|
|
17
|
-
text = driver.evaluate_script('arguments[0].textContent', self)
|
|
17
|
+
text = driver.evaluate_script('arguments[0].textContent', self) || ''
|
|
18
18
|
text.gsub(/[\u200b\u200e\u200f]/, '')
|
|
19
19
|
.gsub(/[\ \n\f\t\v\u2028\u2029]+/, ' ')
|
|
20
20
|
.gsub(/\A[[:space:]&&[^\u00a0]]+/, '')
|
|
@@ -199,10 +199,6 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
|
199
199
|
native.attribute('isContentEditable') == 'true'
|
|
200
200
|
end
|
|
201
201
|
|
|
202
|
-
def ==(other)
|
|
203
|
-
native == other.native
|
|
204
|
-
end
|
|
205
|
-
|
|
206
202
|
def path
|
|
207
203
|
driver.evaluate_script GET_XPATH_SCRIPT, self
|
|
208
204
|
end
|
|
@@ -274,7 +270,7 @@ private
|
|
|
274
270
|
elsif clear == :backspace
|
|
275
271
|
# Clear field by sending the correct number of backspace keys.
|
|
276
272
|
backspaces = [:backspace] * self.value.to_s.length
|
|
277
|
-
send_keys(
|
|
273
|
+
send_keys(:end, *backspaces, value)
|
|
278
274
|
elsif clear.is_a? Array
|
|
279
275
|
send_keys(*clear, value)
|
|
280
276
|
else
|
|
@@ -282,7 +278,7 @@ private
|
|
|
282
278
|
if rapid == true || ((value.length > auto_rapid_set_length) && rapid != false)
|
|
283
279
|
send_keys(value[0..3])
|
|
284
280
|
driver.execute_script RAPID_APPEND_TEXT, self, value[4...-3]
|
|
285
|
-
send_keys(value[-3
|
|
281
|
+
send_keys(value[-3..])
|
|
286
282
|
else
|
|
287
283
|
send_keys(value)
|
|
288
284
|
end
|
|
@@ -298,7 +294,7 @@ private
|
|
|
298
294
|
|
|
299
295
|
scroll_if_needed do
|
|
300
296
|
action_with_modifiers(click_options) do |action|
|
|
301
|
-
if
|
|
297
|
+
if block
|
|
302
298
|
yield action
|
|
303
299
|
else
|
|
304
300
|
click_options.coords? ? action.click : action.click(native)
|
|
@@ -488,6 +484,12 @@ private
|
|
|
488
484
|
JS
|
|
489
485
|
end
|
|
490
486
|
|
|
487
|
+
def native_id
|
|
488
|
+
# Selenium 3 -> 4 changed the return of ref
|
|
489
|
+
type_or_id, id = native.ref
|
|
490
|
+
id || type_or_id
|
|
491
|
+
end
|
|
492
|
+
|
|
491
493
|
GET_XPATH_SCRIPT = <<~'JS'
|
|
492
494
|
(function(el, xml){
|
|
493
495
|
var xpath = '';
|
|
@@ -65,7 +65,7 @@ class Capybara::Selenium::ChromeNode < Capybara::Selenium::Node
|
|
|
65
65
|
return super unless native_displayed?
|
|
66
66
|
|
|
67
67
|
begin
|
|
68
|
-
bridge.send(:execute, :is_element_displayed, id:
|
|
68
|
+
bridge.send(:execute, :is_element_displayed, id: native_id)
|
|
69
69
|
rescue Selenium::WebDriver::Error::UnknownCommandError
|
|
70
70
|
# If the is_element_displayed command is unknown, no point in trying again
|
|
71
71
|
driver.options[:native_displayed] = false
|
|
@@ -69,7 +69,7 @@ class Capybara::Selenium::EdgeNode < Capybara::Selenium::Node
|
|
|
69
69
|
return super unless chrome_edge? && native_displayed?
|
|
70
70
|
|
|
71
71
|
begin
|
|
72
|
-
bridge.send(:execute, :is_element_displayed, id:
|
|
72
|
+
bridge.send(:execute, :is_element_displayed, id: native_id)
|
|
73
73
|
rescue Selenium::WebDriver::Error::UnknownCommandError
|
|
74
74
|
# If the is_element_displayed command is unknown, no point in trying again
|
|
75
75
|
driver.options[:native_displayed] = false
|
|
@@ -76,7 +76,7 @@ class Capybara::Selenium::FirefoxNode < Capybara::Selenium::Node
|
|
|
76
76
|
return super unless native_displayed?
|
|
77
77
|
|
|
78
78
|
begin
|
|
79
|
-
bridge.send(:execute, :is_element_displayed, id:
|
|
79
|
+
bridge.send(:execute, :is_element_displayed, id: native_id)
|
|
80
80
|
rescue Selenium::WebDriver::Error::UnknownCommandError
|
|
81
81
|
# If the is_element_displayed command is unknown, no point in trying again
|
|
82
82
|
driver.options[:native_displayed] = false
|
|
@@ -14,7 +14,7 @@ class Capybara::Selenium::SafariNode < Capybara::Selenium::Node
|
|
|
14
14
|
warn 'You are attempting to click a table row which has issues in safaridriver - '\
|
|
15
15
|
'Your test should probably be clicking on a table cell like a user would. '\
|
|
16
16
|
'Clicking the first cell in the row instead.'
|
|
17
|
-
return find_css('th:first-child,td:first-child')[0].click(keys, options)
|
|
17
|
+
return find_css('th:first-child,td:first-child')[0].click(keys, **options)
|
|
18
18
|
end
|
|
19
19
|
raise
|
|
20
20
|
rescue ::Selenium::WebDriver::Error::WebDriverError => e
|
|
@@ -74,7 +74,7 @@ class Capybara::Selenium::SafariNode < Capybara::Selenium::Node
|
|
|
74
74
|
if clear == :backspace
|
|
75
75
|
# Clear field by sending the correct number of backspace keys.
|
|
76
76
|
backspaces = [:backspace] * self.value.to_s.length
|
|
77
|
-
send_keys(
|
|
77
|
+
send_keys([:control, 'e'], *backspaces, value)
|
|
78
78
|
else
|
|
79
79
|
super.tap do
|
|
80
80
|
# React doesn't see the safaridriver element clear
|
|
@@ -16,7 +16,10 @@ module Capybara
|
|
|
16
16
|
|
|
17
17
|
def initialize(app)
|
|
18
18
|
@app = app
|
|
19
|
-
@
|
|
19
|
+
@disable_css_markup = format(DISABLE_CSS_MARKUP_TEMPLATE,
|
|
20
|
+
selector: self.class.selector_for(Capybara.disable_animation))
|
|
21
|
+
@disable_js_markup = format(DISABLE_JS_MARKUP_TEMPLATE,
|
|
22
|
+
selector: self.class.selector_for(Capybara.disable_animation))
|
|
20
23
|
end
|
|
21
24
|
|
|
22
25
|
def call(env)
|
|
@@ -33,22 +36,17 @@ module Capybara
|
|
|
33
36
|
|
|
34
37
|
private
|
|
35
38
|
|
|
36
|
-
attr_reader :
|
|
39
|
+
attr_reader :disable_css_markup, :disable_js_markup
|
|
37
40
|
|
|
38
41
|
def html_content?
|
|
39
42
|
/html/.match?(@headers['Content-Type'])
|
|
40
43
|
end
|
|
41
44
|
|
|
42
45
|
def insert_disable(html)
|
|
43
|
-
html.sub(%r{(</body>)}, "#{
|
|
46
|
+
html.sub(%r{(</head>)}, "#{disable_css_markup}\\1").sub(%r{(</body>)}, "#{disable_js_markup}\\1")
|
|
44
47
|
end
|
|
45
48
|
|
|
46
|
-
|
|
47
|
-
<script>
|
|
48
|
-
//<![CDATA[
|
|
49
|
-
(typeof jQuery !== 'undefined') && (jQuery.fx.off = true);
|
|
50
|
-
//]]>
|
|
51
|
-
</script>
|
|
49
|
+
DISABLE_CSS_MARKUP_TEMPLATE = <<~HTML
|
|
52
50
|
<style>
|
|
53
51
|
%<selector>s, %<selector>s::before, %<selector>s::after {
|
|
54
52
|
transition: none !important;
|
|
@@ -58,6 +56,14 @@ module Capybara
|
|
|
58
56
|
}
|
|
59
57
|
</style>
|
|
60
58
|
HTML
|
|
59
|
+
|
|
60
|
+
DISABLE_JS_MARKUP_TEMPLATE = <<~HTML
|
|
61
|
+
<script>
|
|
62
|
+
//<![CDATA[
|
|
63
|
+
(typeof jQuery !== 'undefined') && (jQuery.fx.off = true);
|
|
64
|
+
//]]>
|
|
65
|
+
</script>
|
|
66
|
+
HTML
|
|
61
67
|
end
|
|
62
68
|
end
|
|
63
69
|
end
|
data/lib/capybara/session.rb
CHANGED
|
@@ -311,6 +311,16 @@ module Capybara
|
|
|
311
311
|
driver.send_keys(*args, **kw_args)
|
|
312
312
|
end
|
|
313
313
|
|
|
314
|
+
##
|
|
315
|
+
#
|
|
316
|
+
# Returns the element with focus.
|
|
317
|
+
#
|
|
318
|
+
# Not supported by Rack Test
|
|
319
|
+
#
|
|
320
|
+
def active_element
|
|
321
|
+
Capybara::Queries::ActiveElementQuery.new.resolve_for(self)[0].tap(&:allow_reload!)
|
|
322
|
+
end
|
|
323
|
+
|
|
314
324
|
##
|
|
315
325
|
#
|
|
316
326
|
# Executes the given block within the context of a node. {#within} takes the
|
|
@@ -408,11 +418,11 @@ module Capybara
|
|
|
408
418
|
idx = scopes.index(:frame)
|
|
409
419
|
top_level_scopes = [:frame, nil]
|
|
410
420
|
if idx
|
|
411
|
-
if scopes.slice(idx
|
|
421
|
+
if scopes.slice(idx..).any? { |scope| !top_level_scopes.include?(scope) }
|
|
412
422
|
raise Capybara::ScopeError, "`switch_to_frame(:top)` cannot be called from inside a descendant frame's "\
|
|
413
423
|
'`within` block.'
|
|
414
424
|
end
|
|
415
|
-
scopes.slice!(idx
|
|
425
|
+
scopes.slice!(idx..)
|
|
416
426
|
driver.switch_to_frame(:top)
|
|
417
427
|
end
|
|
418
428
|
else
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
Capybara::SpecHelper.spec '#active_element', requires: [:active_element] do
|
|
4
|
+
it 'should return the active element' do
|
|
5
|
+
@session.visit('/form')
|
|
6
|
+
@session.send_keys(:tab)
|
|
7
|
+
|
|
8
|
+
expect(@session.active_element).to match_selector(:css, '[tabindex="1"]')
|
|
9
|
+
|
|
10
|
+
@session.send_keys(:tab)
|
|
11
|
+
|
|
12
|
+
expect(@session.active_element).to match_selector(:css, '[tabindex="2"]')
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it 'should support reloading' do
|
|
16
|
+
@session.visit('/form')
|
|
17
|
+
expect(@session.active_element).to match_selector(:css, 'body')
|
|
18
|
+
@session.execute_script <<-JS
|
|
19
|
+
window.setTimeout(() => {
|
|
20
|
+
document.querySelector('#form_title').focus();
|
|
21
|
+
}, 1000)
|
|
22
|
+
JS
|
|
23
|
+
expect(@session.active_element).to match_selector(:css, 'body', wait: false)
|
|
24
|
+
expect(@session.active_element).to match_selector(:css, '#form_title', wait: 2)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'should return a Capybara::Element' do
|
|
28
|
+
@session.visit('/form')
|
|
29
|
+
expect(@session.active_element).to be_a Capybara::Node::Element
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -203,12 +203,10 @@ Capybara::SpecHelper.spec '#all' do
|
|
|
203
203
|
expect { @session.all(:css, 'h1, p', between: 0..3) }.to raise_error(Capybara::ExpectationNotMet)
|
|
204
204
|
end
|
|
205
205
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
end
|
|
211
|
-
TEST
|
|
206
|
+
it 'treats an endless range as minimum' do
|
|
207
|
+
expect { @session.all(:css, 'h1, p', between: 2..) }.not_to raise_error
|
|
208
|
+
expect { @session.all(:css, 'h1, p', between: 5..) }.to raise_error(Capybara::ExpectationNotMet)
|
|
209
|
+
end
|
|
212
210
|
|
|
213
211
|
eval <<~TEST, binding, __FILE__, __LINE__ + 1 if RUBY_VERSION.to_f > 2.6
|
|
214
212
|
it'treats a beginless range as maximum' do
|
|
@@ -236,6 +236,15 @@ Capybara::SpecHelper.spec '#check' do
|
|
|
236
236
|
expect(@session).to have_field('multi_label_checkbox', checked: true, visible: :hidden)
|
|
237
237
|
end
|
|
238
238
|
end
|
|
239
|
+
|
|
240
|
+
context 'with allow_label_click options', requires: [:js] do
|
|
241
|
+
it 'should allow offsets to click location on label' do
|
|
242
|
+
expect(@session.find(:checkbox, 'form_cars_lotus', unchecked: true, visible: :hidden)).to be_truthy
|
|
243
|
+
@session.check('form_cars_lotus', allow_label_click: { x: 90, y: 10 })
|
|
244
|
+
@session.click_button('awesome')
|
|
245
|
+
expect(extract_results(@session)['cars']).to include('lotus')
|
|
246
|
+
end
|
|
247
|
+
end
|
|
239
248
|
end
|
|
240
249
|
end
|
|
241
250
|
end
|
|
@@ -11,6 +11,12 @@ Capybara::SpecHelper.spec '#choose' do
|
|
|
11
11
|
expect(extract_results(@session)['gender']).to eq('male')
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
+
it 'ignores readonly attribute on radio buttons' do
|
|
15
|
+
@session.choose('gender_both')
|
|
16
|
+
@session.click_button('awesome')
|
|
17
|
+
expect(extract_results(@session)['gender']).to eq('both')
|
|
18
|
+
end
|
|
19
|
+
|
|
14
20
|
it 'should choose a radio button by label' do
|
|
15
21
|
@session.choose('Both')
|
|
16
22
|
@session.click_button('awesome')
|
|
@@ -22,4 +22,8 @@ Capybara::SpecHelper.spec '#have_any_of_selectors' do
|
|
|
22
22
|
expect(@session).to have_any_of_selectors('p a#blah', 'h2#h2three')
|
|
23
23
|
end.to raise_error ::RSpec::Expectations::ExpectationNotMetError
|
|
24
24
|
end
|
|
25
|
+
|
|
26
|
+
it 'should be negateable' do
|
|
27
|
+
expect(@session).not_to have_any_of_selectors(:css, 'span a#foo', 'h2#h2nope', 'h2#h2one_no')
|
|
28
|
+
end
|
|
25
29
|
end
|
|
@@ -65,6 +65,18 @@ Capybara::SpecHelper.spec '#has_button?' do
|
|
|
65
65
|
it 'should not affect other selectors when enable_aria_role: false' do
|
|
66
66
|
expect(@session).to have_button('Click me!', enable_aria_role: false)
|
|
67
67
|
end
|
|
68
|
+
|
|
69
|
+
context 'with focused:', requires: [:active_element] do
|
|
70
|
+
it 'should be true if a field has focus when focused: true' do
|
|
71
|
+
@session.send_keys(:tab)
|
|
72
|
+
|
|
73
|
+
expect(@session).to have_button('A Button', focused: true)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
it 'should be true if a field does not have focus when focused: false' do
|
|
77
|
+
expect(@session).to have_button('A Button', focused: false)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
68
80
|
end
|
|
69
81
|
|
|
70
82
|
Capybara::SpecHelper.spec '#has_no_button?' do
|
|
@@ -117,4 +129,16 @@ Capybara::SpecHelper.spec '#has_no_button?' do
|
|
|
117
129
|
it 'should not affect other selectors when enable_aria_role: false' do
|
|
118
130
|
expect(@session).to have_no_button('Junk button that does not exist', enable_aria_role: false)
|
|
119
131
|
end
|
|
132
|
+
|
|
133
|
+
context 'with focused:', requires: [:active_element] do
|
|
134
|
+
it 'should be true if a button does not have focus when focused: true' do
|
|
135
|
+
expect(@session).to have_no_button('A Button', focused: true)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
it 'should be false if a button has focus when focused: false' do
|
|
139
|
+
@session.send_keys(:tab)
|
|
140
|
+
|
|
141
|
+
expect(@session).to have_no_button('A Button', focused: false)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
120
144
|
end
|
|
@@ -110,6 +110,18 @@ Capybara::SpecHelper.spec '#has_field' do
|
|
|
110
110
|
end
|
|
111
111
|
end
|
|
112
112
|
|
|
113
|
+
context 'with focused:', requires: [:active_element] do
|
|
114
|
+
it 'should be true if a field has focus' do
|
|
115
|
+
2.times { @session.send_keys(:tab) }
|
|
116
|
+
|
|
117
|
+
expect(@session).to have_field('An Input', focused: true)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
it 'should be false if a field does not have focus' do
|
|
121
|
+
expect(@session).to have_field('An Input', focused: false)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
113
125
|
context 'with valid', requires: [:js] do
|
|
114
126
|
it 'should be true if field is valid' do
|
|
115
127
|
@session.fill_in 'required', with: 'something'
|
|
@@ -184,6 +196,18 @@ Capybara::SpecHelper.spec '#has_no_field' do
|
|
|
184
196
|
expect(@session).to have_no_field('Languages', type: 'textarea')
|
|
185
197
|
end
|
|
186
198
|
end
|
|
199
|
+
|
|
200
|
+
context 'with focused:', requires: [:active_element] do
|
|
201
|
+
it 'should be true if a field does not have focus when focused: true' do
|
|
202
|
+
expect(@session).to have_no_field('An Input', focused: true)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
it 'should be false if a field has focus when focused: true' do
|
|
206
|
+
2.times { @session.send_keys(:tab) }
|
|
207
|
+
|
|
208
|
+
expect(@session).not_to have_no_field('An Input', focused: true)
|
|
209
|
+
end
|
|
210
|
+
end
|
|
187
211
|
end
|
|
188
212
|
|
|
189
213
|
Capybara::SpecHelper.spec '#has_checked_field?' do
|
|
@@ -18,6 +18,18 @@ Capybara::SpecHelper.spec '#has_link?' do
|
|
|
18
18
|
expect(@session).not_to have_link('A link', href: '/nonexistent-href')
|
|
19
19
|
expect(@session).not_to have_link('A link', href: /nonexistent/)
|
|
20
20
|
end
|
|
21
|
+
|
|
22
|
+
context 'with focused:', requires: [:active_element] do
|
|
23
|
+
it 'should be true if the given link is on the page and has focus' do
|
|
24
|
+
@session.send_keys(:tab)
|
|
25
|
+
|
|
26
|
+
expect(@session).to have_link('labore', focused: true)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it 'should be false if the given link is on the page and does not have focus' do
|
|
30
|
+
expect(@session).to have_link('labore', focused: false)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
21
33
|
end
|
|
22
34
|
|
|
23
35
|
Capybara::SpecHelper.spec '#has_no_link?' do
|
|
@@ -36,4 +48,16 @@ Capybara::SpecHelper.spec '#has_no_link?' do
|
|
|
36
48
|
expect(@session).to have_no_link('A link', href: '/nonexistent-href')
|
|
37
49
|
expect(@session).to have_no_link('A link', href: %r{/nonexistent-href})
|
|
38
50
|
end
|
|
51
|
+
|
|
52
|
+
context 'with focused:', requires: [:active_element] do
|
|
53
|
+
it 'should be true if the given link is on the page and has focus' do
|
|
54
|
+
expect(@session).to have_no_link('labore', focused: true)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it 'should be false if the given link is on the page and does not have focus' do
|
|
58
|
+
@session.send_keys(:tab)
|
|
59
|
+
|
|
60
|
+
expect(@session).to have_no_link('labore', focused: false)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
39
63
|
end
|
|
@@ -356,7 +356,7 @@ Capybara::SpecHelper.spec '#has_no_text?' do
|
|
|
356
356
|
expect(@session).to have_no_text(/xxxxyzzz/)
|
|
357
357
|
end
|
|
358
358
|
|
|
359
|
-
it 'should be false if the text in the page
|
|
359
|
+
it 'should be false if the text in the page matches given regexp' do
|
|
360
360
|
@session.visit('/with_html')
|
|
361
361
|
expect(@session).not_to have_no_text(/Lorem/)
|
|
362
362
|
end
|
|
@@ -1115,7 +1115,7 @@ Capybara::SpecHelper.spec 'node' do
|
|
|
1115
1115
|
end
|
|
1116
1116
|
|
|
1117
1117
|
describe '#evaluate_script', requires: %i[js es_args] do
|
|
1118
|
-
it 'should evaluate the given script in the context of the element and
|
|
1118
|
+
it 'should evaluate the given script in the context of the element and return whatever it produces' do
|
|
1119
1119
|
@session.visit('/with_js')
|
|
1120
1120
|
el = @session.find(:css, '#with_change_event')
|
|
1121
1121
|
expect(el.evaluate_script('this.value')).to eq('default value')
|
|
@@ -15,7 +15,7 @@ Capybara::SpecHelper.spec '#scroll_to', requires: [:scroll] do
|
|
|
15
15
|
el = @session.find(:css, '#scroll')
|
|
16
16
|
@session.scroll_to(el, align: :bottom)
|
|
17
17
|
el_bottom = el.evaluate_script('this.getBoundingClientRect().bottom')
|
|
18
|
-
viewport_bottom = el.evaluate_script('document.
|
|
18
|
+
viewport_bottom = el.evaluate_script('document.documentElement.clientHeight')
|
|
19
19
|
expect(el_bottom).to be_within(1).of(viewport_bottom)
|
|
20
20
|
end
|
|
21
21
|
|
|
@@ -23,7 +23,7 @@ Capybara::SpecHelper.spec '#scroll_to', requires: [:scroll] do
|
|
|
23
23
|
el = @session.find(:css, '#scroll')
|
|
24
24
|
@session.scroll_to(el, align: :center)
|
|
25
25
|
el_center = el.evaluate_script('(function(rect){return (rect.top + rect.bottom)/2})(this.getBoundingClientRect())')
|
|
26
|
-
viewport_bottom = el.evaluate_script('document.
|
|
26
|
+
viewport_bottom = el.evaluate_script('document.documentElement.clientHeight')
|
|
27
27
|
expect(el_center).to be_within(2).of(viewport_bottom / 2)
|
|
28
28
|
end
|
|
29
29
|
|
|
@@ -35,13 +35,13 @@ Capybara::SpecHelper.spec '#scroll_to', requires: [:scroll] do
|
|
|
35
35
|
|
|
36
36
|
it 'can scroll the window to the vertical bottom' do
|
|
37
37
|
@session.scroll_to :bottom
|
|
38
|
-
max_scroll = @session.evaluate_script('document.
|
|
38
|
+
max_scroll = @session.evaluate_script('document.documentElement.scrollHeight - document.documentElement.clientHeight')
|
|
39
39
|
expect(@session.evaluate_script('[window.scrollX || window.pageXOffset, window.scrollY || window.pageYOffset]')).to eq [0, max_scroll]
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
it 'can scroll the window to the vertical center' do
|
|
43
43
|
@session.scroll_to :center
|
|
44
|
-
max_scroll = @session.evaluate_script('document.documentElement.scrollHeight - document.
|
|
44
|
+
max_scroll = @session.evaluate_script('document.documentElement.scrollHeight - document.documentElement.clientHeight')
|
|
45
45
|
expect(@session.evaluate_script('[window.scrollX || window.pageXOffset, window.scrollY || window.pageYOffset]')).to eq [0, max_scroll / 2]
|
|
46
46
|
end
|
|
47
47
|
|
|
@@ -36,7 +36,7 @@ module Capybara
|
|
|
36
36
|
Capybara.test_id = nil
|
|
37
37
|
Capybara.predicates_wait = true
|
|
38
38
|
Capybara.default_normalize_ws = false
|
|
39
|
-
Capybara.
|
|
39
|
+
Capybara.use_html5_parsing = !ENV['HTML5_PARSING'].nil?
|
|
40
40
|
Capybara.w3c_click_offset = false
|
|
41
41
|
reset_threadsafe
|
|
42
42
|
end
|
|
@@ -117,8 +117,9 @@ module Capybara
|
|
|
117
117
|
|
|
118
118
|
def extract_results(session)
|
|
119
119
|
expect(session).to have_xpath("//pre[@id='results']")
|
|
120
|
-
|
|
121
|
-
|
|
120
|
+
perms = [(::Sinatra::IndifferentHash if defined? ::Sinatra::IndifferentHash)].compact
|
|
121
|
+
results = Capybara::HTML(session.body).xpath("//pre[@id='results']").first.inner_html.lstrip
|
|
122
|
+
YAML.safe_load results, permitted_classes: perms
|
|
122
123
|
end
|
|
123
124
|
|
|
124
125
|
def be_an_invalid_element_error(session)
|
|
@@ -53,8 +53,8 @@ class TestApp < Sinatra::Base
|
|
|
53
53
|
|
|
54
54
|
get '/referer_base' do
|
|
55
55
|
'<a href="/get_referer">direct link</a>' \
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
'<a href="/redirect_to_get_referer">link via redirect</a>' \
|
|
57
|
+
'<form action="/get_referer" method="get"><input type="submit"></form>'
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
get '/redirect_to_get_referer' do
|
|
@@ -163,21 +163,30 @@ class TestApp < Sinatra::Base
|
|
|
163
163
|
|
|
164
164
|
get '/with_title' do
|
|
165
165
|
<<-HTML
|
|
166
|
-
|
|
167
|
-
<
|
|
168
|
-
<
|
|
169
|
-
|
|
166
|
+
<!DOCTYPE html>
|
|
167
|
+
<html lang="en">
|
|
168
|
+
<head>
|
|
169
|
+
<meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
|
|
170
|
+
<title>#{params[:title] || 'Test Title'}</title>
|
|
171
|
+
</head>
|
|
172
|
+
|
|
173
|
+
<body>
|
|
174
|
+
<svg><title>abcdefg</title></svg>
|
|
175
|
+
</body>
|
|
176
|
+
</html>
|
|
170
177
|
HTML
|
|
171
178
|
end
|
|
172
179
|
|
|
173
180
|
get '/download.csv' do
|
|
174
181
|
content_type 'text/csv'
|
|
175
182
|
'This, is, comma, separated' \
|
|
176
|
-
|
|
183
|
+
'Thomas, Walpole, was , here'
|
|
177
184
|
end
|
|
178
185
|
|
|
179
186
|
get '/:view' do |view|
|
|
180
|
-
|
|
187
|
+
view_template = "#{__dir__}/views/#{view}.erb"
|
|
188
|
+
has_layout = File.exist?(view_template) && File.open(view_template) { |f| f.first.downcase.include?('doctype') }
|
|
189
|
+
erb view.to_sym, locals: { referrer: request.referrer }, layout: !has_layout
|
|
181
190
|
end
|
|
182
191
|
|
|
183
192
|
post '/form' do
|