Selenium::Remote::Driver@0.25: now with slightly less JRE
We've just released version 0.25 of Selenium::Remote::Driver to the
various CPANs. The big push this time around was to get around our
hard dependency on the JRE. Previously, the Perl bindings demanded a
standalone selenium server be operating on the browser's machine. So,
if you wanted to run tests on your own box, you'd need the Java
Runtime Environment installed, as the selenium-standalone-server is a
.jar and needs the JRE to execute. However, as akafred pointed
out, this is a prohibitive constraint (perhaps especially so for Perl
programmers?).
When I first started out working with Webdriver years ago, I only used the standalone server and for a while I thought there was no other way to run the tests. Eventually, when various webdrivers began replacing Selenium RC, I found out that it was possible to "talk" directly to them with the same exact API as the standalone server, but I never connected that with the ability to avoid needing the standalone server and the JRE!
Anyway, thanks to some long-awaited prodding, Perl can now run Firefox, Chrome, and PhantomJS without the JRE! What follows are some implementation details, and simple usage examples. :)
usage
Hopefully the usage is pretty straightforward - instead of
constructing a Selenium::Remote::Driver instance, use the
constructor for the browser of your choice instead. Just like the
Selenium standalone server, Firefox will work out of the box, as long
as you have Firefox installed locally on your machine in the default
location:
my $firefox = Selenium::Firefox->new;
$firefox->get('http://www.mozilla.org');
# "We’re building a better Internet — Mozilla"
print $firefox->get_title;
The usage is exactly analogous for the ::Chrome and ::PhantomJS classes, except you need to have the browser installed along with its associated webdriver. For PhantomJS, GhostDriver is automatically bundled for all recent versions, but for Chrome, you'll need to download the Chromedriver separately.
If your driver executables are not in your $PATH, or you'd like to
specify a certain one, the constructors take a binary option that
lets you tell us what executable to start. You can also specify a
binary_port if there's a specific port that you'd like the webdriver
server to bind to.
my $firefox = Selenium::Firefox->new(
binary => '/custom/path/to/firefox-bin',
binary_port => 65432
);
As expected, usage is analogous for the other two classes.
As a last note, we skipped creation of Selenium::InternetExplorer hoping that YAGNI, but if that's the browser that floats your boat, we can definitely throw a class together for the next release. Let us know in our Google group, or in the Github issues!
implementation
The implementation is ideally pretty straightforward:
- Find the binary webdriver
- Figure out what arguments to pass it to make it mimic a standalone server
- Start it on the right port
- Afterwards, clean it up so we don't orphan processes
The first step is easy - just check the $PATH/%PATH% - exceeept
for Firefox, where it's apparenty quite complicated. Different
operating systems install it in multiple distinctly different places,
and if different versions of Firefox are named differently - basically
it's seems like a big headache, and I mostly threw my hands up if the
Firefox binary wasn't in the first specific default location. There's
also a bit of extra complication involved to let the user choose their
own path, and validating it for them afterwards.
Passing the arguments is more or less straightforward as well -
exceeept for Firefox, which doesn't take arguments, as the webdriver
for Firefox is actually just a Firefox extension. Luckily, we
previously implemented a Firefox::Profile class, so we just use the
newly renamed Selenium::Firefox::Profile to create a profile with
the extension loaded. We actually now bundle the webdriver.xpi
extension from the 2.45.0 version of Selenium in our release, and each
time a new Selenium is released, we'll have to do a mirror release of
our bindings with the updated extension. The other webdriver language
bindings also start up Firefox with a few pre-compiled .so files for
solving focus errors that I was also too lazy to do.
Finding an open port is simple enough, and we got to re-use
Selenium::Waiter's wait_until in a few places. And, starting the
webdriver up is usually easy, although at first I was a bit unfamiliar
using system to start up an asynchronous process across different
platforms (namely Windows...).
Finally, cleaning up the process we started is straightforward on OS X
and Linux, as I think it just cleans itself up when the Perl script
ends. At least, that's what some superficial tests seemed to prove - I
didn't see anything in ps aux after the test was over, and I
couldn't open sockets to the server port afterwards. But, on Windows,
the task stays open and we need to kill it by string-matching the
title of the process. It ended up being a bit of a mess!
We went through a bunch of attempts & refactors at organizing the functionality and ended up at something that's decently organized - the Selenium::CanStartBinary role implements most of the common work between the different classes. Since Firefox is special, it gets a few extra classes of its own. Meanwhile, each class holds its own arguments, default binary name, and default binary port.
conclusion
For a first pass, I think the functionality works pretty decently - as usual, I've been using it locally for a month or two now on OS X with no major issues, but I'm sure I missed a few bugs. If you run across any bugs, definitely let us know or even fix them for us, since I bet you can do it better than me! :D Cheers...