1
0
Fork 0
mirror of https://github.com/teamcapybara/capybara.git synced 2022-11-09 12:08:07 -05:00

Simulate file/external data drop in chrome and firefox and move HTML5 drag/drop tests into shared tests

This commit is contained in:
Thomas Walpole 2019-05-15 14:25:50 -07:00
parent 4c097e4324
commit 94f7f8881a
15 changed files with 228 additions and 58 deletions

View file

@ -1,3 +1,10 @@
# Version 3.21.0
Release date: unreleased
### Added
* Chrome and Firefox via the selenium driver support dropping files/data on elements
# Version 3.20.1
Release date: 2019-05-17

View file

@ -69,6 +69,10 @@ module Capybara
raise NotImplementedError
end
def drop(*args)
raise NotImplementedError
end
def scroll_by(x, y)
raise NotImplementedError
end

View file

@ -392,6 +392,30 @@ module Capybara
self
end
##
#
# Drop items on the current element.
#
# target = page.find('#foo')
# target.drop('/some/path/file.csv')
#
# @overload drop(path, ...)
# @param [String, #to_path] path Location of the file to drop on the element
#
# @overload drop(strings, ...)
# @param [Hash] strings A hash of type to data to be dropped - { "text/url" => "https://www.google.com" }
#
# @return [Capybara::Node::Element] The element
def drop(*args)
options = args.map do |arg|
return arg.to_path if arg.respond_to?(:to_path)
arg
end
synchronize { base.drop(*options) }
self
end
##
#
# Scroll the page or element

View file

@ -22,6 +22,69 @@ class Capybara::Selenium::Node
native.property('draggable')
end
def html5_drop(*args)
if args[0].is_a? String
input = driver.evaluate_script ATTACH_FILE
input.set_file(args)
driver.execute_script DROP_FILE, self, input
else
items = args.each_with_object([]) do |arg, arr|
arg.each_with_object(arr) do |(type, data), arr_|
arr_ << { type: type, data: data }
end
end
driver.execute_script DROP_STRING, items, self
end
end
DROP_STRING = <<~JS
var strings = arguments[0],
el = arguments[1],
dt = new DataTransfer(),
opts = { cancelable: true, bubbles: true, dataTransfer: dt };
for (var i=0; i < strings.length; i++){
if (dt.items) {
dt.items.add(strings[i]['data'], strings[i]['type']);
} else {
dt.setData(strings[i]['type'], strings[i]['data']);
}
}
var dropEvent = new DragEvent('drop', opts);
el.dispatchEvent(dropEvent);
JS
DROP_FILE = <<~JS
var el = arguments[0],
input = arguments[1],
files = input.files,
dt = new DataTransfer(),
opts = { cancelable: true, bubbles: true, dataTransfer: dt };
input.parentElement.removeChild(input);
if (dt.items){
for (var i=0; i<files.length; i++){
dt.items.add(files[i]);
}
} else {
Object.defineProperty(dt, "files", {
value: files,
writable: false
});
}
var dropEvent = new DragEvent('drop', opts);
el.dispatchEvent(dropEvent);
JS
ATTACH_FILE = <<~JS
(function(){
var input = document.createElement('INPUT');
input.type = "file";
input.id = "_capybara_drop_file";
input.multiple = true;
document.body.appendChild(input);
return input;
})()
JS
MOUSEDOWN_TRACKER = <<~JS
document.addEventListener('mousedown', ev => {
window.capybara_mousedown_prevented = ev.defaultPrevented;

View file

@ -137,6 +137,10 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
element.scroll_if_needed { browser_action.move_to(element.native).release.perform }
end
def drop(*_)
raise NotImplementedError, 'Out of browser drop emulation is not implemented for the current browser'
end
def tag_name
@tag_name ||= native.tag_name.downcase
end

View file

@ -34,6 +34,10 @@ class Capybara::Selenium::ChromeNode < Capybara::Selenium::Node
html5_drag_to(element)
end
def drop(*args)
html5_drop(*args)
end
def click(*)
super
rescue ::Selenium::WebDriver::Error::WebDriverError => e

View file

@ -52,6 +52,10 @@ class Capybara::Selenium::FirefoxNode < Capybara::Selenium::Node
html5_drag_to(element)
end
def drop(*args)
html5_drop(*args)
end
def hover
return super unless browser_version >= 65.0

View file

@ -18,7 +18,33 @@ $(function() {
});
$('#drop_html5, #drop_html5_scroll').on('drop', function(ev){
ev.preventDefault();
$(this).html('HTML5 Dropped ' + ev.originalEvent.dataTransfer.getData("text"));
var oev = ev.originalEvent;
if (oev.dataTransfer.items) {
for (var i = 0; i < oev.dataTransfer.items.length; i++){
var item = oev.dataTransfer.items[i];
if (item.kind === 'file'){
var file = item.getAsFile();
$(this).append('HTML5 Dropped file: ' + file.name);
} else {
var _this = this;
var callback = (function(type){
return function(s){
$(_this).append('HTML5 Dropped string: ' + type + ' ' + s)
}
})(item.type);
item.getAsString(callback);
}
}
} else {
$(this).html('HTML5 Dropped ' + oev.dataTransfer.getData("text"));
for (var i = 0; i < oev.dataTransfer.files.length; i++) {
$(this).append('HTML5 Dropped file: ' + oev.dataTransfer.files[i].name);
}
for (var i = 0; i < oev.dataTransfer.types.length; i++) {
var type = oev.dataTransfer.types[i];
$(this).append('HTML5 Dropped string: ' + type + ' ' + oev.dataTransfer.getData(type));
}
}
});
$('#clickable').click(function(e) {
var link = $(this);

View file

@ -199,10 +199,4 @@ Capybara::SpecHelper.spec '#attach_file' do
expect(extract_results(@session)['hidden_image']).to end_with(File.basename(__FILE__))
end
end
private
def with_os_path_separators(path)
Gem.win_platform? ? path.to_s.tr('/', '\\') : path.to_s
end
end

View file

@ -414,6 +414,87 @@ Capybara::SpecHelper.spec 'node' do
link.drag_to target
expect(@session).to have_xpath('//div[contains(., "Dropped!")]')
end
context 'HTML5', requires: %i[js html5_drag] do
it 'should HTML5 drag and drop an object' do
@session.visit('/with_js')
element = @session.find('//div[@id="drag_html5"]')
target = @session.find('//div[@id="drop_html5"]')
element.drag_to(target)
expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped string: text/plain drag_html5")]')
end
it 'should set clientX/Y in dragover events' do
@session.visit('/with_js')
element = @session.find('//div[@id="drag_html5"]')
target = @session.find('//div[@id="drop_html5"]')
element.drag_to(target)
expect(@session).to have_css('div.log', text: /DragOver with client position: [1-9]\d*,[1-9]\d*/, count: 2)
end
it 'should not HTML5 drag and drop on a non HTML5 drop element' do
@session.visit('/with_js')
element = @session.find('//div[@id="drag_html5"]')
target = @session.find('//div[@id="drop_html5"]')
target.execute_script("$(this).removeClass('drop');")
element.drag_to(target)
sleep 1
expect(@session).not_to have_xpath('//div[contains(., "HTML5 Dropped")]')
end
it 'should HTML5 drag and drop when scrolling needed' do
@session.visit('/with_js')
element = @session.find('//div[@id="drag_html5_scroll"]')
target = @session.find('//div[@id="drop_html5_scroll"]')
element.drag_to(target)
expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped string: text/plain drag_html5_scroll")]')
end
it 'should drag HTML5 default draggable elements' do
@session.visit('/with_js')
link = @session.find_link('drag_link_html5')
target = @session.find(:id, 'drop_html5')
link.drag_to target
expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped")]')
end
end
end
describe 'Element#drop', requires: %i[js html5_drag] do
it 'can drop a file' do
@session.visit('/with_js')
target = @session.find('//div[@id="drop_html5"]')
target.drop(
with_os_path_separators(File.expand_path('../fixtures/capybara.jpg', File.dirname(__FILE__)))
)
expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped file: capybara.jpg")]')
end
it 'can drop multiple files' do
@session.visit('/with_js')
target = @session.find('//div[@id="drop_html5"]')
target.drop(
with_os_path_separators(File.expand_path('../fixtures/capybara.jpg', File.dirname(__FILE__))),
with_os_path_separators(File.expand_path('../fixtures/test_file.txt', File.dirname(__FILE__)))
)
expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped file: capybara.jpg")]')
expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped file: test_file.txt")]')
end
it 'can drop strings' do
@session.visit('/with_js')
target = @session.find('//div[@id="drop_html5"]')
target.drop('text/plain' => 'Some dropped text')
expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped string: text/plain Some dropped text")]')
end
it 'can drop multiple strings' do
@session.visit('/with_js')
target = @session.find('//div[@id="drop_html5"]')
target.drop('text/plain' => 'Some dropped text', 'text/url' => 'http://www.google.com')
expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped string: text/plain Some dropped text")]')
expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped string: text/url http://www.google.com")]')
end
end
describe '#hover', requires: [:hover] do

View file

@ -124,6 +124,10 @@ module Capybara
def be_an_invalid_element_error(session)
satisfy { |error| session.driver.invalid_element_errors.any? { |e| error.is_a? e } }
end
def with_os_path_separators(path)
Gem.win_platform? ? path.to_s.tr('/', '\\') : path.to_s
end
end
end

View file

@ -64,6 +64,8 @@ Capybara::SpecHelper.run_specs TestSessions::SeleniumFirefox, 'selenium', capyba
pending "Geckodriver doesn't provide a way to remove cookies outside the current domain"
when 'Capybara::Session selenium #attach_file with a block can upload by clicking the file input'
pending "Geckodriver doesn't allow clicking on file inputs"
when /drag_to.*HTML5/
pending "Firefox < 62 doesn't support a DataTransfer constuctor" if firefox_lt?(62.0, @session)
end
end

View file

@ -110,6 +110,8 @@ Capybara::SpecHelper.run_specs TestSessions::SeleniumIE, 'selenium', capybara_sk
pending 'IE treats blank href as a parent request (against HTML spec)'
when /#attach_file with a block/
skip 'Hangs IE testing for unknown reason'
when /drag_to.*HTML5/
pending "IE doesn't support a DataTransfer constuctor"
end
end

View file

@ -73,6 +73,8 @@ Capybara::SpecHelper.run_specs TestSessions::Safari, SAFARI_DRIVER.to_s, capybar
when 'Capybara::Session selenium_safari #go_back should fetch a response from the driver from the previous page',
'Capybara::Session selenium_safari #go_forward should fetch a response from the driver from the previous page'
skip 'safaridriver loses the ability to find elements in the document after `go_back`'
when /drag_to.*HTML5/
pending "Safari doesn't support"
end
end

View file

@ -303,57 +303,6 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
end
end
describe 'Element#drag_to' do
before do
skip "Firefox < 62 doesn't support a DataTransfer constuctor" if firefox_lt?(62.0, session)
skip "IE doesn't support a DataTransfer constuctor" if ie?(session)
skip "Safari doesn't support" if safari?(session)
end
it 'should HTML5 drag and drop an object' do
session.visit('/with_js')
element = session.find('//div[@id="drag_html5"]')
target = session.find('//div[@id="drop_html5"]')
element.drag_to(target)
expect(session).to have_xpath('//div[contains(., "HTML5 Dropped drag_html5")]')
end
it 'should set clientX/Y in dragover events' do
session.visit('/with_js')
element = session.find('//div[@id="drag_html5"]')
target = session.find('//div[@id="drop_html5"]')
element.drag_to(target)
session.all(:css, 'div.log').each { |el| puts el.text }
expect(session).to have_css('div.log', text: /DragOver with client position: [1-9]\d*,[1-9]\d*/, count: 2)
end
it 'should not HTML5 drag and drop on a non HTML5 drop element' do
session.visit('/with_js')
element = session.find('//div[@id="drag_html5"]')
target = session.find('//div[@id="drop_html5"]')
target.execute_script("$(this).removeClass('drop');")
element.drag_to(target)
sleep 1
expect(session).not_to have_xpath('//div[contains(., "HTML5 Dropped drag_html5")]')
end
it 'should HTML5 drag and drop when scrolling needed' do
session.visit('/with_js')
element = session.find('//div[@id="drag_html5_scroll"]')
target = session.find('//div[@id="drop_html5_scroll"]')
element.drag_to(target)
expect(session).to have_xpath('//div[contains(., "HTML5 Dropped drag_html5_scroll")]')
end
it 'should drag HTML5 default draggable elements' do
session.visit('/with_js')
link = session.find_link('drag_link_html5')
target = session.find(:id, 'drop_html5')
link.drag_to target
expect(session).to have_xpath('//div[contains(., "HTML5 Dropped")]')
end
end
describe 'Capybara#Node#attach_file' do
it 'can attach a directory' do
pending "Geckodriver doesn't support uploading a directory" if firefox?(session)