capybara 3.12.0 → 3.13.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 +13 -0
- data/README.md +13 -3
- data/lib/capybara.rb +8 -4
- data/lib/capybara/config.rb +3 -1
- data/lib/capybara/driver/base.rb +2 -2
- data/lib/capybara/driver/node.rb +11 -2
- data/lib/capybara/minitest.rb +3 -3
- data/lib/capybara/minitest/spec.rb +10 -3
- data/lib/capybara/node/actions.rb +4 -0
- data/lib/capybara/node/base.rb +13 -5
- data/lib/capybara/node/document.rb +12 -0
- data/lib/capybara/node/element.rb +37 -0
- data/lib/capybara/node/finders.rb +1 -0
- data/lib/capybara/node/matchers.rb +19 -4
- data/lib/capybara/node/simple.rb +7 -2
- data/lib/capybara/queries/selector_query.rb +93 -9
- data/lib/capybara/rspec/matchers.rb +11 -3
- data/lib/capybara/rspec/matchers/become_closed.rb +1 -1
- data/lib/capybara/rspec/matchers/match_style.rb +38 -0
- data/lib/capybara/selector.rb +30 -30
- data/lib/capybara/selector/selector.rb +47 -3
- data/lib/capybara/selenium/driver.rb +12 -11
- data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +3 -3
- data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +2 -2
- data/lib/capybara/selenium/driver_specializations/internet_explorer_driver.rb +13 -0
- data/lib/capybara/selenium/extensions/find.rb +81 -0
- data/lib/capybara/selenium/extensions/scroll.rb +78 -0
- data/lib/capybara/selenium/node.rb +52 -28
- data/lib/capybara/session.rb +13 -1
- data/lib/capybara/spec/public/test.js +1 -0
- data/lib/capybara/spec/session/assert_style_spec.rb +4 -4
- data/lib/capybara/spec/session/attach_file_spec.rb +6 -6
- data/lib/capybara/spec/session/click_button_spec.rb +1 -1
- data/lib/capybara/spec/session/evaluate_script_spec.rb +1 -0
- data/lib/capybara/spec/session/execute_script_spec.rb +1 -0
- data/lib/capybara/spec/session/fill_in_spec.rb +7 -1
- data/lib/capybara/spec/session/find_field_spec.rb +1 -1
- data/lib/capybara/spec/session/find_spec.rb +11 -0
- data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +0 -1
- data/lib/capybara/spec/session/has_css_spec.rb +32 -0
- data/lib/capybara/spec/session/has_select_spec.rb +2 -2
- data/lib/capybara/spec/session/has_selector_spec.rb +7 -0
- data/lib/capybara/spec/session/has_text_spec.rb +1 -1
- data/lib/capybara/spec/session/matches_style_spec.rb +35 -0
- data/lib/capybara/spec/session/scroll_spec.rb +117 -0
- data/lib/capybara/spec/session/select_spec.rb +5 -0
- data/lib/capybara/spec/spec_helper.rb +1 -0
- data/lib/capybara/spec/views/obscured.erb +1 -1
- data/lib/capybara/spec/views/scroll.erb +20 -0
- data/lib/capybara/spec/views/with_html.erb +1 -1
- data/lib/capybara/version.rb +1 -1
- data/lib/capybara/window.rb +1 -1
- data/spec/basic_node_spec.rb +11 -0
- data/spec/dsl_spec.rb +1 -1
- data/spec/minitest_spec.rb +2 -2
- data/spec/minitest_spec_spec.rb +1 -1
- data/spec/rack_test_spec.rb +1 -0
- data/spec/result_spec.rb +2 -2
- data/spec/selector_spec.rb +11 -1
- data/spec/selenium_spec_firefox.rb +1 -1
- data/spec/selenium_spec_ie.rb +70 -9
- data/spec/session_spec.rb +9 -0
- data/spec/shared_selenium_session.rb +4 -3
- data/spec/spec_helper.rb +2 -0
- metadata +38 -6
- data/lib/capybara/rspec/matchers/have_style.rb +0 -23
- data/lib/capybara/spec/session/has_style_spec.rb +0 -25
@@ -35,6 +35,7 @@ module Capybara
|
|
35
35
|
# * :unchecked (Boolean) — Match unchecked fields?
|
36
36
|
# * :disabled (Boolean) — Match disabled field?
|
37
37
|
# * :multiple (Boolean) — Match fields that accept multiple values
|
38
|
+
# * :style (String, Regexp, Hash)
|
38
39
|
#
|
39
40
|
# * **:fieldset** - Select fieldset elements
|
40
41
|
# * Locator: Matches id or contents of wrapped legend
|
@@ -42,6 +43,7 @@ module Capybara
|
|
42
43
|
# * :id (String, Regexp, XPath::Expression) — Matches id attribute
|
43
44
|
# * :legend (String) — Matches contents of wrapped legend
|
44
45
|
# * :class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
|
46
|
+
# * :style (String, Regexp, Hash)
|
45
47
|
#
|
46
48
|
# * **:link** - Find links ( <a> elements with an href attribute )
|
47
49
|
# * Locator: Matches the id or title attributes, or the string content of the link, or the alt attribute of a contained img element
|
@@ -51,6 +53,7 @@ module Capybara
|
|
51
53
|
# * :alt (String) — Matches the alt attribute of a contained img element
|
52
54
|
# * :class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
|
53
55
|
# * :href (String, Regexp, nil) — Matches the normalized href of the link, if nil will find <a> elements with no href attribute
|
56
|
+
# * :style (String, Regexp, Hash)
|
54
57
|
#
|
55
58
|
# * **:button** - Find buttons ( input [of type submit, reset, image, button] or button elements )
|
56
59
|
# * Locator: Matches the id, Capybara.test_id attribute, value, or title attributes, string content of a button, or the alt attribute of an image type button or of a descendant image of a button
|
@@ -60,6 +63,7 @@ module Capybara
|
|
60
63
|
# * :class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
|
61
64
|
# * :value (String) — Matches the value of an input button
|
62
65
|
# * :type
|
66
|
+
# * :style (String, Regexp, Hash)
|
63
67
|
#
|
64
68
|
# * **:link_or_button** - Find links or buttons
|
65
69
|
# * Locator: See :link and :button selectors
|
@@ -75,6 +79,7 @@ module Capybara
|
|
75
79
|
# * :class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
|
76
80
|
# * :disabled (Boolean) — Match disabled field?
|
77
81
|
# * :multiple (Boolean) — Match fields that accept multiple values
|
82
|
+
# * :style (String, Regexp, Hash)
|
78
83
|
#
|
79
84
|
# * **:radio_button** - Find radio buttons
|
80
85
|
# * Locator: Match id, Capybara.test_id attribute, name, or associated label text
|
@@ -86,6 +91,7 @@ module Capybara
|
|
86
91
|
# * :unchecked (Boolean) — Match unchecked fields?
|
87
92
|
# * :disabled (Boolean) — Match disabled field?
|
88
93
|
# * :option (String) — Match the value
|
94
|
+
# * :style (String, Regexp, Hash)
|
89
95
|
#
|
90
96
|
# * **:checkbox** - Find checkboxes
|
91
97
|
# * Locator: Match id, Capybara.test_id attribute, name, or associated label text
|
@@ -97,6 +103,7 @@ module Capybara
|
|
97
103
|
# * *:unchecked (Boolean) — Match unchecked fields?
|
98
104
|
# * *:disabled (Boolean) — Match disabled field?
|
99
105
|
# * *:option (String) — Match the value
|
106
|
+
# * :style (String, Regexp, Hash)
|
100
107
|
#
|
101
108
|
# * **:select** - Find select elements
|
102
109
|
# * Locator: Match id, Capybara.test_id attribute, name, placeholder, or associated label text
|
@@ -111,6 +118,7 @@ module Capybara
|
|
111
118
|
# * :with_options (Array<String>) — Partial match options
|
112
119
|
# * :selected (String, Array<String>) — Match the selection(s)
|
113
120
|
# * :with_selected (String, Array<String>) — Partial match the selection(s)
|
121
|
+
# * :style (String, Regexp, Hash)
|
114
122
|
#
|
115
123
|
# * **:option** - Find option elements
|
116
124
|
# * Locator: Match text of option
|
@@ -136,6 +144,7 @@ module Capybara
|
|
136
144
|
# * :class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
|
137
145
|
# * :disabled (Boolean) — Match disabled field?
|
138
146
|
# * :multiple (Boolean) — Match field that accepts multiple values
|
147
|
+
# * :style (String, Regexp, Hash)
|
139
148
|
#
|
140
149
|
# * **:label** - Find label elements
|
141
150
|
# * Locator: Match id or text contents
|
@@ -148,6 +157,7 @@ module Capybara
|
|
148
157
|
# * :id (String, Regexp, XPath::Expression) — Match id attribute of table
|
149
158
|
# * :caption (String) — Match text of associated caption
|
150
159
|
# * :class ((String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
|
160
|
+
# * :style (String, Regexp, Hash)
|
151
161
|
#
|
152
162
|
# * **:frame** - Find frame/iframe elements
|
153
163
|
# * Locator: Match id or name
|
@@ -155,6 +165,7 @@ module Capybara
|
|
155
165
|
# * :id (String, Regexp, XPath::Expression) — Match id attribute
|
156
166
|
# * :name (String) — Match name attribute
|
157
167
|
# * :class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
|
168
|
+
# * :style (String, Regexp, Hash)
|
158
169
|
#
|
159
170
|
# * **:element**
|
160
171
|
# * Locator: Type of element ('div', 'a', etc) - if not specified defaults to '*'
|
@@ -173,8 +184,8 @@ module Capybara
|
|
173
184
|
all.fetch(name.to_sym) { |sel_type| raise ArgumentError, "Unknown selector type (:#{sel_type})" }
|
174
185
|
end
|
175
186
|
|
176
|
-
def add(name, &block)
|
177
|
-
all[name.to_sym] = Capybara::Selector.new(name.to_sym, &block)
|
187
|
+
def add(name, **options, &block)
|
188
|
+
all[name.to_sym] = Capybara::Selector.new(name.to_sym, **options, &block)
|
178
189
|
end
|
179
190
|
|
180
191
|
def update(name, &block)
|
@@ -190,7 +201,7 @@ module Capybara
|
|
190
201
|
end
|
191
202
|
end
|
192
203
|
|
193
|
-
def initialize(name, &block)
|
204
|
+
def initialize(name, locator_type: nil, raw_locator: false, &block)
|
194
205
|
@name = name
|
195
206
|
@filter_set = FilterSet.add(name) {}
|
196
207
|
@match = nil
|
@@ -201,6 +212,8 @@ module Capybara
|
|
201
212
|
@expression_filters = {}
|
202
213
|
@locator_filter = nil
|
203
214
|
@default_visibility = nil
|
215
|
+
@locator_type = locator_type
|
216
|
+
@raw_locator = raw_locator
|
204
217
|
@config = {
|
205
218
|
enable_aria_label: false,
|
206
219
|
test_id: nil
|
@@ -301,6 +314,8 @@ module Capybara
|
|
301
314
|
else
|
302
315
|
warn 'Selector has no format'
|
303
316
|
end
|
317
|
+
ensure
|
318
|
+
warn "Locator #{locator.inspect} must #{locator_description}. This will raise an error in a future version of Capybara." unless locator_valid?(locator)
|
304
319
|
end
|
305
320
|
|
306
321
|
##
|
@@ -428,8 +443,37 @@ module Capybara
|
|
428
443
|
Thread.current["capybara_#{object_id}_errors"] = nil
|
429
444
|
end
|
430
445
|
|
446
|
+
# @api private
|
447
|
+
def raw_locator?
|
448
|
+
!!@raw_locator
|
449
|
+
end
|
450
|
+
|
431
451
|
private
|
432
452
|
|
453
|
+
def locator_types
|
454
|
+
return nil unless @locator_type
|
455
|
+
|
456
|
+
Array(@locator_type)
|
457
|
+
end
|
458
|
+
|
459
|
+
def locator_valid?(locator)
|
460
|
+
return true unless locator && locator_types
|
461
|
+
|
462
|
+
locator_types&.any? do |type_or_method|
|
463
|
+
type_or_method.is_a?(Symbol) ? locator.respond_to?(type_or_method) : type_or_method === locator # rubocop:disable Style/CaseEquality
|
464
|
+
end
|
465
|
+
end
|
466
|
+
|
467
|
+
def locator_description
|
468
|
+
locator_types.group_by { |lt| lt.is_a? Symbol }.map do |symbol, types_or_methods|
|
469
|
+
if symbol
|
470
|
+
"respond to #{types_or_methods.join(' or ')}"
|
471
|
+
else
|
472
|
+
"be an instance of #{types_or_methods.join(' or ')}"
|
473
|
+
end
|
474
|
+
end.join(' or ')
|
475
|
+
end
|
476
|
+
|
433
477
|
def errors
|
434
478
|
Thread.current["capybara_#{object_id}_errors"] || []
|
435
479
|
end
|
@@ -4,6 +4,8 @@ require 'uri'
|
|
4
4
|
require 'English'
|
5
5
|
|
6
6
|
class Capybara::Selenium::Driver < Capybara::Driver::Base
|
7
|
+
include Capybara::Selenium::Find
|
8
|
+
|
7
9
|
DEFAULT_OPTIONS = {
|
8
10
|
browser: :firefox,
|
9
11
|
clear_local_storage: nil,
|
@@ -73,14 +75,6 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
73
75
|
browser.current_url
|
74
76
|
end
|
75
77
|
|
76
|
-
def find_xpath(selector)
|
77
|
-
browser.find_elements(:xpath, selector).map(&method(:build_node))
|
78
|
-
end
|
79
|
-
|
80
|
-
def find_css(selector)
|
81
|
-
browser.find_elements(:css, selector).map(&method(:build_node))
|
82
|
-
end
|
83
|
-
|
84
78
|
def wait?; true; end
|
85
79
|
def needs_server?; true; end
|
86
80
|
|
@@ -348,8 +342,12 @@ private
|
|
348
342
|
end
|
349
343
|
end
|
350
344
|
|
351
|
-
def
|
352
|
-
|
345
|
+
def find_context
|
346
|
+
browser
|
347
|
+
end
|
348
|
+
|
349
|
+
def build_node(native_node, initial_cache = {})
|
350
|
+
::Capybara::Selenium::Node.new(self, native_node, initial_cache)
|
353
351
|
end
|
354
352
|
|
355
353
|
def specialize_driver(sel_driver)
|
@@ -359,6 +357,8 @@ private
|
|
359
357
|
when :firefox
|
360
358
|
require 'capybara/selenium/patches/pause_duration_fix' if pause_broken?(sel_driver)
|
361
359
|
extend FirefoxDriver if sel_driver.capabilities.is_a?(::Selenium::WebDriver::Remote::W3C::Capabilities)
|
360
|
+
when :ie, :internet_explorer
|
361
|
+
extend InternetExplorerDriver
|
362
362
|
end
|
363
363
|
end
|
364
364
|
|
@@ -385,7 +385,7 @@ private
|
|
385
385
|
until find_xpath('/html/body/*').empty?
|
386
386
|
raise Capybara::ExpectationNotMet, 'Timed out waiting for Selenium session reset' if timer.expired?
|
387
387
|
|
388
|
-
sleep 0.
|
388
|
+
sleep 0.01
|
389
389
|
end
|
390
390
|
end
|
391
391
|
|
@@ -402,3 +402,4 @@ end
|
|
402
402
|
|
403
403
|
require 'capybara/selenium/driver_specializations/chrome_driver'
|
404
404
|
require 'capybara/selenium/driver_specializations/firefox_driver'
|
405
|
+
require 'capybara/selenium/driver_specializations/internet_explorer_driver'
|
@@ -23,7 +23,7 @@ module Capybara::Selenium::Driver::ChromeDriver
|
|
23
23
|
|
24
24
|
# Chromedriver doesn't wait long enough for state to change when coming out of fullscreen
|
25
25
|
# and raises unnecessary error. Wait a bit and try again.
|
26
|
-
sleep 0.
|
26
|
+
sleep 0.25
|
27
27
|
super
|
28
28
|
end
|
29
29
|
|
@@ -51,8 +51,8 @@ private
|
|
51
51
|
result['value']
|
52
52
|
end
|
53
53
|
|
54
|
-
def build_node(native_node)
|
55
|
-
::Capybara::Selenium::ChromeNode.new(self, native_node)
|
54
|
+
def build_node(native_node, initial_cache = {})
|
55
|
+
::Capybara::Selenium::ChromeNode.new(self, native_node, initial_cache)
|
56
56
|
end
|
57
57
|
|
58
58
|
def bridge
|
@@ -44,7 +44,7 @@ module Capybara::Selenium::Driver::FirefoxDriver
|
|
44
44
|
|
45
45
|
private
|
46
46
|
|
47
|
-
def build_node(native_node)
|
48
|
-
::Capybara::Selenium::FirefoxNode.new(self, native_node)
|
47
|
+
def build_node(native_node, initial_cache = {})
|
48
|
+
::Capybara::Selenium::FirefoxNode.new(self, native_node, initial_cache)
|
49
49
|
end
|
50
50
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Capybara::Selenium::Driver::InternetExplorerDriver
|
4
|
+
def switch_to_frame(frame)
|
5
|
+
return super unless frame == :parent
|
6
|
+
|
7
|
+
# iedriverserver has an issue if the current frame is removed from within it
|
8
|
+
# so we have to move to the default_content and iterate back through the frames
|
9
|
+
handles = @frame_handles[current_window_handle]
|
10
|
+
browser.switch_to.default_content
|
11
|
+
handles.tap(&:pop).each { |fh| browser.switch_to.frame(fh) }
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Capybara
|
4
|
+
module Selenium
|
5
|
+
module Find
|
6
|
+
def find_xpath(selector, uses_visibility: false, styles: nil, **_options)
|
7
|
+
find_by(:xpath, selector, uses_visibility: uses_visibility, texts: [], styles: styles)
|
8
|
+
end
|
9
|
+
|
10
|
+
def find_css(selector, uses_visibility: false, texts: [], styles: nil, **_options)
|
11
|
+
find_by(:css, selector, uses_visibility: uses_visibility, texts: texts, styles: styles)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def find_by(format, selector, uses_visibility:, texts:, styles:)
|
17
|
+
els = find_context.find_elements(format, selector)
|
18
|
+
hints = []
|
19
|
+
if (els.size > 2) && !ENV['DISABLE_CAPYBARA_SELENIUM_OPTIMIZATIONS']
|
20
|
+
els = filter_by_text(els, texts) unless texts.empty?
|
21
|
+
|
22
|
+
hints_js = +''
|
23
|
+
functions = []
|
24
|
+
if uses_visibility
|
25
|
+
hints_js << <<~VISIBILITY_JS
|
26
|
+
var vis_func = #{is_displayed_atom};
|
27
|
+
VISIBILITY_JS
|
28
|
+
functions << 'vis_func'
|
29
|
+
end
|
30
|
+
|
31
|
+
if styles.is_a? Hash
|
32
|
+
hints_js << <<~STYLE_JS
|
33
|
+
var style_func = function(el){
|
34
|
+
var el_styles = window.getComputedStyle(el);
|
35
|
+
return #{styles.keys.map(&:to_s)}.reduce(function(res, style){
|
36
|
+
res[style] = el_styles[style];
|
37
|
+
return res;
|
38
|
+
}, {});
|
39
|
+
};
|
40
|
+
STYLE_JS
|
41
|
+
functions << 'style_func'
|
42
|
+
end
|
43
|
+
|
44
|
+
unless functions.empty?
|
45
|
+
hints_js << <<~EACH_JS
|
46
|
+
return arguments[0].map(function(el){
|
47
|
+
return [#{functions.join(',')}].map(function(fn){ return fn.call(null, el) }); });
|
48
|
+
EACH_JS
|
49
|
+
|
50
|
+
hints = es_context.execute_script hints_js, els
|
51
|
+
hints.map! do |results|
|
52
|
+
result = {}
|
53
|
+
result[:style] = results.pop if styles.is_a? Hash
|
54
|
+
result[:visible] = results.pop if uses_visibility
|
55
|
+
result
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
els.map.with_index { |el, idx| build_node(el, hints[idx] || {}) }
|
60
|
+
end
|
61
|
+
|
62
|
+
def filter_by_text(elements, texts)
|
63
|
+
es_context.execute_script <<~JS, elements, texts
|
64
|
+
var texts = arguments[1]
|
65
|
+
return arguments[0].filter(function(el){
|
66
|
+
var content = el.textContent.toLowerCase();
|
67
|
+
return texts.every(function(txt){ return content.indexOf(txt.toLowerCase()) != -1 });
|
68
|
+
})
|
69
|
+
JS
|
70
|
+
end
|
71
|
+
|
72
|
+
def es_context
|
73
|
+
respond_to?(:execute_script) ? self : driver
|
74
|
+
end
|
75
|
+
|
76
|
+
def is_displayed_atom # rubocop:disable Naming/PredicateName
|
77
|
+
@@is_displayed_atom ||= browser.send(:bridge).send(:read_atom, 'isDisplayed')
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Capybara
|
4
|
+
module Selenium
|
5
|
+
module Scroll
|
6
|
+
def scroll_by(x, y)
|
7
|
+
driver.execute_script <<~JS, self, x, y
|
8
|
+
var el = arguments[0];
|
9
|
+
if (el.scrollBy){
|
10
|
+
el.scrollBy(arguments[1], arguments[2]);
|
11
|
+
} else {
|
12
|
+
el.scrollTop = el.scrollTop + arguments[2];
|
13
|
+
el.scrollLeft = el.scrollLeft + arguments[1];
|
14
|
+
}
|
15
|
+
JS
|
16
|
+
end
|
17
|
+
|
18
|
+
def scroll_to(element, location, position = nil)
|
19
|
+
# location, element = element, nil if element.is_a? Symbol
|
20
|
+
if element.is_a? Capybara::Selenium::Node
|
21
|
+
scroll_element_to_location(element, location)
|
22
|
+
elsif location.is_a? Symbol
|
23
|
+
scroll_to_location(location)
|
24
|
+
else
|
25
|
+
scroll_to_coords(*position)
|
26
|
+
end
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def scroll_element_to_location(element, location)
|
33
|
+
scroll_opts = case location
|
34
|
+
when :top
|
35
|
+
'true'
|
36
|
+
when :bottom
|
37
|
+
'false'
|
38
|
+
when :center
|
39
|
+
"{behavior: 'instant', block: 'center'}"
|
40
|
+
else
|
41
|
+
raise ArgumentError, "Invalid scroll_to location: #{location}"
|
42
|
+
end
|
43
|
+
driver.execute_script <<~JS, element
|
44
|
+
arguments[0].scrollIntoView(#{scroll_opts})
|
45
|
+
JS
|
46
|
+
end
|
47
|
+
|
48
|
+
def scroll_to_location(location)
|
49
|
+
scroll_y = case location
|
50
|
+
when :top
|
51
|
+
'0'
|
52
|
+
when :bottom
|
53
|
+
'arguments[0].scrollHeight'
|
54
|
+
when :center
|
55
|
+
'(arguments[0].scrollHeight - arguments[0].clientHeight)/2'
|
56
|
+
end
|
57
|
+
driver.execute_script <<~JS, self
|
58
|
+
if (arguments[0].scrollTo){
|
59
|
+
arguments[0].scrollTo(0, #{scroll_y});
|
60
|
+
} else {
|
61
|
+
arguments[0].scrollTop = #{scroll_y};
|
62
|
+
}
|
63
|
+
JS
|
64
|
+
end
|
65
|
+
|
66
|
+
def scroll_to_coords(x, y)
|
67
|
+
driver.execute_script <<~JS, self, x, y
|
68
|
+
if (arguments[0].scrollTo){
|
69
|
+
arguments[0].scrollTo(arguments[1], arguments[2]);
|
70
|
+
} else {
|
71
|
+
arguments[0].scrollTop = arguments[2];
|
72
|
+
arguments[0].scrollLeft = arguments[1];
|
73
|
+
}
|
74
|
+
JS
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -1,13 +1,20 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Selenium specific implementation of the Capybara::Driver::Node API
|
4
|
+
|
5
|
+
require 'capybara/selenium/extensions/find'
|
6
|
+
require 'capybara/selenium/extensions/scroll'
|
7
|
+
|
4
8
|
class Capybara::Selenium::Node < Capybara::Driver::Node
|
9
|
+
include Capybara::Selenium::Find
|
10
|
+
include Capybara::Selenium::Scroll
|
11
|
+
|
5
12
|
def visible_text
|
6
13
|
native.text
|
7
14
|
end
|
8
15
|
|
9
16
|
def all_text
|
10
|
-
text = driver.
|
17
|
+
text = driver.evaluate_script('arguments[0].textContent', self)
|
11
18
|
text.gsub(/[\u200b\u200e\u200f]/, '')
|
12
19
|
.gsub(/[\ \n\f\t\v\u2028\u2029]+/, ' ')
|
13
20
|
.gsub(/\A[[:space:]&&[^\u00a0]]+/, '')
|
@@ -148,35 +155,12 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
148
155
|
native.attribute('isContentEditable')
|
149
156
|
end
|
150
157
|
|
151
|
-
def find_xpath(locator)
|
152
|
-
native.find_elements(:xpath, locator).map { |el| self.class.new(driver, el) }
|
153
|
-
end
|
154
|
-
|
155
|
-
def find_css(locator)
|
156
|
-
native.find_elements(:css, locator).map { |el| self.class.new(driver, el) }
|
157
|
-
end
|
158
|
-
|
159
158
|
def ==(other)
|
160
159
|
native == other.native
|
161
160
|
end
|
162
161
|
|
163
162
|
def path
|
164
|
-
|
165
|
-
|
166
|
-
result = []
|
167
|
-
default_ns = path.last[:namespaceURI]
|
168
|
-
while (node = path.shift)
|
169
|
-
parent = path.first
|
170
|
-
selector = node[:tagName]
|
171
|
-
if node[:namespaceURI] != default_ns
|
172
|
-
selector = XPath.child.where((XPath.local_name == selector) & (XPath.namespace_uri == node[:namespaceURI])).to_s
|
173
|
-
end
|
174
|
-
|
175
|
-
selector += sibling_index(parent, node, selector) if parent
|
176
|
-
result.push selector
|
177
|
-
end
|
178
|
-
|
179
|
-
'/' + result.reverse.join('/')
|
163
|
+
driver.evaluate_script GET_XPATH_SCRIPT, self
|
180
164
|
end
|
181
165
|
|
182
166
|
protected
|
@@ -305,7 +289,7 @@ private
|
|
305
289
|
# Ensure we are focused on the element
|
306
290
|
click
|
307
291
|
|
308
|
-
|
292
|
+
driver.execute_script <<-JS, self
|
309
293
|
var range = document.createRange();
|
310
294
|
var sel = window.getSelection();
|
311
295
|
arguments[0].focus();
|
@@ -313,7 +297,6 @@ private
|
|
313
297
|
sel.removeAllRanges();
|
314
298
|
sel.addRange(range);
|
315
299
|
JS
|
316
|
-
driver.execute_script script, self
|
317
300
|
|
318
301
|
# The action api has a speed problem but both chrome and firefox 58 raise errors
|
319
302
|
# if we use the faster direct send_keys. For now just send_keys to the element
|
@@ -341,8 +324,12 @@ private
|
|
341
324
|
each_key(keys) { |key| actions.key_up(key) }
|
342
325
|
end
|
343
326
|
|
327
|
+
def browser
|
328
|
+
driver.browser
|
329
|
+
end
|
330
|
+
|
344
331
|
def browser_action
|
345
|
-
|
332
|
+
browser.action
|
346
333
|
end
|
347
334
|
|
348
335
|
def each_key(keys)
|
@@ -357,6 +344,43 @@ private
|
|
357
344
|
end
|
358
345
|
end
|
359
346
|
|
347
|
+
def find_context
|
348
|
+
native
|
349
|
+
end
|
350
|
+
|
351
|
+
def build_node(native_node, initial_cache = {})
|
352
|
+
self.class.new(driver, native_node, initial_cache)
|
353
|
+
end
|
354
|
+
|
355
|
+
GET_XPATH_SCRIPT = <<~'JS'
|
356
|
+
(function(el, xml){
|
357
|
+
var xpath = '';
|
358
|
+
var pos, tempitem2;
|
359
|
+
|
360
|
+
while(el !== xml.documentElement) {
|
361
|
+
pos = 0;
|
362
|
+
tempitem2 = el;
|
363
|
+
while(tempitem2) {
|
364
|
+
if (tempitem2.nodeType === 1 && tempitem2.nodeName === el.nodeName) { // If it is ELEMENT_NODE of the same name
|
365
|
+
pos += 1;
|
366
|
+
}
|
367
|
+
tempitem2 = tempitem2.previousSibling;
|
368
|
+
}
|
369
|
+
|
370
|
+
if (el.namespaceURI != xml.documentElement.namespaceURI) {
|
371
|
+
xpath = "*[local-name()='"+el.nodeName+"' and namespace-uri()='"+(el.namespaceURI===null?'':el.namespaceURI)+"']["+pos+']'+'/'+xpath;
|
372
|
+
} else {
|
373
|
+
xpath = el.nodeName.toUpperCase()+"["+pos+"]/"+xpath;
|
374
|
+
}
|
375
|
+
|
376
|
+
el = el.parentNode;
|
377
|
+
}
|
378
|
+
xpath = '/'+xml.documentElement.nodeName.toUpperCase()+'/'+xpath;
|
379
|
+
xpath = xpath.replace(/\/$/, '');
|
380
|
+
return xpath;
|
381
|
+
})(arguments[0], document)
|
382
|
+
JS
|
383
|
+
|
360
384
|
# SettableValue encapsulates time/date field formatting
|
361
385
|
class SettableValue
|
362
386
|
attr_reader :value
|