AnyEvent-WebDriver
view release on metacpan or search on metacpan
use AnyEvent::WebDriver;
# create a new webdriver object
my $wd = new AnyEvent::WebDriver;
# create a new session with default capabilities.
$wd->new_session ({});
$wd->navigate_to ("https://duckduckgo.com/html");
my $searchbox = $wd->find_element (css => 'input[type="text"]');
$wd->element_send_keys ($searchbox => "free software");
$wd->element_click ($wd->find_element (css => 'input[type="submit"]'));
# session gets autodeleted by default, so wait a bit
sleep 10;
# this is an example of an action sequence
$wd->actions
->move ($wd->find_element (...), 40, 5)
->click
->type ("some text")
->key ("{Enter}")
->perform;
DESCRIPTION
This module aims to implement the W3C WebDriver
<https://www.w3.org/TR/webdriver1/> specification which is the
standardised equivalent to the Selenium WebDriver API, which in turn
aims at remotely controlling web browsers such as Firefox or Chromium.
One of the design goals of this module was to stay very close to the
language and words used in the WebDriver specification itself, so to
make most of this module, or, in fact, to make any reasonable use of
this module, you would need to refer to the W3C WebDriver
recommendation, which can be found here
<https://www.w3.org/TR/webdriver1/>:
https://www.w3.org/TR/webdriver1/
Mozilla's "geckodriver" has had webdriver support for a long time, while
"chromedriver" only has basic and mostly undocumented webdriver support
as of release 77.
In Debian GNU/Linux, you can install the chromedriver for chromium via
the "chromium-driver" package. Unfortunately, there is no (working)
package for geckodriver, but you can download it from github
<https://github.com/mozilla/geckodriver/releases>.
CONVENTIONS
Unless otherwise stated, all delays and time differences in this module
are represented as an integer number of milliseconds, which is perhaps
surprising to users of my other modules but is what the WebDriver spec
uses.
WEBDRIVER OBJECTS
new AnyEvent::WebDriver key => value...
Create a new WebDriver object. Example for a remote WebDriver
connection (the only type supported at the moment):
my $wd = new AnyEvent::WebDriver endpoint => "http://localhost:4444";
Supported keys are:
endpoint => $string
For remote connections, the endpoint to connect to (defaults to
"http://localhost:4444").
proxy => $proxyspec
The proxy to use (same as the "proxy" argument used by
AnyEvent::HTTP). The default is "undef", which disables proxies.
To use the system-provided proxy (e.g. "http_proxy" environment
variable), specify the string "default".
autodelete => $boolean
If true (the default), then automatically execute
"delete_session" when the WebDriver object is destroyed with an
active session. If set to a false value, then the session will
continue to exist.
Note that due to bugs in perl that are unlikely to get fixed,
"autodelete" is likely ineffective during global destruction and
might even crash your process, so you should ensure objects go
out of scope before that, or explicitly call "delete_session",
if you want the session to be cleaned up.
timeout => $seconds
The HTTP timeout, in (fractional) seconds (default: 300). This
timeout is reset on any activity, so it is not an overall
request timeout. Also, individual requests might extend this
timeout if they are known to take longer.
persistent => 1 | "undef"
If true (the default) then persistent connections will be used
for all requests, which assumes you have a reasonably stable
connection (such as to "localhost" :) and that the WebDriver has
a persistent timeout much higher than what AnyEvent::HTTP uses.
You can force connections to be closed for non-idempotent
requests (the safe default of AnyEvent::HTTP) by setting this to
"undef".
$al = $wd->actions
Creates an action list associated with this WebDriver. See ACTION
LISTS, below, for full details.
$sessionstring = $wd->save_session
Save the current session in a string so it can be restored load with
"load_session". Note that only the session data itself is stored
(currently the session id and capabilities), not the endpoint
information itself.
The main use of this function is in conjunction with disabled
"autodelete", to save a session to e.g., and restore it later. It
could presumably used for other applications, such as using the same
session from multiple processes and so on.
$wd->load_session ($sessionstring)
$wd->set_session ($sessionid, $capabilities)
Starts using the given session, as identified by $sessionid.
$capabilities should be the original session capabilities, although
the current version of this module does not make any use of it.
The $sessionid is stored in "$wd->{sid}" (and could be fetched form
there for later use), while the capabilities are stored in
"$wd->{capabilities}".
SIMPLIFIED API
This section documents the simplified API, which is really just a very
thin wrapper around the WebDriver protocol commands. They all block the
caller until the result is available (using AnyEvent condvars), so must
not be called from an event loop callback - see "EVENT BASED API" for an
alternative.
The method names are pretty much taken directly from the W3C WebDriver
specification, e.g. the request documented in the "Get All Cookies"
section is implemented via the "get_all_cookies" method.
The order is the same as in the WebDriver draft at the time of this
writing, and only minimal massaging is done to request parameters and
results.
SESSIONS
$wd->new_session ({ key => value... })
Try to connect to the WebDriver and initialize a new session with a
"new session" command, passing the given key-value pairs as value
(e.g. "capabilities").
No session-dependent methods must be called before this function
returns successfully, and only one session can be created per
WebDriver object.
On success, "$wd->{sid}" is set to the session ID, and
"$wd->{capabilities}" is set to the returned capabilities.
Simple example of creating a WebDriver object and a new session:
my $wd = new AnyEvent::WebDriver endpoint => "http://localhost:4444";
$wd->new_session ({});
Real-world example with capability negotiation:
$wd->new_session ({
capabilities => {
alwaysMatch => {
pageLoadStrategy => "eager",
unhandledPromptBehavior => "dismiss",
# proxy => { proxyType => "manual", httpProxy => "1.2.3.4:56", sslProxy => "1.2.3.4:56" },
},
firstMatch => [
{
browserName => "firefox",
"moz:firefoxOptions" => {
binary => "firefox/firefox",
args => ["-devtools", "-headless"],
prefs => {
"dom.webnotifications.enabled" => \0,
"dom.push.enabled" => \0,
"dom.disable_beforeunload" => \1,
"browser.link.open_newwindow" => 3,
"browser.link.open_newwindow.restrictions" => 0,
"dom.popup_allowed_events" => "",
"dom.disable_open_during_load" => \1,
},
},
},
{
browserName => "chrome",
"goog:chromeOptions" => {
binary => "/bin/chromium",
args => ["--no-sandbox", "--headless"],
prefs => {
# ...
},
},
},
{
# generic fallback
},
],
},
});
Firefox-specific capability documentation can be found on MDN
<https://developer.mozilla.org/en-US/docs/Web/WebDriver/Capabilities
>, Chrome-specific capability documentation might be found here
<http://chromedriver.chromium.org/capabilities>, but the latest
release at the time of this writing (chromedriver 77) has
essentially no documentation about webdriver capabilities (even MDN
has better documentation about chromwedriver!)
If you have URLs for Safari/IE/Edge etc. capabilities, feel free to
tell me about them.
$wd->delete_session
Deletes the session - the WebDriver object must not be used after
this call (except for calling this method).
perform it multiple times.
No additional actions must be added after compiling an action list.
EVENT BASED API
This module wouldn't be a good AnyEvent citizen if it didn't have a true
event-based API.
In fact, the simplified API, as documented above, is emulated via the
event-based API and an "AUTOLOAD" function that automatically provides
blocking wrappers around the callback-based API.
Every method documented in the "SIMPLIFIED API" section has an
equivalent event-based method that is formed by appending a underscore
("_") to the method name, and appending a callback to the argument list
(mnemonic: the underscore indicates the "the action is not yet finished"
after the call returns).
For example, instead of a blocking calls to "new_session", "navigate_to"
and "back", you can make a callback-based ones:
my $cv = AE::cv;
$wd->new_session ({}, sub {
my ($status, $value) = @_,
die "error $value->{error}" if $status ne "200";
$wd->navigate_to_ ("http://www.nethype.de", sub {
$wd->back_ (sub {
print "all done\n";
$cv->send;
});
});
});
$cv->recv;
While the blocking methods "croak" on errors, the callback-based ones
all pass two values to the callback, $status and $res, where $status is
the HTTP status code (200 for successful requests, typically 4xx or 5xx
for errors), and $res is the value of the "value" key in the JSON
response object.
Other than that, the underscore variants and the blocking variants are
identical.
LOW LEVEL API
All the simplified API methods are very thin wrappers around WebDriver
commands of the same name. They are all implemented in terms of the
low-level methods ("req", "get", "post" and "delete"), which exist in
blocking and callback-based variants ("req_", "get_", "post_" and
"delete_").
Examples are after the function descriptions.
$wd->req_ ($method, $uri, $body, $cb->($status, $value))
$value = $wd->req ($method, $uri, $body)
Appends the $uri to the "endpoint/session/{sessionid}/" URL and
makes a HTTP $method request ("GET", "POST" etc.). "POST" requests
can provide a UTF-8-encoded JSON text as HTTP request body, or the
empty string to indicate no body is used.
For the callback version, the callback gets passed the HTTP status
code (200 for every successful request), and the value of the
"value" key in the JSON response object as second argument.
$wd->get_ ($uri, $cb->($status, $value))
$value = $wd->get ($uri)
Simply a call to "req_" with $method set to "GET" and an empty body.
$wd->post_ ($uri, $data, $cb->($status, $value))
$value = $wd->post ($uri, $data)
Simply a call to "req_" with $method set to "POST" - if $body is
"undef", then an empty object is send, otherwise, $data must be a
valid request object, which gets encoded into JSON for you.
$wd->delete_ ($uri, $cb->($status, $value))
$value = $wd->delete ($uri)
Simply a call to "req_" with $method set to "DELETE" and an empty
body.
Example: implement "get_all_cookies", which is a simple "GET" request
without any parameters:
$cookies = $wd->get ("cookie");
Example: implement "execute_script", which needs some parameters:
$results = $wd->post ("execute/sync" => { script => "$javascript", args => [] });
Example: call "find_elements" to find all "IMG" elements:
$elems = $wd->post (elements => { using => "css selector", value => "img" });
HISTORY
This module was unintentionally created (it started inside some quickly
hacked-together script) simply because I couldn't get the existing
"Selenium::Remote::Driver" module to work reliably, ever, despite
multiple attempts over the years and trying to report multiple bugs,
which have been completely ignored. It's also not event-based, so,
yeah...
AUTHOR
Marc Lehmann <schmorp@schmorp.de>
http://anyevent.schmorp.de
( run in 1.033 second using v1.01-cache-2.11-cpan-39bf76dae61 )