Fire HTML5 drag and drop events in selenium driver for Chrome and FF 62+

This commit is contained in:
Thomas Walpole 2018-08-10 10:40:58 -07:00
parent 6755e2b883
commit 0538cb6688
6 changed files with 119 additions and 4 deletions

View File

@ -10,9 +10,38 @@ class Capybara::Selenium::ChromeNode < Capybara::Selenium::Node
raise
end
def drag_to(element)
return super unless self[:draggable] == 'true'
scroll_if_needed { driver.browser.action.click_and_hold(native).perform }
driver.execute_script HTML5_DRAG_DROP_SCRIPT, self, element
end
private
def bridge
driver.browser.send(:bridge)
end
HTML5_DRAG_DROP_SCRIPT = <<~JS
var source = arguments[0];
var target = arguments[1];
var dt = new DataTransfer();
var opts = { cancelable: true, bubbles: true, dataTransfer: dt };
var dragEvent = new DragEvent('dragstart', opts);
source.dispatchEvent(dragEvent);
target.scrollIntoView({behavior: 'instant', block: 'center', inline: 'center'});
var dragOverEvent = new DragEvent('dragover', opts);
target.dispatchEvent(dragOverEvent);
var dragLeaveEvent = new DragEvent('dragleave', opts);
target.dispatchEvent(dragLeaveEvent);
if (dragOverEvent.defaultPrevented) {
var dropEvent = new DragEvent('drop', opts);
target.dispatchEvent(dropEvent);
}
var dragEndEvent = new DragEvent('dragend', opts);
source.dispatchEvent(dragEndEvent);
JS
end

View File

@ -14,7 +14,7 @@ class Capybara::Selenium::MarionetteNode < Capybara::Selenium::Node
def disabled?
# Not sure exactly what version of FF fixed the below issue, but it is definitely fixed in 61+
return super unless driver.browser.capabilities[:browser_version].to_f < 61.0
return super unless browser_version < 61.0
return true if super
# workaround for selenium-webdriver/geckodriver reporting elements as enabled when they are nested in disabling elements
@ -27,7 +27,7 @@ class Capybara::Selenium::MarionetteNode < Capybara::Selenium::Node
def set_file(value) # rubocop:disable Naming/AccessorMethodName
native.clear # By default files are appended so we have to clear here
return super if driver.browser.capabilities[:browser_version].to_f >= 62.0
return super if browser_version >= 62.0
# Workaround lack of support for multiple upload by uploading one at a time
path_names = value.to_s.empty? ? [] : value
@ -54,6 +54,13 @@ class Capybara::Selenium::MarionetteNode < Capybara::Selenium::Node
actions.perform
end
def drag_to(element)
return super unless (browser_version >= 62.0) && (self[:draggable] == 'true')
scroll_if_needed { driver.browser.action.click_and_hold(native).perform }
driver.execute_script HTML5_DRAG_DROP_SCRIPT, self, element
end
private
def _send_keys(keys, actions, down_keys = nil)
@ -99,4 +106,30 @@ private
result = bridge.http.call(:post, "session/#{bridge.session_id}/file", file: Selenium::WebDriver::Zipper.zip_file(local_file))
result['value']
end
def browser_version
driver.browser.capabilities[:browser_version].to_f
end
HTML5_DRAG_DROP_SCRIPT = <<~JS
var source = arguments[0];
var target = arguments[1];
var dt = new DataTransfer();
var opts = { cancelable: true, bubbles: true, dataTransfer: dt };
var dragEvent = new DragEvent('dragstart', opts);
source.dispatchEvent(dragEvent);
target.scrollIntoView({behavior: 'instant', block: 'center', inline: 'center'});
var dragOverEvent = new DragEvent('dragover', opts);
target.dispatchEvent(dragOverEvent);
var dragLeaveEvent = new DragEvent('dragleave', opts);
target.dispatchEvent(dragLeaveEvent);
if (dragOverEvent.defaultPrevented) {
var dropEvent = new DragEvent('drop', opts);
target.dispatchEvent(dropEvent);
}
var dragEndEvent = new DragEvent('dragend', opts);
source.dispatchEvent(dragEndEvent);
JS
end

View File

@ -8,6 +8,16 @@ $(function() {
$(this).html('Dropped!');
}
});
$('#drag_html5, #drag_html5_scroll').on('dragstart', function(ev){
ev.originalEvent.dataTransfer.setData("text", ev.target.id);
});
$('#drop_html5, #drop_html5_scroll').on('dragover', function(ev){
if ($(this).hasClass('drop')) { ev.preventDefault(); }
});
$('#drop_html5, #drop_html5_scroll').on('drop', function(ev){
ev.preventDefault();
$(this).html('HTML5 Dropped ' + ev.originalEvent.dataTransfer.getData("text"));
});
$('#clickable').click(function(e) {
var link = $(this);
setTimeout(function() {

View File

@ -306,7 +306,7 @@ Capybara::SpecHelper.spec 'node' do
element = @session.find('//div[@id="drag"]')
target = @session.find('//div[@id="drop"]')
element.drag_to(target)
expect(@session.find('//div[contains(., "Dropped!")]')).not_to be_nil
expect(@session).to have_xpath('//div[contains(., "Dropped!")]')
end
it 'should drag and drop if scrolling is needed' do
@ -314,7 +314,7 @@ Capybara::SpecHelper.spec 'node' do
element = @session.find('//div[@id="drag_scroll"]')
target = @session.find('//div[@id="drop_scroll"]')
element.drag_to(target)
expect(@session.find('//div[contains(., "Dropped!")]')).not_to be_nil
expect(@session).to have_xpath('//div[contains(., "Dropped!")]')
end
end

View File

@ -19,6 +19,16 @@
<p>It should be dropped here.</p>
</div>
<div id="drop_html5_scroll" class="drop">
<p>It should be dropped here.</p>
</div>
<div id="drag_html5" draggable="true">
<p>This is an HTML5 draggable element.</p>
</div>
<div id="drop_html5" class="drop">
<p>It should be dropped here.</p>
</div>
<p><a href="#" id="clickable">Click me</a></p>
<p><a href="#" id="slow-click">Slowly</a></p>
@ -135,6 +145,9 @@
<div id="drop_scroll">
<p>It should be dropped here.</p>
</div>
<div id="drag_html5_scroll" draggable="true">
<p>This is an HTML5 draggable element.</p>
</div>
<script type="text/javascript">
// a javascript comment

View File

@ -285,6 +285,36 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
end
end
describe 'Element#drag_to', :focus_ do
it 'should HTML5 drag and drop an object' do
pending "Firefox < 62 doesn't support a DataTransfer constuctor" if marionette_lt?(62.0, session)
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 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 HTML6 drag and drop when scrolling needed' do
pending "Firefox < 62 doesn't support a DataTransfer constuctor" if marionette_lt?(62.0, session)
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
end
context 'Windows' do
it "can't close the primary window" do
expect do