package Selenium::Remote::Driver;
use strict;
use warnings;
use Selenium::Remote::RemoteConnection;
# ABSTRACT: Perl Client for Selenium Remote Driver
=for Pod::Coverage BUILD
=for Pod::Coverage DEMOLISH
=head1 SYNOPSIS
use Selenium::Remote::Driver;
my $driver = Selenium::Remote::Driver->new;
$driver->get('http://www.google.com');
print $driver->get_title();
$driver->quit();
=cut
=head1 DESCRIPTION
Selenium is a test tool that allows you to write
automated web application UI tests in any programming language against
any HTTP website using any mainstream JavaScript-enabled browser. This module is
an implementation of the client for the Remote driver that Selenium provides.
You can find bindings for other languages at this location:
L
This module sends commands directly to the Server using HTTP. Using this module
together with the Selenium Server, you can automatically control any supported
browser. To use this module, you need to have already downloaded and started
the Selenium Server (Selenium Server is a Java application).
=cut
=head1 USAGE
=head2 Without Standalone Server
As of v0.25, it's possible to use this module without a standalone
server - that is, you would not need the JRE or the JDK to run your
Selenium tests. See L, L,
L, L,and L
for details. If you'd like additional browsers besides these,
give us a holler over in
L.
=head2 Remote Driver Response
Selenium::Remote::Driver uses the
L
And the
L
to communicate with the Selenium Server. If an error occurs while
executing the command then the server sends back an HTTP error code
with a JSON encoded reponse that indicates the precise
L.
The module will then croak with the error message associated with this
code. If no error occurred, then the subroutine called will return the
value sent back from the server (if a return value was sent).
So a rule of thumb while invoking methods on the driver is if the method did not
croak when called, then you can safely assume the command was successful even if
nothing was returned by the method.
=head2 WebElement
Selenium Webdriver represents all the HTML elements as WebElement, which is
in turn represented by L module. So any method that
deals with WebElements will return and/or expect WebElement object. The POD for
that module describes all the methods that perform various actions on the
WebElements like click, submit etc.
To interact with any WebElement you have to first "find" it, read the POD for
find_element or find_elements for further info. Once you find the required element
then you can perform various actions. If you don't call find_* method first, all
your further actions will fail for that element. Finally, just remember that you
don't have to instantiate WebElement objects at all - they will be automatically
created when you use the find_* methods.
A sub-class of Selenium::Remote::WebElement may be used instead of Selenium::Remote::WebElement,
by providing that class name as an option the constructor:
my $driver = Selenium::Remote::Driver->new( webelement_class => ... );
For example, a testing-subclass may extend the web-element object with testing methods.
=head2 LWP Read Timeout errors
It's possible to make Selenium calls that take longer than the default
L timeout. For example, setting the asynchronous
script timeout greater than the LWP::UserAgent timeout and then
executing a long running asynchronous snippet of javascript will
immediately trigger an error like:
Error while executing command: executeAsyncScript: Server returned
error message read timeout at...
You can get around this by configuring LWP's timeout value, either by
constructing your own LWP and passing it in to ::Driver during
instantiation
my $timeout_ua = LWP::UserAgent->new;
$timeout_ua->timeout(360); # this value is in seconds!
my $d = Selenium::Remote::Driver->new( ua => $timeout_ua );
or by configuring the timeout on the fly as necessary:
use feature qw/say/;
use Selenium::Remote::Driver;
my $d = Selenium::Remote::Driver->new;
say $d->ua->timeout; # 180 seconds is the default
$d->ua->timeout(2); # LWP wants seconds, not milliseconds!
$d->set_timeout('script', 1000); # S::R::D wants milliseconds!
# Async scripts only return when the callback is invoked. Since there
# is no callback here, Selenium will block for the entire duration of
# the async timeout script. This will hit Selenium's async script
# timeout before hitting LWP::UserAgent's read timeout
$d->execute_async_script('return "hello"');
$d->quit;
=head1 TESTING
If are writing automated tests using this module, you may be
interested in L which is also included
in this distribution. It includes convenience testing methods for many
of the selenum methods available here.
Your other option is to use this module in conjunction with your
choice of testing modules, like L or L as
you please.
=head1 WC3 WEBDRIVER COMPATIBILITY
WC3 Webdriver is a constantly evolving standard, so some things may or may not work at any given time.
Furthermore, out of date drivers probably identify as WD3, while only implementing a few methods and retaining JSONWire functionality.
One way of dealing with this is setting:
$driver->{is_wd3} = 0
Of course, this will prevent access of any new WC3 methods, but will probably make your tests pass until your browser's driver gets it's act together.
There are also some JSONWire behaviors that we emulate in methods, such as Selenium::Remote::WebElement::get_attribute.
You can get around that by passing an extra flag to the sub, or setting:
$driver->{emulate_jsonwire} = 0;
When in WC3 Webdriver mode.
=head2 FINDERS
This constant is a hashref map of the old element finder aliases from wd2 to wd3.
use Data::Dumper;
print Dumper($Selenium::Remote::Driver::FINDERS);
=head2 WC3 WEBDRIVER CURRENT STATUS
That said, the following 'sanity tests' in the at/ (acceptance test) directory of the module passed on the following versions:
=over 4
=item Selenium Server: 3.8.1 - all tests
=item geckodriver: 0.19.1 - at/sanity.test, at/firefox.test (Selenium::Firefox)
=item chromedriver: 2.35 - at/sanity-chrome.test, at/chrome.test (Selenium::Chrome)
=item edgedriver: 5.16299 - at/sanity-edge.test
=item InternetExplorerDriver : 3.8.1 - at/sanity-ie.test (be sure to enable 'allow local files to run active content in your 'advanced settings' pane)
=item safaridriver : 11.0.2 - at/sanity-safari.test (be sure to enable 'allow automated testing' in the developer menu) -- it appears WC3 spec is *unimplemented*
=back
These tests are intended to be run directly against a working selenium server on the local host with said drivers configured.
If you are curious as to what 'works and does not' on your driver versions (and a few other quirks),
it is strongly encouraged you look at where the test calls the methods you are interested in.
While other browsers/drivers (especially legacy ones) likely work fine as well,
any new browser/driver will likely have problems if it's not listed above.
There is also a 'legacy.test' file available to run against old browsers/selenium (2.x servers, pre geckodriver).
This should only be used to verify backwards-compatibility has not been broken.
=head2 Firefox Notes
If you are intending to pass extra_capabilities to firefox on a WD3 enabled server with geckodriver, you MUST do the following:
$Selenium::Remote::Driver::FORCE_WD3=1;
This is because the gecko driver prefers legacy capabilities, both of which are normally passed for compatibility reasons.
=head2 Chrome Notes
Use the option goog:chromeOptions instead of chromeOptions, if you are intending to pass extra_capabilities on a
WD3 enabled server with chromedriver enabled.
https://sites.google.com/a/chromium.org/chromedriver/capabilities
Also, if you instantiate the object in WC3 mode (which is the default), the remote driver will throw exceptions you have no choice but to catch,
rather than falling back to JSONWire methods where applicable like geckodriver does.
As of chrome 75 (and it's appropriate driver versions), the WC3 spec has finally been implemented.
As such, to use chrome older than this, you will have to manually force on JSONWire mode:
$Selenium::Remote::Driver::FORCE_WD2=1;
=head2 Notes on Running Selenium at Scale via selenium.jar
When running many, many tests in parallel you can eventually reach resource exhaustion.
You have to instruct the Selenium JAR to do some cleanup to avoid explosions:
Inside of your selenium server's node.json (running a grid), you would put in the following:
"configuration" :
{
"cleanUpCycle":2000
}
Or run the selenium jar with the -cleanupCycle parameter. Of course use whatever # of seconds is appropriate to your situation.
=head1 CONSTRUCTOR
=head2 new
Dies if communication with the selenium server cannot be established.
Input: (all optional)
Desired capabilities - HASH - Following options are accepted:
=over 4
=item B - - IP or FQDN of the Webdriver server machine. Default: 'localhost'
=item B - - Port on which the Webdriver server is listening. Default: 4444
=item B - - desired browser string: {phantomjs|firefox|internet explorer|MicrosoftEdge|safari|htmlunit|iphone|chrome}
=item B - - desired browser version number
=item B - - desired platform: {WINDOWS|XP|VISTA|MAC|LINUX|UNIX|ANY}
=item B - - whether SSL certs should be accepted, default is true.
=item B - Profile - Use Selenium::Firefox::Profile to create a Firefox profile for the browser to use.
=item B - - Whether or not to use Javascript. You probably won't disable this, as you would be using L instead. Default: True
=item B - - Whether to automatically close the browser session on the server when the object goes out of scope. Default: False.
=item B - - Default method by which to evaluate selectors. Default: 'xpath'
=item B - - Provide a Session ID to highjack a browser session on the remote server. Useful for micro-optimizers. Default: undef
=item B - STRING - OPTIONAL, 'normal|eager|none'. default 'normal'. WebDriver3 only.
=item B - HASH - Any other extra capabilities. Accepted keys will vary by browser. If firefox_profile is passed, the args (or profile) key will be overwritten, depending on how it was passed.
=item B - BOOL - Turn Debug mode on from the start if true, rather than having to call debug_on().
=back
On WebDriver3 the 'extra_capabilities' will be automatically converted into the parameter needed by your browser.
For example, extra_capabilities is passed to the server as the moz:firefoxOptions parameter.
You can also specify some options in the constructor hash that are
not part of the browser-related desired capabilities.
=over 4
=item B - - whether driver should end session on remote server on close.
=item B - - OPTIONAL, base url for the website Selenium acts on. This can save you from repeating the domain in every call to $driver->get()
=item B - - choose default finder used for find_element* {class|class_name|css|id|link|link_text|name|partial_link_text|tag_name|xpath}
=item B - - An array ref [ height, width ] that the browser window should use as its initial size immediately after instantiation
=item B - CODEREF - A CODEREF that we will call in event of any exceptions. See L for more details.
=item B - - sub-class of Selenium::Remote::WebElement if you wish to use an alternate WebElement class.
=item B - LWP::UserAgent instance - if you wish to use a specific $ua, like from Test::LWP::UserAgent
=item B - HASH - Proxy configuration with the following keys:
=over 4
=item B - - REQUIRED, Possible values are:
direct - A direct connection - no proxy in use,
manual - Manual proxy settings configured, e.g. setting a proxy for HTTP, a proxy for FTP, etc,
pac - Proxy autoconfiguration from a URL,
autodetect - proxy autodetection, probably with WPAD,
system - Use system settings
=item B - - REQUIRED if proxyType is 'pac', ignored otherwise. Expected format: http://hostname.com:1234/pacfile or file:///path/to/pacfile
=item B - - OPTIONAL, ignored if proxyType is not 'manual'. Expected format: hostname.com:1234
=item B - - OPTIONAL, ignored if proxyType is not 'manual'. Expected format: hostname.com:1234
=item B - - OPTIONAL, ignored if proxyType is not 'manual'. Expected format: hostname.com:1234
=item B - - OPTIONAL, ignored if proxyType is not 'manual'. Expected format: hostname.com:1234. WebDriver 3 only.
=item B - - OPTIONAL, ignored if proxyType is not 'manual'. WebDriver 3 only.
=item B - - OPTIONAL, list of URLs to bypass the proxy for. WebDriver3 only.
=item B - - Base64 encoded ZIP file of a firefox profile directory, for use when you don't want/need Selenium::Firefox::Profile.
=back
=back
Output:
Selenium::Remote::Driver object
Usage:
my $driver = Selenium::Remote::Driver->new;
#or
my $driver = Selenium::Remote::Driver->new('browser_name' => 'firefox',
'platform' => 'MAC');
#or (for Firefox 47 or lower on Selenium 3+)
my $driver = Selenium::Remote::Driver->new('browser_name' => 'firefox',
'platform' => 'MAC',
'extra_capabilities' => {
'marionette' => \0,
});
#or
my $driver = Selenium::Remote::Driver->new('remote_server_addr' => '10.10.1.1',
'port' => '2222',
'auto_close' => 0);
#or
my $driver = Selenium::Remote::Driver->new('browser_name' =>'chrome',
'extra_capabilities' => {
'goog:chromeOptions' => {
'args' => [
'window-size=1260,960',
'incognito'
],
'prefs' => {
'session' => {
'restore_on_startup' => 4,
'urls_to_restore_on_startup' => [
'http://www.google.com',
'http://docs.seleniumhq.org'
]},
'first_run_tabs' => [
'http://www.google.com',
'http://docs.seleniumhq.org'
]
}
}
});
#or
my $driver = Selenium::Remote::Driver->new('proxy' => {'proxyType' => 'manual', 'httpProxy' => 'myproxy.com:1234'});
#or
my $driver = Selenium::Remote::Driver->new('default_finder' => 'css');
=head3 error_handler
=head3 clear_error_handler
OPTIONAL constructor arg & associated setter/clearer: if you wish to
install your own error handler, you may pass a code ref in to
C during instantiation like follows:
my $driver = Selenium::Remote::Driver->new(
error_handler => sub { print $_[1]; croak 'goodbye'; }
);
Additionally, you can set and/or clear it at any time on an
already-instantiated driver:
# later, change the error handler to something else
$driver->error_handler( sub { print $_[1]; croak 'hello'; } );
# stop handling errors manually and use the default S:R:D behavior
# (we will croak about the exception)
$driver->clear_error_handler;
Your error handler will receive three arguments: the first argument is
the C<$driver> object itself, and the second argument is the exception
message and stack trace in one multiline string. The final argument(s) are the
argument array to the command just executed.
B: If you set your own error handler, you are entirely
responsible for handling webdriver exceptions, _including_ croaking
behavior. That is, when you set an error handler, we will no longer
croak on Webdriver exceptions - it's up to you to do so. For
consistency with the standard S:R:D behavior, we recommend your error
handler also croak when it's done, especially since your test
shouldn't be running into unexpected errors. Catching specific or
desired errors in your error handler makes sense, but not croaking at
all can leave you in unfamiliar territory. Reaching an unexpected
exception might mean your test has gone off the rails, and the further
your test gets from the source of the of the exception, the harder it
will be to debug.
B: Four methods will still croak on their own: L,
L, L, and
L. If these methods throw a Webdriver Exception,
your error handler _will still be_ invoked inside an C, and then
they'll croak with their own error message that indicates the locator
and strategy used. So, your strategies for avoiding exceptions when
finding elements do not change (either use find_elements and check
the returned array size, wrap your calls to find_element* in an
C, or use the parameterized versions find_element_*).
=head2 new_from_caps
Description:
For experienced users who want complete control over the desired
capabilities, use this alternative constructor along with the
C hash key in the init hash. Unlike "new",
this constructor will not assume any defaults for your desired
capabilities.
This alternate constructor IGNORES all other browser-related
desiredCapability options; the only options that will be respected
are those that are NOT part of the Capabilities JSON Object as
described in the Json Wire Protocol.
Input:
The only respected keys in the input hash are:
desired_capabilities - HASHREF - defaults to {}
remote_server_addr - STRING - defaults to localhost
port - INTEGER - defaults to 4444
default_finder - STRING - defaults to xpath
webelement_class - STRING - defaults to Selenium::Remote::WebElement
auto_close - BOOLEAN - defaults to 1
error_handler - CODEREF - defaults to croaking on exceptions
Except for C, these keys perform exactly the
same as listed in the regular "new" constructor.
The hashref you pass in as desired_capabilities only gets json
encoded before being passed to the Selenium server; no default
options of any sort will be added.
This means you must handle normalization and casing of the input
options (like "browser_name" vs "browserName") and take care of
things like encoding the firefox profile if applicable. More
information about the desired capabilities object is available on
the Selenium wiki:
https://code.google.com/p/selenium/wiki/JsonWireProtocol#Capabilities_JSON_Object
Output:
Remote Driver object
Usage:
my $driver = Selenium::Remote::Driver->new_from_caps(
'desired_capabilities' => {'browserName' => 'firefox'}
);
The above would generate a POST to the webdriver server at
localhost:4444 with the exact payload of '{"desiredCapabilities":
{"browserName": "firefox" }}'.
=for Pod::Coverage has_base_url
=for Pod::Coverage has_desired_capabilities
=for Pod::Coverage has_error_handler
=for Pod::Coverage has_firefox_profile
=for Pod::Coverage has_inner_window_size
=for Pod::Coverage has_javascript
=for Pod::Coverage has_port
=for Pod::Coverage has_remote_server_addr
=cut
sub new {
my ( $class, %opts ) = @_;
my $conn = Selenium::Remote::RemoteConnection->new(
remote_server_addr => $opts{remote_server_addr},
port => $opts{port},
);
$conn->check_status();
if( $opts{'force_version'} eq '4' || ( $conn->{'version'} && $conn->{'version'} == 4 ) ) {
require Selenium::Remote::Driver::v4;
return Selenium::Remote::Driver::v4->new(
'port' => $opts{port},
'host' => $opts{remote_server_addr},
'browser' => $opts{browser_name},
'debug' => $opts{debug},
);
}
require Selenium::Remote::Driver::v3;
return Selenium::Remote::Driver::v3->new(%opts);
}
1;