ActionChains.pm 11 KB

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