capybara 3.12.0 → 3.13.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|