ActionChains.pm 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. package Selenium::ActionChains;
  2. use strict;
  3. use warnings;
  4. # ABSTRACT: Action chains for Selenium::Remote::Driver
  5. use Moo;
  6. =for Pod::Coverage driver
  7. =cut
  8. has 'driver' => ( is => 'ro', );
  9. has 'actions' => (
  10. is => 'lazy',
  11. builder => sub { [] },
  12. clearer => 1,
  13. );
  14. sub perform {
  15. my $self = shift;
  16. foreach my $action ( @{ $self->actions } ) {
  17. $action->();
  18. }
  19. }
  20. sub click {
  21. my $self = shift;
  22. my $element = shift;
  23. if ($element) {
  24. $self->move_to_element($element);
  25. }
  26. # left click
  27. push @{ $self->actions }, sub { $self->driver->click('LEFT') };
  28. $self;
  29. }
  30. sub click_and_hold {
  31. my $self = shift;
  32. my $element = shift;
  33. if ($element) {
  34. $self->move_to_element($element);
  35. }
  36. push @{ $self->actions }, sub { $self->driver->button_down };
  37. $self;
  38. }
  39. sub context_click {
  40. my $self = shift;
  41. my $element = shift;
  42. if ($element) {
  43. $self->move_to_element($element);
  44. }
  45. # right click
  46. push @{ $self->actions }, sub { $self->driver->click('RIGHT') };
  47. $self;
  48. }
  49. sub double_click {
  50. my $self = shift;
  51. my $element = shift;
  52. if ($element) {
  53. $self->move_to_element($element);
  54. }
  55. push @{ $self->actions }, sub { $self->driver->double_click };
  56. $self;
  57. }
  58. sub release {
  59. my $self = shift;
  60. my $element = shift;
  61. if ($element) {
  62. $self->move_to_element($element);
  63. }
  64. push @{ $self->actions }, sub { $self->driver->button_up };
  65. $self;
  66. }
  67. sub drag_and_drop {
  68. my $self = shift;
  69. my ( $source, $target ) = @_;
  70. $self->click_and_hold($source);
  71. $self->release($target);
  72. $self;
  73. }
  74. sub drag_and_drop_by_offset {
  75. my $self = shift;
  76. my ( $source, $xoffset, $yoffset ) = @_;
  77. $self->click_and_hold($source);
  78. $self->move_by_offset( $xoffset, $yoffset );
  79. $self->release($source);
  80. $self;
  81. }
  82. sub move_to_element {
  83. my $self = shift;
  84. my $element = shift;
  85. push @{ $self->actions },
  86. sub { $self->driver->move_to( element => $element ) };
  87. $self;
  88. }
  89. sub move_by_offset {
  90. my $self = shift;
  91. my ( $xoffset, $yoffset ) = @_;
  92. push @{ $self->actions }, sub {
  93. $self->driver->move_to( xoffset => $xoffset, yoffset => $yoffset );
  94. };
  95. $self;
  96. }
  97. sub move_to_element_with_offset {
  98. my $self = shift;
  99. my ( $element, $xoffset, $yoffset ) = @_;
  100. push @{ $self->actions }, sub {
  101. $self->driver->move_to(
  102. element => $element,
  103. xoffset => $xoffset,
  104. yoffset => $yoffset
  105. );
  106. };
  107. $self;
  108. }
  109. sub key_down {
  110. my $self = shift;
  111. my ( $value, $element ) = @_;
  112. if ( defined($element) ) {
  113. $self->click($element);
  114. }
  115. push @{ $self->actions },
  116. sub { $self->driver->general_action( actions => [ { type => 'key', id => 'key', actions => [ { type => 'keyDown', value => $value } ] } ] ) };
  117. $self;
  118. }
  119. sub key_up {
  120. my $self = shift;
  121. my ( $value, $element ) = @_;
  122. if ( defined($element) ) {
  123. $self->click($element);
  124. }
  125. push @{ $self->actions },
  126. sub { $self->driver->$self->driver->general_action( actions => [ { type => 'key', id => 'key', actions => [ { type => 'keyUp', value => $value } ] } ] ) };
  127. $self;
  128. }
  129. sub send_keys {
  130. my $self = shift;
  131. my $keys = shift;
  132. push @{ $self->actions },
  133. sub { $self->driver->get_active_element->send_keys($keys) };
  134. $self;
  135. }
  136. sub send_keys_to_element {
  137. my $self = shift;
  138. my ( $element, $keys ) = @_;
  139. push @{ $self->actions }, sub { $element->send_keys($keys) };
  140. $self;
  141. }
  142. 1;
  143. __END__
  144. =pod
  145. =head1 SYNOPSIS
  146. use Selenium::Remote::Driver;
  147. use Selenium::ActionChains;
  148. my $driver = Selenium::Remote::Driver->new;
  149. my $action_chains = Selenium::ActionChains->new(driver => $driver);
  150. $driver->get("http://www.some.web/site");
  151. my $elt_1 = $driver->find_element("//*[\@id='someid']");
  152. my $elt_2 = $driver->find_element("//*[\@id='someotherid']");
  153. $action_chains->send_keys_to_element($elt_1)->click($elt_2)->perform;
  154. =head1 DESCRIPTION
  155. This module implements ActionChains for Selenium, which is a way of automating
  156. low level interactions like mouse movements, mouse button actions , key presses and
  157. context menu interactions.
  158. 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>.
  159. =head1 DRAG AND DROP IS NOT WORKING !
  160. 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>.
  161. Nevertheless, we decided to implement the function, because eventually one day it will work.
  162. 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>.
  163. =head1 FUNCTIONS
  164. =head2 new
  165. Creates a new ActionChains object. Requires a Selenium::Remote::Driver as a mandatory parameter:
  166. my $driver = Selenium::Remote::Driver->new;
  167. my $action_chains = Selenium::ActionChains->new(driver => $driver);
  168. =head2 perform
  169. Performs all the actions stored in the ActionChains object in the order they were called:
  170. Args: None
  171. Usage:
  172. my $action_chains = Selenium::ActionChains->new(driver => $driver);
  173. # assuming that $some_element and $other_element are valid
  174. # Selenium::Remote::WebElement objects
  175. $action_chains->click($some_element);
  176. $action_chains->move_to_element($other_element);
  177. $action_chains->click($other_element);
  178. # click some_element, move to other_element, then click other_element
  179. $action_chains->perform;
  180. =head2 click
  181. Clicks an element. If none specified, clicks on current mouse position.
  182. Args: A Selenium::Remote::WebElement object
  183. Usage:
  184. my $element = $driver->find_element("//div[\@id='some_id']");
  185. $action_chains->click($element);
  186. =head2 click_and_hold
  187. Holds down the left mouse button on an element. If none specified, clicks on current
  188. mouse position.
  189. Args: A Selenium::Remote::WebElement object
  190. Usage:
  191. my $element = $driver->find_element("//div[\@id='some_id']");
  192. $action_chains->click_and_hold($element);
  193. =head2 context_click
  194. Right clicks an element. If none specified, right clicks on current mouse
  195. position.
  196. Args: A Selenium::Remote::WebElement object
  197. Usage:
  198. my $element = $driver->find_element("//div[\@id='some_id']");
  199. $action_chains->context_click($element);
  200. =head2 double_click
  201. Double clicks an element. If none specified, double clicks on current mouse
  202. position.
  203. Args: A Selenium::Remote::WebElement object
  204. Usage:
  205. my $element = $driver->find_element("//div[\@id='some_id']");
  206. $action_chains->double_click($element);
  207. =head2 drag_and_drop - NOT WORKING
  208. Holds down the left mouse button on the source element, then moves to the target
  209. element and releases the mouse button. IT IS NOT WORKING DUE TO CURRENT SELENIUM
  210. LIMITATIONS.
  211. Args:
  212. A source Selenium::Remote::WebElement object
  213. A target Selenium::Remote::WebElement object
  214. Usage:
  215. my $src_element = $driver->find_element("//*[\@class='foo']");
  216. my $tgt_element = $driver->find_element("//*[\@class='bar']");
  217. $action_chains->drag_and_drop($src_element,$tgt_element);
  218. =head2 drag_and_drop_by_offset - NOT WORKING
  219. Holds down the left mouse button on the source element, then moves to the offset
  220. specified and releases the mouse button. IT IS NOT WORKING DUE TO CURRENT SELENIUM
  221. LIMITATIONS.
  222. Args:
  223. A source Selenium::Remote::WebElement object
  224. An integer X offset
  225. An integer Y offset
  226. Usage:
  227. my $src_element = $driver->find_element("//*[\@class='foo']");
  228. my $xoffset = 10;
  229. my $yoffset = 10;
  230. $action_chains->drag_and_drop($src_element,$xoffset,$yoffset);
  231. =head2 key_down
  232. Sends key presses only, without releasing them.
  233. Should be used only with modifier keys (Control, Alt, Shift)
  234. Args:
  235. An array ref to keys to send. Use the KEY constant from Selenium::Remote::WDKeys
  236. The element to send keys to. If none, sends keys to the current focused element
  237. Usage:
  238. use Selenium::Remote::WDKeys 'KEYS';
  239. $action_chains->key_down( [ KEYS->{'alt'} ] );
  240. =head2 key_up
  241. Releases a mofifier key.
  242. Args:
  243. An array ref to keys to send. Use the KEY constant from Selenium::Remote::WDKeys
  244. The element to send keys to. If none, sends keys to the current focused element
  245. Usage:
  246. use Selenium::Remote::WDKeys 'KEYS';
  247. my $element = $driver->find_element('foo','id');
  248. $action_chains->key_up( [ KEYS->{'alt'} ],$element);
  249. =head2 move_by_offset
  250. Moves the mouse to an offset from current mouse position.
  251. Args:
  252. An integer X offset
  253. An integer Y offset
  254. Usage:
  255. $action_chains->move_by_offset(10,100);
  256. =head2 move_to_element
  257. Moves the mouse to the middle of an element
  258. Args:
  259. A Selenium::Remote::WebElement to move to
  260. Usage:
  261. my $element = $driver->find_element('foo','id');
  262. $action_chains->move_to_element($element);
  263. =head2 move_to_element_with_offset
  264. Moves the mouse by an offset of the specified element.
  265. Offsets are relative to the top-left corner of the element
  266. Args:
  267. A Selenium::Remote::WebElement
  268. An integer X offset
  269. An integer Y offset
  270. Usage:
  271. my $element = $driver->find_element('foo','id');
  272. $action_chains->move_to_element_with_offset($element,10,10);
  273. =head2 release
  274. Releases a held mouse_button
  275. Args:
  276. A Selenium::Remote::WebElement, the element to mouse up
  277. Usage:
  278. my $element = $driver->find_element('foo','id');
  279. $action_chains->release($element);
  280. =head2 send_keys
  281. Sends keys to the currently focused element
  282. Args:
  283. The keys to send
  284. Usage:
  285. $action_chains->send_keys('abcd');
  286. =head2 send_keys_to_element
  287. Sends keys to an element
  288. Args:
  289. A Selenium::Remote::WebElement
  290. The keys to send
  291. Usage:
  292. my $element = $driver->find_element('foo','id');
  293. $action_chains->send_keys_to_element($element,'abcd');
  294. =cut