ActionChains.pm 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  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. foreach my $v (@$value) {
  116. push @{ $self->actions },
  117. sub { $self->driver->general_action( actions => [ { type => 'key', id => 'key', actions => [ { type => 'keyDown', value => $v } ] } ] ) };
  118. }
  119. return $self;
  120. }
  121. sub key_up {
  122. my $self = shift;
  123. my ( $value, $element ) = @_;
  124. if ( defined($element) ) {
  125. $self->click($element);
  126. }
  127. foreach my $v (@$value) {
  128. push @{ $self->actions },
  129. sub { $self->driver->$self->driver->general_action( actions => [ { type => 'key', id => 'key', actions => [ { type => 'keyUp', value => $v } ] } ] ) };
  130. }
  131. return $self;
  132. }
  133. sub send_keys {
  134. my $self = shift;
  135. my $keys = shift;
  136. push @{ $self->actions },
  137. sub { $self->driver->get_active_element->send_keys($keys) };
  138. $self;
  139. }
  140. sub send_keys_to_element {
  141. my $self = shift;
  142. my ( $element, $keys ) = @_;
  143. push @{ $self->actions }, sub { $element->send_keys($keys) };
  144. $self;
  145. }
  146. 1;
  147. __END__
  148. =pod
  149. =head1 SYNOPSIS
  150. use Selenium::Remote::Driver;
  151. use Selenium::ActionChains;
  152. my $driver = Selenium::Remote::Driver->new;
  153. my $action_chains = Selenium::ActionChains->new(driver => $driver);
  154. $driver->get("http://www.some.web/site");
  155. my $elt_1 = $driver->find_element("//*[\@id='someid']");
  156. my $elt_2 = $driver->find_element("//*[\@id='someotherid']");
  157. $action_chains->send_keys_to_element($elt_1)->click($elt_2)->perform;
  158. =head1 DESCRIPTION
  159. This module implements ActionChains for Selenium, which is a way of automating
  160. low level interactions like mouse movements, mouse button actions , key presses and
  161. context menu interactions.
  162. 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>.
  163. =head1 DRAG AND DROP IS NOT WORKING !
  164. 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>.
  165. Nevertheless, we decided to implement the function, because eventually one day it will work.
  166. 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>.
  167. =head1 FUNCTIONS
  168. =head2 new
  169. Creates a new ActionChains object. Requires a Selenium::Remote::Driver as a mandatory parameter:
  170. my $driver = Selenium::Remote::Driver->new;
  171. my $action_chains = Selenium::ActionChains->new(driver => $driver);
  172. =head2 perform
  173. Performs all the actions stored in the ActionChains object in the order they were called:
  174. Args: None
  175. Usage:
  176. my $action_chains = Selenium::ActionChains->new(driver => $driver);
  177. # assuming that $some_element and $other_element are valid
  178. # Selenium::Remote::WebElement objects
  179. $action_chains->click($some_element);
  180. $action_chains->move_to_element($other_element);
  181. $action_chains->click($other_element);
  182. # click some_element, move to other_element, then click other_element
  183. $action_chains->perform;
  184. =head2 click
  185. Clicks an element. If none specified, clicks on current mouse position.
  186. Args: A Selenium::Remote::WebElement object
  187. Usage:
  188. my $element = $driver->find_element("//div[\@id='some_id']");
  189. $action_chains->click($element);
  190. =head2 click_and_hold
  191. Holds down the left mouse button on an element. If none specified, clicks on current
  192. mouse position.
  193. Args: A Selenium::Remote::WebElement object
  194. Usage:
  195. my $element = $driver->find_element("//div[\@id='some_id']");
  196. $action_chains->click_and_hold($element);
  197. =head2 context_click
  198. Right clicks an element. If none specified, right clicks on current mouse
  199. position.
  200. Args: A Selenium::Remote::WebElement object
  201. Usage:
  202. my $element = $driver->find_element("//div[\@id='some_id']");
  203. $action_chains->context_click($element);
  204. =head2 double_click
  205. Double clicks an element. If none specified, double clicks on current mouse
  206. position.
  207. Args: A Selenium::Remote::WebElement object
  208. Usage:
  209. my $element = $driver->find_element("//div[\@id='some_id']");
  210. $action_chains->double_click($element);
  211. =head2 drag_and_drop - NOT WORKING
  212. Holds down the left mouse button on the source element, then moves to the target
  213. element and releases the mouse button. IT IS NOT WORKING DUE TO CURRENT SELENIUM
  214. LIMITATIONS.
  215. Args:
  216. A source Selenium::Remote::WebElement object
  217. A target Selenium::Remote::WebElement object
  218. Usage:
  219. my $src_element = $driver->find_element("//*[\@class='foo']");
  220. my $tgt_element = $driver->find_element("//*[\@class='bar']");
  221. $action_chains->drag_and_drop($src_element,$tgt_element);
  222. =head2 drag_and_drop_by_offset - NOT WORKING
  223. Holds down the left mouse button on the source element, then moves to the offset
  224. specified and releases the mouse button. IT IS NOT WORKING DUE TO CURRENT SELENIUM
  225. LIMITATIONS.
  226. Args:
  227. A source Selenium::Remote::WebElement object
  228. An integer X offset
  229. An integer Y offset
  230. Usage:
  231. my $src_element = $driver->find_element("//*[\@class='foo']");
  232. my $xoffset = 10;
  233. my $yoffset = 10;
  234. $action_chains->drag_and_drop($src_element,$xoffset,$yoffset);
  235. =head2 key_down
  236. Sends key presses only, without releasing them.
  237. Should be used only with modifier keys (Control, Alt, Shift)
  238. Args:
  239. An array ref to keys to send. Use the KEY constant from Selenium::Remote::WDKeys
  240. The element to send keys to. If none, sends keys to the current focused element
  241. Usage:
  242. use Selenium::Remote::WDKeys 'KEYS';
  243. $action_chains->key_down( [ KEYS->{'alt'} ] );
  244. =head2 key_up
  245. Releases a mofifier key.
  246. Args:
  247. An array ref to keys to send. Use the KEY constant from Selenium::Remote::WDKeys
  248. The element to send keys to. If none, sends keys to the current focused element
  249. Usage:
  250. use Selenium::Remote::WDKeys 'KEYS';
  251. my $element = $driver->find_element('foo','id');
  252. $action_chains->key_up( [ KEYS->{'alt'} ],$element);
  253. =head2 move_by_offset
  254. Moves the mouse to an offset from current mouse position.
  255. Args:
  256. An integer X offset
  257. An integer Y offset
  258. Usage:
  259. $action_chains->move_by_offset(10,100);
  260. =head2 move_to_element
  261. Moves the mouse to the middle of an element
  262. Args:
  263. A Selenium::Remote::WebElement to move to
  264. Usage:
  265. my $element = $driver->find_element('foo','id');
  266. $action_chains->move_to_element($element);
  267. =head2 move_to_element_with_offset
  268. Moves the mouse by an offset of the specified element.
  269. Offsets are relative to the top-left corner of the element
  270. Args:
  271. A Selenium::Remote::WebElement
  272. An integer X offset
  273. An integer Y offset
  274. Usage:
  275. my $element = $driver->find_element('foo','id');
  276. $action_chains->move_to_element_with_offset($element,10,10);
  277. =head2 release
  278. Releases a held mouse_button
  279. Args:
  280. A Selenium::Remote::WebElement, the element to mouse up
  281. Usage:
  282. my $element = $driver->find_element('foo','id');
  283. $action_chains->release($element);
  284. =head2 send_keys
  285. Sends keys to the currently focused element
  286. Args:
  287. The keys to send
  288. Usage:
  289. $action_chains->send_keys('abcd');
  290. =head2 send_keys_to_element
  291. Sends keys to an element
  292. Args:
  293. A Selenium::Remote::WebElement
  294. The keys to send
  295. Usage:
  296. my $element = $driver->find_element('foo','id');
  297. $action_chains->send_keys_to_element($element,'abcd');
  298. =cut