capybara 3.23.0 → 3.35.3
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 +264 -11
- data/README.md +10 -6
- data/lib/capybara.rb +20 -8
- data/lib/capybara/config.rb +10 -8
- data/lib/capybara/cucumber.rb +1 -1
- data/lib/capybara/driver/base.rb +4 -0
- data/lib/capybara/driver/node.rb +4 -0
- data/lib/capybara/dsl.rb +10 -2
- data/lib/capybara/helpers.rb +28 -2
- data/lib/capybara/minitest.rb +232 -144
- data/lib/capybara/minitest/spec.rb +156 -97
- data/lib/capybara/node/actions.rb +36 -36
- data/lib/capybara/node/base.rb +6 -6
- data/lib/capybara/node/document.rb +2 -2
- data/lib/capybara/node/document_matchers.rb +3 -3
- data/lib/capybara/node/element.rb +77 -33
- data/lib/capybara/node/finders.rb +24 -17
- data/lib/capybara/node/matchers.rb +79 -64
- data/lib/capybara/node/simple.rb +11 -4
- data/lib/capybara/queries/ancestor_query.rb +6 -10
- data/lib/capybara/queries/base_query.rb +2 -1
- data/lib/capybara/queries/current_path_query.rb +14 -4
- data/lib/capybara/queries/selector_query.rb +259 -23
- data/lib/capybara/queries/sibling_query.rb +5 -11
- data/lib/capybara/queries/style_query.rb +1 -1
- data/lib/capybara/queries/text_query.rb +13 -1
- data/lib/capybara/rack_test/browser.rb +13 -4
- data/lib/capybara/rack_test/driver.rb +2 -1
- data/lib/capybara/rack_test/form.rb +2 -2
- data/lib/capybara/rack_test/node.rb +42 -6
- data/lib/capybara/registration_container.rb +44 -0
- data/lib/capybara/registrations/drivers.rb +18 -12
- data/lib/capybara/registrations/patches/puma_ssl.rb +29 -0
- data/lib/capybara/registrations/servers.rb +9 -2
- data/lib/capybara/result.rb +39 -19
- data/lib/capybara/rspec.rb +2 -0
- data/lib/capybara/rspec/matcher_proxies.rb +5 -5
- data/lib/capybara/rspec/matchers.rb +97 -74
- data/lib/capybara/rspec/matchers/base.rb +19 -6
- data/lib/capybara/rspec/matchers/count_sugar.rb +2 -1
- data/lib/capybara/rspec/matchers/have_ancestor.rb +5 -7
- data/lib/capybara/rspec/matchers/have_current_path.rb +2 -2
- data/lib/capybara/rspec/matchers/have_selector.rb +15 -10
- data/lib/capybara/rspec/matchers/have_sibling.rb +4 -7
- data/lib/capybara/rspec/matchers/have_text.rb +4 -7
- data/lib/capybara/rspec/matchers/have_title.rb +2 -2
- data/lib/capybara/rspec/matchers/match_selector.rb +3 -3
- data/lib/capybara/rspec/matchers/match_style.rb +7 -2
- data/lib/capybara/rspec/matchers/spatial_sugar.rb +39 -0
- data/lib/capybara/selector.rb +46 -19
- data/lib/capybara/selector/builders/css_builder.rb +10 -6
- data/lib/capybara/selector/builders/xpath_builder.rb +4 -2
- data/lib/capybara/selector/css.rb +1 -1
- data/lib/capybara/selector/definition.rb +13 -11
- data/lib/capybara/selector/definition/button.rb +32 -15
- data/lib/capybara/selector/definition/checkbox.rb +2 -2
- data/lib/capybara/selector/definition/css.rb +3 -1
- data/lib/capybara/selector/definition/datalist_input.rb +2 -2
- data/lib/capybara/selector/definition/datalist_option.rb +1 -1
- data/lib/capybara/selector/definition/element.rb +3 -2
- data/lib/capybara/selector/definition/field.rb +1 -1
- data/lib/capybara/selector/definition/file_field.rb +1 -1
- data/lib/capybara/selector/definition/fillable_field.rb +2 -2
- data/lib/capybara/selector/definition/label.rb +5 -3
- data/lib/capybara/selector/definition/link.rb +8 -0
- data/lib/capybara/selector/definition/option.rb +1 -1
- data/lib/capybara/selector/definition/radio_button.rb +2 -2
- data/lib/capybara/selector/definition/select.rb +33 -14
- data/lib/capybara/selector/definition/table.rb +6 -3
- data/lib/capybara/selector/definition/table_row.rb +2 -2
- data/lib/capybara/selector/filter_set.rb +13 -11
- data/lib/capybara/selector/filters/base.rb +6 -1
- data/lib/capybara/selector/filters/locator_filter.rb +1 -1
- data/lib/capybara/selector/regexp_disassembler.rb +7 -0
- data/lib/capybara/selector/selector.rb +13 -3
- data/lib/capybara/selenium/atoms/getAttribute.min.js +1 -1
- data/lib/capybara/selenium/atoms/isDisplayed.min.js +1 -1
- data/lib/capybara/selenium/atoms/src/getAttribute.js +1 -1
- data/lib/capybara/selenium/atoms/src/isDisplayed.js +10 -10
- data/lib/capybara/selenium/driver.rb +86 -24
- data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +24 -21
- data/lib/capybara/selenium/driver_specializations/edge_driver.rb +21 -19
- data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +17 -1
- data/lib/capybara/selenium/driver_specializations/safari_driver.rb +0 -4
- data/lib/capybara/selenium/extensions/file_input_click_emulation.rb +34 -0
- data/lib/capybara/selenium/extensions/find.rb +37 -26
- data/lib/capybara/selenium/extensions/html5_drag.rb +55 -11
- data/lib/capybara/selenium/extensions/modifier_keys_stack.rb +28 -0
- data/lib/capybara/selenium/extensions/scroll.rb +8 -10
- data/lib/capybara/selenium/logger_suppressor.rb +8 -2
- data/lib/capybara/selenium/node.rb +160 -40
- data/lib/capybara/selenium/nodes/chrome_node.rb +72 -12
- data/lib/capybara/selenium/nodes/edge_node.rb +32 -14
- data/lib/capybara/selenium/nodes/firefox_node.rb +28 -32
- data/lib/capybara/selenium/nodes/safari_node.rb +5 -29
- data/lib/capybara/selenium/patches/action_pauser.rb +26 -0
- data/lib/capybara/selenium/patches/atoms.rb +4 -4
- data/lib/capybara/selenium/patches/is_displayed.rb +16 -0
- data/lib/capybara/selenium/patches/logs.rb +32 -7
- data/lib/capybara/server.rb +19 -3
- data/lib/capybara/server/animation_disabler.rb +8 -3
- data/lib/capybara/server/checker.rb +1 -1
- data/lib/capybara/server/middleware.rb +22 -10
- data/lib/capybara/session.rb +66 -40
- data/lib/capybara/session/config.rb +11 -3
- data/lib/capybara/session/matchers.rb +11 -11
- data/lib/capybara/spec/public/offset.js +6 -0
- data/lib/capybara/spec/public/test.js +75 -7
- data/lib/capybara/spec/session/accept_alert_spec.rb +1 -1
- data/lib/capybara/spec/session/all_spec.rb +60 -5
- data/lib/capybara/spec/session/ancestor_spec.rb +5 -0
- data/lib/capybara/spec/session/assert_text_spec.rb +9 -5
- data/lib/capybara/spec/session/check_spec.rb +6 -0
- data/lib/capybara/spec/session/click_button_spec.rb +16 -0
- data/lib/capybara/spec/session/click_link_or_button_spec.rb +9 -0
- data/lib/capybara/spec/session/current_url_spec.rb +11 -1
- data/lib/capybara/spec/session/fill_in_spec.rb +29 -0
- data/lib/capybara/spec/session/find_spec.rb +55 -0
- data/lib/capybara/spec/session/has_ancestor_spec.rb +2 -0
- data/lib/capybara/spec/session/has_button_spec.rb +51 -0
- data/lib/capybara/spec/session/has_css_spec.rb +26 -4
- data/lib/capybara/spec/session/has_current_path_spec.rb +15 -2
- data/lib/capybara/spec/session/has_field_spec.rb +34 -0
- data/lib/capybara/spec/session/has_select_spec.rb +32 -4
- data/lib/capybara/spec/session/has_selector_spec.rb +4 -4
- data/lib/capybara/spec/session/has_table_spec.rb +51 -5
- data/lib/capybara/spec/session/has_text_spec.rb +30 -0
- data/lib/capybara/spec/session/html_spec.rb +1 -1
- data/lib/capybara/spec/session/matches_style_spec.rb +2 -2
- data/lib/capybara/spec/session/node_spec.rb +394 -9
- data/lib/capybara/spec/session/refresh_spec.rb +2 -1
- data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +2 -2
- data/lib/capybara/spec/session/save_page_spec.rb +4 -4
- data/lib/capybara/spec/session/save_screenshot_spec.rb +4 -15
- data/lib/capybara/spec/session/selectors_spec.rb +16 -3
- data/lib/capybara/spec/session/window/switch_to_window_spec.rb +1 -1
- data/lib/capybara/spec/session/window/window_opened_by_spec.rb +1 -1
- data/lib/capybara/spec/session/window/window_spec.rb +8 -8
- data/lib/capybara/spec/session/window/windows_spec.rb +1 -1
- data/lib/capybara/spec/spec_helper.rb +14 -14
- data/lib/capybara/spec/test_app.rb +27 -21
- data/lib/capybara/spec/views/form.erb +47 -4
- data/lib/capybara/spec/views/offset.erb +32 -0
- data/lib/capybara/spec/views/spatial.erb +31 -0
- data/lib/capybara/spec/views/with_animation.erb +37 -1
- data/lib/capybara/spec/views/with_dragula.erb +24 -0
- data/lib/capybara/spec/views/with_html.erb +24 -2
- data/lib/capybara/spec/views/with_jquery_animation.erb +24 -0
- data/lib/capybara/spec/views/with_js.erb +4 -1
- data/lib/capybara/spec/views/with_jstree.erb +26 -0
- data/lib/capybara/spec/views/with_sortable_js.erb +1 -1
- data/lib/capybara/version.rb +1 -1
- data/lib/capybara/window.rb +3 -7
- data/spec/basic_node_spec.rb +15 -14
- data/spec/capybara_spec.rb +28 -28
- data/spec/dsl_spec.rb +16 -3
- data/spec/filter_set_spec.rb +5 -5
- data/spec/fixtures/selenium_driver_rspec_failure.rb +1 -1
- data/spec/fixtures/selenium_driver_rspec_success.rb +1 -1
- data/spec/minitest_spec.rb +3 -2
- data/spec/minitest_spec_spec.rb +46 -46
- data/spec/rack_test_spec.rb +38 -15
- data/spec/regexp_dissassembler_spec.rb +52 -38
- data/spec/result_spec.rb +43 -32
- data/spec/rspec/features_spec.rb +4 -1
- data/spec/rspec/scenarios_spec.rb +4 -0
- data/spec/rspec/shared_spec_matchers.rb +68 -56
- data/spec/rspec_spec.rb +9 -5
- data/spec/selector_spec.rb +32 -17
- data/spec/selenium_spec_chrome.rb +78 -11
- data/spec/selenium_spec_chrome_remote.rb +23 -6
- data/spec/selenium_spec_edge.rb +15 -12
- data/spec/selenium_spec_firefox.rb +24 -19
- data/spec/selenium_spec_firefox_remote.rb +0 -8
- data/spec/selenium_spec_ie.rb +1 -6
- data/spec/server_spec.rb +106 -44
- data/spec/session_spec.rb +5 -5
- data/spec/shared_selenium_node.rb +56 -2
- data/spec/shared_selenium_session.rb +122 -15
- data/spec/spec_helper.rb +2 -2
- metadata +63 -17
- data/lib/capybara/spec/session/source_spec.rb +0 -0
@@ -48,7 +48,12 @@ module Capybara
|
|
48
48
|
|
49
49
|
def apply(subject, name, value, skip_value, ctx)
|
50
50
|
return skip_value if skip?(value)
|
51
|
-
|
51
|
+
|
52
|
+
unless valid_value?(value)
|
53
|
+
raise ArgumentError,
|
54
|
+
"Invalid value #{value.inspect} passed to #{self.class.name.split('::').last} #{name}" \
|
55
|
+
"#{" : #{name}" if @name.is_a?(Regexp)}"
|
56
|
+
end
|
52
57
|
|
53
58
|
if @block.arity == 2
|
54
59
|
filter_context(ctx).instance_exec(subject, value, &@block)
|
@@ -100,6 +100,8 @@ module Capybara
|
|
100
100
|
def extract_strings(process_alternatives)
|
101
101
|
strings = []
|
102
102
|
each do |exp|
|
103
|
+
next if exp.ignore?
|
104
|
+
|
103
105
|
next strings.push(nil) if exp.optional? && !process_alternatives
|
104
106
|
|
105
107
|
next strings.push(exp.alternative_strings) if exp.alternation? && process_alternatives
|
@@ -159,6 +161,11 @@ module Capybara
|
|
159
161
|
alts.all?(&:any?) ? Set.new(alts) : nil
|
160
162
|
end
|
161
163
|
|
164
|
+
def ignore?
|
165
|
+
[Regexp::Expression::Assertion::NegativeLookahead,
|
166
|
+
Regexp::Expression::Assertion::NegativeLookbehind].any? { |klass| @exp.is_a? klass }
|
167
|
+
end
|
168
|
+
|
162
169
|
private
|
163
170
|
|
164
171
|
def indeterminate?
|
@@ -48,6 +48,10 @@ module Capybara
|
|
48
48
|
@config[:enable_aria_label]
|
49
49
|
end
|
50
50
|
|
51
|
+
def enable_aria_role
|
52
|
+
@config[:enable_aria_role]
|
53
|
+
end
|
54
|
+
|
51
55
|
def test_id
|
52
56
|
@config[:test_id]
|
53
57
|
end
|
@@ -56,12 +60,14 @@ module Capybara
|
|
56
60
|
if format
|
57
61
|
raise ArgumentError, "Selector #{@name} does not support #{format}" unless expressions.key?(format)
|
58
62
|
|
59
|
-
instance_exec(locator, options, &expressions[format])
|
63
|
+
instance_exec(locator, **options, &expressions[format])
|
60
64
|
else
|
61
65
|
warn 'Selector has no format'
|
62
66
|
end
|
63
67
|
ensure
|
64
|
-
|
68
|
+
unless locator_valid?(locator)
|
69
|
+
warn "Locator #{locator.class}:#{locator.inspect} for selector #{name.inspect} must #{locator_description}. This will raise an error in a future version of Capybara."
|
70
|
+
end
|
65
71
|
end
|
66
72
|
|
67
73
|
def add_error(error_msg)
|
@@ -126,7 +132,11 @@ module Capybara
|
|
126
132
|
attr_matchers |= XPath.attr(test_id) == locator if test_id
|
127
133
|
|
128
134
|
locate_xpath = locate_xpath[attr_matchers]
|
129
|
-
locate_xpath +
|
135
|
+
locate_xpath + locate_label(locator).descendant(xpath)
|
136
|
+
end
|
137
|
+
|
138
|
+
def locate_label(locator)
|
139
|
+
XPath.descendant(:label)[XPath.string.n.is(locator)]
|
130
140
|
end
|
131
141
|
|
132
142
|
def find_by_attr(attribute, value)
|
@@ -1 +1 @@
|
|
1
|
-
(function(){function u(e){var t=e.tagName.toUpperCase();if("OPTION"==t)return!0;if("INPUT"!=t)return!1;var r=e.type.toLowerCase();return"checkbox"==r||"radio"==r}function s(e){var t="selected",r=e.type&&e.type.toLowerCase();return"checkbox"!=r&&"radio"!=r||(t="checked"),!!e[t]}function c(e,t){var r=e.getAttributeNode(t);return r&&r.specified?r.value:null}var i=["allowfullscreen","allowpaymentrequest","allowusermedia","async","autofocus","autoplay","checked","compact","complete","controls","declare","default","defaultchecked","defaultselected","defer","disabled","ended","formnovalidate","hidden","indeterminate","iscontenteditable","ismap","itemscope","loop","multiple","muted","nohref","nomodule","noresize","noshade","novalidate","nowrap","open","paused","playsinline","pubdate","readonly","required","reversed","scoped","seamless","seeking","selected","truespeed","typemustmatch","willvalidate"],d={"class":"className",readonly:"readOnly"};return function f(e,t){var r=null,a=t.toLowerCase();if("style"==a)return(r=e.style)&&"string"!=typeof r&&(r=r.cssText),r;if(("selected"==a||"checked"==a)&&u(e))return s(e)?"true":null;if(tagName=e.tagName.toUpperCase(),"IMG"==tagName&&"src"==a||"A"==tagName&&"href"==a)return(r=c(e,a))&&(r=e[a]),r;if("spellcheck"==a){if(null
|
1
|
+
(function(){function u(e){var t=e.tagName.toUpperCase();if("OPTION"==t)return!0;if("INPUT"!=t)return!1;var r=e.type.toLowerCase();return"checkbox"==r||"radio"==r}function s(e){var t="selected",r=e.type&&e.type.toLowerCase();return"checkbox"!=r&&"radio"!=r||(t="checked"),!!e[t]}function c(e,t){var r=e.getAttributeNode(t);return r&&r.specified?r.value:null}var i=["allowfullscreen","allowpaymentrequest","allowusermedia","async","autofocus","autoplay","checked","compact","complete","controls","declare","default","defaultchecked","defaultselected","defer","disabled","ended","formnovalidate","hidden","indeterminate","iscontenteditable","ismap","itemscope","loop","multiple","muted","nohref","nomodule","noresize","noshade","novalidate","nowrap","open","paused","playsinline","pubdate","readonly","required","reversed","scoped","seamless","seeking","selected","truespeed","typemustmatch","willvalidate"],d={"class":"className",readonly:"readOnly"};return function f(e,t){var r=null,a=t.toLowerCase();if("style"==a)return(r=e.style)&&"string"!=typeof r&&(r=r.cssText),r;if(("selected"==a||"checked"==a)&&u(e))return s(e)?"true":null;if(tagName=e.tagName.toUpperCase(),"IMG"==tagName&&"src"==a||"A"==tagName&&"href"==a)return(r=c(e,a))&&(r=e[a]),r;if("spellcheck"==a){if(null!==(r=c(e,a))){if("false"==r.toLowerCase())return"false";if("true"==r.toLowerCase())return"true"}return e[a]+""}var l,n=d[t]||t;if(i.some(function(e){e==a}))return(r=!(null===(r=c(e,a)))||e[n])?"true":null;try{l=e[n]}catch(o){}return null!=(r=null==l||"object"==typeof l||"function"==typeof l?c(e,t):l)?r.toString():null}})()
|
@@ -1 +1 @@
|
|
1
|
-
(function(){function
|
1
|
+
(function(){function d(t,e,n){function r(t){var e=x(t);if(0<e.height&&0<e.width)return!0;if("PATH"==t.tagName.toUpperCase()&&(0<e.height||0<e.width)){var n=window.getComputedStyle(t)["stroke-width"];return!!n&&0<parseInt(n,10)}return"hidden"!=window.getComputedStyle(t).overflow&&Array.prototype.slice.call(t.childNodes).some(function(t){return t.nodeType==Node.TEXT_NODE||t.nodeType==Node.ELEMENT_NODE&&r(t)})}function i(t){return C(t)==T.HIDDEN&&Array.prototype.slice.call(t.childNodes).every(function(t){return t.nodeType!=Node.ELEMENT_NODE||i(t)||!r(t)})}var o=t.tagName.toUpperCase();if("BODY"==o)return!0;if("OPTION"==o||"OPTGROUP"==o){var a=c(t,function(t){return"SELECT"==t.tagName.toUpperCase()});return!!a&&d(a,!0,n)}var u=s(t);if(u)return!!u.image&&0<u.rect.width&&0<u.rect.height&&d(u.image,e,n);if("INPUT"==o&&"hidden"==t.type.toLowerCase())return!1;if("NOSCRIPT"==o)return!1;var l=window.getComputedStyle(t).visibility;return"collapse"!=l&&"hidden"!=l&&(!!n(t)&&(!(!e&&0==f(t))&&(!!r(t)&&!i(t))))}function E(t){var e=x(t);return{left:e.left,right:e.left+e.width,top:e.top,bottom:e.top+e.height}}function D(t){return t.parentElement}function C(t){function e(t){function e(t){if(t==u)return!0;var e=window.getComputedStyle(t),n=e.display;return 0!=n.indexOf("inline")&&"contents"!=n&&("absolute"!=r||"static"!=e.position)}var r=window.getComputedStyle(t).position;if("fixed"==r)return i=!0,t==u?null:u;for(var n=D(t);n&&!e(n);)n=D(n);return n}function n(t){var e=t;if("visible"==d)if(t==u&&l)e=l;else if(t==l)return{x:"visible",y:"visible"};var n=window.getComputedStyle(e),r={x:n["overflow-x"],y:n["overflow-y"]};return t==u&&(r.x="visible"==r.x?"auto":r.x,r.y="visible"==r.y?"auto":r.y),r}function r(t){return t==u?{x:window.scrollX,y:window.scrollY}:{x:t.scrollLeft,y:t.scrollTop}}for(var i,o=E(t),a=t.ownerDocument,u=a.documentElement,l=a.body,d=window.getComputedStyle(u).overflow,f=e(t);f;f=e(f)){var h=n(f);if("visible"!=h.x||"visible"!=h.y){var s=x(f);if(0==s.width||0==s.height)return T.HIDDEN;var p=o.right<s.left,c=o.bottom<s.top;if(p&&"hidden"==h.x||c&&"hidden"==h.y)return T.HIDDEN;if(p&&"visible"!=h.x||c&&"visible"!=h.y){var v=r(f),g=o.right<s.left-v.x,w=o.bottom<s.top-v.y;return g&&"visible"!=h.x||w&&"visible"!=h.x?T.HIDDEN:C(f)==T.HIDDEN?T.HIDDEN:T.SCROLL}var N=o.left>=s.left+s.width,m=o.top>=s.top+s.height;if(N&&"hidden"==h.x||m&&"hidden"==h.y)return T.HIDDEN;if(N&&"visible"!=h.x||m&&"visible"!=h.y){if(i){var y=r(f);if(o.left>=u.scrollWidth-y.x||o.right>=u.scrollHeight-y.y)return T.HIDDEN}return C(f)==T.HIDDEN?T.HIDDEN:T.SCROLL}}}return T.NONE}function o(t){var e=t.document.documentElement;return{width:e.clientWidth,height:e.clientHeight}}function p(t,e,n,r){return{left:t,top:e,width:n,height:r}}function x(t){var e,n=s(t);if(n)return n.rect;if("HTML"==t.tagName.toUpperCase()){t.ownerDocument;var r=o(window);return p(0,0,r.width,r.height)}try{e=t.getBoundingClientRect()}catch(i){return p(0,0,0,0)}return p(e.left,e.top,e.right-e.left,e.bottom-e.top)}function f(t){var e=1,n=window.getComputedStyle(t).opacity;n&&(e=Number(n));var r=D(t);return r&&r.nodeType==Node.ELEMENT_NODE&&(e*=f(r)),e}function h(t){var e=t.shape.toLowerCase(),n=t.coords.split(",");if("rect"==e&&4==n.length){var r=n[0],i=n[1];return p(r,i,n[2]-r,n[3]-i)}if("circle"==e&&3==n.length){var o=n[0],a=n[1],u=n[2];return p(o-u,a-u,2*u,2*u)}if("poly"==e&&2<n.length){for(var l=n[0],d=n[1],f=l,h=d,s=2;s+1<n.length;s+=2)l=Math.min(l,n[s]),f=Math.max(f,n[s]),d=Math.min(d,n[s+1]),h=Math.max(h,n[s+1]);return p(l,d,f-l,h-d)}return p(0,0,0,0)}function s(t){var e=t.tagName.toUpperCase(),n="MAP"==e;if(!n&&"AREA"!=e)return null;var r=n?t:"MAP"==D(t).tagName.toUpperCase()?D(t):null,i=null,o=null;if(r&&r.name&&((i=r.ownerDocument.querySelector("*[usemap='#"+r.name+"']"))&&(o=x(i),!n&&"default"!=t.shape.toLowerCase()))){var a=h(t),u=Math.min(Math.max(a.left,0),o.width),l=Math.min(Math.max(a.top,0),o.height),d=Math.min(a.width,o.width-u),f=Math.min(a.height,o.height-l);o=p(u+o.left,l+o.top,d,f)}return{image:i,rect:o||p(0,0,0,0)}}function c(t,e){for(t&&(t=D(t));t;){if(e(t))return t;t=D(t)}return null}function r(t){var e=t.parentNode;if(e&&e.shadowRoot&&t.assignedSlot!==undefined)return t.assignedSlot?t.assignedSlot.parentNode:null;if(t.getDestinationInsertionPoints){var n=t.getDestinationInsertionPoints();if(0<n.length)return n[n.length-1]}return e}var T={NONE:"none",HIDDEN:"hidden",SCROLL:"scroll"};return function i(t,e){function n(t){if("none"==window.getComputedStyle(t).display)return!1;var e=r(t);if("function"==typeof ShadowRoot&&e instanceof ShadowRoot){if(e.host.shadowRoot!==e)return!1;e=e.host}return!(!e||e.nodeType!=Node.DOCUMENT_NODE&&e.nodeType!=Node.DOCUMENT_FRAGMENT_NODE)||!(e&&e.tagName&&"DETAILS"==e.tagName.toUpperCase()&&!e.open&&"SUMMARY"!=t.tagName)&&(e&&n(e))}return d(t,!!e,n)}})()
|
@@ -14,15 +14,6 @@
|
|
14
14
|
return true;
|
15
15
|
}
|
16
16
|
|
17
|
-
// Child of DETAILS element is not shown unless the DETAILS element is open
|
18
|
-
// or the child is a SUMMARY element.
|
19
|
-
|
20
|
-
var parent = getParentElement(elem);
|
21
|
-
if (parent && parent.tagName && (parent.tagName.toUpperCase() == "DETAILS") &&
|
22
|
-
!parent.open && !(elemTagName == "SUMMARY")) {
|
23
|
-
return false;
|
24
|
-
}
|
25
|
-
|
26
17
|
// Option or optgroup is shown if enclosing select is shown (ignoring the
|
27
18
|
// select's opacity).
|
28
19
|
if ((elemTagName == "OPTION") ||
|
@@ -73,12 +64,14 @@
|
|
73
64
|
if (rect.height > 0 && rect.width > 0) {
|
74
65
|
return true;
|
75
66
|
}
|
67
|
+
|
76
68
|
// A vertical or horizontal SVG Path element will report zero width or
|
77
69
|
// height but is "shown" if it has a positive stroke-width.
|
78
70
|
if ((e.tagName.toUpperCase() == "PATH") && (rect.height > 0 || rect.width > 0)) {
|
79
71
|
var strokeWidth = window.getComputedStyle(e)["stroke-width"];
|
80
72
|
return !!strokeWidth && (parseInt(strokeWidth, 10) > 0);
|
81
73
|
}
|
74
|
+
|
82
75
|
// Zero-sized elements should still be considered to have positive size
|
83
76
|
// if they have a child element or text node with positive size, unless
|
84
77
|
// the element has an 'overflow' style of "hidden".
|
@@ -165,7 +158,7 @@
|
|
165
158
|
// the overflow style of the body, and the body is really overflow:visible.
|
166
159
|
var overflowElem = e;
|
167
160
|
if (htmlOverflowStyle == "visible") {
|
168
|
-
//
|
161
|
+
// NOTE: bodyElem will be null/undefined in SVG documents.
|
169
162
|
if (e == htmlElem && bodyElem) {
|
170
163
|
overflowElem = bodyElem;
|
171
164
|
} else if (e == bodyElem) {
|
@@ -446,6 +439,13 @@
|
|
446
439
|
return true;
|
447
440
|
}
|
448
441
|
|
442
|
+
// Child of DETAILS element is not shown unless the DETAILS element is open
|
443
|
+
// or the child is a SUMMARY element.
|
444
|
+
if (parent && parent.tagName && (parent.tagName.toUpperCase() == "DETAILS") &&
|
445
|
+
!parent.open && !(e.tagName == "SUMMARY")) {
|
446
|
+
return false;
|
447
|
+
}
|
448
|
+
|
449
449
|
return parent && displayed(parent);
|
450
450
|
}
|
451
451
|
|
@@ -11,17 +11,45 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
11
11
|
clear_local_storage: nil,
|
12
12
|
clear_session_storage: nil
|
13
13
|
}.freeze
|
14
|
-
SPECIAL_OPTIONS = %i[browser clear_local_storage clear_session_storage timeout].freeze
|
14
|
+
SPECIAL_OPTIONS = %i[browser clear_local_storage clear_session_storage timeout native_displayed].freeze
|
15
|
+
CAPS_VERSION = Gem::Requirement.new('~> 4.0.0.alpha6')
|
16
|
+
|
15
17
|
attr_reader :app, :options
|
16
18
|
|
17
19
|
class << self
|
20
|
+
attr_reader :selenium_webdriver_version
|
21
|
+
|
18
22
|
def load_selenium
|
19
23
|
require 'selenium-webdriver'
|
20
24
|
require 'capybara/selenium/logger_suppressor'
|
21
25
|
require 'capybara/selenium/patches/atoms'
|
22
|
-
|
26
|
+
require 'capybara/selenium/patches/is_displayed'
|
27
|
+
require 'capybara/selenium/patches/action_pauser'
|
28
|
+
|
29
|
+
# Look up the version of `selenium-webdriver` to
|
30
|
+
# see if it's a version we support.
|
31
|
+
#
|
32
|
+
# By default, we use Gem.loaded_specs to determine
|
33
|
+
# the version number. However, in some cases, such
|
34
|
+
# as when loading `selenium-webdriver` outside of
|
35
|
+
# Rubygems, we fall back to referencing
|
36
|
+
# Selenium::WebDriver::VERSION. Ideally we'd
|
37
|
+
# use the constant in all cases, but earlier versions
|
38
|
+
# of `selenium-webdriver` didn't provide the constant.
|
39
|
+
@selenium_webdriver_version =
|
40
|
+
if Gem.loaded_specs['selenium-webdriver']
|
41
|
+
Gem.loaded_specs['selenium-webdriver'].version
|
42
|
+
else
|
43
|
+
Gem::Version.new(Selenium::WebDriver::VERSION)
|
44
|
+
end
|
45
|
+
|
46
|
+
unless Gem::Requirement.new('>= 3.5.0').satisfied_by? @selenium_webdriver_version
|
47
|
+
warn "Warning: You're using an unsupported version of selenium-webdriver, please upgrade."
|
48
|
+
end
|
49
|
+
|
50
|
+
@selenium_webdriver_version
|
23
51
|
rescue LoadError => e
|
24
|
-
raise e unless e.message.
|
52
|
+
raise e unless e.message.include?('selenium-webdriver')
|
25
53
|
|
26
54
|
raise LoadError, "Capybara's selenium driver is unable to load `selenium-webdriver`, please install the gem and add `gem 'selenium-webdriver'` to your Gemfile if you are using bundler."
|
27
55
|
end
|
@@ -45,7 +73,15 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
45
73
|
end
|
46
74
|
end
|
47
75
|
processed_options = options.reject { |key, _val| SPECIAL_OPTIONS.include?(key) }
|
48
|
-
|
76
|
+
|
77
|
+
@browser = if options[:browser] == :firefox &&
|
78
|
+
RUBY_VERSION >= '3.0' &&
|
79
|
+
Capybara::Selenium::Driver.selenium_webdriver_version <= Gem::Version.new('4.0.0.alpha1')
|
80
|
+
# selenium-webdriver 3.x doesn't correctly pass options through for Firefox with Ruby 3 so workaround that
|
81
|
+
Selenium::WebDriver::Firefox::Driver.new(**processed_options)
|
82
|
+
else
|
83
|
+
Selenium::WebDriver.for(options[:browser], processed_options)
|
84
|
+
end
|
49
85
|
|
50
86
|
specialize_driver
|
51
87
|
setup_exit_handler
|
@@ -54,6 +90,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
54
90
|
end
|
55
91
|
|
56
92
|
def initialize(app, **options)
|
93
|
+
super()
|
57
94
|
self.class.load_selenium
|
58
95
|
@app = app
|
59
96
|
@browser = nil
|
@@ -81,6 +118,8 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
81
118
|
|
82
119
|
def html
|
83
120
|
browser.page_source
|
121
|
+
rescue Selenium::WebDriver::Error::JavascriptError => e
|
122
|
+
raise unless e.message.include?('documentElement is null')
|
84
123
|
end
|
85
124
|
|
86
125
|
def title
|
@@ -109,6 +148,10 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
109
148
|
unwrap_script_result(result)
|
110
149
|
end
|
111
150
|
|
151
|
+
def send_keys(*args)
|
152
|
+
active_element.send_keys(*args)
|
153
|
+
end
|
154
|
+
|
112
155
|
def save_screenshot(path, **_options)
|
113
156
|
browser.save_screenshot(path)
|
114
157
|
end
|
@@ -142,7 +185,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
142
185
|
|
143
186
|
switch_to_frame(:parent)
|
144
187
|
begin
|
145
|
-
|
188
|
+
frame.base.obscured?(x: x, y: y)
|
146
189
|
ensure
|
147
190
|
switch_to_frame(frame)
|
148
191
|
end
|
@@ -218,7 +261,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
218
261
|
|
219
262
|
def accept_modal(_type, **options)
|
220
263
|
yield if block_given?
|
221
|
-
modal = find_modal(options)
|
264
|
+
modal = find_modal(**options)
|
222
265
|
|
223
266
|
modal.send_keys options[:with] if options[:with]
|
224
267
|
|
@@ -229,7 +272,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
229
272
|
|
230
273
|
def dismiss_modal(_type, **options)
|
231
274
|
yield if block_given?
|
232
|
-
modal = find_modal(options)
|
275
|
+
modal = find_modal(**options)
|
233
276
|
message = modal.text
|
234
277
|
modal.dismiss
|
235
278
|
message
|
@@ -237,7 +280,8 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
237
280
|
|
238
281
|
def quit
|
239
282
|
@browser&.quit
|
240
|
-
rescue Selenium::WebDriver::Error::SessionNotCreatedError, Errno::ECONNREFUSED
|
283
|
+
rescue Selenium::WebDriver::Error::SessionNotCreatedError, Errno::ECONNREFUSED,
|
284
|
+
Selenium::WebDriver::Error::InvalidSessionIdError
|
241
285
|
# Browser must have already gone
|
242
286
|
rescue Selenium::WebDriver::Error::UnknownError => e
|
243
287
|
unless silenced_unknown_error_message?(e.message) # Most likely already gone
|
@@ -289,7 +333,7 @@ private
|
|
289
333
|
def clear_browser_state
|
290
334
|
delete_all_cookies
|
291
335
|
clear_storage
|
292
|
-
rescue *clear_browser_state_errors
|
336
|
+
rescue *clear_browser_state_errors
|
293
337
|
# delete_all_cookies fails when we've previously gone
|
294
338
|
# to about:blank, so we rescue this error and do nothing
|
295
339
|
# instead.
|
@@ -300,13 +344,10 @@ private
|
|
300
344
|
end
|
301
345
|
|
302
346
|
def unhandled_alert_errors
|
303
|
-
@unhandled_alert_errors ||=
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
end
|
308
|
-
end
|
309
|
-
end
|
347
|
+
@unhandled_alert_errors ||= with_legacy_error(
|
348
|
+
[Selenium::WebDriver::Error::UnexpectedAlertOpenError],
|
349
|
+
'UnhandledAlertError'
|
350
|
+
)
|
310
351
|
end
|
311
352
|
|
312
353
|
def delete_all_cookies
|
@@ -316,7 +357,7 @@ private
|
|
316
357
|
def clear_storage
|
317
358
|
clear_session_storage unless options[:clear_session_storage] == false
|
318
359
|
clear_local_storage unless options[:clear_local_storage] == false
|
319
|
-
rescue Selenium::WebDriver::Error::JavascriptError
|
360
|
+
rescue Selenium::WebDriver::Error::JavascriptError
|
320
361
|
# session/local storage may not be available if on non-http pages (e.g. about:blank)
|
321
362
|
end
|
322
363
|
|
@@ -327,7 +368,9 @@ private
|
|
327
368
|
begin
|
328
369
|
@browser&.execute_script('window.sessionStorage.clear()')
|
329
370
|
rescue # rubocop:disable Style/RescueStandardError
|
330
|
-
|
371
|
+
unless options[:clear_session_storage].nil?
|
372
|
+
warn 'sessionStorage clear requested but is not supported by this driver'
|
373
|
+
end
|
331
374
|
end
|
332
375
|
end
|
333
376
|
end
|
@@ -339,7 +382,9 @@ private
|
|
339
382
|
begin
|
340
383
|
@browser&.execute_script('window.localStorage.clear()')
|
341
384
|
rescue # rubocop:disable Style/RescueStandardError
|
342
|
-
|
385
|
+
unless options[:clear_local_storage].nil?
|
386
|
+
warn 'localStorage clear requested but is not supported by this driver'
|
387
|
+
end
|
343
388
|
end
|
344
389
|
end
|
345
390
|
end
|
@@ -348,7 +393,7 @@ private
|
|
348
393
|
@browser.navigate.to(url)
|
349
394
|
sleep 0.1 # slight wait for alert
|
350
395
|
@browser.switch_to.alert.accept
|
351
|
-
rescue modal_error
|
396
|
+
rescue modal_error
|
352
397
|
# alert now gone, should mean navigation happened
|
353
398
|
end
|
354
399
|
|
@@ -378,8 +423,13 @@ private
|
|
378
423
|
begin
|
379
424
|
wait.until do
|
380
425
|
alert = @browser.switch_to.alert
|
381
|
-
regexp = text.is_a?(Regexp) ? text : Regexp.escape(text.to_s)
|
382
|
-
alert.text.match?(regexp)
|
426
|
+
regexp = text.is_a?(Regexp) ? text : Regexp.new(Regexp.escape(text.to_s))
|
427
|
+
matched = alert.text.match?(regexp)
|
428
|
+
unless matched
|
429
|
+
raise Capybara::ModalNotFound, "Unable to find modal dialog with #{text} - found '#{alert.text}' instead."
|
430
|
+
end
|
431
|
+
|
432
|
+
alert
|
383
433
|
end
|
384
434
|
rescue *find_modal_errors
|
385
435
|
raise Capybara::ModalNotFound, "Unable to find modal dialog#{" with #{text}" if text}"
|
@@ -387,10 +437,14 @@ private
|
|
387
437
|
end
|
388
438
|
|
389
439
|
def find_modal_errors
|
390
|
-
@find_modal_errors ||= [Selenium::WebDriver::Error::TimeoutError]
|
440
|
+
@find_modal_errors ||= with_legacy_error([Selenium::WebDriver::Error::TimeoutError], 'TimeOutError')
|
441
|
+
end
|
442
|
+
|
443
|
+
def with_legacy_error(errors, legacy_error)
|
444
|
+
errors.tap do |errs|
|
391
445
|
unless selenium_4?
|
392
446
|
::Selenium::WebDriver.logger.suppress_deprecations do
|
393
|
-
|
447
|
+
errs << Selenium::WebDriver::Error.const_get(legacy_error)
|
394
448
|
end
|
395
449
|
end
|
396
450
|
end
|
@@ -421,10 +475,18 @@ private
|
|
421
475
|
browser
|
422
476
|
end
|
423
477
|
|
478
|
+
def active_element
|
479
|
+
browser.switch_to.active_element
|
480
|
+
end
|
481
|
+
|
424
482
|
def build_node(native_node, initial_cache = {})
|
425
483
|
::Capybara::Selenium::Node.new(self, native_node, initial_cache)
|
426
484
|
end
|
427
485
|
|
486
|
+
def bridge
|
487
|
+
browser.send(:bridge)
|
488
|
+
end
|
489
|
+
|
428
490
|
def specialize_driver
|
429
491
|
browser_type = browser.browser
|
430
492
|
Capybara::Selenium::Driver.specializations.select { |k, _v| k === browser_type }.each_value do |specialization| # rubocop:disable Style/CaseEquality
|
@@ -6,26 +6,26 @@ require 'capybara/selenium/patches/logs'
|
|
6
6
|
module Capybara::Selenium::Driver::ChromeDriver
|
7
7
|
def self.extended(base)
|
8
8
|
bridge = base.send(:bridge)
|
9
|
-
bridge.extend Capybara::Selenium::ChromeLogs unless bridge.respond_to?(:
|
9
|
+
bridge.extend Capybara::Selenium::ChromeLogs unless bridge.respond_to?(:log)
|
10
|
+
bridge.extend Capybara::Selenium::IsDisplayed unless bridge.send(:commands, :is_element_displayed)
|
11
|
+
base.options[:native_displayed] = false if base.options[:native_displayed].nil?
|
10
12
|
end
|
11
13
|
|
12
14
|
def fullscreen_window(handle)
|
13
15
|
within_given_window(handle) do
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
result['value']
|
21
|
-
end
|
16
|
+
super
|
17
|
+
rescue NoMethodError => e
|
18
|
+
raise unless e.message.include?('full_screen_window')
|
19
|
+
|
20
|
+
result = bridge.http.call(:post, "session/#{bridge.session_id}/window/fullscreen", {})
|
21
|
+
result['value']
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
25
|
def resize_window_to(handle, width, height)
|
26
26
|
super
|
27
27
|
rescue Selenium::WebDriver::Error::UnknownError => e
|
28
|
-
raise unless e.message.
|
28
|
+
raise unless e.message.include?('failed to change window state')
|
29
29
|
|
30
30
|
# Chromedriver doesn't wait long enough for state to change when coming out of fullscreen
|
31
31
|
# and raises unnecessary error. Wait a bit and try again.
|
@@ -43,8 +43,8 @@ module Capybara::Selenium::Driver::ChromeDriver
|
|
43
43
|
|
44
44
|
timer = Capybara::Helpers.timer(expire_in: 10)
|
45
45
|
begin
|
46
|
-
@browser.navigate.to('about:blank')
|
47
46
|
clear_storage unless uniform_storage_clear?
|
47
|
+
@browser.navigate.to('about:blank')
|
48
48
|
wait_for_empty_page(timer)
|
49
49
|
rescue *unhandled_alert_errors
|
50
50
|
accept_unhandled_reset_alert
|
@@ -63,12 +63,15 @@ private
|
|
63
63
|
end
|
64
64
|
|
65
65
|
def clear_all_storage?
|
66
|
-
|
66
|
+
storage_clears.none? false
|
67
67
|
end
|
68
68
|
|
69
69
|
def uniform_storage_clear?
|
70
|
-
|
71
|
-
|
70
|
+
storage_clears.uniq { |s| s == false }.length <= 1
|
71
|
+
end
|
72
|
+
|
73
|
+
def storage_clears
|
74
|
+
options.values_at(:clear_session_storage, :clear_local_storage)
|
72
75
|
end
|
73
76
|
|
74
77
|
def clear_storage
|
@@ -90,19 +93,19 @@ private
|
|
90
93
|
end
|
91
94
|
|
92
95
|
def execute_cdp(cmd, params = {})
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
+
if browser.respond_to? :execute_cdp
|
97
|
+
browser.execute_cdp(cmd, **params)
|
98
|
+
else
|
99
|
+
args = { cmd: cmd, params: params }
|
100
|
+
result = bridge.http.call(:post, "session/#{bridge.session_id}/goog/cdp/execute", args)
|
101
|
+
result['value']
|
102
|
+
end
|
96
103
|
end
|
97
104
|
|
98
105
|
def build_node(native_node, initial_cache = {})
|
99
106
|
::Capybara::Selenium::ChromeNode.new(self, native_node, initial_cache)
|
100
107
|
end
|
101
108
|
|
102
|
-
def bridge
|
103
|
-
browser.send(:bridge)
|
104
|
-
end
|
105
|
-
|
106
109
|
def chromedriver_version
|
107
110
|
@chromedriver_version ||= begin
|
108
111
|
caps = browser.capabilities
|