This commit is contained in:
2024-10-14 00:08:40 +02:00
parent dbfba56f66
commit 1462d52e13
4572 changed files with 2658864 additions and 0 deletions

View File

@@ -0,0 +1,483 @@
package Selenium::ActionChains;
$Selenium::ActionChains::VERSION = '1.33';
use strict;
use warnings;
# ABSTRACT: Action chains for Selenium::Remote::Driver
use Moo;
has 'driver' => ( is => 'ro', );
has 'actions' => (
is => 'lazy',
builder => sub { [] },
clearer => 1,
);
sub perform {
my $self = shift;
foreach my $action ( @{ $self->actions } ) {
$action->();
}
}
sub click {
my $self = shift;
my $element = shift;
if ($element) {
$self->move_to_element($element);
}
# left click
push @{ $self->actions }, sub { $self->driver->click('LEFT') };
$self;
}
sub click_and_hold {
my $self = shift;
my $element = shift;
if ($element) {
$self->move_to_element($element);
}
push @{ $self->actions }, sub { $self->driver->button_down };
$self;
}
sub context_click {
my $self = shift;
my $element = shift;
if ($element) {
$self->move_to_element($element);
}
# right click
push @{ $self->actions }, sub { $self->driver->click('RIGHT') };
$self;
}
sub double_click {
my $self = shift;
my $element = shift;
if ($element) {
$self->move_to_element($element);
}
push @{ $self->actions }, sub { $self->driver->double_click };
$self;
}
sub release {
my $self = shift;
my $element = shift;
if ($element) {
$self->move_to_element($element);
}
push @{ $self->actions }, sub { $self->driver->button_up };
$self;
}
sub drag_and_drop {
my $self = shift;
my ( $source, $target ) = @_;
$self->click_and_hold($source);
$self->release($target);
$self;
}
sub drag_and_drop_by_offset {
my $self = shift;
my ( $source, $xoffset, $yoffset ) = @_;
$self->click_and_hold($source);
$self->move_by_offset( $xoffset, $yoffset );
$self->release($source);
$self;
}
sub move_to_element {
my $self = shift;
my $element = shift;
push @{ $self->actions },
sub { $self->driver->move_to( element => $element ) };
$self;
}
sub move_by_offset {
my $self = shift;
my ( $xoffset, $yoffset ) = @_;
push @{ $self->actions }, sub {
$self->driver->move_to( xoffset => $xoffset, yoffset => $yoffset );
};
$self;
}
sub move_to_element_with_offset {
my $self = shift;
my ( $element, $xoffset, $yoffset ) = @_;
push @{ $self->actions }, sub {
$self->driver->move_to(
element => $element,
xoffset => $xoffset,
yoffset => $yoffset
);
};
$self;
}
sub key_down {
my $self = shift;
my ( $value, $element ) = @_;
if ( defined($element) ) {
$self->click($element);
}
push @{ $self->actions },
sub { $self->driver->send_keys_to_active_element(@$value) };
$self;
}
sub key_up {
my $self = shift;
my ( $value, $element ) = @_;
if ( defined($element) ) {
$self->click($element);
}
push @{ $self->actions },
sub { $self->driver->send_keys_to_active_element(@$value) };
$self;
}
sub send_keys {
my $self = shift;
my $keys = shift;
push @{ $self->actions },
sub { $self->driver->get_active_element->send_keys($keys) };
$self;
}
sub send_keys_to_element {
my $self = shift;
my ( $element, $keys ) = @_;
push @{ $self->actions }, sub { $element->send_keys($keys) };
$self;
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
Selenium::ActionChains - Action chains for Selenium::Remote::Driver
=head1 VERSION
version 1.33
=head1 SYNOPSIS
use Selenium::Remote::Driver;
use Selenium::ActionChains;
my $driver = Selenium::Remote::Driver->new;
my $action_chains = Selenium::ActionChains->new(driver => $driver);
$driver->get("http://www.some.web/site");
my $elt_1 = $driver->find_element("//*[\@id='someid']");
my $elt_2 = $driver->find_element("//*[\@id='someotherid']");
$action_chains->send_keys_to_element($elt_1)->click($elt_2)->perform;
=head1 DESCRIPTION
This module implements ActionChains for Selenium, which is a way of automating
low level interactions like mouse movements, mouse button actions , key presses and
context menu interactions.
The code was inspired by the L<Python implementation|http://selenium.googlecode.com/svn/trunk/docs/api/py/_modules/selenium/webdriver/common/action_chains.html#ActionChains>.
=for Pod::Coverage driver
=head1 DRAG AND DROP IS NOT WORKING !
The implementation contains a drag_and_drop function, but due to Selenium limitations, it is L<not working|https://code.google.com/p/selenium/issues/detail?id=3604>.
Nevertheless, we decided to implement the function, because eventually one day it will work.
In the meantime, there are workarounds that can be used to simulate drag and drop, like L<this StackOverflow post|http://stackoverflow.com/questions/29381233/how-to-simulate-html5-drag-and-drop-in-selenium-webdriver-in-python>.
=head1 FUNCTIONS
=head2 new
Creates a new ActionChains object. Requires a Selenium::Remote::Driver as a mandatory parameter:
my $driver = Selenium::Remote::Driver->new;
my $action_chains = Selenium::ActionChains->new(driver => $driver);
=head2 perform
Performs all the actions stored in the ActionChains object in the order they were called:
Args: None
Usage:
my $action_chains = Selenium::ActionChains->new(driver => $driver);
# assuming that $some_element and $other_element are valid
# Selenium::Remote::WebElement objects
$action_chains->click($some_element);
$action_chains->move_to_element($other_element);
$action_chains->click($other_element);
# click some_element, move to other_element, then click other_element
$action_chains->perform;
=head2 click
Clicks an element. If none specified, clicks on current mouse position.
Args: A Selenium::Remote::WebElement object
Usage:
my $element = $driver->find_element("//div[\@id='some_id']");
$action_chains->click($element);
=head2 click_and_hold
Holds down the left mouse button on an element. If none specified, clicks on current
mouse position.
Args: A Selenium::Remote::WebElement object
Usage:
my $element = $driver->find_element("//div[\@id='some_id']");
$action_chains->click_and_hold($element);
=head2 context_click
Right clicks an element. If none specified, right clicks on current mouse
position.
Args: A Selenium::Remote::WebElement object
Usage:
my $element = $driver->find_element("//div[\@id='some_id']");
$action_chains->context_click($element);
=head2 double_click
Double clicks an element. If none specified, double clicks on current mouse
position.
Args: A Selenium::Remote::WebElement object
Usage:
my $element = $driver->find_element("//div[\@id='some_id']");
$action_chains->double_click($element);
=head2 drag_and_drop - NOT WORKING
Holds down the left mouse button on the source element, then moves to the target
element and releases the mouse button. IT IS NOT WORKING DUE TO CURRENT SELENIUM
LIMITATIONS.
Args:
A source Selenium::Remote::WebElement object
A target Selenium::Remote::WebElement object
Usage:
my $src_element = $driver->find_element("//*[\@class='foo']");
my $tgt_element = $driver->find_element("//*[\@class='bar']");
$action_chains->drag_and_drop($src_element,$tgt_element);
=head2 drag_and_drop_by_offset - NOT WORKING
Holds down the left mouse button on the source element, then moves to the offset
specified and releases the mouse button. IT IS NOT WORKING DUE TO CURRENT SELENIUM
LIMITATIONS.
Args:
A source Selenium::Remote::WebElement object
An integer X offset
An integer Y offset
Usage:
my $src_element = $driver->find_element("//*[\@class='foo']");
my $xoffset = 10;
my $yoffset = 10;
$action_chains->drag_and_drop($src_element,$xoffset,$yoffset);
=head2 key_down
Sends key presses only, without releasing them.
Should be used only with modifier keys (Control, Alt, Shift)
Args:
An array ref to keys to send. Use the KEY constant from Selenium::Remote::WDKeys
The element to send keys to. If none, sends keys to the current focused element
Usage:
use Selenium::Remote::WDKeys 'KEYS';
$action_chains->key_down( [ KEYS->{'alt'} ] );
=head2 key_up
Releases a mofifier key.
Args:
An array ref to keys to send. Use the KEY constant from Selenium::Remote::WDKeys
The element to send keys to. If none, sends keys to the current focused element
Usage:
use Selenium::Remote::WDKeys 'KEYS';
my $element = $driver->find_element('foo','id');
$action_chains->key_up( [ KEYS->{'alt'} ],$element);
=head2 move_by_offset
Moves the mouse to an offset from current mouse position.
Args:
An integer X offset
An integer Y offset
Usage:
$action_chains->move_by_offset(10,100);
=head2 move_to_element
Moves the mouse to the middle of an element
Args:
A Selenium::Remote::WebElement to move to
Usage:
my $element = $driver->find_element('foo','id');
$action_chains->move_to_element($element);
=head2 move_to_element_with_offset
Moves the mouse by an offset of the specified element.
Offsets are relative to the top-left corner of the element
Args:
A Selenium::Remote::WebElement
An integer X offset
An integer Y offset
Usage:
my $element = $driver->find_element('foo','id');
$action_chains->move_to_element_with_offset($element,10,10);
=head2 release
Releases a held mouse_button
Args:
A Selenium::Remote::WebElement, the element to mouse up
Usage:
my $element = $driver->find_element('foo','id');
$action_chains->release($element);
=head2 send_keys
Sends keys to the currently focused element
Args:
The keys to send
Usage:
$action_chains->send_keys('abcd');
=head2 send_keys_to_element
Sends keys to an element
Args:
A Selenium::Remote::WebElement
The keys to send
Usage:
my $element = $driver->find_element('foo','id');
$action_chains->send_keys_to_element($element,'abcd');
=head1 SEE ALSO
Please see those modules/websites for more information related to this module.
=over 4
=item *
L<Selenium::Remote::Driver|Selenium::Remote::Driver>
=back
=head1 BUGS
Please report any bugs or feature requests on the bugtracker website
L<https://github.com/teodesian/Selenium-Remote-Driver/issues>
When submitting a bug or request, please include a test-file or a
patch to an existing test-file that illustrates the bug or desired
feature.
=head1 AUTHORS
Current Maintainers:
=over 4
=item *
Daniel Gempesaw <gempesaw@gmail.com>
=item *
Emmanuel Peroumalnaïk <peroumalnaik.emmanuel@gmail.com>
=back
Previous maintainers:
=over 4
=item *
Luke Closs <cpan@5thplane.com>
=item *
Mark Stosberg <mark@stosberg.com>
=back
Original authors:
=over 4
=item *
Aditya Ivaturi <ivaturi@gmail.com>
=back
=head1 COPYRIGHT AND LICENSE
Copyright (c) 2010-2011 Aditya Ivaturi, Gordon Child
Copyright (c) 2014-2017 Daniel Gempesaw
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
=cut

View File

@@ -0,0 +1,628 @@
package Selenium::CanStartBinary;
$Selenium::CanStartBinary::VERSION = '1.33';
use strict;
use warnings;
# ABSTRACT: Teach a WebDriver how to start its own binary aka no JRE!
use File::Spec;
use Selenium::CanStartBinary::ProbePort
qw/find_open_port_above find_open_port probe_port/;
use Selenium::Firefox::Binary qw/setup_firefox_binary_env/;
use Selenium::Waiter qw/wait_until/;
use Moo::Role;
use constant IS_WIN => $^O eq 'MSWin32';
requires 'binary';
requires 'binary_port';
requires '_binary_args';
has '_real_binary' => (
is => 'lazy',
builder => sub {
my ($self) = @_;
if ( $self->_is_old_ff ) {
return $self->firefox_binary;
}
else {
return $self->binary;
}
}
);
has '_is_old_ff' => (
is => 'lazy',
builder => sub {
my ($self) = @_;
return $self->isa('Selenium::Firefox') && !$self->marionette_enabled;
}
);
has '+port' => (
is => 'lazy',
builder => sub {
my ($self) = @_;
if ( $self->_real_binary ) {
if ( $self->fixed_ports ) {
return find_open_port( $self->binary_port );
}
else {
return find_open_port_above( $self->binary_port );
}
}
else {
return 4444;
}
}
);
has 'fixed_ports' => (
is => 'lazy',
default => sub { 0 }
);
has custom_args => (
is => 'lazy',
predicate => 1,
default => sub { '' }
);
has 'marionette_port' => (
is => 'lazy',
builder => sub {
my ($self) = @_;
if ( $self->_is_old_ff ) {
return 0;
}
else {
if ( $self->fixed_ports ) {
return find_open_port( $self->marionette_binary_port );
}
else {
return find_open_port_above( $self->marionette_binary_port );
}
}
}
);
has startup_timeout => (
is => 'lazy',
default => sub { 10 }
);
has 'binary_mode' => (
is => 'lazy',
init_arg => undef,
builder => 1,
predicate => 1
);
has 'try_binary' => (
is => 'lazy',
default => sub { 0 },
trigger => sub {
my ($self) = @_;
$self->binary_mode if $self->try_binary;
}
);
has 'window_title' => (
is => 'lazy',
init_arg => undef,
builder => sub {
my ($self) = @_;
my ( undef, undef, $file ) =
File::Spec->splitpath( $self->_real_binary );
my $port = $self->port;
return $file . ':' . $port;
}
);
has '_command' => (
is => 'lazy',
init_arg => undef,
builder => sub {
my ($self) = @_;
return $self->_construct_command;
}
);
has 'logfile' => (
is => 'lazy',
default => sub {
return '/nul' if IS_WIN;
return '/dev/null';
}
);
sub BUILDARGS {
# There's a bit of finagling to do to since we can't ensure the
# attribute instantiation order. To decide whether we're going into
# binary mode, we need the remote_server_addr and port. But, they're
# both lazy and only instantiated immediately before S:R:D's
# remote_conn attribute. Once remote_conn is set, we can't change it,
# so we need the following order:
#
# parent: remote_server_addr, port
# role: binary_mode (aka _build_binary_mode)
# parent: remote_conn
#
# Since we can't force an order, we introduced try_binary which gets
# decided during BUILDARGS to tip us off as to whether we should try
# binary mode or not.
my ( undef, %args ) = @_;
if ( !exists $args{remote_server_addr} && !exists $args{port} ) {
$args{try_binary} = 1;
# Windows may throw a fit about invalid pointers if we try to
# connect to localhost instead of 127.1
$args{remote_server_addr} = '127.0.0.1';
}
else {
$args{try_binary} = 0;
$args{binary_mode} = 0;
}
return {%args};
}
sub _build_binary_mode {
my ($self) = @_;
# We don't know what to do without a binary driver to start up
return unless $self->_real_binary;
# Either the user asked for 4444, or we couldn't find an open port
my $port = $self->port + 0;
return if $port == 4444;
if ( $self->fixed_ports && $port == 0 ) {
die 'port '
. $self->binary_port
. ' is not free and have requested fixed ports';
}
$self->_handle_firefox_setup($port);
system( $self->_command );
my $success =
wait_until { probe_port($port) } timeout => $self->startup_timeout;
if ($success) {
return 1;
}
else {
die 'Unable to connect to the '
. $self->_real_binary
. ' binary on port '
. $port;
}
}
sub _handle_firefox_setup {
my ( $self, $port ) = @_;
# This is a no-op for other browsers
return unless $self->isa('Selenium::Firefox');
my $user_profile =
$self->has_firefox_profile
? $self->firefox_profile
: 0;
my $profile =
setup_firefox_binary_env( $port, $self->marionette_port, $user_profile );
if ( $self->_is_old_ff ) {
# For non-geckodriver/non-marionette, we want to get rid of
# the profile so that we don't accidentally zip it and encode
# it down the line while Firefox is trying to read from it.
$self->clear_firefox_profile if $self->has_firefox_profile;
}
else {
# For geckodriver/marionette, we keep the enhanced profile around because
# we need to send it to geckodriver as a zipped b64-encoded
# directory.
$self->firefox_profile($profile);
}
}
sub shutdown_binary {
my ($self) = @_;
if ( $self->auto_close && defined $self->session_id ) {
$self->quit();
}
if ( $self->has_binary_mode && $self->binary_mode ) {
# Tell the binary itself to shutdown
my $port = $self->port;
my $ua = $self->ua;
$ua->get( 'http://127.0.0.1:' . $port . '/wd/hub/shutdown' );
# Close the orphaned command windows on windows
$self->shutdown_windows_binary;
$self->shutdown_unix_binary;
}
}
sub shutdown_unix_binary {
my ($self) = @_;
if (!IS_WIN) {
my $cmd = "lsof -t -i :".$self->port();
my ( $pid ) = grep { $_ && $_ ne $$ } split( /\s+/, scalar `$cmd` );
if ($pid) {
print "Killing Driver PID $pid listening on port "
. $self->port . "...\n";
eval { kill 'KILL', $pid };
warn
"Could not kill driver process! you may have to clean up manually."
if $@;
}
}
}
sub shutdown_windows_binary {
my ($self) = @_;
if (IS_WIN) {
if ( $self->_is_old_ff ) {
# FIXME: Blech, handle a race condition that kills the
# driver before it's finished cleaning up its sessions. In
# particular, when the perl process ends, it wants to
# clean up the temp directory it created for the Firefox
# profile. But, if the Firefox process is still running,
# it will have a lock on the temp profile directory, and
# perl will get upset. This "solution" is _very_ bad.
sleep(2);
# Firefox doesn't have a Driver/Session architecture - the
# only thing running is Firefox itself, so there's no
# other task to kill.
return;
}
system( 'taskkill /FI "WINDOWTITLE eq '
. $self->window_title
. '" > nul 2>&1' );
}
}
sub DEMOLISH {
my ( $self, $in_gd ) = @_;
# if we're in global destruction, all bets are off.
return if $in_gd;
$self->shutdown_binary;
}
sub _construct_command {
my ($self) = @_;
my $executable = $self->_real_binary;
# Executable path names may have spaces
$executable = '"' . $executable . '"';
# The different binaries take different arguments for proper setup
$executable .= $self->_binary_args;
if ( $self->has_custom_args ) {
$executable .= ' ' . $self->custom_args;
}
# Handle Windows vs Unix discrepancies for invoking shell commands
my ( $prefix, $suffix ) = ( $self->_cmd_prefix, $self->_cmd_suffix );
return join( ' ', ( $prefix, $executable, $suffix ) );
}
sub _cmd_prefix {
my ($self) = @_;
my $prefix = '';
if (IS_WIN) {
$prefix = 'start "' . $self->window_title . '"';
if ( $self->_is_old_ff ) {
# For older versions of Firefox that run without
# marionette, the command we're running actually starts up
# the browser itself, so we don't want to minimize it.
return $prefix;
}
else {
# If we're firefox with marionette, or any other browser,
# the command we're running is the driver, and we don't
# need want the command window in the foreground.
return $prefix . ' /MIN ';
}
}
return $prefix;
}
sub _cmd_suffix {
my ($self) = @_;
return " > " . $self->logfile . " 2>&1 " if IS_WIN;
return " > " . $self->logfile . " 2>&1 &";
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
Selenium::CanStartBinary - Teach a WebDriver how to start its own binary aka no JRE!
=head1 VERSION
version 1.33
=head1 DESCRIPTION
This role takes care of the details for starting up a Webdriver
instance. It does not do any downloading or installation of any sort -
you're still responsible for obtaining and installing the necessary
binaries into your C<$PATH> for this role to find. You may be
interested in L<Selenium::Chrome>, L<Selenium::Firefox>, or
L<Selenium::PhantomJS> if you're looking for classes that already
consume this role.
The role determines whether or not it should try to do its own magic
based on whether the consuming class is instantiated with a
C<remote_server_addr> and/or C<port>.
# We'll start up the Chrome binary for you
my $chrome_via_binary = Selenium::Chrome->new;
# Look for a selenium server running on 4444.
my $chrome_via_server = Selenium::Chrome->new( port => 4444 );
If they're missing, we assume the user wants to use a webdriver
directly and act accordingly. We handle finding the proper associated
binary (or you can specify it with L</binary>), figuring out what
arguments it wants, setting up any necessary environments, and
starting up the binary.
There's a number of TODOs left over - namely Windows support is
severely lacking, and we're pretty naive when we attempt to locate the
executables on our own.
In the following documentation, C<required> refers to when you're
consuming the role, not the C<required> when you're instantiating a
class that has already consumed the role.
=head1 ATTRIBUTES
=head2 binary
Required: Specify the path to the executable in question, or the name
of the executable for us to find via L<File::Which/which>.
=head2 binary_port
Required: Specify a default port that for the webdriver binary to try
to bind to. If that port is unavailable, we'll probe above that port
until we find a valid one.
=head2 _binary_args
Required: Specify the arguments that the particular binary needs in
order to start up correctly. In particular, you may need to tell the
binary about the proper port when we start it up, or that it should
use a particular prefix to match up with the behavior of the Remote
Driver server.
If your binary doesn't need any arguments, just have the default be an
empty string.
=head2 port
The role will attempt to determine the proper port for us. Consuming
roles should set a default port in L</binary_port> at which we will
begin searching for an open port.
Note that if we cannot locate a suitable L</binary>, port will be set
to 4444 so we can attempt to look for a Selenium server at
C<127.0.0.1:4444>.
=head2 fixed_ports
Optional: By default, if binary_port and marionette_port are not free
a higher free port is probed and acquired if possible, until a free one
if found or a timeout is exceeded.
my $driver1 = Selenium::Chrome->new;
my $driver2 = Selenium::Chrome->new( port => 1234 );
The default behavior can be overridden. In this case, only the default
or given binary_port and marionette_port are probed, without probing
higher ports. This ensures that either the default or given port will be
assigned, or no port will be assigned at all.
my $driver1 = Selenium::Chrome->new( fixed_ports => 1 );
my $driver2 = Selenium::Chrome->new( port => 1234, fixed_ports => 1);
=head2 custom_args
Optional: If you want to pass additional options to the binary when it
starts up, you can add that here. For example, if your binary accepts
an argument on the command line like C<--log-path=/path/to/log>, and
you'd like to specify that the binary uses that option, you could do:
my $chrome = Selenium::Chrome->new(
custom_args => '--log-path=/path/to/log'
);
To specify multiple arguments, just include them all in the string.
=head2 startup_timeout
Optional: you can modify how long we will wait for the binary to start
up. By default, we will start the binary and check the intended
destination port for 10 seconds before giving up. If the machine
you're using to run your browsers is slower or smaller, you may need
to increase this timeout.
The following:
my $f = Selenium::Firefox->new(
startup_timeout => 60
);
will wait up to 60 seconds for the firefox binary to respond on the
proper port. To use this constructor option, you should specify a time
in seconds as an integer, and it will be passed to the arguments
section of a L<Selenium::Waiter/wait_until> subroutine call.
=head2 binary_mode
Mostly intended for internal use, its builder coordinates all the side
effects of interacting with the binary: locating the executable,
finding an open port, setting up the environment, shelling out to
start the binary, and ensuring that the webdriver is listening on the
correct port.
If all of the above steps pass, it will return truthy after
instantiation. If any of them fail, it should return falsy and the
class should attempt normal L<Selenium::Remote::Driver> behavior.
=head2 window_title
Intended for internal use: this will build us a unique title for the
background binary process of the Webdriver. Then, when we're cleaning
up, we know what the window title is that we're going to C<taskkill>.
=head2 command
Intended for internal use: this read-only attribute is built by us,
but it can be useful after instantiation to see exactly what command
was run to start the webdriver server.
my $f = Selenium::Firefox->new;
say $f->_command;
=head2 logfile
Normally we log what occurs in the driver to /dev/null (or /nul on windows).
Setting this will redirect it to the provided file.
=for Pod::Coverage *EVERYTHING*
=head1 SEE ALSO
Please see those modules/websites for more information related to this module.
=over 4
=item *
L<Selenium::Remote::Driver|Selenium::Remote::Driver>
=item *
L<Selenium::Chrome|Selenium::Chrome>
=item *
L<Selenium::Firefox|Selenium::Firefox>
=item *
L<Selenium::PhantomJS|Selenium::PhantomJS>
=back
=head1 BUGS
Please report any bugs or feature requests on the bugtracker website
L<https://github.com/teodesian/Selenium-Remote-Driver/issues>
When submitting a bug or request, please include a test-file or a
patch to an existing test-file that illustrates the bug or desired
feature.
=head1 AUTHORS
Current Maintainers:
=over 4
=item *
Daniel Gempesaw <gempesaw@gmail.com>
=item *
Emmanuel Peroumalnaïk <peroumalnaik.emmanuel@gmail.com>
=back
Previous maintainers:
=over 4
=item *
Luke Closs <cpan@5thplane.com>
=item *
Mark Stosberg <mark@stosberg.com>
=back
Original authors:
=over 4
=item *
Aditya Ivaturi <ivaturi@gmail.com>
=back
=head1 COPYRIGHT AND LICENSE
Copyright (c) 2010-2011 Aditya Ivaturi, Gordon Child
Copyright (c) 2014-2017 Daniel Gempesaw
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
=cut

View File

@@ -0,0 +1,172 @@
package Selenium::CanStartBinary::FindBinary;
$Selenium::CanStartBinary::FindBinary::VERSION = '1.33';
use strict;
use warnings;
# ABSTRACT: Coercions for finding webdriver binaries on your system
use Cwd qw/abs_path/;
use File::Which qw/which/;
use IO::Socket::INET;
use Selenium::Firefox::Binary qw/firefox_path/;
require Exporter;
our @ISA = qw/Exporter/;
our @EXPORT_OK = qw/coerce_simple_binary coerce_firefox_binary/;
use constant IS_WIN => $^O eq 'MSWin32';
sub coerce_simple_binary {
my ($executable) = @_;
my $manual_binary = _validate_manual_binary($executable);
if ($manual_binary) {
return $manual_binary;
}
else {
return _naive_find_binary($executable);
}
}
sub coerce_firefox_binary {
my ($executable) = @_;
my $manual_binary = _validate_manual_binary($executable);
if ($manual_binary) {
return $manual_binary;
}
else {
return firefox_path();
}
}
sub _validate_manual_binary {
my ($executable) = @_;
my $abs_executable = eval {
my $path = abs_path($executable);
die unless -e $path;
$path;
};
if ($abs_executable) {
if ( -x $abs_executable || IS_WIN ) {
return $abs_executable;
}
else {
die 'The binary at '
. $executable
. ' is not executable. Choose the correct file or chmod +x it as needed.';
}
}
}
sub _naive_find_binary {
my ($executable) = @_;
my $naive_binary = which($executable);
if ( defined $naive_binary ) {
return $naive_binary;
}
else {
warn qq(Unable to find the $executable binary in your \$PATH.);
return;
}
}
__END__
=pod
=encoding UTF-8
=head1 NAME
Selenium::CanStartBinary::FindBinary - Coercions for finding webdriver binaries on your system
=head1 VERSION
version 1.33
=for Pod::Coverage *EVERYTHING*
=head1 SEE ALSO
Please see those modules/websites for more information related to this module.
=over 4
=item *
L<Selenium::Remote::Driver|Selenium::Remote::Driver>
=back
=head1 BUGS
Please report any bugs or feature requests on the bugtracker website
L<https://github.com/teodesian/Selenium-Remote-Driver/issues>
When submitting a bug or request, please include a test-file or a
patch to an existing test-file that illustrates the bug or desired
feature.
=head1 AUTHORS
Current Maintainers:
=over 4
=item *
Daniel Gempesaw <gempesaw@gmail.com>
=item *
Emmanuel Peroumalnaïk <peroumalnaik.emmanuel@gmail.com>
=back
Previous maintainers:
=over 4
=item *
Luke Closs <cpan@5thplane.com>
=item *
Mark Stosberg <mark@stosberg.com>
=back
Original authors:
=over 4
=item *
Aditya Ivaturi <ivaturi@gmail.com>
=back
=head1 COPYRIGHT AND LICENSE
Copyright (c) 2010-2011 Aditya Ivaturi, Gordon Child
Copyright (c) 2014-2017 Daniel Gempesaw
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
=cut

View File

@@ -0,0 +1,143 @@
package Selenium::CanStartBinary::ProbePort;
$Selenium::CanStartBinary::ProbePort::VERSION = '1.33';
use strict;
use warnings;
# ABSTRACT: Utility functions for finding open ports to eventually bind to
use IO::Socket::INET;
use Selenium::Waiter qw/wait_until/;
require Exporter;
our @ISA = qw/Exporter/;
our @EXPORT_OK = qw/find_open_port_above find_open_port probe_port/;
sub find_open_port_above {
my ($port) = @_;
my $free_port = wait_until {
if ( probe_port($port) ) {
$port++;
return 0;
}
else {
return $port;
}
};
return $free_port;
}
sub find_open_port {
my ($port) = @_;
probe_port($port) ? return 0 : return $port;
}
sub probe_port {
my ($port) = @_;
return IO::Socket::INET->new(
PeerAddr => '127.0.0.1',
PeerPort => $port,
Timeout => 3
);
}
__END__
=pod
=encoding UTF-8
=head1 NAME
Selenium::CanStartBinary::ProbePort - Utility functions for finding open ports to eventually bind to
=head1 VERSION
version 1.33
=for Pod::Coverage *EVERYTHING*
=head1 SEE ALSO
Please see those modules/websites for more information related to this module.
=over 4
=item *
L<Selenium::Remote::Driver|Selenium::Remote::Driver>
=back
=head1 BUGS
Please report any bugs or feature requests on the bugtracker website
L<https://github.com/teodesian/Selenium-Remote-Driver/issues>
When submitting a bug or request, please include a test-file or a
patch to an existing test-file that illustrates the bug or desired
feature.
=head1 AUTHORS
Current Maintainers:
=over 4
=item *
Daniel Gempesaw <gempesaw@gmail.com>
=item *
Emmanuel Peroumalnaïk <peroumalnaik.emmanuel@gmail.com>
=back
Previous maintainers:
=over 4
=item *
Luke Closs <cpan@5thplane.com>
=item *
Mark Stosberg <mark@stosberg.com>
=back
Original authors:
=over 4
=item *
Aditya Ivaturi <ivaturi@gmail.com>
=back
=head1 COPYRIGHT AND LICENSE
Copyright (c) 2010-2011 Aditya Ivaturi, Gordon Child
Copyright (c) 2014-2017 Daniel Gempesaw
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
=cut

View File

@@ -0,0 +1,228 @@
package Selenium::Chrome;
$Selenium::Chrome::VERSION = '1.33';
use strict;
use warnings;
# ABSTRACT: Use ChromeDriver without a Selenium server
use Moo;
use Selenium::CanStartBinary::FindBinary qw/coerce_simple_binary/;
extends 'Selenium::Remote::Driver';
has '+browser_name' => (
is => 'ro',
default => sub { 'chrome' }
);
has 'binary' => (
is => 'lazy',
coerce => \&coerce_simple_binary,
default => sub { 'chromedriver' },
predicate => 1
);
has 'binary_port' => (
is => 'lazy',
default => sub { 9515 }
);
has '_binary_args' => (
is => 'lazy',
builder => sub {
my ($self) = @_;
my $context = $self->wd_context_prefix;
$context =~ s{^/}{};
return ' --port=' . $self->port . ' --url-base=' . $context . ' ';
}
);
with 'Selenium::CanStartBinary';
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
Selenium::Chrome - Use ChromeDriver without a Selenium server
=head1 VERSION
version 1.33
=head1 SYNOPSIS
my $driver = Selenium::Chrome->new;
# when you're done
$driver->shutdown_binary;
=head1 DESCRIPTION
This class allows you to use the ChromeDriver without needing the JRE
or a selenium server running. When you refrain from passing the
C<remote_server_addr> and C<port> arguments, we will search for the
chromedriver executable binary in your $PATH. We'll try to start the
binary connect to it, shutting it down at the end of the test.
If the chromedriver binary is not found, we'll fall back to the
default L<Selenium::Remote::Driver> behavior of assuming defaults of
127.0.0.1:4444 after waiting a few seconds.
If you specify a remote server address, or a port, we'll assume you
know what you're doing and take no additional behavior.
If you're curious whether your Selenium::Chrome instance is using a
separate ChromeDriver binary, or through the selenium server, you can
check the C<binary_mode> attr after instantiation.
=head1 ATTRIBUTES
=head2 binary
Optional: specify the path to your binary. If you don't specify
anything, we'll try to find it on our own via L<File::Which/which>.
=head2 binary_port
Optional: specify the port that we should bind to. If you don't
specify anything, we'll default to the driver's default port. Since
there's no a priori guarantee that this will be an open port, this is
_not_ necessarily the port that we end up using - if the port here is
already bound, we'll search above it until we find an open one.
See L<Selenium::CanStartBinary/port> for more details, and
L<Selenium::Remote::Driver/port> after instantiation to see what the
actual port turned out to be.
=head2 custom_args
Optional: specify any additional command line arguments you'd like
invoked during the binary startup. See
L<Selenium::CanStartBinary/custom_args> for more information.
=head2 startup_timeout
Optional: specify how long to wait for the binary to start itself and
listen on its port. The default duration is arbitrarily 10 seconds. It
accepts an integer number of seconds to wait: the following will wait
up to 20 seconds:
Selenium::Chrome->new( startup_timeout => 20 );
See L<Selenium::CanStartBinary/startup_timeout> for more information.
=head2 fixed_ports
Optional: Throw instead of searching for additional ports; see
L<Selenium::CanStartBinary/fixed_ports> for more info.
=head1 METHODS
=head2 shutdown_binary
Call this method instead of L<Selenium::Remote::Driver/quit> to ensure
that the binary executable is also closed, instead of simply closing
the browser itself. If the browser is still around, it will call
C<quit> for you. After that, it will try to shutdown the browser
binary by making a GET to /shutdown and on Windows, it will attempt to
do a C<taskkill> on the binary CMD window.
$self->shutdown_binary;
It doesn't take any arguments, and it doesn't return anything.
We do our best to call this when the C<$driver> option goes out of
scope, but if that happens during global destruction, there's nothing
we can do.
=for Pod::Coverage has_binary
=head1 SEE ALSO
Please see those modules/websites for more information related to this module.
=over 4
=item *
L<Selenium::Remote::Driver|Selenium::Remote::Driver>
=back
=head1 BUGS
Please report any bugs or feature requests on the bugtracker website
L<https://github.com/teodesian/Selenium-Remote-Driver/issues>
When submitting a bug or request, please include a test-file or a
patch to an existing test-file that illustrates the bug or desired
feature.
=head1 AUTHORS
Current Maintainers:
=over 4
=item *
Daniel Gempesaw <gempesaw@gmail.com>
=item *
Emmanuel Peroumalnaïk <peroumalnaik.emmanuel@gmail.com>
=back
Previous maintainers:
=over 4
=item *
Luke Closs <cpan@5thplane.com>
=item *
Mark Stosberg <mark@stosberg.com>
=back
Original authors:
=over 4
=item *
Aditya Ivaturi <ivaturi@gmail.com>
=back
=head1 COPYRIGHT AND LICENSE
Copyright (c) 2010-2011 Aditya Ivaturi, Gordon Child
Copyright (c) 2014-2017 Daniel Gempesaw
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
=cut

View File

@@ -0,0 +1,228 @@
package Selenium::Edge;
$Selenium::Edge::VERSION = '1.33';
use strict;
use warnings;
# ABSTRACT: Use EdgeDriver without a Selenium server
use Moo;
use Selenium::CanStartBinary::FindBinary qw/coerce_simple_binary/;
extends 'Selenium::Remote::Driver';
has '+browser_name' => (
is => 'ro',
default => sub { 'MicrosoftEdge' }
);
has 'binary' => (
is => 'lazy',
coerce => \&coerce_simple_binary,
default => sub { 'MicrosoftWebDriver.exe' },
predicate => 1
);
has 'binary_port' => (
is => 'lazy',
default => sub { 17556 }
);
has '_binary_args' => (
is => 'lazy',
builder => sub {
my ($self) = @_;
my $context = $self->wd_context_prefix;
$context =~ s{^/}{};
return ' --port=' . $self->port . ' --url-base=' . $context . ' ';
}
);
with 'Selenium::CanStartBinary';
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
Selenium::Edge - Use EdgeDriver without a Selenium server
=head1 VERSION
version 1.33
=head1 SYNOPSIS
my $driver = Selenium::Edge->new;
# when you're done
$driver->shutdown_binary;
=head1 DESCRIPTION
This class allows you to use the EdgeDriver without needing the JRE
or a selenium server running. When you refrain from passing the
C<remote_server_addr> and C<port> arguments, we will search for the
edgedriver executable binary in your $PATH. We'll try to start the
binary connect to it, shutting it down at the end of the test.
If the MicrosoftWebDriver binary is not found, we'll fall back to the
default L<Selenium::Remote::Driver> behavior of assuming defaults of
127.0.0.1:4444 after waiting a few seconds.
If you specify a remote server address, or a port, we'll assume you
know what you're doing and take no additional behavior.
If you're curious whether your Selenium::Edge instance is using a
separate MicrosoftWebDriver binary, or through the selenium server, you can
check the C<binary_mode> attr after instantiation.
=head1 ATTRIBUTES
=head2 binary
Optional: specify the path to your binary. If you don't specify
anything, we'll try to find it on our own via L<File::Which/which>.
=head2 binary_port
Optional: specify the port that we should bind to. If you don't
specify anything, we'll default to the driver's default port. Since
there's no a priori guarantee that this will be an open port, this is
_not_ necessarily the port that we end up using - if the port here is
already bound, we'll search above it until we find an open one.
See L<Selenium::CanStartBinary/port> for more details, and
L<Selenium::Remote::Driver/port> after instantiation to see what the
actual port turned out to be.
=head2 custom_args
Optional: specify any additional command line arguments you'd like
invoked during the binary startup. See
L<Selenium::CanStartBinary/custom_args> for more information.
=head2 startup_timeout
Optional: specify how long to wait for the binary to start itself and
listen on its port. The default duration is arbitrarily 10 seconds. It
accepts an integer number of seconds to wait: the following will wait
up to 20 seconds:
Selenium::Edge->new( startup_timeout => 20 );
See L<Selenium::CanStartBinary/startup_timeout> for more information.
=head2 fixed_ports
Optional: Throw instead of searching for additional ports; see
L<Selenium::CanStartBinary/fixed_ports> for more info.
=head1 METHODS
=head2 shutdown_binary
Call this method instead of L<Selenium::Remote::Driver/quit> to ensure
that the binary executable is also closed, instead of simply closing
the browser itself. If the browser is still around, it will call
C<quit> for you. After that, it will try to shutdown the browser
binary by making a GET to /shutdown and on Windows, it will attempt to
do a C<taskkill> on the binary CMD window.
$self->shutdown_binary;
It doesn't take any arguments, and it doesn't return anything.
We do our best to call this when the C<$driver> option goes out of
scope, but if that happens during global destruction, there's nothing
we can do.
=for Pod::Coverage has_binary
=head1 SEE ALSO
Please see those modules/websites for more information related to this module.
=over 4
=item *
L<Selenium::Remote::Driver|Selenium::Remote::Driver>
=back
=head1 BUGS
Please report any bugs or feature requests on the bugtracker website
L<https://github.com/teodesian/Selenium-Remote-Driver/issues>
When submitting a bug or request, please include a test-file or a
patch to an existing test-file that illustrates the bug or desired
feature.
=head1 AUTHORS
Current Maintainers:
=over 4
=item *
Daniel Gempesaw <gempesaw@gmail.com>
=item *
Emmanuel Peroumalnaïk <peroumalnaik.emmanuel@gmail.com>
=back
Previous maintainers:
=over 4
=item *
Luke Closs <cpan@5thplane.com>
=item *
Mark Stosberg <mark@stosberg.com>
=back
Original authors:
=over 4
=item *
Aditya Ivaturi <ivaturi@gmail.com>
=back
=head1 COPYRIGHT AND LICENSE
Copyright (c) 2010-2011 Aditya Ivaturi, Gordon Child
Copyright (c) 2014-2017 Daniel Gempesaw
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
=cut

View File

@@ -0,0 +1,464 @@
package Selenium::Firefox;
$Selenium::Firefox::VERSION = '1.33';
use strict;
use warnings;
# ABSTRACT: Use FirefoxDriver without a Selenium server
use Moo;
use Carp;
use Selenium::Firefox::Binary qw/firefox_path/;
use Selenium::CanStartBinary::FindBinary
qw/coerce_simple_binary coerce_firefox_binary/;
extends 'Selenium::Remote::Driver';
has '+browser_name' => (
is => 'ro',
default => sub { 'firefox' }
);
has 'binary' => (
is => 'lazy',
coerce => \&coerce_simple_binary,
default => sub { 'geckodriver' },
predicate => 1
);
has 'binary_port' => (
is => 'lazy',
default => sub { 9090 }
);
has '_binary_args' => (
is => 'lazy',
builder => sub {
my ($self) = @_;
if ( $self->marionette_enabled ) {
my $args =
' --port '
. $self->port
. ' --marionette-port '
. $self->marionette_binary_port
. ' --binary "'
. $self->firefox_binary . '"';
return $args;
}
else {
return ' -no-remote';
}
}
);
has '+wd_context_prefix' => (
is => 'ro',
default => sub {
my ($self) = @_;
if ( $self->marionette_enabled ) {
return '';
}
else {
return '/hub';
}
}
);
has 'marionette_binary_port' => (
is => 'lazy',
default => sub { 2828 }
);
has 'marionette_enabled' => (
is => 'lazy',
default => 1
);
has 'firefox_binary' => (
is => 'lazy',
coerce => \&coerce_firefox_binary,
predicate => 1,
builder => 'firefox_path'
);
has '_execute_script_suffix' => (
is => 'lazy',
default => 'Gecko'
);
sub get_context {
my $self = shift;
if ( $self->_is_old_ff ) {
return 0;
}
my $res = { 'command' => 'getContext' };
return $self->_execute_command($res);
}
sub set_context {
my ( $self, $context ) = @_;
if ( $self->_is_old_ff ) {
return 0;
}
if ( not defined $context ) {
croak "Expecting context";
}
if ( $context !~ m/chrome|content/i ) {
croak "Expecting context value: chrome or content";
}
my $res = { 'command' => 'setContext' };
return $self->_execute_command( $res, { context => $context } );
}
with 'Selenium::CanStartBinary';
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
Selenium::Firefox - Use FirefoxDriver without a Selenium server
=head1 VERSION
version 1.33
=head1 SYNOPSIS
# These two are the same, and will only work with Firefox 48+
my $driver = Selenium::Firefox->new;
$driver = Selenium::Firefox->new( marionette_enabled => 1 );
#Do stuff...
$driver->shutdown_binary;
# For Firefox 47 and older, disable marionette:
$driver = Selenium::Firefox->new( marionette_enabled => 0 );
$driver->shutdown_binary;
=head1 DESCRIPTION
B<Breaking Changes:> There are breaking changes in v1.0+ of this
module if you're using it to start FF47; please see L</"BREAKING
CHANGES">. You can ignore this if you're using v1.0+ of this module to
start FF48.
This class allows you to use the FirefoxDriver without needing the JRE
or a selenium server running. Unlike starting up an instance of
S::R::D, do not pass the C<remote_server_addr> and C<port> arguments,
and we will search for the Firefox executable in your C<$PATH>. We'll
try to start the binary, connect to it, and shut it down at the end of
the test.
If the Firefox application is not found in the expected places, we'll
fall back to the default L<Selenium::Remote::Driver> behavior of
assuming defaults of 127.0.0.1:4444 after waiting a few seconds.
If you specify a remote server address, or a port, our assumption is
that you are doing standard S::R::D behavior and we will not attempt
any binary startup.
If you're curious whether your Selenium::Firefox instance is using a
separate Firefox binary, or through the selenium server, you can check
the value of the C<binary_mode> attr after instantiation.
=head1 ATTRIBUTES
=head2 binary
Optional: specify the path to the C<geckodriver> binary - this is NOT
the path to the Firefox browser. To specify the path to your Firefox
browser binary, see the L</firefox_binary> attr.
For Firefox 48 and greater, this is the path to your C<geckodriver>
executable. If you don't specify anything, we'll search for
C<geckodriver> in your C<$PATH>.
For Firefox 47 and older, this attribute does not apply, because the
older FF browsers do not use the separate driver binary startup.
=head2 binary_port
Optional: specify the port that we should bind to. If you don't
specify anything, we'll default to the driver's default port. Since
there's no a priori guarantee that this will be an open port, this is
_not_ necessarily the port that we end up using - if the port here is
already bound, we'll search above it until we find an open one.
See L<Selenium::CanStartBinary/port> for more details, and
L<Selenium::Remote::Driver/port> after instantiation to see what the
actual port turned out to be.
=head2 firefox_profile
Optional: Pass in an instance of L<Selenium::Firefox::Profile>
pre-configured as you please. The preferences you specify will be
merged with the ones necessary for setting up webdriver, and as a
result some options may be overwritten or ignored.
my $profile = Selenium::Firefox::Profile->new;
my $firefox = Selenium::Firefox->new(
firefox_profile => $profile
);
=head2 marionette_binary_port
Optional: specify the port that we should bind marionette to. If you don't
specify anything, we'll default to the marionette's default port. Since
there's no a priori guarantee that this will be an open port, this is
_not_ necessarily the port that we end up using - if the port here is
already bound, we'll search above it until we find an open one.
Selenium::Firefox->new(
marionette_enabled => 1,
marionette_binary_port => 12345,
);
Attempting to specify a C<marionette_binary_port> in conjunction with
setting C<marionette_enabled> does not make sense and will most likely
not do anything useful.
=head2 marionette_enabled
Optional: specify whether
L<marionette|https://developer.mozilla.org/en-US/docs/Mozilla/QA/Marionette>
should be enabled or not. By default, marionette is enabled, which
assumes you are running with Firefox 48 or newer. To use this module to
start Firefox 47 or older, you must pass C<< marionette_enabled => 0 >>.
my $ff48 = Selenium::Firefox->new( marionette_enabled => 1 ); # defaults to 1
my $ff47 = Selenium::Firefox->new( marionette_enabled => 0 );
=head2 firefox_binary
Optional: specify the path to the Firefox browser executable. Although
we will attempt to locate this in your C<$PATH>, you may specify it
explicitly here. Note that path here must point to a file that exists
and is executable, or we will croak.
For Firefox 48 and newer, this will be passed to C<geckodriver> such
that it will attempt to start up the Firefox at the specified path. If
you do not specify anything, we will look for the Firefox browser on
our own in the normal places, but if the browser cannot be found,
we'll probably C<die> during instantiation.
For Firefox 47 and older, this browser path should be the file that we
directly start up.
=head2 custom_args
Optional: specify any additional command line arguments you'd like
invoked during the binary startup. See
L<Selenium::CanStartBinary/custom_args> for more information.
For Firefox 48 and newer, these arguments will be passed to
geckodriver during start up.
For Firefox 47 and older, these arguments will be passed to the
Firefox browser during start up.
=head2 startup_timeout
Optional: specify how long to wait for the binary to start itself and
listen on its port. The default duration is arbitrarily 10 seconds. It
accepts an integer number of seconds to wait: the following will wait
up to 20 seconds:
Selenium::Firefox->new( startup_timeout => 20 );
See L<Selenium::CanStartBinary/startup_timeout> for more information.
=head2 fixed_ports
Optional: Throw instead of searching for additional ports; see
L<Selenium::CanStartBinary/fixed_ports> for more info.
=head1 METHODS
=head2 shutdown_binary
Call this method instead of L<Selenium::Remote::Driver/quit> to ensure
that the binary executable is also closed, instead of simply closing
the browser itself. If the browser is still around, it will call
C<quit> for you. After that, it will try to shutdown the browser
binary by making a GET to /shutdown and on Windows, it will attempt to
do a C<taskkill> on the binary CMD window.
$self->shutdown_binary;
It doesn't take any arguments, and it doesn't return anything.
We do our best to call this when the C<$driver> option goes out of
scope, but if that happens during global destruction, there's nothing
we can do.
=for Pod::Coverage has_binary
=for Pod::Coverage has_firefox_binary
=head1 BREAKING CHANGES
In version v1.0+ and newer, the default behavior is to enable
marionette & geckodriver mode. This means that an existing script that
works with v0.2701 and Firefox v47 will require modification if you
upgrade Selenium::Firefox to v1.0+. That is,
# v0.2701 of Selenium::Firefox works with FF47 like such; this will not
# work for FF47 after upgrade:
my $fx47_old = Selenium::Firefox->new;
...
$fx47_old->shutdown_binary;
# v1.0 of Selenium::Firefox works with FF47 like this
my $fx47_new = Selenium::Firefox->new( marionette_enabled => 0);
...
$fx47_new->shutdown_binary;
We default to assuming FF48 and geckodriver mode because all
forthcoming versions of the Firefox browser will be using the
geckodriver architecture, and also because that's consistent with the
rest of the driver setups, which all have separate driver binaries
apart from the browser itself. This means that:
# v0.2701 of Selenium::Firefox cannot start up FF48 at all
# v1.0+ of Selenium::Firefox works with FF48+ like this:
my $fx48 = Selenium::Firefox->new;
As with the other drivers, Selenium::Firefox in marionette/geckodriver
mode requires a C<geckodriver> executable in the path or provided
during startup, and it will also attempt to find the path to your
Firefox browser. During testing, we found that it was necessary for us
to pass the Firefox browser file path to the C<geckodriver> executable
during start up, or else C<geckodriver> would have trouble finding
Firefox.
=head2 get_context
Description:
Firefox extension: Retrieve browser's scope (chrome or content).
Chrome is a privileged scope where you can access things like the
Firefox UI itself. Content scope is where things like webpages live.
Output:
STRING - context {CHROME|CONTENT}
Usage:
print $firefox_driver->get_context();
=head2 set_context
Description:
Firefox extension: Set browser's scope (chrome or content).
Chrome is a privileged scope where you can access things like the
Firefox UI itself. Content scope is where things like webpages live.
Input:
Required:
<STRING> - context {CHROME|CONTENT}
Usage:
$firefox_driver->set_context( $context );
Output:
BOOLEAN - success or failure
=head1 SEE ALSO
Please see those modules/websites for more information related to this module.
=over 4
=item *
L<Selenium::Remote::Driver|Selenium::Remote::Driver>
=back
=head1 BUGS
Please report any bugs or feature requests on the bugtracker website
L<https://github.com/teodesian/Selenium-Remote-Driver/issues>
When submitting a bug or request, please include a test-file or a
patch to an existing test-file that illustrates the bug or desired
feature.
=head1 AUTHORS
Current Maintainers:
=over 4
=item *
Daniel Gempesaw <gempesaw@gmail.com>
=item *
Emmanuel Peroumalnaïk <peroumalnaik.emmanuel@gmail.com>
=back
Previous maintainers:
=over 4
=item *
Luke Closs <cpan@5thplane.com>
=item *
Mark Stosberg <mark@stosberg.com>
=back
Original authors:
=over 4
=item *
Aditya Ivaturi <ivaturi@gmail.com>
=back
=head1 COPYRIGHT AND LICENSE
Copyright (c) 2010-2011 Aditya Ivaturi, Gordon Child
Copyright (c) 2014-2017 Daniel Gempesaw
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
=cut

View File

@@ -0,0 +1,220 @@
package Selenium::Firefox::Binary;
$Selenium::Firefox::Binary::VERSION = '1.33';
use strict;
use warnings;
# ABSTRACT: Subroutines for locating and properly initializing the Firefox Binary
use File::Which qw/which/;
use Selenium::Firefox::Profile;
require Exporter;
our @ISA = qw/Exporter/;
our @EXPORT_OK = qw/firefox_path setup_firefox_binary_env/;
sub _firefox_windows_path {
# TODO: make this slightly less dumb
my @program_files = (
$ENV{PROGRAMFILES} // 'C:\Program Files',
$ENV{'PROGRAMFILES(X86)'} // 'C:\Program Files (x86)',
);
foreach (@program_files) {
my $binary_path = $_ . '\Mozilla Firefox\firefox.exe';
return $binary_path if -x $binary_path;
}
# Fall back to a completely naive strategy
warn
q/We couldn't find a viable firefox.EXE; you may want to specify it via the binary attribute./;
return which('firefox');
}
sub _firefox_darwin_path {
my $default_firefox =
'/Applications/Firefox.app/Contents/MacOS/firefox-bin';
if ( -e $default_firefox && -x $default_firefox ) {
return $default_firefox;
}
else {
return which('firefox-bin');
}
}
sub _firefox_unix_path {
# TODO: maybe which('firefox3'), which('firefox2') ?
return which('firefox') || '/usr/bin/firefox';
}
sub firefox_path {
my $path;
if ( $^O eq 'MSWin32' ) {
$path = _firefox_windows_path();
}
elsif ( $^O eq 'darwin' ) {
$path = _firefox_darwin_path();
}
else {
$path = _firefox_unix_path;
}
if ( not -x $path ) {
die $path . ' is not an executable file.';
}
return $path;
}
# We want the profile to persist to the end of the session, not just
# the end of this function.
my $profile;
sub setup_firefox_binary_env {
my ( $port, $marionette_port, $caller_profile ) = @_;
$profile = $caller_profile || Selenium::Firefox::Profile->new;
$profile->add_webdriver( $port, $marionette_port );
$profile->add_marionette($marionette_port);
# For non-geckodriver/marionette startup, we instruct Firefox to
# use the profile by specifying the appropriate environment
# variables for it to hook onto.
if ( !$marionette_port ) {
$ENV{'XRE_PROFILE_PATH'} = $profile->_layout_on_disk;
$ENV{'MOZ_NO_REMOTE'} = '1'; # able to launch multiple instances
$ENV{'MOZ_CRASHREPORTER_DISABLE'} = '1'; # disable breakpad
$ENV{'NO_EM_RESTART'} =
'1'; # prevent the binary from detaching from the console.log
}
else {
# In case the user created an old Firefox, which would've set
# those ENV variables, and then wanted to create a new Firefox
# afterwards, the env variables would still be around, and the
# new Firefox would respect the XRE_PROFILE_PATH and try to
# load it in the new geckodriver Firefox, which would cause an
# extension compatibility check
my @env_vars = qw/
XRE_PROFILE_PATH
MOZ_NO_REMOTE
MOZ_CRASHREPORTER_DISABLE
NO_EM_RESTART
/;
foreach (@env_vars) {
delete $ENV{$_};
}
}
return $profile;
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
Selenium::Firefox::Binary - Subroutines for locating and properly initializing the Firefox Binary
=head1 VERSION
version 1.33
=head1 SUBROUTINES
=head2 firefox_path
Return the path to the firefox binary on your system.
=head2 setup_firefox_binary_env
Sets various environment variables to make firefox work correctly with webDriver.
=head1 SEE ALSO
Please see those modules/websites for more information related to this module.
=over 4
=item *
L<Selenium::Remote::Driver|Selenium::Remote::Driver>
=back
=head1 BUGS
Please report any bugs or feature requests on the bugtracker website
L<https://github.com/teodesian/Selenium-Remote-Driver/issues>
When submitting a bug or request, please include a test-file or a
patch to an existing test-file that illustrates the bug or desired
feature.
=head1 AUTHORS
Current Maintainers:
=over 4
=item *
Daniel Gempesaw <gempesaw@gmail.com>
=item *
Emmanuel Peroumalnaïk <peroumalnaik.emmanuel@gmail.com>
=back
Previous maintainers:
=over 4
=item *
Luke Closs <cpan@5thplane.com>
=item *
Mark Stosberg <mark@stosberg.com>
=back
Original authors:
=over 4
=item *
Aditya Ivaturi <ivaturi@gmail.com>
=back
=head1 COPYRIGHT AND LICENSE
Copyright (c) 2010-2011 Aditya Ivaturi, Gordon Child
Copyright (c) 2014-2017 Daniel Gempesaw
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
=cut

View File

@@ -0,0 +1,470 @@
package Selenium::Firefox::Profile;
$Selenium::Firefox::Profile::VERSION = '1.33';
# ABSTRACT: Use custom profiles with Selenium::Remote::Driver
# TODO: convert this to Moo!
use strict;
use warnings;
use Archive::Zip qw( :ERROR_CODES );
use Carp qw(croak);
use Cwd qw(abs_path);
use File::Copy qw(copy);
use File::Temp;
use File::Basename qw(dirname);
use IO::Uncompress::Unzip 2.030 qw($UnzipError);
use JSON qw(decode_json);
use MIME::Base64;
use Scalar::Util qw(blessed looks_like_number);
use XML::Simple;
sub new {
my $class = shift;
my %args = @_;
my $profile_dir;
if ( $args{profile_dir} && -d $args{profile_dir} ) {
$profile_dir = $args{profile_dir};
}
else {
$profile_dir = File::Temp->newdir();
}
# TODO: accept user prefs, boolean prefs, and extensions in
# constructor
my $self = {
profile_dir => $profile_dir,
user_prefs => {},
extensions => []
};
bless $self, $class or die "Can't bless $class: $!";
return $self;
}
sub set_preference {
my ( $self, %prefs ) = @_;
foreach ( keys %prefs ) {
my $value = $prefs{$_};
my $clean_value = '';
if ( JSON::is_bool($value) ) {
$self->set_boolean_preference( $_, $value );
next;
}
elsif ( $value =~ /^(['"]).*\1$/ or looks_like_number($value) ) {
# plain integers: 0, 1, 32768, or integers wrapped in strings:
# "0", "1", "20140204". in either case, there's nothing for us
# to do.
$clean_value = $value;
}
else {
# otherwise it's hopefully a string that we'll need to
# quote on our own
$clean_value = '"' . $value . '"';
}
$self->{user_prefs}->{$_} = $clean_value;
}
}
sub set_boolean_preference {
my ( $self, %prefs ) = @_;
foreach ( keys %prefs ) {
my $value = $prefs{$_};
$self->{user_prefs}->{$_} = $value ? 'true' : 'false';
}
}
sub get_preference {
my ( $self, $pref ) = @_;
return $self->{user_prefs}->{$pref};
}
sub add_extension {
my ( $self, $xpi ) = @_;
croak 'File not found: ' . $xpi unless -e $xpi;
my $xpi_abs_path = abs_path($xpi);
croak '$xpi_abs_path: extensions must be in .xpi format'
unless $xpi_abs_path =~ /\.xpi$/;
push( @{ $self->{extensions} }, $xpi_abs_path );
}
sub add_webdriver {
my ( $self, $port, $is_marionette ) = @_;
my $prefs = $self->_load_prefs;
my $current_user_prefs = $self->{user_prefs};
$self->set_preference(
%{ $prefs->{mutable} },
# having the user prefs here allows them to overwrite the
# mutable loaded prefs
%{$current_user_prefs},
# but the frozen ones cannot be overwritten
%{ $prefs->{frozen} },
'webdriver_firefox_port' => $port
);
if ( !$is_marionette ) {
$self->_add_webdriver_xpi;
}
return $self;
}
sub _load_prefs {
# The appropriate webdriver preferences are stored in an adjacent
# JSON file; it's useful things like disabling default browser
# checks and setting an empty single page as the start up tab
# configuration. Unfortunately, these change with each version of
# webdriver.
my $this_dir = dirname( abs_path(__FILE__) );
my $default_prefs_filename = $this_dir . '/webdriver_prefs.json';
my $json;
{
local $/;
open( my $fh, '<', $default_prefs_filename );
$json = <$fh>;
close($fh);
}
my $prefs = decode_json($json);
return $prefs;
}
sub _add_webdriver_xpi {
my ($self) = @_;
my $this_dir = dirname( abs_path(__FILE__) );
my $webdriver_extension = $this_dir . '/webdriver.xpi';
$self->add_extension($webdriver_extension);
}
sub add_marionette {
my ( $self, $port ) = @_;
return if !$port;
$self->set_preference( 'marionette.defaultPrefs.port', $port );
}
sub _encode {
my $self = shift;
# The remote webdriver accepts the Firefox profile as a base64
# encoded zip file
$self->_layout_on_disk();
my $zip = Archive::Zip->new();
$zip->addTree( $self->{profile_dir} );
my $string = "";
open( my $fh, ">", \$string );
binmode($fh);
unless ( $zip->writeToFileHandle($fh) == AZ_OK ) {
die 'write error';
}
return encode_base64( $string, '' );
}
sub _layout_on_disk {
my $self = shift;
$self->_write_preferences();
$self->_install_extensions();
return $self->{profile_dir};
}
sub _write_preferences {
my $self = shift;
my $userjs = $self->{profile_dir} . "/user.js";
open( my $fh, ">>", $userjs )
or die "Cannot open $userjs for writing preferences: $!";
foreach ( keys %{ $self->{user_prefs} } ) {
print $fh 'user_pref("'
. $_ . '", '
. $self->get_preference($_) . ');' . "\n";
}
close($fh);
}
sub _install_extensions {
my $self = shift;
my $extension_dir = $self->{profile_dir} . "/extensions/";
mkdir $extension_dir unless -d $extension_dir;
# TODO: handle extensions that need to be unpacked
foreach my $xpi ( @{ $self->{extensions} } ) {
# For Firefox to recognize the extension, we have to put the
# .xpi in the /extensions/ folder and change the filename to
# its id, which is found in the install.rdf in the root of the
# zip.
my $rdf_string = $self->_extract_install_rdf($xpi);
my $rdf = XMLin($rdf_string);
my $name = $rdf->{Description}->{'em:id'};
my $xpi_dest = $extension_dir . $name . ".xpi";
copy( $xpi, $xpi_dest )
or croak "Error copying $_ to $xpi_dest : $!";
}
}
sub _extract_install_rdf {
my ( $self, $xpi ) = @_;
my $unzipped = IO::Uncompress::Unzip->new($xpi)
or die "Cannot unzip $xpi: $UnzipError";
my $install_rdf = '';
while ( $unzipped->nextStream ) {
my $filename = $unzipped->getHeaderInfo->{Name};
if ( $filename eq 'install.rdf' ) {
my $buffer;
while ( ( my $status = $unzipped->read($buffer) ) > 0 ) {
$install_rdf .= $buffer;
}
return $install_rdf;
}
}
croak
'Invalid Firefox extension: could not find install.rdf in the .XPI at: '
. $xpi;
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
Selenium::Firefox::Profile - Use custom profiles with Selenium::Remote::Driver
=head1 VERSION
version 1.33
=head1 DESCRIPTION
You can use this module to create a custom Firefox Profile for your
Selenium tests. Currently, you can set browser preferences and add
extensions to the profile before passing it in the constructor for a
new L<Selenium::Remote::Driver> or L<Selenium::Firefox>.
=head1 SYNPOSIS
use Selenium::Remote::Driver;
use Selenium::Firefox::Profile;
my $profile = Selenium::Firefox::Profile->new;
$profile->set_preference(
'browser.startup.homepage' => 'http://www.google.com',
'browser.cache.disk.capacity' => 358400
);
$profile->set_boolean_preference(
'browser.shell.checkDefaultBrowser' => 0
);
$profile->add_extension('t/www/redisplay.xpi');
my $driver = Selenium::Remote::Driver->new(
'firefox_profile' => $profile
);
$driver->get('http://www.google.com');
print $driver->get_title();
=head1 CONSTRUCTOR
=head2 new (%args)
profile_dir - <string> directory to look for the firefox profile. Defaults to a Tempdir.
=head1 METHODS
=head2 set_preference
Set string and integer preferences on the profile object. You can set
multiple preferences at once. If you need to set a boolean preference,
either use JSON::true/JSON::false, or see C<set_boolean_preference()>.
$profile->set_preference("quoted.integer.pref" => '"20140314220517"');
# user_pref("quoted.integer.pref", "20140314220517");
$profile->set_preference("plain.integer.pref" => 9005);
# user_pref("plain.integer.pref", 9005);
$profile->set_preference("string.pref" => "sample string value");
# user_pref("string.pref", "sample string value");
=head2 set_boolean_preference
Set preferences that require boolean values of 'true' or 'false'. You
can set multiple preferences at once. For string or integer
preferences, use C<set_preference()>.
$profile->set_boolean_preference("false.pref" => 0);
# user_pref("false.pref", false);
$profile->set_boolean_preference("true.pref" => 1);
# user_pref("true.pref", true);
=head2 get_preference
Retrieve the computed value of a preference. Strings will be double
quoted and boolean values will be single quoted as "true" or "false"
accordingly.
$profile->set_boolean_preference("true.pref" => 1);
print $profile->get_preference("true.pref") # true
$profile->set_preference("string.pref" => "an extra set of quotes");
print $profile->get_preference("string.pref") # "an extra set of quotes"
=head2 add_extension
Add an existing C<.xpi> to the profile by providing its path. This
only works with packaged C<.xpi> files, not plain/un-packed extension
directories.
$profile->add_extension('t/www/redisplay.xpi');
=head2 add_webdriver
Primarily for internal use, we set the appropriate firefox preferences
for a new geckodriver session.
=head2 add_webdriver_xpi
Primarily for internal use. This adds the fxgoogle .xpi that is used
for webdriver communication in FF47 and older. For FF48 and newer, the
old method using an extension to orchestrate the webdriver
communication with the Firefox browser has been obsoleted by the
introduction of C<geckodriver>.
=head2 add_marionette
Primarily for internal use, configure Marionette to the
current Firefox profile.
=head1 SEE ALSO
Please see those modules/websites for more information related to this module.
=over 4
=item *
L<Selenium::Remote::Driver|Selenium::Remote::Driver>
=item *
L<http://kb.mozillazine.org/About:config_entries|http://kb.mozillazine.org/About:config_entries>
=item *
L<https://developer.mozilla.org/en-US/docs/Mozilla/Preferences/A_brief_guide_to_Mozilla_preferences|https://developer.mozilla.org/en-US/docs/Mozilla/Preferences/A_brief_guide_to_Mozilla_preferences>
=back
=head1 BUGS
Please report any bugs or feature requests on the bugtracker website
L<https://github.com/teodesian/Selenium-Remote-Driver/issues>
When submitting a bug or request, please include a test-file or a
patch to an existing test-file that illustrates the bug or desired
feature.
=head1 AUTHORS
Current Maintainers:
=over 4
=item *
Daniel Gempesaw <gempesaw@gmail.com>
=item *
Emmanuel Peroumalnaïk <peroumalnaik.emmanuel@gmail.com>
=back
Previous maintainers:
=over 4
=item *
Luke Closs <cpan@5thplane.com>
=item *
Mark Stosberg <mark@stosberg.com>
=back
Original authors:
=over 4
=item *
Aditya Ivaturi <ivaturi@gmail.com>
=back
=head1 COPYRIGHT AND LICENSE
Copyright (c) 2010-2011 Aditya Ivaturi, Gordon Child
Copyright (c) 2014-2017 Daniel Gempesaw
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
=cut

View File

@@ -0,0 +1,69 @@
{
"frozen": {
"app.update.auto": false,
"app.update.enabled": false,
"browser.displayedE10SNotice": 4,
"browser.download.manager.showWhenStarting": false,
"browser.EULA.override": true,
"browser.EULA.3.accepted": true,
"browser.link.open_external": 2,
"browser.link.open_newwindow": 2,
"browser.offline": false,
"browser.reader.detectedFirstArticle": true,
"browser.safebrowsing.enabled": false,
"browser.safebrowsing.malware.enabled": false,
"browser.search.update": false,
"browser.selfsupport.url" : "",
"browser.sessionstore.resume_from_crash": false,
"browser.shell.checkDefaultBrowser": false,
"browser.tabs.warnOnClose": false,
"browser.tabs.warnOnOpen": false,
"datareporting.healthreport.service.enabled": false,
"datareporting.healthreport.uploadEnabled": false,
"datareporting.healthreport.service.firstRun": false,
"datareporting.healthreport.logging.consoleEnabled": false,
"datareporting.policy.dataSubmissionEnabled": false,
"datareporting.policy.dataSubmissionPolicyAccepted": false,
"devtools.errorconsole.enabled": true,
"dom.disable_open_during_load": false,
"extensions.autoDisableScopes": 10,
"extensions.blocklist.enabled": false,
"extensions.checkCompatibility.nightly": false,
"extensions.logging.enabled": true,
"extensions.update.enabled": false,
"extensions.update.notifyUser": false,
"javascript.enabled": true,
"network.manage-offline-status": false,
"network.http.phishy-userpass-length": 255,
"offline-apps.allow_by_default": true,
"prompts.tab_modal.enabled": false,
"security.csp.enable": false,
"security.fileuri.origin_policy": 3,
"security.fileuri.strict_origin_policy": false,
"signon.rememberSignons": false,
"toolkit.networkmanager.disable": true,
"toolkit.telemetry.prompted": 2,
"toolkit.telemetry.enabled": false,
"toolkit.telemetry.rejected": true,
"xpinstall.signatures.required": false,
"xpinstall.whitelist.required": false
},
"mutable": {
"browser.dom.window.dump.enabled": true,
"browser.laterrun.enabled": false,
"browser.newtab.url": "about:blank",
"browser.newtabpage.enabled": false,
"browser.startup.page": 0,
"browser.startup.homepage": "about:blank",
"browser.startup.homepage_override.mstone": "ignore",
"browser.usedOnWindows10.introURL": "about:blank",
"dom.max_chrome_script_run_time": 30,
"dom.max_script_run_time": 30,
"dom.report_all_js_exceptions": true,
"javascript.options.showInConsole": true,
"startup.homepage_welcome_url": "about:blank",
"startup.homepage_welcome_url.additional": "about:blank",
"webdriver_accept_untrusted_certs": true,
"webdriver_assume_untrusted_issuer": true
}
}

View File

@@ -0,0 +1,142 @@
package Selenium::InternetExplorer;
$Selenium::InternetExplorer::VERSION = '1.33';
use strict;
use warnings;
# ABSTRACT: A convenience package for creating a IE instance
use Moo;
extends 'Selenium::Remote::Driver';
has '+browser_name' => (
is => 'ro',
default => sub { 'internet_explorer' }
);
has '+platform' => (
is => 'ro',
default => sub { 'WINDOWS' }
);
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
Selenium::InternetExplorer - A convenience package for creating a IE instance
=head1 VERSION
version 1.33
=head1 SYNOPSIS
my $driver = Selenium::InternetExplorer->new;
# when you're done
$driver->shutdown_binary;
=head1 METHODS
=head2 shutdown_binary
Call this method instead of L<Selenium::Remote::Driver/quit> to ensure
that the binary executable is also closed, instead of simply closing
the browser itself. If the browser is still around, it will call
C<quit> for you. After that, it will try to shutdown the browser
binary by making a GET to /shutdown and on Windows, it will attempt to
do a C<taskkill> on the binary CMD window.
$self->shutdown_binary;
It doesn't take any arguments, and it doesn't return anything.
We do our best to call this when the C<$driver> option goes out of
scope, but if that happens during global destruction, there's nothing
we can do.
=head1 SEE ALSO
Please see those modules/websites for more information related to this module.
=over 4
=item *
L<Selenium::Remote::Driver|Selenium::Remote::Driver>
=back
=head1 BUGS
Please report any bugs or feature requests on the bugtracker website
L<https://github.com/teodesian/Selenium-Remote-Driver/issues>
When submitting a bug or request, please include a test-file or a
patch to an existing test-file that illustrates the bug or desired
feature.
=head1 AUTHORS
Current Maintainers:
=over 4
=item *
Daniel Gempesaw <gempesaw@gmail.com>
=item *
Emmanuel Peroumalnaïk <peroumalnaik.emmanuel@gmail.com>
=back
Previous maintainers:
=over 4
=item *
Luke Closs <cpan@5thplane.com>
=item *
Mark Stosberg <mark@stosberg.com>
=back
Original authors:
=over 4
=item *
Aditya Ivaturi <ivaturi@gmail.com>
=back
=head1 COPYRIGHT AND LICENSE
Copyright (c) 2010-2011 Aditya Ivaturi, Gordon Child
Copyright (c) 2014-2017 Daniel Gempesaw
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
=cut

View File

@@ -0,0 +1,233 @@
package Selenium::PhantomJS;
$Selenium::PhantomJS::VERSION = '1.33';
use strict;
use warnings;
# ABSTRACT: Use GhostDriver without a Selenium server
use Moo;
use Selenium::CanStartBinary::FindBinary qw/coerce_simple_binary/;
extends 'Selenium::Remote::Driver';
has '+browser_name' => (
is => 'ro',
default => sub { 'phantomjs' }
);
has 'binary' => (
is => 'lazy',
coerce => \&coerce_simple_binary,
default => sub { 'phantomjs' },
predicate => 1
);
has 'binary_port' => (
is => 'lazy',
default => sub { 8910 }
);
has '_binary_args' => (
is => 'lazy',
builder => sub {
my ($self) = @_;
return ' --webdriver=127.0.0.1:' . $self->port;
}
);
with 'Selenium::CanStartBinary';
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
Selenium::PhantomJS - Use GhostDriver without a Selenium server
=head1 VERSION
version 1.33
=head1 SYNOPSIS
my $driver = Selenium::PhantomJS->new;
# when you're done
$driver->shutdown_binary;
=head1 DESCRIPTION
This class allows you to use PhantomJS via Ghostdriver without needing
the JRE or a selenium server running. When you refrain from passing
the C<remote_server_addr> and C<port> arguments, we will search for
the phantomjs executable binary in your $PATH. We'll try to start the
binary connect to it, shutting it down at the end of the test.
If the binary is not found, we'll fall back to the default
L<Selenium::Remote::Driver> behavior of assuming defaults of
127.0.0.1:4444 after waiting a few seconds.
If you specify a remote server address, or a port, we'll assume you
know what you're doing and take no additional behavior.
If you're curious whether your Selenium::PhantomJS instance is using a
separate PhantomJS binary, or through the selenium server, you can check
the C<binary_mode> attr after instantiation.
my $driver = Selenium::PhantomJS->new;
print $driver->binary_mode;
N.B. - if you're using Windows and you installed C<phantomjs> via
C<npm install -g phantomjs>, there is a very high probability that we
will _not_ close down your phantomjs binary correctly after your
test. You will be able to tell if we leave around empty command
windows that you didn't start yourself. The easiest way to fix this is
to download PhantomJS manually from their
L<website|http://phantomjs.org/download.html> and put it in your
C<%PATH%>. If this is a blocking issue for you, let us know in
L<Github|https://github.com/gempesaw/Selenium-Remote-Driver>; thanks!
=head1 ATTRIBUTES
=head2 binary
Optional: specify the path to your binary. If you don't specify
anything, we'll try to find it on our own via L<File::Which/which>.
=head2 binary_port
Optional: specify the port that we should bind to. If you don't
specify anything, we'll default to the driver's default port. Since
there's no a priori guarantee that this will be an open port, this is
_not_ necessarily the port that we end up using - if the port here is
already bound, we'll search above it until we find an open one.
See L<Selenium::CanStartBinary/port> for more details, and
L<Selenium::Remote::Driver/port> after instantiation to see what the
actual port turned out to be.
=head2 custom_args
Optional: specify any additional command line arguments you'd like
invoked during the binary startup. See
L<Selenium::CanStartBinary/custom_args> for more information.
=head2 startup_timeout
Optional: specify how long to wait for the binary to start itself and
listen on its port. The default duration is arbitrarily 10 seconds. It
accepts an integer number of seconds to wait: the following will wait
up to 20 seconds:
Selenium::PhantomJS->new( startup_timeout => 20 );
See L<Selenium::CanStartBinary/startup_timeout> for more information.
=head1 METHODS
=head2 shutdown_binary
Call this method instead of L<Selenium::Remote::Driver/quit> to ensure
that the binary executable is also closed, instead of simply closing
the browser itself. If the browser is still around, it will call
C<quit> for you. After that, it will try to shutdown the browser
binary by making a GET to /shutdown and on Windows, it will attempt to
do a C<taskkill> on the binary CMD window.
$self->shutdown_binary;
It doesn't take any arguments, and it doesn't return anything.
We do our best to call this when the C<$driver> option goes out of
scope, but if that happens during global destruction, there's nothing
we can do.
=for Pod::Coverage has_binary
=head1 SEE ALSO
Please see those modules/websites for more information related to this module.
=over 4
=item *
L<Selenium::Remote::Driver|Selenium::Remote::Driver>
=back
=head1 BUGS
Please report any bugs or feature requests on the bugtracker website
L<https://github.com/teodesian/Selenium-Remote-Driver/issues>
When submitting a bug or request, please include a test-file or a
patch to an existing test-file that illustrates the bug or desired
feature.
=head1 AUTHORS
Current Maintainers:
=over 4
=item *
Daniel Gempesaw <gempesaw@gmail.com>
=item *
Emmanuel Peroumalnaïk <peroumalnaik.emmanuel@gmail.com>
=back
Previous maintainers:
=over 4
=item *
Luke Closs <cpan@5thplane.com>
=item *
Mark Stosberg <mark@stosberg.com>
=back
Original authors:
=over 4
=item *
Aditya Ivaturi <ivaturi@gmail.com>
=back
=head1 COPYRIGHT AND LICENSE
Copyright (c) 2010-2011 Aditya Ivaturi, Gordon Child
Copyright (c) 2014-2017 Daniel Gempesaw
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
=cut

View File

@@ -0,0 +1,614 @@
package Selenium::Remote::Commands;
$Selenium::Remote::Commands::VERSION = '1.33';
use strict;
use warnings;
use Carp qw{croak};
# ABSTRACT: Implement commands for Selenium::Remote::Driver for use with webdriver 2
use Moo;
has '_cmds' => (
is => 'lazy',
reader => 'get_cmds',
builder => sub {
return {
'status' => {
'method' => 'GET',
'url' => 'status',
'no_content_success' => 0
},
'newSession' => {
'method' => 'POST',
'url' => 'session',
'no_content_success' => 0
},
'getSessions' => {
'method' => 'GET',
'url' => 'sessions',
'no_content_success' => 0
},
'getCapabilities' => {
'method' => 'GET',
'url' => 'session/:sessionId',
'no_content_success' => 0
},
'setTimeout' => {
'method' => 'POST',
'url' => 'session/:sessionId/timeouts',
'no_content_success' => 1
},
'setAsyncScriptTimeout' => {
'method' => 'POST',
'url' => 'session/:sessionId/timeouts/async_script',
'no_content_success' => 1
},
'setImplicitWaitTimeout' => {
'method' => 'POST',
'url' => 'session/:sessionId/timeouts/implicit_wait',
'no_content_success' => 1
},
'quit' => {
'method' => 'DELETE',
'url' => 'session/:sessionId',
'no_content_success' => 1
},
'getCurrentWindowHandle' => {
'method' => 'GET',
'url' => 'session/:sessionId/window_handle',
'no_content_success' => 0
},
'getWindowHandles' => {
'method' => 'GET',
'url' => 'session/:sessionId/window_handles',
'no_content_success' => 0
},
'getWindowSize' => {
'method' => 'GET',
'url' => 'session/:sessionId/window/:windowHandle/size',
'no_content_success' => 0
},
'getWindowPosition' => {
'method' => 'GET',
'url' => 'session/:sessionId/window/:windowHandle/position',
'no_content_success' => 0
},
'maximizeWindow' => {
'method' => 'POST',
'url' => 'session/:sessionId/window/:windowHandle/maximize',
'no_content_success' => 1
},
'setWindowSize' => {
'method' => 'POST',
'url' => 'session/:sessionId/window/:windowHandle/size',
'no_content_success' => 1
},
'setWindowPosition' => {
'method' => 'POST',
'url' => 'session/:sessionId/window/:windowHandle/position',
'no_content_success' => 1
},
'getCurrentUrl' => {
'method' => 'GET',
'url' => 'session/:sessionId/url',
'no_content_success' => 0
},
'get' => {
'method' => 'POST',
'url' => 'session/:sessionId/url',
'no_content_success' => 1
},
'goForward' => {
'method' => 'POST',
'url' => 'session/:sessionId/forward',
'no_content_success' => 1
},
'goBack' => {
'method' => 'POST',
'url' => 'session/:sessionId/back',
'no_content_success' => 1
},
'refresh' => {
'method' => 'POST',
'url' => 'session/:sessionId/refresh',
'no_content_success' => 1
},
'executeScript' => {
'method' => 'POST',
'url' => 'session/:sessionId/execute',
'no_content_success' => 0
},
'executeAsyncScript' => {
'method' => 'POST',
'url' => 'session/:sessionId/execute_async',
'no_content_success' => 0
},
'screenshot' => {
'method' => 'GET',
'url' => 'session/:sessionId/screenshot',
'no_content_success' => 0
},
'availableEngines' => {
'method' => 'GET',
'url' => 'session/:sessionId/ime/available_engines',
'no_content_success' => 0
},
'switchToFrame' => {
'method' => 'POST',
'url' => 'session/:sessionId/frame',
'no_content_success' => 1
},
'switchToWindow' => {
'method' => 'POST',
'url' => 'session/:sessionId/window',
'no_content_success' => 1
},
'getAllCookies' => {
'method' => 'GET',
'url' => 'session/:sessionId/cookie',
'no_content_success' => 0
},
'addCookie' => {
'method' => 'POST',
'url' => 'session/:sessionId/cookie',
'no_content_success' => 1
},
'deleteAllCookies' => {
'method' => 'DELETE',
'url' => 'session/:sessionId/cookie',
'no_content_success' => 1
},
'deleteCookieNamed' => {
'method' => 'DELETE',
'url' => 'session/:sessionId/cookie/:name',
'no_content_success' => 1
},
'getPageSource' => {
'method' => 'GET',
'url' => 'session/:sessionId/source',
'no_content_success' => 0
},
'getTitle' => {
'method' => 'GET',
'url' => 'session/:sessionId/title',
'no_content_success' => 0
},
'findElement' => {
'method' => 'POST',
'url' => 'session/:sessionId/element',
'no_content_success' => 0
},
'findElements' => {
'method' => 'POST',
'url' => 'session/:sessionId/elements',
'no_content_success' => 0
},
'getActiveElement' => {
'method' => 'POST',
'url' => 'session/:sessionId/element/active',
'no_content_success' => 0
},
'describeElement' => {
'method' => 'GET',
'url' => 'session/:sessionId/element/:id',
'no_content_success' => 0
},
'findChildElement' => {
'method' => 'POST',
'url' => 'session/:sessionId/element/:id/element',
'no_content_success' => 0
},
'findChildElements' => {
'method' => 'POST',
'url' => 'session/:sessionId/element/:id/elements',
'no_content_success' => 0
},
'clickElement' => {
'method' => 'POST',
'url' => 'session/:sessionId/element/:id/click',
'no_content_success' => 1
},
'submitElement' => {
'method' => 'POST',
'url' => 'session/:sessionId/element/:id/submit',
'no_content_success' => 1
},
'sendKeysToElement' => {
'method' => 'POST',
'url' => 'session/:sessionId/element/:id/value',
'no_content_success' => 1
},
'sendKeysToActiveElement' => {
'method' => 'POST',
'url' => 'session/:sessionId/keys',
'no_content_success' => 1
},
'sendModifier' => {
'method' => 'POST',
'url' => 'session/:sessionId/modifier',
'no_content_success' => 1
},
'isElementSelected' => {
'method' => 'GET',
'url' => 'session/:sessionId/element/:id/selected',
'no_content_success' => 0
},
'setElementSelected' => {
'method' => 'POST',
'url' => 'session/:sessionId/element/:id/selected',
'no_content_success' => 0
},
'toggleElement' => {
'method' => 'POST',
'url' => 'session/:sessionId/element/:id/toggle',
'no_content_success' => 0
},
'isElementEnabled' => {
'method' => 'GET',
'url' => 'session/:sessionId/element/:id/enabled',
'no_content_success' => 0
},
'getElementLocation' => {
'method' => 'GET',
'url' => 'session/:sessionId/element/:id/location',
'no_content_success' => 0
},
'getElementLocationInView' => {
'method' => 'GET',
'url' => 'session/:sessionId/element/:id/location_in_view',
'no_content_success' => 0
},
'getElementTagName' => {
'method' => 'GET',
'url' => 'session/:sessionId/element/:id/name',
'no_content_success' => 0
},
'clearElement' => {
'method' => 'POST',
'url' => 'session/:sessionId/element/:id/clear',
'no_content_success' => 1
},
'getElementAttribute' => {
'method' => 'GET',
'url' => 'session/:sessionId/element/:id/attribute/:name',
'no_content_success' => 0
},
'elementEquals' => {
'method' => 'GET',
'url' => 'session/:sessionId/element/:id/equals/:other',
'no_content_success' => 0
},
'isElementDisplayed' => {
'method' => 'GET',
'url' => 'session/:sessionId/element/:id/displayed',
'no_content_success' => 0
},
'close' => {
'method' => 'DELETE',
'url' => 'session/:sessionId/window',
'no_content_success' => 1
},
'getElementSize' => {
'method' => 'GET',
'url' => 'session/:sessionId/element/:id/size',
'no_content_success' => 0
},
'getElementText' => {
'method' => 'GET',
'url' => 'session/:sessionId/element/:id/text',
'no_content_success' => 0
},
'getElementValueOfCssProperty' => {
'method' => 'GET',
'url' => 'session/:sessionId/element/:id/css/:propertyName',
'no_content_success' => 0
},
'mouseMoveToLocation' => {
'method' => 'POST',
'url' => 'session/:sessionId/moveto',
'no_content_success' => 1
},
'getAlertText' => {
'method' => 'GET',
'url' => 'session/:sessionId/alert_text',
'no_content_success' => 0
},
'sendKeysToPrompt' => {
'method' => 'POST',
'url' => 'session/:sessionId/alert_text',
'no_content_success' => 1
},
'acceptAlert' => {
'method' => 'POST',
'url' => 'session/:sessionId/accept_alert',
'no_content_success' => 1
},
'dismissAlert' => {
'method' => 'POST',
'url' => 'session/:sessionId/dismiss_alert',
'no_content_success' => 1
},
'click' => {
'method' => 'POST',
'url' => 'session/:sessionId/click',
'no_content_success' => 1
},
'doubleClick' => {
'method' => 'POST',
'url' => 'session/:sessionId/doubleclick',
'no_content_success' => 1
},
'buttonDown' => {
'method' => 'POST',
'url' => 'session/:sessionId/buttondown',
'no_content_success' => 1
},
'buttonUp' => {
'method' => 'POST',
'url' => 'session/:sessionId/buttonup',
'no_content_success' => 1
},
'uploadFile' => {
'method' => 'POST',
'url' => 'session/:sessionId/file',
'no_content_success' => 0
},
'getLocalStorageItem' => {
'method' => 'GET',
'url' => '/session/:sessionId/local_storage/key/:key',
'no_content_success' => 0
},
'deleteLocalStorageItem' => {
'method' => 'DELETE',
'url' => '/session/:sessionId/local_storage/key/:key',
'no_content_success' => 1
},
'cacheStatus' => {
'method' => 'GET',
'url' => 'session/:sessionId/application_cache/status',
'no_content_success' => 0
},
'setGeolocation' => {
'method' => 'POST',
'url' => 'session/:sessionId/location',
'no_content_success' => 1
},
'getGeolocation' => {
'method' => 'GET',
'url' => 'session/:sessionId/location',
'no_content_success' => 0
},
'getLog' => {
'method' => 'POST',
'url' => 'session/:sessionId/log',
'no_content_success' => 0
},
'getLogTypes' => {
'method' => 'GET',
'url' => 'session/:sessionId/log/types',
'no_content_success' => 0
},
'setOrientation' => {
'method' => 'POST',
'url' => 'session/:sessionId/orientation',
'no_content_success' => 1
},
'getOrientation' => {
'method' => 'GET',
'url' => 'session/:sessionId/orientation',
'no_content_success' => 0
},
# firefox extension
'setContext' => {
'method' => 'POST',
'url' => 'session/:sessionId/moz/context',
'no_content_success' => 1
},
'getContext' => {
'method' => 'GET',
'url' => 'session/:sessionId/moz/context',
'no_content_success' => 0
},
# geckodriver workarounds
'executeScriptGecko' => {
'method' => 'POST',
'url' => 'session/:sessionId/execute/sync',
'no_content_success' => 0
},
'executeAsyncScriptGecko' => {
'method' => 'POST',
'url' => 'session/:sessionId/execute/async',
'no_content_success' => 0
},
# /session/:sessionId/local_storage
# /session/:sessionId/local_storage/key/:key
# /session/:sessionId/local_storage/size
# /session/:sessionId/session_storage
# /session/:sessionId/session_storage/key/:key
# /session/:sessionId/session_storage/size
};
}
);
# helper methods to manipulate the _cmds hash
sub get_url {
my ( $self, $command ) = @_;
return $self->get_cmds->{$command}->{url};
}
sub get_method {
my ( $self, $command ) = @_;
return $self->get_cmds->{$command}->{method};
}
sub get_no_content_success {
my ( $self, $command ) = @_;
return $self->get_cmds->{$command}->{no_content_success};
}
# This method will replace the template & return
sub get_params {
my ( $self, $args ) = @_;
if ( !( defined $args->{'session_id'} ) ) {
return;
}
my $data = {};
my $command = $args->{'command'};
#Allow fall-back in the event the command passed doesn't exist
return unless $self->get_cmds()->{$command};
my $url = $self->get_url($command);
# Do the var substitutions.
$url =~ s/:sessionId/$args->{'session_id'}/;
$url =~ s/:id/$args->{'id'}/;
$url =~ s/:name/$args->{'name'}/;
$url =~ s/:propertyName/$args->{'property_name'}/;
$url =~ s/:other/$args->{'other'}/;
$url =~ s/:windowHandle/$args->{'window_handle'}/;
$data->{'method'} = $self->get_method($command);
$data->{'no_content_success'} = $self->get_no_content_success($command);
$data->{'url'} = $url;
return $data;
}
sub parse_response {
my ( $self, $res, $resp ) = @_;
if ( ref($resp) eq 'HASH' ) {
if ( $resp->{cmd_status} && $resp->{cmd_status} eq 'OK' ) {
return $resp->{cmd_return};
}
my $msg = "Error while executing command";
$msg .= ": $resp->{cmd_error}" if $resp->{cmd_error};
if ( $resp->{cmd_return} ) {
if ( ref( $resp->{cmd_return} ) eq 'HASH' ) {
$msg .= ": $res->{command}"
if $res->{command};
$msg .= ": $resp->{cmd_return}->{error}->{msg}"
if $resp->{cmd_return}->{error}->{msg};
$msg .= ": $resp->{cmd_return}->{message}"
if $resp->{cmd_return}->{message};
}
else {
$msg .= ": $resp->{cmd_return}";
}
}
croak $msg;
}
return $resp;
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
Selenium::Remote::Commands - Implement commands for Selenium::Remote::Driver for use with webdriver 2
=head1 VERSION
version 1.33
=head1 DESCRIPTION
Defines all the HTTP endpoints available to execute on a selenium v2 server.
If you have either a customized Selenium Server, or want new features
you should update the _cmds hash.
=for Pod::Coverage *EVERYTHING*
=head1 SEE ALSO
Please see those modules/websites for more information related to this module.
=over 4
=item *
L<Selenium::Remote::Driver|Selenium::Remote::Driver>
=back
=head1 BUGS
Please report any bugs or feature requests on the bugtracker website
L<https://github.com/teodesian/Selenium-Remote-Driver/issues>
When submitting a bug or request, please include a test-file or a
patch to an existing test-file that illustrates the bug or desired
feature.
=head1 AUTHORS
Current Maintainers:
=over 4
=item *
Daniel Gempesaw <gempesaw@gmail.com>
=item *
Emmanuel Peroumalnaïk <peroumalnaik.emmanuel@gmail.com>
=back
Previous maintainers:
=over 4
=item *
Luke Closs <cpan@5thplane.com>
=item *
Mark Stosberg <mark@stosberg.com>
=back
Original authors:
=over 4
=item *
Aditya Ivaturi <ivaturi@gmail.com>
=back
=head1 COPYRIGHT AND LICENSE
Copyright (c) 2010-2011 Aditya Ivaturi, Gordon Child
Copyright (c) 2014-2017 Daniel Gempesaw
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
=cut

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,124 @@
package Selenium::Remote::Driver::CanSetWebdriverContext;
$Selenium::Remote::Driver::CanSetWebdriverContext::VERSION = '1.33';
# ABSTRACT: Customize the webdriver context prefix for various drivers
use strict;
use warnings;
use Moo::Role;
has 'wd_context_prefix' => (
is => 'lazy',
default => sub { '/wd/hub' }
);
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
Selenium::Remote::Driver::CanSetWebdriverContext - Customize the webdriver context prefix for various drivers
=head1 VERSION
version 1.33
=head1 DESCRIPTION
Some drivers don't use the typical C</wd/hub> context prefix for the
webdriver HTTP communication. For example, the newer versions of the
Firefox driver extension use the context C</hub> instead. This role
just has the one attribute with a default webdriver context prefix,
and is consumed in L<Selenium::Remote::Driver> and
L<Selenium::Remote::RemoteConnection>.
If you're new to webdriver, you probably want to head over to
L<Selenium::Remote::Driver>'s docs; this package is more of an
internal-facing concern.
=head1 SEE ALSO
Please see those modules/websites for more information related to this module.
=over 4
=item *
L<Selenium::Remote::Driver|Selenium::Remote::Driver>
=back
=head1 BUGS
Please report any bugs or feature requests on the bugtracker website
L<https://github.com/teodesian/Selenium-Remote-Driver/issues>
When submitting a bug or request, please include a test-file or a
patch to an existing test-file that illustrates the bug or desired
feature.
=head1 AUTHORS
Current Maintainers:
=over 4
=item *
Daniel Gempesaw <gempesaw@gmail.com>
=item *
Emmanuel Peroumalnaïk <peroumalnaik.emmanuel@gmail.com>
=back
Previous maintainers:
=over 4
=item *
Luke Closs <cpan@5thplane.com>
=item *
Mark Stosberg <mark@stosberg.com>
=back
Original authors:
=over 4
=item *
Aditya Ivaturi <ivaturi@gmail.com>
=back
=head1 COPYRIGHT AND LICENSE
Copyright (c) 2010-2011 Aditya Ivaturi, Gordon Child
Copyright (c) 2014-2017 Daniel Gempesaw
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
=cut

View File

@@ -0,0 +1,127 @@
package Selenium::Remote::Driver::Firefox::Profile;
$Selenium::Remote::Driver::Firefox::Profile::VERSION = '1.33';
# ABSTRACT: Use custom profiles with Selenium::Remote::Driver
use strict;
use warnings;
use Selenium::Firefox::Profile;
BEGIN {
push our @ISA, 'Selenium::Firefox::Profile';
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
Selenium::Remote::Driver::Firefox::Profile - Use custom profiles with Selenium::Remote::Driver
=head1 VERSION
version 1.33
=head1 DESCRIPTION
We've renamed this class to the slightly less wordy
L<Selenium::Firefox::Profile>. This is only around as an alias to
hopefully prevent old code from breaking.
=head1 SEE ALSO
Please see those modules/websites for more information related to this module.
=over 4
=item *
L<Selenium::Remote::Driver|Selenium::Remote::Driver>
=item *
L<Selenium::Firefox::Profile|Selenium::Firefox::Profile>
=item *
L<http://kb.mozillazine.org/About:config_entries|http://kb.mozillazine.org/About:config_entries>
=item *
L<https://developer.mozilla.org/en-US/docs/Mozilla/Preferences/A_brief_guide_to_Mozilla_preferences|https://developer.mozilla.org/en-US/docs/Mozilla/Preferences/A_brief_guide_to_Mozilla_preferences>
=back
=head1 BUGS
Please report any bugs or feature requests on the bugtracker website
L<https://github.com/teodesian/Selenium-Remote-Driver/issues>
When submitting a bug or request, please include a test-file or a
patch to an existing test-file that illustrates the bug or desired
feature.
=head1 AUTHORS
Current Maintainers:
=over 4
=item *
Daniel Gempesaw <gempesaw@gmail.com>
=item *
Emmanuel Peroumalnaïk <peroumalnaik.emmanuel@gmail.com>
=back
Previous maintainers:
=over 4
=item *
Luke Closs <cpan@5thplane.com>
=item *
Mark Stosberg <mark@stosberg.com>
=back
Original authors:
=over 4
=item *
Aditya Ivaturi <ivaturi@gmail.com>
=back
=head1 COPYRIGHT AND LICENSE
Copyright (c) 2010-2011 Aditya Ivaturi, Gordon Child
Copyright (c) 2014-2017 Daniel Gempesaw
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
=cut

View File

@@ -0,0 +1,247 @@
package Selenium::Remote::ErrorHandler;
$Selenium::Remote::ErrorHandler::VERSION = '1.33';
use strict;
use warnings;
# ABSTRACT: Error handler for Selenium::Remote::Driver
use Moo;
use Carp qw(croak);
# We're going to handle only codes that are errors.
# http://code.google.com/p/selenium/wiki/JsonWireProtocol
has STATUS_CODE => (
is => 'lazy',
builder => sub {
return {
7 => {
'code' => 'NO_SUCH_ELEMENT',
'msg' =>
'An element could not be located on the page using the given search parameters.',
},
8 => {
'code' => 'NO_SUCH_FRAME',
'msg' =>
'A request to switch to a frame could not be satisfied because the frame could not be found.',
},
9 => {
'code' => 'UNKNOWN_COMMAND',
'msg' =>
'The requested resource could not be found, or a request was received using an HTTP method that is not supported by the mapped resource.',
},
10 => {
'code' => 'STALE_ELEMENT_REFERENCE',
'msg' =>
'An element command failed because the referenced element is no longer attached to the DOM.',
},
11 => {
'code' => 'ELEMENT_NOT_VISIBLE',
'msg' =>
'An element command could not be completed because the element is not visible on the page.',
},
12 => {
'code' => 'INVALID_ELEMENT_STATE',
'msg' =>
'An element command could not be completed because the element is in an invalid state (e.g. attempting to click a disabled element).',
},
13 => {
'code' => 'UNKNOWN_ERROR',
'msg' =>
'An unknown server-side error occurred while processing the command.',
},
15 => {
'code' => 'ELEMENT_IS_NOT_SELECTABLE',
'msg' =>
'An attempt was made to select an element that cannot be selected.',
},
19 => {
'code' => 'XPATH_LOOKUP_ERROR',
'msg' =>
'An error occurred while searching for an element by XPath.',
},
21 => {
'code' => 'Timeout',
'msg' =>
'An operation did not complete before its timeout expired.',
},
23 => {
'code' => 'NO_SUCH_WINDOW',
'msg' =>
'A request to switch to a different window could not be satisfied because the window could not be found.',
},
24 => {
'code' => 'INVALID_COOKIE_DOMAIN',
'msg' =>
'An illegal attempt was made to set a cookie under a different domain than the current page.',
},
25 => {
'code' => 'UNABLE_TO_SET_COOKIE',
'msg' =>
'A request to set a cookie\'s value could not be satisfied.',
},
26 => {
'code' => 'UNEXPECTED_ALERT_OPEN',
'msg' => 'A modal dialog was open, blocking this operation',
},
27 => {
'code' => 'NO_ALERT_OPEN_ERROR',
'msg' =>
'An attempt was made to operate on a modal dialog when one was not open.',
},
28 => {
'code' => 'SCRIPT_TIMEOUT',
'msg' =>
'A script did not complete before its timeout expired.',
},
29 => {
'code' => 'INVALID_ELEMENT_COORDINATES',
'msg' =>
'The coordinates provided to an interactions operation are invalid.',
},
30 => {
'code' => 'IME_NOT_AVAILABLE',
'msg' => 'IME was not available.',
},
31 => {
'code' => 'IME_ENGINE_ACTIVATION_FAILED',
'msg' => 'An IME engine could not be started.',
},
32 => {
'code' => 'INVALID_SELECTOR',
'msg' => 'Argument was an invalid selector (e.g. XPath/CSS).',
},
};
}
);
sub process_error {
my ( $self, $resp ) = @_;
# TODO: Handle screen if it sent back with the response. Either we could
# let the end user handle it or we can save it an image file at a temp
# location & return the path.
# handle stacktrace-only responses by assuming unknown error
my $is_stacktrace = !$resp->{status};
$resp->{status} = 13 unless $resp->{status};
my $ret;
#XXX capitalization is inconsistent among geckodriver versions
$ret->{'stackTrace'} = $resp->{'value'}->{'stacktrace'}
// $resp->{'value'}->{'stackTrace'};
$ret->{'error'} =
$is_stacktrace
? $resp->{value}->{error}
: $self->STATUS_CODE->{ $resp->{'status'} };
$ret->{'message'} = $resp->{'value'}->{'message'};
return $ret;
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
Selenium::Remote::ErrorHandler - Error handler for Selenium::Remote::Driver
=head1 VERSION
version 1.33
=head1 SUBROUTINES
=head2 process_error (Selenium::Remote::Driver $driver, HTTP::Response $response)
Instead of just returning the end user a server returned error code, this returns a more human readable & usable error message.
Used internally in Selenium::Remote::Driver, but overriding this might be useful in some situations.
You could additionally alter the STATUS_CODE parameter of this module to add extra handlers if the situation warrants it.
=head1 SEE ALSO
Please see those modules/websites for more information related to this module.
=over 4
=item *
L<Selenium::Remote::Driver|Selenium::Remote::Driver>
=back
=head1 BUGS
Please report any bugs or feature requests on the bugtracker website
L<https://github.com/teodesian/Selenium-Remote-Driver/issues>
When submitting a bug or request, please include a test-file or a
patch to an existing test-file that illustrates the bug or desired
feature.
=head1 AUTHORS
Current Maintainers:
=over 4
=item *
Daniel Gempesaw <gempesaw@gmail.com>
=item *
Emmanuel Peroumalnaïk <peroumalnaik.emmanuel@gmail.com>
=back
Previous maintainers:
=over 4
=item *
Luke Closs <cpan@5thplane.com>
=item *
Mark Stosberg <mark@stosberg.com>
=back
Original authors:
=over 4
=item *
Aditya Ivaturi <ivaturi@gmail.com>
=back
=head1 COPYRIGHT AND LICENSE
Copyright (c) 2010-2011 Aditya Ivaturi, Gordon Child
Copyright (c) 2014-2017 Daniel Gempesaw
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
=cut

View File

@@ -0,0 +1,133 @@
package Selenium::Remote::Finders;
$Selenium::Remote::Finders::VERSION = '1.33';
use strict;
use warnings;
# ABSTRACT: Handle construction of generic parameter finders
use Try::Tiny;
use Carp qw/carp/;
use Moo::Role;
use namespace::clean;
sub _build_find_by {
my ( $self, $by ) = @_;
return sub {
my ( $driver, $locator ) = @_;
my $strategy = $by;
return try {
return $driver->find_element( $locator, $strategy );
}
catch {
carp $_;
return 0;
};
}
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
Selenium::Remote::Finders - Handle construction of generic parameter finders
=head1 VERSION
version 1.33
=head1 DESCRIPTION
This package just takes care of setting up parameter finders - that
is, the C<find_element_by_.*> versions of the find element
functions. You probably don't need to do anything with this package;
instead, see L<Selenium::Remote::Driver/find_element> documentation
for the specific finder functions.
=head1 SEE ALSO
Please see those modules/websites for more information related to this module.
=over 4
=item *
L<Selenium::Remote::Driver|Selenium::Remote::Driver>
=back
=head1 BUGS
Please report any bugs or feature requests on the bugtracker website
L<https://github.com/teodesian/Selenium-Remote-Driver/issues>
When submitting a bug or request, please include a test-file or a
patch to an existing test-file that illustrates the bug or desired
feature.
=head1 AUTHORS
Current Maintainers:
=over 4
=item *
Daniel Gempesaw <gempesaw@gmail.com>
=item *
Emmanuel Peroumalnaïk <peroumalnaik.emmanuel@gmail.com>
=back
Previous maintainers:
=over 4
=item *
Luke Closs <cpan@5thplane.com>
=item *
Mark Stosberg <mark@stosberg.com>
=back
Original authors:
=over 4
=item *
Aditya Ivaturi <ivaturi@gmail.com>
=back
=head1 COPYRIGHT AND LICENSE
Copyright (c) 2010-2011 Aditya Ivaturi, Gordon Child
Copyright (c) 2014-2017 Daniel Gempesaw
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
=cut

View File

@@ -0,0 +1,143 @@
package Selenium::Remote::Mock::Commands;
$Selenium::Remote::Mock::Commands::VERSION = '1.33';
# ABSTRACT: utility class to mock Selenium::Remote::Commands
use strict;
use warnings;
use Moo;
extends 'Selenium::Remote::Commands';
# override get_params so we do not rewrite the parameters
sub get_params {
my $self = shift;
my $args = shift;
my $data = {};
my $command = delete $args->{command};
$data->{'url'} = $self->get_url($command);
$data->{'method'} = $self->get_method($command);
$data->{'no_content_success'} = $self->get_no_content_success($command);
$data->{'url_params'} = $args;
return $data;
}
sub get_method_name_from_parameters {
my $self = shift;
my $params = shift;
my $method_name = '';
my $cmds = $self->get_cmds();
foreach my $cmd ( keys %{$cmds} ) {
if ( ( $cmds->{$cmd}->{method} eq $params->{method} )
&& ( $cmds->{$cmd}->{url} eq $params->{url} ) )
{
$method_name = $cmd;
last;
}
}
return $method_name;
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
Selenium::Remote::Mock::Commands - utility class to mock Selenium::Remote::Commands
=head1 VERSION
version 1.33
=head1 DESCRIPTION
Utility class to be for testing purposes, with L<Selenium::Remote::Mock::RemoteConnection> only.
=for Pod::Coverage *EVERYTHING*
=head1 SEE ALSO
Please see those modules/websites for more information related to this module.
=over 4
=item *
L<Selenium::Remote::Driver|Selenium::Remote::Driver>
=back
=head1 BUGS
Please report any bugs or feature requests on the bugtracker website
L<https://github.com/teodesian/Selenium-Remote-Driver/issues>
When submitting a bug or request, please include a test-file or a
patch to an existing test-file that illustrates the bug or desired
feature.
=head1 AUTHORS
Current Maintainers:
=over 4
=item *
Daniel Gempesaw <gempesaw@gmail.com>
=item *
Emmanuel Peroumalnaïk <peroumalnaik.emmanuel@gmail.com>
=back
Previous maintainers:
=over 4
=item *
Luke Closs <cpan@5thplane.com>
=item *
Mark Stosberg <mark@stosberg.com>
=back
Original authors:
=over 4
=item *
Aditya Ivaturi <ivaturi@gmail.com>
=back
=head1 COPYRIGHT AND LICENSE
Copyright (c) 2010-2011 Aditya Ivaturi, Gordon Child
Copyright (c) 2014-2017 Daniel Gempesaw
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
=cut

View File

@@ -0,0 +1,417 @@
package Selenium::Remote::Mock::RemoteConnection;
$Selenium::Remote::Mock::RemoteConnection::VERSION = '1.33';
# ABSTRACT: utility class to mock the responses from Selenium server
use strict;
use warnings;
use Moo;
use JSON;
use Carp;
use Try::Tiny;
use HTTP::Response;
use Data::Dumper;
extends 'Selenium::Remote::RemoteConnection';
has 'spec' => (
is => 'ro',
default => sub { {} },
);
has 'mock_cmds' => ( is => 'ro', );
has 'fake_session_id' => (
is => 'lazy',
builder => sub {
my $id = join '',
map +( 0 .. 9, 'a' .. 'z', 'A' .. 'Z' )[ rand( 10 + 26 * 2 ) ],
1 .. 50;
return $id;
},
);
has 'record' => (
is => 'ro',
default => sub { 0 }
);
has 'replay' => ( is => 'ro', );
has 'replay_file' => ( is => 'ro', );
has 'session_store' => (
is => 'rw',
default => sub { {} }
);
has 'session_id' => (
is => 'rw',
default => sub { undef },
);
has 'remote_server_addr' => (
is => 'lazy',
default => sub { 'localhost' }
);
sub BUILD {
my $self = shift;
croak 'Cannot define replay and record attributes at the same time'
if ( ( $self->replay ) && ( $self->record ) );
croak 'replay_file attribute needs to be defined'
if ( ( $self->replay ) && !( $self->replay_file ) );
croak 'replay attribute needs to be defined'
if ( !( $self->replay ) && ( $self->replay_file ) );
$self->port('4444');
if ( $self->replay ) {
$self->load_session_store( $self->replay_file );
}
}
sub check_status {
return;
}
sub load_session_store {
my $self = shift;
my $file = shift;
croak "'$file' is not a valid file" unless ( -f $file );
open( my $fh, '<', $file ) or croak "Opening '$file' failed";
# here we use a fake session id since we have no way of figuring out
# which session is good or not
local $/ = undef;
my $json = JSON->new;
$json->allow_blessed;
my $decoded_json = $json->allow_nonref(1)->utf8(1)->decode(<$fh>);
close($fh);
$self->session_store($decoded_json);
}
sub dump_session_store {
my $self = shift;
my ($file) = @_;
open( my $fh, '>', $file ) or croak "Opening '$file' failed";
my $session_store = $self->session_store;
my $dump = {};
foreach my $path ( keys %{$session_store} ) {
$dump->{$path} = $session_store->{$path};
}
my $json = JSON->new;
$json->allow_blessed;
my $json_session = $json->allow_nonref->utf8->pretty->encode($dump);
print $fh $json_session;
close($fh);
}
sub request {
my $self = shift;
my ( $resource, $params ) = @_;
my $method = $resource->{method};
my $url = $resource->{url};
my $no_content_success = $resource->{no_content_success} // 0;
my $content = '';
my $json = JSON->new;
$json->allow_blessed;
if ($params) {
$content = $json->allow_nonref->utf8->canonical(1)->encode($params);
}
my $url_params = $resource->{url_params};
print "REQ: $method, $url, $content\n" if $self->debug;
if ( $self->record ) {
my $response = $self->SUPER::request( $resource, $params, 1 );
push @{ $self->session_store->{"$method $url $content"} },
$response->as_string;
return $self->_process_response( $response, $no_content_success );
}
if ( $self->replay ) {
my $resp;
my $arr_of_resps = $self->session_store->{"$method $url $content"}
// [];
if ( scalar(@$arr_of_resps) ) {
$resp = shift @$arr_of_resps;
$resp = HTTP::Response->parse($resp);
}
else {
$resp = HTTP::Response->new( '501', "Failed to find a response" );
}
return $self->_process_response( $resp, $no_content_success );
}
my $mock_cmds = $self->mock_cmds;
my $spec = $self->spec;
my $cmd = $mock_cmds->get_method_name_from_parameters(
{ method => $method, url => $url } );
my $ret = { cmd_status => 'OK', cmd_return => 1 };
if ( defined( $spec->{$cmd} ) ) {
my $return_sub = $spec->{$cmd};
my $mock_return = $return_sub->( $url_params, $params );
if ( ref($mock_return) eq 'HASH' ) {
$ret->{cmd_status} = $mock_return->{status};
$ret->{cmd_return} = $mock_return->{return};
$ret->{cmd_error} = $mock_return->{error} // '';
}
else {
$ret = $mock_return;
}
$ret->{session_id} = $self->fake_session_id if ( ref($ret) eq 'HASH' );
}
else {
$ret->{sessionId} = $self->fake_session_id;
}
return $ret;
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
Selenium::Remote::Mock::RemoteConnection - utility class to mock the responses from Selenium server
=head1 VERSION
version 1.33
=head1 SYNOPSIS
=head2 Record interactions
use strict;
use warnings;
use Selenium::Remote::Driver;
use Selenium::Remote::Mock::RemoteConnection;
# create a new Mock object to record the interactions with Selenium
# Server
my $mock_connection = Selenium::Remote::Mock::RemoteConnection->new( record => 1 );
# the Mock object is passed to the driver in place of what would be
# a regular Selenium::Remote::RemoteConnection object
my $driver = Selenium::Remote::Driver->new( remote_conn => $mock_connection );
# always store the session id, as it will become undef once
# $driver->quit is called
my $session_id = $driver->session_id;
# do all the selenium things and quit
$driver->get('http://www.google.com');
$driver->get('http://www.wikipedia.com');
$driver->quit;
# dump the session to a file
$mock_connection->dump_session_store( 'my_record.json' );
This code, above doing some basic Selenium interactions, will end up generating a JSON file containing all the requests and their responses for your Selenium session.
The JSON file looks like this :
'{
"HTTP_REQUEST_URL {request_parameters}":[response1,response2,...],
...
}'
The reason why we store array of responses is that the exact same request can be made more than once during a session, so we have to store every response to the same requests.
=head2 Replay interactions
#!perl
use strict;
use warnings;
use Test::More;
use Test::Selenium::Remote::Driver;
use Selenium::Remote::Mock::RemoteConnection;
my $mock_connection_2 =
Selenium::Remote::Mock::RemoteConnection->new( replay => 1,
replay_file => 'my_record.json' );
# javascript + version parameters added or else it will not work
my $driver =
Test::Selenium::Remote::Driver->new( remote_conn => $mock_connection_2, javascript => 1, version => '' );
$driver->get_ok('http://www.google.com');
$driver->get_ok('http://www.wikipedia.com');
$driver->quit;
done_testing;
Using the file generated with the recording snippet from the section before, we are able to mock the responses.
Note that there is one small limitation (that I hope to remove in future versions), is that a record generated with L<Selenium::Remote::Driver> is not directly useable with L<Test::Selenium::Remote::Driver>.
This is mainly because the way the two instances are created are a bit different, which leads to different requests made, for creating a session for instance.
For now, what works for sure is recording and replaying from the same class.
=head2 Mock responses
#!perl
use Test::More;
use Test::Selenium::Remote::Driver;
use Selenium::Remote::WebElement;
use Selenium::Remote::Mock::Commands;
use Selenium::Remote::Mock::RemoteConnection;
my $spec = {
findElement => sub {
my (undef,$searched_item) = @_;
return { status => 'OK', return => { ELEMENT => '123456' } }
if ( $searched_item->{value} eq 'q' );
return { status => 'NOK', return => 0, error => 'element not found' };
},
getPageSource => sub { return 'this output matches regex'},
};
my $mock_commands = Selenium::Remote::Mock::Commands->new;
my $successful_driver =
Test::Selenium::Remote::Driver->new(
remote_conn => Selenium::Remote::Mock::RemoteConnection->new( spec => $spec, mock_cmds => $mock_commands ),
commands => $mock_commands,
);
$successful_driver->find_element_ok('q','find_element_ok works');
dies_ok { $successful_driver->find_element_ok('notq') } 'find_element_ok dies if element not found';
$successful_driver->find_no_element_ok('notq','find_no_element_ok works');
$successful_driver->content_like( qr/matches/, 'content_like works');
$successful_driver->content_unlike( qr/nomatch/, 'content_unlike works');
done_testing();
Mocking responses by hand requires a more advanced knowledge of the underlying implementation of L<Selenium::Remote::Driver>.
What we mock here is the processed response that will be returned by L<Selenium::Remote::RemoteConnection> to '_execute_command' call.
To accomplish this we need :
=over
=item *
a spec: a HASHREF which keys are the name of the methods we want to mock. Note that those keys should also be valid keys from the _cmds attribute in L<Selenium::Remote::Commands>.
The value of each key is a sub which will be given two parameters:
=over
=item *
$url_params : the values that should have been replaced in the URL
For instance, on the example above, it would have been:
{ session_id => 'some_session_id'}
=item *
$params : the original parameters of the request.
On the example above it would have been:
{ value => 'q', using => 'xpath'}
=back
The sub used as a value in the spec is not expected to return anything, so you have to craft very carefully what you return so that it will produce the expected result.
=item *
a mock_cmd: a L<Selenium::Remote::Mock::Commands> object. This is used mainly to hijack the normal commands so that placeholders do not get replaced in the URLs.
=back
=head1 DESCRIPTION
Selenium::Remote::Mock::RemoteConnection is a class to act as a short-circuit or a pass through to the connection to a Selenium Server.
Using this class in place of L<Selenium::Remote::RemoteConnection> allows to:
=over
=item *
record interactions with the Selenium Server into a JSON file
=item *
replay recorded interactions from a JSON file to mock answers from the Selenium Server
=item *
mock responses to specific functions
=back
=for Pod::Coverage *EVERYTHING*
=head1 BUGS
This code is really early alpha, so its API might change. Use with caution !
=head1 SEE ALSO
Please see those modules/websites for more information related to this module.
=over 4
=item *
L<Selenium::Remote::Driver|Selenium::Remote::Driver>
=back
=head1 BUGS
Please report any bugs or feature requests on the bugtracker website
L<https://github.com/teodesian/Selenium-Remote-Driver/issues>
When submitting a bug or request, please include a test-file or a
patch to an existing test-file that illustrates the bug or desired
feature.
=head1 AUTHORS
Current Maintainers:
=over 4
=item *
Daniel Gempesaw <gempesaw@gmail.com>
=item *
Emmanuel Peroumalnaïk <peroumalnaik.emmanuel@gmail.com>
=back
Previous maintainers:
=over 4
=item *
Luke Closs <cpan@5thplane.com>
=item *
Mark Stosberg <mark@stosberg.com>
=back
Original authors:
=over 4
=item *
Aditya Ivaturi <ivaturi@gmail.com>
=back
=head1 COPYRIGHT AND LICENSE
Copyright (c) 2010-2011 Aditya Ivaturi, Gordon Child
Copyright (c) 2014-2017 Daniel Gempesaw
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
=cut

View File

@@ -0,0 +1,361 @@
package Selenium::Remote::RemoteConnection;
$Selenium::Remote::RemoteConnection::VERSION = '1.33';
use strict;
use warnings;
#ABSTRACT: Connect to a selenium server
use Moo;
use Try::Tiny;
use LWP::UserAgent;
use HTTP::Headers;
use HTTP::Request;
use Carp qw(croak);
use JSON;
use Data::Dumper;
use Selenium::Remote::ErrorHandler;
use Scalar::Util qw{looks_like_number};
has 'remote_server_addr' => ( is => 'rw', );
has 'port' => ( is => 'rw', );
has 'debug' => (
is => 'rw',
default => sub { 0 }
);
has 'ua' => (
is => 'lazy',
builder => sub { return LWP::UserAgent->new; }
);
has 'error_handler' => (
is => 'lazy',
builder => sub { return Selenium::Remote::ErrorHandler->new; }
);
with 'Selenium::Remote::Driver::CanSetWebdriverContext';
sub check_status {
my $self = shift;
my $status;
try {
$status = $self->request( { method => 'GET', url => 'status' } );
}
catch {
croak "Could not connect to SeleniumWebDriver: $_";
};
if ( $status->{cmd_status} ne 'OK' ) {
# Could be grid, see if we can talk to it
$status = undef;
$status =
$self->request( { method => 'GET', url => 'grid/api/hub/status' } );
}
unless ( $status->{cmd_status} eq 'OK' ) {
croak "Selenium server did not return proper status";
}
}
sub request {
my ( $self, $resource, $params, $dont_process_response ) = @_;
my $method = $resource->{method};
my $url = $resource->{url};
my $no_content_success = $resource->{no_content_success} // 0;
my $content = '';
my $fullurl = '';
# Construct full url.
if ( $url =~ m/^http/g ) {
$fullurl = $url;
}
elsif ( $url =~ m/^\// ) {
# This is used when we get a 302 Redirect with a Location header.
$fullurl =
"http://" . $self->remote_server_addr . ":" . $self->port . $url;
}
elsif ( $url =~ m/grid/g ) {
$fullurl =
"http://" . $self->remote_server_addr . ":" . $self->port . "/$url";
}
else {
$fullurl =
"http://"
. $self->remote_server_addr . ":"
. $self->port
. $self->wd_context_prefix . "/$url";
}
if ( ( defined $params ) && $params ne '' ) {
#WebDriver 3 shims
if ( $resource->{payload} ) {
foreach my $key ( keys( %{ $resource->{payload} } ) ) {
$params->{$key} = $resource->{payload}->{$key};
}
}
my $json = JSON->new;
$json->allow_blessed;
$content = $json->allow_nonref->utf8->encode($params);
}
print "REQ: $method, $fullurl, $content\n" if $self->debug;
# HTTP request
my $header =
HTTP::Headers->new( Content_Type => 'application/json; charset=utf-8' );
$header->header( 'Accept' => 'application/json' );
my $request = HTTP::Request->new( $method, $fullurl, $header, $content );
my $response = $self->ua->request($request);
if ($dont_process_response) {
return $response;
}
return $self->_process_response( $response, $no_content_success );
}
sub _process_response {
my ( $self, $response, $no_content_success ) = @_;
my $data; # server response 'value' that'll be returned to the user
my $json = JSON->new;
if ( $response->is_redirect ) {
my $redirect = {
method => 'GET',
url => $response->header('location')
};
return $self->request($redirect);
}
else {
my $decoded_json = undef;
print "RES: " . $response->decoded_content . "\n\n" if $self->debug;
if ( ( $response->message ne 'No Content' )
&& ( $response->content ne '' ) )
{
if ( $response->content_type !~ m/json/i ) {
$data->{'cmd_status'} = 'NOTOK';
$data->{'cmd_return'}->{message} =
'Server returned error message '
. $response->content
. ' instead of data';
return $data;
}
$decoded_json =
$json->allow_nonref(1)->utf8(1)->decode( $response->content );
$data->{'sessionId'} = $decoded_json->{'sessionId'};
}
if ( $response->is_error ) {
$data->{'cmd_status'} = 'NOTOK';
if ( defined $decoded_json ) {
$data->{'cmd_return'} =
$self->error_handler->process_error($decoded_json);
}
else {
$data->{'cmd_return'} =
'Server returned error code '
. $response->code
. ' and no data';
}
return $data;
}
elsif ( $response->is_success ) {
$data->{'cmd_status'} = 'OK';
if ( defined $decoded_json ) {
#XXX MS edge doesn't follow spec here either
if ( looks_like_number( $decoded_json->{status} )
&& $decoded_json->{status} > 0
&& $decoded_json->{value}{message} )
{
$data->{cmd_status} = 'NOT OK';
$data->{cmd_return} = $decoded_json->{value};
return $data;
}
#XXX shockingly, neither does InternetExplorerDriver
if ( ref $decoded_json eq 'HASH' && $decoded_json->{error} ) {
$data->{cmd_status} = 'NOT OK';
$data->{cmd_return} = $decoded_json;
return $data;
}
if ($no_content_success) {
$data->{'cmd_return'} = 1;
}
else {
$data->{'cmd_return'} = $decoded_json->{'value'};
if ( ref( $data->{cmd_return} ) eq 'HASH'
&& exists $data->{cmd_return}->{sessionId} )
{
$data->{sessionId} = $data->{cmd_return}->{sessionId};
}
}
}
else {
$data->{'cmd_return'} =
'Server returned status code '
. $response->code
. ' but no data';
}
return $data;
}
else {
# No idea what the server is telling me, must be high
$data->{'cmd_status'} = 'NOTOK';
$data->{'cmd_return'} =
'Server returned status code '
. $response->code
. ' which I don\'t understand';
return $data;
}
}
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
Selenium::Remote::RemoteConnection - Connect to a selenium server
=head1 VERSION
version 1.33
=head1 SYNOPSIS
my $driver = Selenium::Remote::Driver->new();
eval { $driver->remote_conn->check_status() };
die "do something to kick the server" if $@;
=head1 DESCRIPTION
You shouldn't really need to use this module unless debugging or checking connections when testing dangerous things.
=head1 CONSTRUCTOR
=head2 new(%parameters)
Accepts 5 parameters:
=over 4
=item B<remote_server_addr> - address of selenium server
=item B<port> - port of selenium server
=item B<ua> - Useful to override with Test::LWP::UserAgent in unit tests
=item B<debug> - Should be self-explanatory
=item B<error_handler> - Defaults to Selenium::Remote::ErrorHandler.
=back
These can be set any time later by getter/setters with the same name.
=head1 METHODS
=head2 check_status
Croaks unless the selenium server is responsive. Sometimes is useful to call in-between tests (the server CAN die on you...)
=head2 request
Make a request of the Selenium server. Mostly useful for debugging things going wrong with Selenium::Remote::Driver when not in normal operation.
=head1 SEE ALSO
Please see those modules/websites for more information related to this module.
=over 4
=item *
L<Selenium::Remote::Driver|Selenium::Remote::Driver>
=back
=head1 BUGS
Please report any bugs or feature requests on the bugtracker website
L<https://github.com/teodesian/Selenium-Remote-Driver/issues>
When submitting a bug or request, please include a test-file or a
patch to an existing test-file that illustrates the bug or desired
feature.
=head1 AUTHORS
Current Maintainers:
=over 4
=item *
Daniel Gempesaw <gempesaw@gmail.com>
=item *
Emmanuel Peroumalnaïk <peroumalnaik.emmanuel@gmail.com>
=back
Previous maintainers:
=over 4
=item *
Luke Closs <cpan@5thplane.com>
=item *
Mark Stosberg <mark@stosberg.com>
=back
Original authors:
=over 4
=item *
Aditya Ivaturi <ivaturi@gmail.com>
=back
=head1 COPYRIGHT AND LICENSE
Copyright (c) 2010-2011 Aditya Ivaturi, Gordon Child
Copyright (c) 2014-2017 Daniel Gempesaw
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
=cut

View File

@@ -0,0 +1,364 @@
package Selenium::Remote::Spec;
$Selenium::Remote::Spec::VERSION = '1.33';
use strict;
use warnings;
# ABSTRACT: Implement commands for Selenium::Remote::Driver
use Carp qw{croak};
use List::Util qw{any};
use Moo;
extends 'Selenium::Remote::Commands';
#Ripped from the headlines: https://w3c.github.io/webdriver/webdriver-spec.html
#then add 2 params for our use
#Method URI Template no_content_success internal_name Command
our $spec = qq{
POST session 0 newSession New Session
POST session 0 getCapabilities Get Capabilities (v2->v3 shim)
DELETE session/:sessionId 1 quit Delete Session
GET status 0 status Status
GET session/:sessionId/timeouts 0 getTimeouts Get Timeouts
POST session/:sessionId/timeouts 1 setTimeout Set Page Load timeout (v2->v3 shim)
POST session/:sessionId/timeouts/async_script 1 setAsyncScriptTimeout Set Async script timeout (v2->v3 shim)
POST session/:sessionId/timeouts/implicit_wait 1 setImplicitWaitTimeout Set Implicit wait timeout (v2->v3 shim)
POST session/:sessionId/url 1 get Navigate To
GET session/:sessionId/url 0 getCurrentUrl Get Current URL
POST session/:sessionId/back 1 goBack Back
POST session/:sessionId/forward 1 goForward Forward
POST session/:sessionId/refresh 1 refresh Refresh
GET session/:sessionId/title 0 getTitle Get Title
GET session/:sessionId/window 0 getCurrentWindowHandle Get Currently Focused Window Handle
DELETE session/:sessionId/window 1 close Close Currently Focused Window
POST session/:sessionId/window 1 switchToWindow Switch To Window
GET session/:sessionId/window/handles 0 getWindowHandles Get Window Handles
POST session/:sessionId/frame 1 switchToFrame Switch To Frame
POST session/:sessionId/frame/parent 1 switchToParentFrame Switch To Parent Frame
GET session/:sessionId/window/rect 0 getWindowRect Get Window Size/Position (v2->v3 shim)
POST session/:sessionId/window/rect 1 setWindowRect Set Window Size/Position (v2->v3 shim)
POST session/:sessionId/window/maximize 1 maximizeWindow Maximize Window
POST session/:sessionId/window/minimize 1 minimizeWindow Minimize Window
POST session/:sessionId/window/fullscreen 1 fullscreenWindow Fullscreen Window
GET session/:sessionId/element/active 0 getActiveElement Get Active Element
POST session/:sessionId/element 0 findElement Find Element
POST session/:sessionId/elements 0 findElements Find Elements
POST session/:sessionId/element/:id/element 0 findChildElement Find Element From Element
POST session/:sessionId/element/:id/elements 0 findChildElements Find Elements From Element
GET session/:sessionId/element/:id/selected 0 isElementSelected Is Element Selected
GET session/:sessionId/element/:id/attribute/:name 0 getElementAttribute Get Element Attribute
GET session/:sessionId/element/:id/property/:name 0 getElementProperty Get Element Property
GET session/:sessionId/element/:id/css/:propertyName 0 getElementValueOfCssProperty Get Element CSS Value
GET session/:sessionId/element/:id/text 0 getElementText Get Element Text
GET session/:sessionId/element/:id/name 0 getElementTagName Get Element Tag Name
GET session/:sessionId/element/:id/rect 0 getElementRect Get Element Rect
GET session/:sessionId/element/:id/enabled 0 isElementEnabled Is Element Enabled
POST session/:sessionId/element/:id/click 1 clickElement Element Click
POST session/:sessionId/element/:id/clear 1 clearElement Element Clear
POST session/:sessionId/element/:id/value 1 sendKeysToElement Element Send Keys
GET session/:sessionId/source 0 getPageSource Get Page Source
POST session/:sessionId/execute/sync 0 executeScript Execute Script
POST session/:sessionId/execute/async 0 executeAsyncScript Execute Async Script
GET session/:sessionId/cookie 0 getAllCookies Get All Cookies
GET session/:sessionId/cookie/:name 0 getCookieNamed Get Named Cookie
POST session/:sessionId/cookie 1 addCookie Add Cookie
DELETE session/:sessionId/cookie/:name 1 deleteCookieNamed Delete Cookie
DELETE session/:sessionId/cookie 1 deleteAllCookies Delete All Cookies
POST session/:sessionId/actions 1 generalAction Perform Actions
DELETE session/:sessionId/actions 1 releaseGeneralAction Release Actions
POST session/:sessionId/alert/dismiss 1 dismissAlert Dismiss Alert
POST session/:sessionId/alert/accept 1 acceptAlert Accept Alert
GET session/:sessionId/alert/text 0 getAlertText Get Alert Text
POST session/:sessionId/alert/text 1 sendKeysToPrompt Send Alert Text
GET session/:sessionId/screenshot 0 screenshot Take Screenshot
GET session/:sessionId/moz/screenshot/full 0 mozScreenshotFull Take Full Screenshot
GET session/:sessionId/element/:id/screenshot 0 elementScreenshot Take Element Screenshot
};
our $spec_parsed;
sub get_spec {
return $spec_parsed if $spec_parsed;
my @split = split( /\n/, $spec );
foreach my $line (@split) {
next unless $line;
my ( $method, $uri, $nc_success, $key, @description ) =
split( / +/, $line );
$spec_parsed->{$key} = {
method => $method,
url => $uri,
no_content_success => int($nc_success)
, #XXX this *should* always be 0, but specs lie
description => join( ' ', @description ),
};
}
return $spec_parsed;
}
has '_cmds' => (
is => 'lazy',
reader => 'get_cmds',
builder => \&get_spec,
);
has '_caps' => (
is => 'lazy',
reader => 'get_caps',
builder => sub {
return [
'browserName', 'acceptInsecureCerts',
'browserVersion', 'platformName',
'proxy', 'pageLoadStrategy',
'setWindowRect', 'timeouts',
'unhandledPromptBehavior', 'moz:firefoxOptions',
'chromeOptions',
];
}
);
has '_caps_map' => (
is => 'lazy',
reader => 'get_caps_map',
builder => sub {
return {
browserName => 'browserName',
acceptSslCerts => 'acceptInsecureCerts',
version => 'browserVersion',
platform => 'platformName',
proxy => 'proxy',
};
}
);
sub get_params {
my ( $self, $args ) = @_;
if ( !( defined $args->{'session_id'} ) ) {
return;
}
#Allow fall-back in the event the command passed doesn't exist
return unless $self->get_cmds()->{ $args->{command} };
my $url = $self->get_url( $args->{command} );
my $data = {};
# Do the var substitutions.
$url =~ s/:sessionId/$args->{'session_id'}/;
$url =~ s/:id/$args->{'id'}/;
$url =~ s/:name/$args->{'name'}/;
$url =~ s/:propertyName/$args->{'property_name'}/;
$url =~ s/:other/$args->{'other'}/;
$url =~ s/:windowHandle/$args->{'window_handle'}/;
$data->{'method'} = $self->get_method( $args->{command} );
$data->{'no_content_success'} =
$self->get_no_content_success( $args->{command} );
$data->{'url'} = $url;
#URL & data polyfills for the way selenium2 used to do things, etc
$data->{payload} = {};
if ( $args->{type} ) {
$data->{payload}->{pageLoad} = $args->{ms}
if $data->{url} =~ m/timeouts$/ && $args->{type} eq 'page load';
$data->{payload}->{script} = $args->{ms}
if $data->{url} =~ m/timeouts$/ && $args->{type} eq 'script';
$data->{payload}->{implicit} = $args->{ms}
if $data->{url} =~ m/timeouts$/ && $args->{type} eq 'implicit';
}
#finder polyfills
#orig: class, class_name, css, id, link, link_text, partial_link_text, tag_name, name, xpath
#new: "css selector", "link text", "partial link text", "tag name", "xpath"
#map: class, class_name, id, name, link = 'css selector'
if ( $args->{using} && $args->{value} ) {
$data->{payload}->{using} = 'css selector'
if grep { $args->{using} eq $_ } ( 'id', 'class name', 'name' );
$data->{payload}->{value} = "[id='$args->{value}']"
if $args->{using} eq 'id';
$data->{payload}->{value} = ".$args->{value}"
if $args->{using} eq 'class name';
$data->{payload}->{value} = "[name='$args->{value}']"
if $args->{using} eq 'name';
}
if ( $data->{url} =~ s/timeouts\/async_script$/timeouts/g ) {
$data->{payload}->{script} = $args->{ms};
$data->{payload}->{type} = 'script'; #XXX chrome doesn't follow the spec
}
if ( $data->{url} =~ s/timeouts\/implicit_wait$/timeouts/g ) {
$data->{payload}->{implicit} = $args->{ms};
$data->{payload}->{type} =
'implicit'; #XXX chrome doesn't follow the spec
}
$data->{payload}->{value} = $args->{text}
if $args->{text} && $args->{command} ne 'sendKeysToElement';
$data->{payload}->{handle} = $args->{window_handle}
if grep { $args->{command} eq $_ }
qw{fullscreenWindow minimizeWindow maximizeWindow};
return $data;
}
sub parse_response {
my ( $self, undef, $resp ) = @_;
if ( ref($resp) eq 'HASH' ) {
if ( $resp->{cmd_status} && $resp->{cmd_status} eq 'OK' ) {
return $resp->{cmd_return};
}
my $msg = "Error while executing command";
if ( ref $resp->{cmd_return} eq 'HASH' ) {
$msg .= ": $resp->{cmd_return}{error}"
if $resp->{cmd_return}{error};
$msg .= ": $resp->{cmd_return}{message}"
if $resp->{cmd_return}{message};
}
else {
$msg .= ": $resp->{cmd_return}";
}
croak $msg;
}
return $resp;
}
#Utility
sub get_spec_differences {
my $v2_spec = Selenium::Remote::Commands->new()->get_cmds();
my $v3_spec = Selenium::Remote::Spec->new()->get_cmds();
foreach my $key ( keys(%$v2_spec) ) {
print "v2 $key NOT present in v3 spec!!!\n"
unless any { $_ eq $key } keys(%$v3_spec);
}
foreach my $key ( keys(%$v3_spec) ) {
print "v3 $key NOT present in v2 spec!!!\n"
unless any { $_ eq $key } keys(%$v2_spec);
}
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
Selenium::Remote::Spec - Implement commands for Selenium::Remote::Driver
=head1 VERSION
version 1.33
=head1 DESCRIPTION
Defines all the HTTP endpoints available to execute on a selenium server.
If you have either a customized Selenium Server, or want new features
you should update the _cmds hash.
=for Pod::Coverage *EVERYTHING*
=head1 Webdriver 3 capabilities
WD3 giveth and taketh away some caps. Here's all you get:
Browser name: "browserName" string Identifies the user agent.
Browser version: "browserVersion" string Identifies the version of the user agent.
Platform name: "platformName" string Identifies the operating system of the endpoint node.
Accept insecure TLS certificates: "acceptInsecureCerts" boolean Indicates whether untrusted and self-signed TLS certificates are implicitly trusted on navigation for the duration of the session.
Proxy configuration: "proxy" JSON Defines the current sessions proxy configuration.
New Stuff:
Page load strategy: "pageLoadStrategy" string Defines the current sessions page load strategy.
Window dimensioning/positioning: "setWindowRect" boolean Indicates whether the remote end supports all of the commands in Resizing and Positioning Windows.
Session timeouts configuration: "timeouts" JSON Describes the timeouts imposed on certain session operations.
Unhandled prompt behavior: "unhandledPromptBehavior" string Describes the current sessions user prompt handler.
=head1 SEE ALSO
Please see those modules/websites for more information related to this module.
=over 4
=item *
L<Selenium::Remote::Driver|Selenium::Remote::Driver>
=back
=head1 BUGS
Please report any bugs or feature requests on the bugtracker website
L<https://github.com/teodesian/Selenium-Remote-Driver/issues>
When submitting a bug or request, please include a test-file or a
patch to an existing test-file that illustrates the bug or desired
feature.
=head1 AUTHORS
Current Maintainers:
=over 4
=item *
Daniel Gempesaw <gempesaw@gmail.com>
=item *
Emmanuel Peroumalnaïk <peroumalnaik.emmanuel@gmail.com>
=back
Previous maintainers:
=over 4
=item *
Luke Closs <cpan@5thplane.com>
=item *
Mark Stosberg <mark@stosberg.com>
=back
Original authors:
=over 4
=item *
Aditya Ivaturi <ivaturi@gmail.com>
=back
=head1 COPYRIGHT AND LICENSE
Copyright (c) 2010-2011 Aditya Ivaturi, Gordon Child
Copyright (c) 2014-2017 Daniel Gempesaw
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
=cut

View File

@@ -0,0 +1,173 @@
package Selenium::Remote::WDKeys;
$Selenium::Remote::WDKeys::VERSION = '1.33';
# ABSTRACT: Representation of keystrokes used by Selenium::Remote::WebDriver
use strict;
use warnings;
use base 'Exporter';
# http://code.google.com/p/selenium/wiki/JsonWireProtocol#/session/:sessionId/element/:id/value
use constant KEYS => {
'null' => "\N{U+E000}",
'cancel' => "\N{U+E001}",
'help' => "\N{U+E002}",
'backspace' => "\N{U+E003}",
'tab' => "\N{U+E004}",
'clear' => "\N{U+E005}",
'return' => "\N{U+E006}",
'enter' => "\N{U+E007}",
'shift' => "\N{U+E008}",
'control' => "\N{U+E009}",
'alt' => "\N{U+E00A}",
'pause' => "\N{U+E00B}",
'escape' => "\N{U+E00C}",
'space' => "\N{U+E00D}",
'page_up' => "\N{U+E00E}",
'page_down' => "\N{U+E00f}",
'end' => "\N{U+E010}",
'home' => "\N{U+E011}",
'left_arrow' => "\N{U+E012}",
'up_arrow' => "\N{U+E013}",
'right_arrow' => "\N{U+E014}",
'down_arrow' => "\N{U+E015}",
'insert' => "\N{U+E016}",
'delete' => "\N{U+E017}",
'semicolon' => "\N{U+E018}",
'equals' => "\N{U+E019}",
'numpad_0' => "\N{U+E01A}",
'numpad_1' => "\N{U+E01B}",
'numpad_2' => "\N{U+E01C}",
'numpad_3' => "\N{U+E01D}",
'numpad_4' => "\N{U+E01E}",
'numpad_5' => "\N{U+E01f}",
'numpad_6' => "\N{U+E020}",
'numpad_7' => "\N{U+E021}",
'numpad_8' => "\N{U+E022}",
'numpad_9' => "\N{U+E023}",
'multiply' => "\N{U+E024}",
'add' => "\N{U+E025}",
'separator' => "\N{U+E026}",
'subtract' => "\N{U+E027}",
'decimal' => "\N{U+E028}",
'divide' => "\N{U+E029}",
'f1' => "\N{U+E031}",
'f2' => "\N{U+E032}",
'f3' => "\N{U+E033}",
'f4' => "\N{U+E034}",
'f5' => "\N{U+E035}",
'f6' => "\N{U+E036}",
'f7' => "\N{U+E037}",
'f8' => "\N{U+E038}",
'f9' => "\N{U+E039}",
'f10' => "\N{U+E03A}",
'f11' => "\N{U+E03B}",
'f12' => "\N{U+E03C}",
'command_meta' => "\N{U+E03D}",
'ZenkakuHankaku' => "\N{U+E040}", #Asian language keys, maybe altGr too?
#There are other code points for say, left versus right meta/shift/alt etc, but I don't seriously believe anyone uses that level of sophistication on the web yet.
};
our @EXPORT = ('KEYS');
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
Selenium::Remote::WDKeys - Representation of keystrokes used by Selenium::Remote::WebDriver
=head1 VERSION
version 1.33
=head1 DESCRIPTION
The constant KEYS is defined here.
=head1 SEE ALSO
Please see those modules/websites for more information related to this module.
=over 4
=item *
L<Selenium::Remote::Driver|Selenium::Remote::Driver>
=back
=head1 BUGS
Please report any bugs or feature requests on the bugtracker website
L<https://github.com/teodesian/Selenium-Remote-Driver/issues>
When submitting a bug or request, please include a test-file or a
patch to an existing test-file that illustrates the bug or desired
feature.
=head1 AUTHORS
Current Maintainers:
=over 4
=item *
Daniel Gempesaw <gempesaw@gmail.com>
=item *
Emmanuel Peroumalnaïk <peroumalnaik.emmanuel@gmail.com>
=back
Previous maintainers:
=over 4
=item *
Luke Closs <cpan@5thplane.com>
=item *
Mark Stosberg <mark@stosberg.com>
=back
Original authors:
=over 4
=item *
Aditya Ivaturi <ivaturi@gmail.com>
=back
=head1 COPYRIGHT AND LICENSE
Copyright (c) 2010-2011 Aditya Ivaturi, Gordon Child
Copyright (c) 2014-2017 Daniel Gempesaw
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
=cut

View File

@@ -0,0 +1,883 @@
package Selenium::Remote::WebElement;
$Selenium::Remote::WebElement::VERSION = '1.33';
# ABSTRACT: Representation of an HTML Element used by Selenium Remote Driver
use strict;
use warnings;
use Moo;
use Carp qw(carp croak);
has 'id' => (
is => 'ro',
required => 1,
coerce => sub {
my ($value) = @_;
if ( ref($value) eq 'HASH' ) {
if ( exists $value->{ELEMENT} ) {
# The JSONWireProtocol web element object looks like
#
# { "ELEMENT": $INTEGER_ID }
return $value->{ELEMENT};
}
elsif ( exists $value->{'element-6066-11e4-a52e-4f735466cecf'} ) {
# but the WebDriver spec web element uses a magic
# string. See the spec for more information:
#
# https://www.w3.org/TR/webdriver/#elements
return $value->{'element-6066-11e4-a52e-4f735466cecf'};
}
else {
croak
'When passing in an object to the WebElement id attribute, it must have at least one of the ELEMENT or element-6066-11e4-a52e-4f735466cecf keys.';
}
}
else {
return $value;
}
}
);
has 'driver' => (
is => 'ro',
required => 1,
handles => [qw(_execute_command)],
);
sub child {
return $_[0]->{driver}->find_child_element(@_);
}
sub children {
return $_[0]->{driver}->find_child_elements(@_);
}
sub click {
my ($self) = @_;
my $res = { 'command' => 'clickElement', 'id' => $self->id };
return $self->_execute_command($res);
}
sub submit {
my ($self) = @_;
if (
$self->driver->{is_wd3}
&& !(
grep { $self->driver->browser_name eq $_ } qw{chrome MicrosoftEdge}
)
)
{
if ( $self->get_tag_name() ne 'form' ) {
return $self->driver->execute_script(
"return arguments[0].form.submit();",
{ 'element-6066-11e4-a52e-4f735466cecf' => $self->{id} } );
}
else {
return $self->driver->execute_script(
"return arguments[0].submit();",
{ 'element-6066-11e4-a52e-4f735466cecf' => $self->{id} } );
}
}
my $res = { 'command' => 'submitElement', 'id' => $self->id };
return $self->_execute_command($res);
}
sub send_keys {
my ( $self, @strings ) = @_;
croak "no keys to send" unless scalar @strings >= 1;
my $res = { 'command' => 'sendKeysToElement', 'id' => $self->id };
# We need to send an array of single characters to be WebDriver
# spec compatible. That is, for @strings = ('hel', 'lo'), the
# corresponding value must be ('h', 'e', 'l', 'l', 'o' ). This
# format conforms with the Spec AND works with the Selenium
# standalone server.
my $strings = join( '', map { $_ . "" } @strings );
my $params = {
'value' => [ split( '', $strings ) ],
text => $strings,
};
return $self->_execute_command( $res, $params );
}
sub is_selected {
my ($self) = @_;
return $self->get_property('checked')
if $self->driver->{is_wd3}
&& !( grep { $self->driver->browser_name eq $_ }
qw{chrome MicrosoftEdge} );
my $res = { 'command' => 'isElementSelected', 'id' => $self->id };
return $self->_execute_command($res);
}
sub set_selected {
my ($self) = @_;
if ( $self->driver->{is_wd3} ) {
return if $self->is_selected();
return $self->click();
}
my $res = { 'command' => 'setElementSelected', 'id' => $self->id };
return $self->_execute_command($res);
}
sub toggle {
my ($self) = @_;
if ( $self->driver->{is_wd3} ) {
return $self->click() unless $self->is_selected();
return $self->driver->execute_script(
qq/ if (arguments[0].checked) { arguments[0].checked = 0 }; return arguments[0].checked; /,
{ 'element-6066-11e4-a52e-4f735466cecf' => $self->{id} }
);
}
my $res = { 'command' => 'toggleElement', 'id' => $self->id };
return $self->_execute_command($res);
}
sub is_enabled {
my ($self) = @_;
if (
$self->driver->{is_wd3}
&& !(
grep { $self->driver->browser_name eq $_ } qw{chrome MicrosoftEdge}
)
)
{
return 1 if $self->get_tag_name() ne 'input';
return $self->get_property('disabled') ? 0 : 1;
}
my $res = { 'command' => 'isElementEnabled', 'id' => $self->id };
return $self->_execute_command($res);
}
sub get_element_location {
my ($self) = @_;
if (
$self->driver->{is_wd3}
&& !(
grep { $self->driver->browser_name eq $_ } qw{chrome MicrosoftEdge}
)
)
{
my $data = $self->get_element_rect();
delete $data->{height};
delete $data->{width};
return $data;
}
my $res = { 'command' => 'getElementLocation', 'id' => $self->id };
return $self->_execute_command($res);
}
sub get_size {
my ($self) = @_;
if (
$self->driver->{is_wd3}
&& !(
grep { $self->driver->browser_name eq $_ } qw{chrome MicrosoftEdge}
)
)
{
my $data = $self->get_element_rect();
delete $data->{x};
delete $data->{y};
return $data;
}
my $res = { 'command' => 'getElementSize', 'id' => $self->id };
return $self->_execute_command($res);
}
sub get_element_rect {
my ($self) = @_;
my $res = { 'command' => 'getElementRect', 'id' => $self->id };
return $self->_execute_command($res);
}
sub get_element_location_in_view {
my ($self) = @_;
#XXX chrome is dopey here
return $self->driver->execute_script(
qq{
if (typeof(arguments[0]) !== 'undefined' && arguments[0].nodeType === Node.ELEMENT_NODE) {
arguments[0].scrollIntoView();
var pos = arguments[0].getBoundingClientRect();
return {y:pos.top,x:pos.left};
}
return {};
}, { 'element-6066-11e4-a52e-4f735466cecf' => $self->{id} }
)
if $self->driver->{is_wd3} && grep { $self->driver->browser_name eq $_ }
( 'firefox', 'internet explorer' );
my $res = { 'command' => 'getElementLocationInView', 'id' => $self->id };
return $self->_execute_command($res);
}
sub get_tag_name {
my ($self) = @_;
my $res = { 'command' => 'getElementTagName', 'id' => $self->id };
return $self->_execute_command($res);
}
sub clear {
my ($self) = @_;
my $res = { 'command' => 'clearElement', 'id' => $self->id };
return $self->_execute_command($res);
}
sub get_attribute {
my ( $self, $attr_name, $no_i_really_mean_it ) = @_;
if ( not defined $attr_name ) {
croak 'Attribute name not provided';
}
#Handle global JSONWire emulation flag
$no_i_really_mean_it = 1 unless $self->{driver}->{emulate_jsonwire};
return $self->get_property($attr_name)
if $self->driver->{is_wd3}
&& !( grep { $self->driver->browser_name eq $_ }
qw{chrome MicrosoftEdge} )
&& !$no_i_really_mean_it;
my $res = {
'command' => 'getElementAttribute',
'id' => $self->id,
'name' => $attr_name,
};
return $self->_execute_command($res);
}
sub get_property {
my ( $self, $prop ) = @_;
return $self->get_attribute($prop)
if $self->driver->{is_wd3}
&& ( grep { $self->driver->browser_name eq $_ }
qw{chrome MicrosoftEdge} );
my $res =
{ 'command' => 'getElementProperty', id => $self->id, name => $prop };
return $self->_execute_command($res);
}
sub get_value {
my ($self) = @_;
return $self->get_attribute('value');
}
sub is_displayed {
my ($self) = @_;
if (
$self->driver->{is_wd3}
&& !(
grep { $self->driver->browser_name eq $_ } qw{chrome MicrosoftEdge}
)
)
{
return 0
if $self->get_tag_name() eq 'input'
&& $self->get_property('type') eq 'hidden'; #hidden type inputs
return 0 unless $self->_is_in_viewport();
return int( $self->get_css_attribute('display') ne 'none' );
}
my $res = { 'command' => 'isElementDisplayed', 'id' => $self->id };
return $self->_execute_command($res);
}
sub _is_in_viewport {
my ($self) = @_;
return $self->driver->execute_script(
qq{
var rect = arguments[0].getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
}, { 'element-6066-11e4-a52e-4f735466cecf' => $self->{id} }
);
}
sub is_hidden {
my ($self) = @_;
return !$self->is_displayed();
}
sub drag {
my ( $self, $target ) = @_;
require Selenium::ActionChains;
my $chain = Selenium::ActionChains->new( driver => $self->driver );
return $chain->drag_and_drop( $self, $target )->perform();
}
sub get_text {
my ($self) = @_;
my $res = { 'command' => 'getElementText', 'id' => $self->id };
return $self->_execute_command($res);
}
sub get_css_attribute {
my ( $self, $attr_name ) = @_;
if ( not defined $attr_name ) {
croak 'CSS attribute name not provided';
}
my $res = {
'command' => 'getElementValueOfCssProperty',
'id' => $self->id,
'property_name' => $attr_name,
};
return $self->_execute_command($res);
}
sub describe {
my ($self) = @_;
my $res = { 'command' => 'describeElement', 'id' => $self->id };
return $self->_execute_command($res);
}
sub screenshot {
my ( $self, $scroll ) = @_;
$scroll //= 1;
my $res = { 'command' => 'elementScreenshot', id => $self->id };
my $input = { scroll => int($scroll) };
return $self->_execute_command( $res, $input );
}
sub capture_screenshot {
my ( $self, $filename, $scroll ) = @_;
croak '$filename is required' unless $filename;
open( my $fh, '>', $filename );
binmode $fh;
print $fh MIME::Base64::decode_base64( $self->screenshot($scroll) );
CORE::close $fh;
return 1;
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
Selenium::Remote::WebElement - Representation of an HTML Element used by Selenium Remote Driver
=head1 VERSION
version 1.33
=head1 DESCRIPTION
Selenium Webdriver represents all the HTML elements as WebElements.
This module provides a mechanism to represent them as objects &
perform various actions on the related elements. This module should
not be instantiated directly by the end user. Selenium::Remote::Driver
instantiates this module when required. Typically, the find_element
method in Selenium::Remote::Driver returns this object on which
various element related operations can be carried out.
What is probably most useful on this page is the list of methods below
that you can perform on an element once you've found one and S::R::D
has made an instance of this for you.
=head1 CONSTRUCTOR
=head2 new
=over 4
=item B<id>
Required: Pass in a string representing the ID of the object. The
string should be obtained from the response object of making one of
the C<find_element> calls from L<Selenium::Remote::Driver>.
The attribute is also set up to handle spec compliant element response
objects via its `coerce` such that any of the following will work and
are all equivalent:
my $old_elem = Selenium::Remote::WebElement->new(
id => 1,
driver => $driver
);
my $new_remote_elem = Selenium::Remote::WebElement->new(
id => { ELEMENT => 1 },
driver => $driver
);
my $new_spec_elem = Selenium::Remote::WebElement->new(
id => { 'element-6066-11e4-a52e-4f735466cecf' => 1 },
driver => $driver
);
and then after instantiation, all three would give the following for
`id`:
print $elem->id; # prints 1
=item B<driver>
Required: Pass in a Selenium::Remote::Driver instance or one of its
subclasses. The WebElement needs the appropriate Driver session to
execute its commands properly.
=back
For typical usage of S::R::D and this module, none of this
matters and it should Just Work without you having to worry about it
at all. For further reading, the L<W3C
spec|https://www.w3.org/TR/webdriver/#elements> strictly dictates the
exact behavior.
=head1 FUNCTIONS
=head2 child(selector, method)
=head2 children(selector, method)
Alias to Selenium::Remote::Driver::find_child_element and find_child_elements, respectively.
=head2 click
Description:
Click the element.
Usage:
$elem->click();
=head2 submit
Description:
Submit a FORM element. The submit command may also be applied to any element
that is a descendant of a FORM element.
Compatibility:
On webdriver3 enabled servers, this uses a JS shim, which WILL NOT submit correctly unless your element is an <input>.
Try clicking it if possible instead.
Usage:
$elem->submit();
=head2 send_keys
Description:
Send a sequence of key strokes to an element. If you want to send specific
Keyboard events, then use the WDKeys module along with theis method. See e.g.
for reference
Input: 1
Required:
{ARRAY | STRING} - Array of strings or a string.
Usage:
$elem->send_keys('abcd', 'efg');
$elem->send_keys('hijk');
or
# include the WDKeys module
use Selenium::Remote::WDKeys;
.
.
$elem->send_keys(KEYS->{'space'}, KEYS->{'enter'});
=head2 is_selected
Description:
Determine if an OPTION element, or an INPUT element of type checkbox or
radiobutton is currently selected.
Output:
BOOLEAN - whether the element is selected
Usage:
$elem->is_selected();
=head2 set_selected
Description:
Select an OPTION element, or an INPUT element of type checkbox or radiobutton.
Forces selected=1 on the element..
Usage:
$elem->set_selected();
=head2 toggle
Description:
Toggle whether an OPTION element, or an INPUT element of type checkbox or
radiobutton is currently selected.
Output:
BOOLEAN - Whether the element is selected after toggling its state.
Usage:
$elem->toggle();
=head2 is_enabled
Description:
Determine if an element is currently enabled.
Output:
BOOLEAN - Whether the element is enabled.
Usage:
$elem->is_enabled();
=head2 get_element_location
Description:
Determine an element's location on the page. The point (0, 0) refers to the
upper-left corner of the page.
Compatibility:
On WebDriver 3 enabled servers, this is an alias for get_element_rect().
Output:
HASH - The X and Y coordinates for the element on the page.
Usage:
$elem->get_element_location();
This method is DEPRECATED on webdriver3 enabled servers.
=head2 get_size
Description:
Determine an element's size in pixels. The size will be returned with width
and height properties.
Compatibility:
On WebDriver 3 enabled servers, this is an alias for get_element_rect().
Output:
HASH - The width and height of the element, in pixels.
Usage:
$elem->get_size();
This method is DEPRECATED on webdriver3 enabled servers.
=head2 get_element_rect
Get the element's size AND location in a hash.
Example Output:
{ x => 0, y => 0, height => 10, width => 10 }
=head2 get_element_location_in_view
Description:
Determine an element's location on the screen once it has been scrolled
into view.
Note: This is considered an internal command and should only be used to
determine an element's location for correctly generating native events.
Compatibility:
On Webdriver3 servers, we have to implement this with a JS shim.
This means in some contexts, you won't get any position returned, as the element isn't considered an element internally.
You may have to go up the element stack to find the element that actually has the bounding box.
Output:
{x:number, y:number} The X and Y coordinates for the element on the page.
Usage:
$elem->get_element_location_in_view();
=head2 get_tag_name
Description:
Query for an element's tag name.
Output:
STRING - The element's tag name, as a lowercase string.
Usage:
$elem->get_tag_name();
=head2 clear
Description:
Clear a TEXTAREA or text INPUT element's value.
Usage:
$elem->clear();
=head2 get_attribute
Description:
Get the value of an element's attribute.
Compatibility:
In older webDriver, this actually got the value of an element's property.
If you want to get the initial condition (e.g. the values in the tag hardcoded in HTML), pass 1 as the second argument.
Or, set $driver->{emulate_jsonwire} = 0 to not have to pass the extra arg.
This can only done on WebDriver 3 enabled servers.
Input: 2
Required:
STRING - name of the attribute of the element
Optional:
BOOLEAN - "I really mean that I want the initial condition, quit being so compatible!!!"
Output:
{STRING | NULL} The value of the attribute, or null if it is not set on the element.
Usage:
$elem->get_attribute('name',1);
=head2 get_property
Gets the C<Current Value> of an element's attribute.
Takes a named property as an argument.
Only available on WebDriver 3 enabled servers.
=head2 get_value
Description:
Query for the value of an element, as determined by its value attribute.
Output:
{STRING | NULL} The element's value, or null if it doesn't have a value attribute.
Usage:
$elem->get_value();
=head2 is_displayed
Description:
Determine if an element is currently displayed.
Note: This does *not* tell you an element's 'visibility' property; as it still takes up space in the DOM and is therefore considered 'displayed'.
WC3 Compatibility:
On JSONWire this method really only checked to see whether the element's style was display:none, or whether it was a hidden input.
This is because "displayedness" was pretty loosely defined until fairly late on into the process, and much grief resulted.
In WC3 webdriver, it additionally does a viewport check, to account for the firmer definition of "displayedness":
https://w3c.github.io/webdriver/#element-displayedness
Output:
BOOLEAN - Whether the element is displayed.
Usage:
$elem->is_displayed();
=head2 is_hidden
Description:
Determine if an element is currently hidden.
Output:
BOOLEAN - Whether the element is hidden.
Usage:
$elem->is_hidden();
=head2 drag
Alias for Selenium::ActionChains::drag_and_drop().
Provide element you wish to drag to as argument.
my $target = $driver->find_element('receptacle','id');
my $subject = $driver->find_element('thingy','id');
$subject->drag($target);
=head2 get_text
Description:
Get the innerText of the element.
Output:
STRING - innerText of an element
Usage:
$elem->get_text();
=head2 get_css_attribute
Description:
Query the value of an element's computed CSS property. The CSS property to
query should be specified using the CSS property name, not the JavaScript
property name (e.g. background-color instead of backgroundColor).
Input: 1
Required:
STRING - name of the css-attribute
Output:
STRING - Value of the css attribute
Usage:
$elem->get_css_attribute('background-color');
=head2 describe
Description:
Describe the identified element
Usage:
$elem->describe();
Note: DEPRECATED as of 2.42.2 -- use get_text, get_value, is_displayed, or
whatever appropriate WebElement function you need instead
Entirely unsupported on WebDriver 3 enabled servers.
=head2 screenshot
Description:
Get a screenshot of the visible region that is a subset of the element's bounding box as a base64 encoded image.
Compatibility:
Only available on Webdriver3 enabled selenium servers.
Input (optional):
$scroll_into_view - BOOLEAN default true. If false, will not scroll the element into the viewport first.
Failing to do so may result in an image being cropped partially or entirely.
Output:
STRING - base64 encoded image
Usage:
print $element->screenshot();
To conveniently write the screenshot to a file, see L</capture_screenshot>.
=head2 capture_screenshot
Description:
Capture a screenshot of said element and save as a PNG to provided file name.
Compatibility:
Only available on Webdriver3 enabled selenium servers.
Input (optional):
$scroll_into_view - BOOLEAN default true. If false, will not scroll the element into the viewport first.
Failing to do so may result in an image being cropped partially or entirely.
Output:
TRUE - (Screenshot is written to file)
Usage:
$element->capture_screenshot($filename);
=head1 SEE ALSO
Please see those modules/websites for more information related to this module.
=over 4
=item *
L<Selenium::Remote::Driver|Selenium::Remote::Driver>
=back
=head1 BUGS
Please report any bugs or feature requests on the bugtracker website
L<https://github.com/teodesian/Selenium-Remote-Driver/issues>
When submitting a bug or request, please include a test-file or a
patch to an existing test-file that illustrates the bug or desired
feature.
=head1 AUTHORS
Current Maintainers:
=over 4
=item *
Daniel Gempesaw <gempesaw@gmail.com>
=item *
Emmanuel Peroumalnaïk <peroumalnaik.emmanuel@gmail.com>
=back
Previous maintainers:
=over 4
=item *
Luke Closs <cpan@5thplane.com>
=item *
Mark Stosberg <mark@stosberg.com>
=back
Original authors:
=over 4
=item *
Aditya Ivaturi <ivaturi@gmail.com>
=back
=head1 COPYRIGHT AND LICENSE
Copyright (c) 2010-2011 Aditya Ivaturi, Gordon Child
Copyright (c) 2014-2017 Daniel Gempesaw
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
=cut

View File

@@ -0,0 +1,213 @@
package Selenium::Waiter;
$Selenium::Waiter::VERSION = '1.33';
use strict;
use warnings;
# ABSTRACT: Provides a utility wait_until function
use Try::Tiny;
require Exporter;
our @ISA = qw/Exporter/;
our @EXPORT = qw/wait_until/;
sub wait_until (&%) {
my $assert = shift;
my $args = {
timeout => 30,
interval => 1,
debug => 0,
@_
};
my $start = time;
my $timeout_not_elapsed = sub {
my $elapsed = time - $start;
return $elapsed < $args->{timeout};
};
my $exception = '';
while ( $timeout_not_elapsed->() ) {
my $assert_ret;
my $try_ret = try {
$assert_ret = $assert->();
return $assert_ret if $assert_ret;
}
catch {
$exception = $_;
warn $_ if $args->{debug};
return '';
}
finally {
if ( !$assert_ret ) {
sleep( $args->{interval} );
}
};
return $try_ret if $try_ret;
}
# No need to repeat ourselves if we're already debugging.
warn $exception if $exception && !$args->{debug};
return '';
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
Selenium::Waiter - Provides a utility wait_until function
=head1 VERSION
version 1.33
=head1 SYNOPSIS
use Selenium::Waiter qw/wait_until/;
my $d = Selenium::Remote::Driver->new;
my $div = wait_until { $d->find_element('div', 'css') };
=head1 FUNCTIONS
=head2 wait_until
Exported by default, it takes a BLOCK (required) and optionally a
hash of configuration params. It uses a prototype to take its
arguments, so usage looks look like:
use Selenium::Waiter;
my $div = wait_until { $driver->find_element('div', 'css') };
The above snippet will search for C<css=div> for thirty seconds; if it
ever finds the element, it will immediately return. More generally,
Once the BLOCK returns anything truthy, the C<wait_until> will stop
evaluating and the return of the BLOCK will be returned to you. If the
BLOCK never returns a truthy value, we'll wait until the elapsed time
has increased past the timeout and then return an empty string C<''>.
B<Achtung!> Please make sure that the BLOCK you pass in can be
executed in a timely fashion. For Webdriver, that means that you
should set the appropriate C<implicit_wait> timeout low (a second or
less!) so that we can rerun the assert sub repeatedly. We don't do
anything fancy behind the scenes: we just execute the BLOCK you pass
in and sleep between iterations. If your BLOCK actively blocks for
thirty seconds, like a C<find_element> would do with an
C<implicit_wait> of 30 seconds, we won't be able to help you at all -
that blocking behavior is on the webdriver server side, and is out of
our control. We'd run one iteration, get blocked for thirty seconds,
and return control to you at that point.
=head4 Dying
PLEASE check the return value before proceeding, as we unwisely
suppress any attempts your BLOCK may make to die or croak. The BLOCK
you pass is called in a L<Try::Tiny/try>, and if any of the
invocations of your function throw and the BLOCK never becomes true,
we'll carp exactly once at the end immediately before returning
false. We overwrite the death message from each iteration, so at the
end, you'll only see the most recent death message.
# warns once after thirty seconds: "kept from dying";
wait_until { die 'kept from dying' };
The output of C<die>s from each iteration can be exposed if you wish
to see the massacre:
# carps: "kept from dying" once a second for thirty seconds
wait_until { die 'kept from dying' } debug => 1;
=head4 Timeouts and Intervals
You can also customize the timeout, and/or the retry interval between
iterations.
# prints hi three four times at 0, 3, 6, and 9 seconds
wait_until { print 'hi'; '' } timeout => 10, interval => 3;
=head1 SEE ALSO
Please see those modules/websites for more information related to this module.
=over 4
=item *
L<Selenium::Remote::Driver|Selenium::Remote::Driver>
=back
=head1 BUGS
Please report any bugs or feature requests on the bugtracker website
L<https://github.com/teodesian/Selenium-Remote-Driver/issues>
When submitting a bug or request, please include a test-file or a
patch to an existing test-file that illustrates the bug or desired
feature.
=head1 AUTHORS
Current Maintainers:
=over 4
=item *
Daniel Gempesaw <gempesaw@gmail.com>
=item *
Emmanuel Peroumalnaïk <peroumalnaik.emmanuel@gmail.com>
=back
Previous maintainers:
=over 4
=item *
Luke Closs <cpan@5thplane.com>
=item *
Mark Stosberg <mark@stosberg.com>
=back
Original authors:
=over 4
=item *
Aditya Ivaturi <ivaturi@gmail.com>
=back
=head1 COPYRIGHT AND LICENSE
Copyright (c) 2010-2011 Aditya Ivaturi, Gordon Child
Copyright (c) 2014-2017 Daniel Gempesaw
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
=cut