capybara 3.20.2 → 3.21.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 +14 -0
- data/README.md +4 -4
- data/lib/capybara.rb +11 -11
- data/lib/capybara/driver/node.rb +4 -0
- data/lib/capybara/helpers.rb +1 -1
- data/lib/capybara/minitest.rb +4 -4
- data/lib/capybara/node/actions.rb +36 -29
- data/lib/capybara/node/element.rb +123 -82
- data/lib/capybara/node/finders.rb +2 -7
- data/lib/capybara/selector/definition/css.rb +4 -1
- data/lib/capybara/selector/regexp_disassembler.rb +2 -2
- data/lib/capybara/selenium/extensions/html5_drag.rb +63 -0
- data/lib/capybara/selenium/node.rb +4 -0
- data/lib/capybara/selenium/nodes/chrome_node.rb +4 -0
- data/lib/capybara/selenium/nodes/firefox_node.rb +4 -13
- data/lib/capybara/selenium/nodes/ie_node.rb +14 -3
- data/lib/capybara/selenium/nodes/safari_node.rb +0 -13
- data/lib/capybara/spec/public/test.js +29 -3
- data/lib/capybara/spec/session/attach_file_spec.rb +13 -5
- data/lib/capybara/spec/session/has_css_spec.rb +4 -3
- data/lib/capybara/spec/session/node_spec.rb +85 -0
- data/lib/capybara/spec/spec_helper.rb +4 -0
- data/lib/capybara/version.rb +1 -1
- data/spec/selenium_spec_firefox.rb +2 -0
- data/spec/selenium_spec_ie.rb +13 -7
- data/spec/selenium_spec_safari.rb +2 -0
- data/spec/shared_selenium_session.rb +1 -51
- metadata +4 -4
@@ -8,19 +8,14 @@ module Capybara
|
|
8
8
|
# Find an {Capybara::Node::Element} based on the given arguments. +find+ will raise an error if the element
|
9
9
|
# is not found.
|
10
10
|
#
|
11
|
-
# @!macro waiting_behavior
|
12
|
-
# If the driver is capable of executing JavaScript, this method will wait for a set amount of time
|
13
|
-
# and continuously retry finding the element until either the element is found or the time
|
14
|
-
# expires. The length of time +find+ will wait is controlled through {Capybara.default_max_wait_time}
|
15
|
-
# and defaults to 2 seconds.
|
16
|
-
# @option options [false, true, Numeric] wait (Capybara.default_max_wait_time) Maximum time to wait for matching element to appear.
|
17
|
-
#
|
18
11
|
# page.find('#foo').find('.bar')
|
19
12
|
# page.find(:xpath, './/div[contains(., "bar")]')
|
20
13
|
# page.find('li', text: 'Quox').click_link('Delete')
|
21
14
|
#
|
22
15
|
# @param (see Capybara::Node::Finders#all)
|
23
16
|
#
|
17
|
+
# @macro waiting_behavior
|
18
|
+
#
|
24
19
|
# @!macro system_filters
|
25
20
|
# @option options [String, Regexp] text Only find elements which contain this text or match this regexp
|
26
21
|
# @option options [String, Boolean] exact_text (Capybara.exact_text) When String the elements contained text must match exactly, when Boolean controls whether the :text option must match exactly
|
@@ -1,5 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
Capybara.add_selector(:css, locator_type: [String, Symbol], raw_locator: true) do
|
4
|
-
css
|
4
|
+
css do |css|
|
5
|
+
warn "DEPRECATED: Passing a symbol (#{css.inspect}) as the CSS locator is deprecated - please pass a string instead." if css.is_a? Symbol
|
6
|
+
css
|
7
|
+
end
|
5
8
|
end
|
@@ -22,6 +22,69 @@ class Capybara::Selenium::Node
|
|
22
22
|
native.property('draggable')
|
23
23
|
end
|
24
24
|
|
25
|
+
def html5_drop(*args)
|
26
|
+
if args[0].is_a? String
|
27
|
+
input = driver.evaluate_script ATTACH_FILE
|
28
|
+
input.set_file(args)
|
29
|
+
driver.execute_script DROP_FILE, self, input
|
30
|
+
else
|
31
|
+
items = args.each_with_object([]) do |arg, arr|
|
32
|
+
arg.each_with_object(arr) do |(type, data), arr_|
|
33
|
+
arr_ << { type: type, data: data }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
driver.execute_script DROP_STRING, items, self
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
DROP_STRING = <<~JS
|
41
|
+
var strings = arguments[0],
|
42
|
+
el = arguments[1],
|
43
|
+
dt = new DataTransfer(),
|
44
|
+
opts = { cancelable: true, bubbles: true, dataTransfer: dt };
|
45
|
+
for (var i=0; i < strings.length; i++){
|
46
|
+
if (dt.items) {
|
47
|
+
dt.items.add(strings[i]['data'], strings[i]['type']);
|
48
|
+
} else {
|
49
|
+
dt.setData(strings[i]['type'], strings[i]['data']);
|
50
|
+
}
|
51
|
+
}
|
52
|
+
var dropEvent = new DragEvent('drop', opts);
|
53
|
+
el.dispatchEvent(dropEvent);
|
54
|
+
JS
|
55
|
+
|
56
|
+
DROP_FILE = <<~JS
|
57
|
+
var el = arguments[0],
|
58
|
+
input = arguments[1],
|
59
|
+
files = input.files,
|
60
|
+
dt = new DataTransfer(),
|
61
|
+
opts = { cancelable: true, bubbles: true, dataTransfer: dt };
|
62
|
+
input.parentElement.removeChild(input);
|
63
|
+
if (dt.items){
|
64
|
+
for (var i=0; i<files.length; i++){
|
65
|
+
dt.items.add(files[i]);
|
66
|
+
}
|
67
|
+
} else {
|
68
|
+
Object.defineProperty(dt, "files", {
|
69
|
+
value: files,
|
70
|
+
writable: false
|
71
|
+
});
|
72
|
+
}
|
73
|
+
var dropEvent = new DragEvent('drop', opts);
|
74
|
+
el.dispatchEvent(dropEvent);
|
75
|
+
JS
|
76
|
+
|
77
|
+
ATTACH_FILE = <<~JS
|
78
|
+
(function(){
|
79
|
+
var input = document.createElement('INPUT');
|
80
|
+
input.type = "file";
|
81
|
+
input.id = "_capybara_drop_file";
|
82
|
+
input.multiple = true;
|
83
|
+
document.body.appendChild(input);
|
84
|
+
return input;
|
85
|
+
})()
|
86
|
+
JS
|
87
|
+
|
25
88
|
MOUSEDOWN_TRACKER = <<~JS
|
26
89
|
document.addEventListener('mousedown', ev => {
|
27
90
|
window.capybara_mousedown_prevented = ev.defaultPrevented;
|
@@ -137,6 +137,10 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
137
137
|
element.scroll_if_needed { browser_action.move_to(element.native).release.perform }
|
138
138
|
end
|
139
139
|
|
140
|
+
def drop(*_)
|
141
|
+
raise NotImplementedError, 'Out of browser drop emulation is not implemented for the current browser'
|
142
|
+
end
|
143
|
+
|
140
144
|
def tag_name
|
141
145
|
@tag_name ||= native.tag_name.downcase
|
142
146
|
end
|
@@ -52,6 +52,10 @@ class Capybara::Selenium::FirefoxNode < Capybara::Selenium::Node
|
|
52
52
|
html5_drag_to(element)
|
53
53
|
end
|
54
54
|
|
55
|
+
def drop(*args)
|
56
|
+
html5_drop(*args)
|
57
|
+
end
|
58
|
+
|
55
59
|
def hover
|
56
60
|
return super unless browser_version >= 65.0
|
57
61
|
|
@@ -117,19 +121,6 @@ private
|
|
117
121
|
driver.browser.capabilities[:browser_version].to_f
|
118
122
|
end
|
119
123
|
|
120
|
-
DISABLED_BY_FIELDSET_XPATH = XPath.generate do |x|
|
121
|
-
x.parent(:fieldset)[
|
122
|
-
x.attr(:disabled)
|
123
|
-
] + x.ancestor[
|
124
|
-
~x.self(:legend) |
|
125
|
-
x.preceding_sibling(:legend)
|
126
|
-
][
|
127
|
-
x.parent(:fieldset)[
|
128
|
-
x.attr(:disabled)
|
129
|
-
]
|
130
|
-
]
|
131
|
-
end.to_s.freeze
|
132
|
-
|
133
124
|
class ModifierKeysStack
|
134
125
|
def initialize
|
135
126
|
@stack = []
|
@@ -4,8 +4,19 @@ require 'capybara/selenium/extensions/html5_drag'
|
|
4
4
|
|
5
5
|
class Capybara::Selenium::IENode < Capybara::Selenium::Node
|
6
6
|
def disabled?
|
7
|
-
#
|
8
|
-
#
|
9
|
-
|
7
|
+
# super
|
8
|
+
# optimize to one script call
|
9
|
+
driver.evaluate_script <<~JS.delete("\n"), self
|
10
|
+
arguments[0].msMatchesSelector('
|
11
|
+
:disabled,
|
12
|
+
select:disabled *,
|
13
|
+
optgroup:disabled *,
|
14
|
+
fieldset[disabled],
|
15
|
+
fieldset[disabled] > *:not(legend),
|
16
|
+
fieldset[disabled] > *:not(legend) *,
|
17
|
+
fieldset[disabled] > legend:nth-of-type(n+2),
|
18
|
+
fieldset[disabled] > legend:nth-of-type(n+2) *
|
19
|
+
')
|
20
|
+
JS
|
10
21
|
end
|
11
22
|
end
|
@@ -91,19 +91,6 @@ private
|
|
91
91
|
driver.browser.send(:bridge)
|
92
92
|
end
|
93
93
|
|
94
|
-
DISABLED_BY_FIELDSET_XPATH = XPath.generate do |x|
|
95
|
-
x.parent(:fieldset)[
|
96
|
-
x.attr(:disabled)
|
97
|
-
] + x.ancestor[
|
98
|
-
~x.self(:legend) |
|
99
|
-
x.preceding_sibling(:legend)
|
100
|
-
][
|
101
|
-
x.parent(:fieldset)[
|
102
|
-
x.attr(:disabled)
|
103
|
-
]
|
104
|
-
]
|
105
|
-
end.to_s.freeze
|
106
|
-
|
107
94
|
def _send_keys(keys, actions = browser_action, down_keys = ModifierKeysStack.new)
|
108
95
|
case keys
|
109
96
|
when *MODIFIER_KEYS
|
@@ -18,7 +18,33 @@ $(function() {
|
|
18
18
|
});
|
19
19
|
$('#drop_html5, #drop_html5_scroll').on('drop', function(ev){
|
20
20
|
ev.preventDefault();
|
21
|
-
|
21
|
+
var oev = ev.originalEvent;
|
22
|
+
if (oev.dataTransfer.items) {
|
23
|
+
for (var i = 0; i < oev.dataTransfer.items.length; i++){
|
24
|
+
var item = oev.dataTransfer.items[i];
|
25
|
+
if (item.kind === 'file'){
|
26
|
+
var file = item.getAsFile();
|
27
|
+
$(this).append('HTML5 Dropped file: ' + file.name);
|
28
|
+
} else {
|
29
|
+
var _this = this;
|
30
|
+
var callback = (function(type){
|
31
|
+
return function(s){
|
32
|
+
$(_this).append('HTML5 Dropped string: ' + type + ' ' + s)
|
33
|
+
}
|
34
|
+
})(item.type);
|
35
|
+
item.getAsString(callback);
|
36
|
+
}
|
37
|
+
}
|
38
|
+
} else {
|
39
|
+
$(this).html('HTML5 Dropped ' + oev.dataTransfer.getData("text"));
|
40
|
+
for (var i = 0; i < oev.dataTransfer.files.length; i++) {
|
41
|
+
$(this).append('HTML5 Dropped file: ' + oev.dataTransfer.files[i].name);
|
42
|
+
}
|
43
|
+
for (var i = 0; i < oev.dataTransfer.types.length; i++) {
|
44
|
+
var type = oev.dataTransfer.types[i];
|
45
|
+
$(this).append('HTML5 Dropped string: ' + type + ' ' + oev.dataTransfer.getData(type));
|
46
|
+
}
|
47
|
+
}
|
22
48
|
});
|
23
49
|
$('#clickable').click(function(e) {
|
24
50
|
var link = $(this);
|
@@ -180,7 +206,7 @@ $(function() {
|
|
180
206
|
sessionStorage.setItem('session', 'session_value');
|
181
207
|
localStorage.setItem('local', 'local value');
|
182
208
|
});
|
183
|
-
$('#multiple-file').change(function(e){
|
184
|
-
$('body').append($('<p class="file_change"
|
209
|
+
$('#multiple-file, #hidden_file').change(function(e){
|
210
|
+
$('body').append($('<p class="file_change">File input changed</p>'));
|
185
211
|
})
|
186
212
|
});
|
@@ -180,6 +180,12 @@ Capybara::SpecHelper.spec '#attach_file' do
|
|
180
180
|
@session.attach_file('hidden_file', with_os_path_separators(__FILE__), make_visible: true)
|
181
181
|
expect(@session.evaluate_script('arguments[0].style.display', @session.find(:css, '#hidden_file', visible: :all))).to eq 'none'
|
182
182
|
end
|
183
|
+
|
184
|
+
it 'should fire change' do
|
185
|
+
@session.visit('/with_js')
|
186
|
+
@session.attach_file('hidden_file', with_os_path_separators(__FILE__), make_visible: true)
|
187
|
+
expect(@session).to have_css('.file_change')
|
188
|
+
end
|
183
189
|
end
|
184
190
|
|
185
191
|
context 'with a block', requires: %i[js] do
|
@@ -198,11 +204,13 @@ Capybara::SpecHelper.spec '#attach_file' do
|
|
198
204
|
@session.click_button('awesome')
|
199
205
|
expect(extract_results(@session)['hidden_image']).to end_with(File.basename(__FILE__))
|
200
206
|
end
|
201
|
-
end
|
202
207
|
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
208
|
+
it 'should fire change' do
|
209
|
+
@session.visit('/with_js')
|
210
|
+
@session.attach_file(with_os_path_separators(__FILE__)) do
|
211
|
+
@session.find(:label, 'Label for hidden file input').click
|
212
|
+
end
|
213
|
+
expect(@session).to have_css('.file_change')
|
214
|
+
end
|
207
215
|
end
|
208
216
|
end
|
@@ -10,10 +10,11 @@ Capybara::SpecHelper.spec '#has_css?' do
|
|
10
10
|
expect(@session).to have_css('p a#foo')
|
11
11
|
end
|
12
12
|
|
13
|
-
it 'should
|
13
|
+
it 'should warn when passed a symbol' do
|
14
14
|
# This was never a specifically accepted format but it has worked for a
|
15
|
-
# lot of versions.
|
16
|
-
# TODO:
|
15
|
+
# lot of versions.
|
16
|
+
# TODO: Remove in 4.0
|
17
|
+
expect_any_instance_of(Kernel).to receive(:warn) # rubocop:disable RSpec/AnyInstance
|
17
18
|
expect(@session).to have_css(:p)
|
18
19
|
end
|
19
20
|
|
@@ -242,6 +242,10 @@ Capybara::SpecHelper.spec 'node' do
|
|
242
242
|
expect(@session.find('//div[@id="hidden_attr"]')).not_to be_visible
|
243
243
|
expect(@session.find('//a[@id="hidden_attr_via_ancestor"]')).not_to be_visible
|
244
244
|
expect(@session.find('//input[@id="hidden_input"]')).not_to be_visible
|
245
|
+
end
|
246
|
+
|
247
|
+
it 'template elements should not be visible' do
|
248
|
+
Capybara.ignore_hidden_elements = false
|
245
249
|
expect(@session.find('//template')).not_to be_visible
|
246
250
|
end
|
247
251
|
|
@@ -414,6 +418,87 @@ Capybara::SpecHelper.spec 'node' do
|
|
414
418
|
link.drag_to target
|
415
419
|
expect(@session).to have_xpath('//div[contains(., "Dropped!")]')
|
416
420
|
end
|
421
|
+
|
422
|
+
context 'HTML5', requires: %i[js html5_drag] do
|
423
|
+
it 'should HTML5 drag and drop an object' do
|
424
|
+
@session.visit('/with_js')
|
425
|
+
element = @session.find('//div[@id="drag_html5"]')
|
426
|
+
target = @session.find('//div[@id="drop_html5"]')
|
427
|
+
element.drag_to(target)
|
428
|
+
expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped string: text/plain drag_html5")]')
|
429
|
+
end
|
430
|
+
|
431
|
+
it 'should set clientX/Y in dragover events' do
|
432
|
+
@session.visit('/with_js')
|
433
|
+
element = @session.find('//div[@id="drag_html5"]')
|
434
|
+
target = @session.find('//div[@id="drop_html5"]')
|
435
|
+
element.drag_to(target)
|
436
|
+
expect(@session).to have_css('div.log', text: /DragOver with client position: [1-9]\d*,[1-9]\d*/, count: 2)
|
437
|
+
end
|
438
|
+
|
439
|
+
it 'should not HTML5 drag and drop on a non HTML5 drop element' do
|
440
|
+
@session.visit('/with_js')
|
441
|
+
element = @session.find('//div[@id="drag_html5"]')
|
442
|
+
target = @session.find('//div[@id="drop_html5"]')
|
443
|
+
target.execute_script("$(this).removeClass('drop');")
|
444
|
+
element.drag_to(target)
|
445
|
+
sleep 1
|
446
|
+
expect(@session).not_to have_xpath('//div[contains(., "HTML5 Dropped")]')
|
447
|
+
end
|
448
|
+
|
449
|
+
it 'should HTML5 drag and drop when scrolling needed' do
|
450
|
+
@session.visit('/with_js')
|
451
|
+
element = @session.find('//div[@id="drag_html5_scroll"]')
|
452
|
+
target = @session.find('//div[@id="drop_html5_scroll"]')
|
453
|
+
element.drag_to(target)
|
454
|
+
expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped string: text/plain drag_html5_scroll")]')
|
455
|
+
end
|
456
|
+
|
457
|
+
it 'should drag HTML5 default draggable elements' do
|
458
|
+
@session.visit('/with_js')
|
459
|
+
link = @session.find_link('drag_link_html5')
|
460
|
+
target = @session.find(:id, 'drop_html5')
|
461
|
+
link.drag_to target
|
462
|
+
expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped")]')
|
463
|
+
end
|
464
|
+
end
|
465
|
+
end
|
466
|
+
|
467
|
+
describe 'Element#drop', requires: %i[js html5_drag] do
|
468
|
+
it 'can drop a file' do
|
469
|
+
@session.visit('/with_js')
|
470
|
+
target = @session.find('//div[@id="drop_html5"]')
|
471
|
+
target.drop(
|
472
|
+
with_os_path_separators(File.expand_path('../fixtures/capybara.jpg', File.dirname(__FILE__)))
|
473
|
+
)
|
474
|
+
expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped file: capybara.jpg")]')
|
475
|
+
end
|
476
|
+
|
477
|
+
it 'can drop multiple files' do
|
478
|
+
@session.visit('/with_js')
|
479
|
+
target = @session.find('//div[@id="drop_html5"]')
|
480
|
+
target.drop(
|
481
|
+
with_os_path_separators(File.expand_path('../fixtures/capybara.jpg', File.dirname(__FILE__))),
|
482
|
+
with_os_path_separators(File.expand_path('../fixtures/test_file.txt', File.dirname(__FILE__)))
|
483
|
+
)
|
484
|
+
expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped file: capybara.jpg")]')
|
485
|
+
expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped file: test_file.txt")]')
|
486
|
+
end
|
487
|
+
|
488
|
+
it 'can drop strings' do
|
489
|
+
@session.visit('/with_js')
|
490
|
+
target = @session.find('//div[@id="drop_html5"]')
|
491
|
+
target.drop('text/plain' => 'Some dropped text')
|
492
|
+
expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped string: text/plain Some dropped text")]')
|
493
|
+
end
|
494
|
+
|
495
|
+
it 'can drop multiple strings' do
|
496
|
+
@session.visit('/with_js')
|
497
|
+
target = @session.find('//div[@id="drop_html5"]')
|
498
|
+
target.drop('text/plain' => 'Some dropped text', 'text/url' => 'http://www.google.com')
|
499
|
+
expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped string: text/plain Some dropped text")]')
|
500
|
+
expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped string: text/url http://www.google.com")]')
|
501
|
+
end
|
417
502
|
end
|
418
503
|
|
419
504
|
describe '#hover', requires: [:hover] do
|
@@ -124,6 +124,10 @@ module Capybara
|
|
124
124
|
def be_an_invalid_element_error(session)
|
125
125
|
satisfy { |error| session.driver.invalid_element_errors.any? { |e| error.is_a? e } }
|
126
126
|
end
|
127
|
+
|
128
|
+
def with_os_path_separators(path)
|
129
|
+
Gem.win_platform? ? path.to_s.tr('/', '\\') : path.to_s
|
130
|
+
end
|
127
131
|
end
|
128
132
|
end
|
129
133
|
|
data/lib/capybara/version.rb
CHANGED
@@ -64,6 +64,8 @@ Capybara::SpecHelper.run_specs TestSessions::SeleniumFirefox, 'selenium', capyba
|
|
64
64
|
pending "Geckodriver doesn't provide a way to remove cookies outside the current domain"
|
65
65
|
when 'Capybara::Session selenium #attach_file with a block can upload by clicking the file input'
|
66
66
|
pending "Geckodriver doesn't allow clicking on file inputs"
|
67
|
+
when /drag_to.*HTML5/
|
68
|
+
pending "Firefox < 62 doesn't support a DataTransfer constuctor" if firefox_lt?(62.0, @session)
|
67
69
|
end
|
68
70
|
end
|
69
71
|
|
data/spec/selenium_spec_ie.rb
CHANGED
@@ -6,13 +6,13 @@ require 'shared_selenium_session'
|
|
6
6
|
require 'shared_selenium_node'
|
7
7
|
require 'rspec/shared_spec_matchers'
|
8
8
|
|
9
|
-
if ENV['CI']
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
end
|
9
|
+
# if ENV['CI']
|
10
|
+
# if ::Selenium::WebDriver::Service.respond_to? :driver_path=
|
11
|
+
# ::Selenium::WebDriver::IE::Service
|
12
|
+
# else
|
13
|
+
# ::Selenium::WebDriver::IE
|
14
|
+
# end.driver_path = 'C:\Tools\WebDriver\IEDriverServer.exe'
|
15
|
+
# end
|
16
16
|
|
17
17
|
def selenium_host
|
18
18
|
ENV.fetch('SELENIUM_HOST', '192.168.56.102')
|
@@ -110,6 +110,12 @@ Capybara::SpecHelper.run_specs TestSessions::SeleniumIE, 'selenium', capybara_sk
|
|
110
110
|
pending 'IE treats blank href as a parent request (against HTML spec)'
|
111
111
|
when /#attach_file with a block/
|
112
112
|
skip 'Hangs IE testing for unknown reason'
|
113
|
+
when /drag_to.*HTML5/
|
114
|
+
pending "IE doesn't support a DataTransfer constuctor"
|
115
|
+
when /template elements should not be visible/
|
116
|
+
skip "IE doesn't support template elements"
|
117
|
+
when /Element#drop/
|
118
|
+
pending "IE doesn't support DataTransfer constructor"
|
113
119
|
end
|
114
120
|
end
|
115
121
|
|