-
Notifications
You must be signed in to change notification settings - Fork 91
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #204 from gempesaw/action-chains-experiment
Implement action chains
- Loading branch information
Showing
5 changed files
with
582 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,396 @@ | ||
package Selenium::ActionChains; | ||
|
||
# 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 | ||
=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>. | ||
=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'); | ||
=cut |
Oops, something went wrong.