drag'n'drop with Perl, Webdriver, and Selenium::Remote::Driver
(This post is direct language translation of ep 39 of Elemental Selenium, Dave Haeffner's Ruby series about Webdriver. Go check 'em out, even if you don't use Ruby, as Webdriver concepts are the same across bindings. We're even reusing his fixture code, as it's quite useful!)
Although the webdriver protocol offers a few endpoints that allow you to accomplish drag and drop (button_down, mouse_move_to_location, and button_up), there's an outstanding bug in webdriver for those endpoints on HTML5 pages. So, there's nothing we can do from the side of the Perl bindings, as invoking the endpoints won't actually do the drag and drop. Luckily, there's a javascript workaround that allows us to simulate drag and drop events with jQuery (or Zepto), courtesy of Rob Correia's gist.
First things first, you'll need the helper javascript downloaded
somewhere - you can use __FILE__ or
FindBin to locate it relative to
your perl script. Assuming you save the helper js as drag.js in the
same directory as your test script, your test might look like:
#! /usr/bin/perl
use strict;
use warnings;
use FindBin;
use Selenium::Remote::Driver;
use Test::More;
my $d = Selenium::Remote::Driver->new;
$d->get('http://blog.danielgempesaw.com/post/103347925809');
my $grab = $d->find_element('column-a', 'id');
my $target = $d->find_element('column-b', 'id');
my $drag_file = $FindBin::Bin . '/drag.js';
open (my $fh, "<", $drag_file);
my $drag_js = join('', <$fh>);
close ($fh);
my $simulate_js = '$(arguments[0]).simulateDragDrop({ dropTarget: arguments[1] })';
$d->execute_script( $drag_js . $simulate_js, $grab, $target);
is($grab->get_text, 'B', 'text in column-a has changed!');
is($target->get_text, 'A', 'text in column-b has changed!');
$d->quit;
done_testing;
This is accomplishing exactly the same thing as the Ruby version of
the script. We load up our driver, get to our test page, and then
we're deviating slightly. I prefer to find the elements in webdriver
separately and pass them into the execute_script, as my framework
allows users to locate elements in various with multiple by
strategies, so we'd need account for that deviation. Luckily,
execute_script puts any additional arguments it receives into the
special arguments array in javascript, transforming them to and from
actual DOM elements if necessary. So our $grab and $target are
available in their DOM element form as arguments[0] and
arguments[1], respectively.
The final step is to concatenate the tiny drag library with the javascript to simulate the drag and drop. After that, we just verify that the text in the columns changed, and we're good to go!
I'm wondering if I should include the $drag_js in
Selenium::Remote::Driver somewhere. We recently found a deprecated
drag method on our WebElement class - since the drag endpoint no
longer exists in the JSONWireProtocol, we've begun the process to
deprecate it in our module. However, the aforementioned workaround
means that we could restore our binding's drag method with this hack
until an official fix comes in. But, the official fix might lead to a
change in API that would confuse our users.
On the one hand, it always feels a bit icky to include javascript in a perl module. On the other hand, drag and drop is pretty commonly requested and it could be useful to many users. I think I'll leave it out of our library until there's an explicit need for it, and point people to this method until then.