ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-WebDriver/WebDriver.pm
(Generate patch)

Comparing AnyEvent-WebDriver/WebDriver.pm (file contents):
Revision 1.11 by root, Tue Aug 28 23:47:05 2018 UTC vs.
Revision 1.12 by root, Wed Aug 29 02:17:51 2018 UTC

37To make most of this module, or, in fact, to make any reasonable use of 37To make most of this module, or, in fact, to make any reasonable use of
38this module, you would need to refer to the W3C WebDriver recommendation, 38this module, you would need to refer to the W3C WebDriver recommendation,
39which can be found L<here|https://www.w3.org/TR/webdriver1/>: 39which can be found L<here|https://www.w3.org/TR/webdriver1/>:
40 40
41 https://www.w3.org/TR/webdriver1/ 41 https://www.w3.org/TR/webdriver1/
42
43=head2 CONVENTIONS
44
45Unless otherwise stated, all delays and time differences in this module
46are represented as an integer number of milliseconds.
42 47
43=cut 48=cut
44 49
45package AnyEvent::WebDriver; 50package AnyEvent::WebDriver;
46 51
49use Carp (); 54use Carp ();
50use JSON::XS (); 55use JSON::XS ();
51use AnyEvent (); 56use AnyEvent ();
52use AnyEvent::HTTP (); 57use AnyEvent::HTTP ();
53 58
54our $VERSION = 0.1; 59our $VERSION = 0.2;
55 60
56our $WEB_ELEMENT_IDENTIFIER = "element-6066-11e4-a52e-4f735466cecf"; 61our $WEB_ELEMENT_IDENTIFIER = "element-6066-11e4-a52e-4f735466cecf";
57 62
58my $json = JSON::XS->new 63my $json = JSON::XS->new
59 ->utf8; 64 ->utf8;
134 }; 139 };
135 140
136 goto &$name; 141 goto &$name;
137} 142}
138 143
139=head2 CREATING WEBDRIVER OBJECTS 144=head2 WEBDRIVER OBJECTS
140 145
141=over 146=over
142 147
143=item new AnyEvent::WebDriver key => value... 148=item new AnyEvent::WebDriver key => value...
144 149
196 201
197 $self->delete_session 202 $self->delete_session
198 if exists $self->{sid}; 203 if exists $self->{sid};
199} 204}
200 205
206=item $al = $wd->actions
207
208Creates an action list associated with this WebDriver. See L<ACTION
209LISTS>, below, for full details.
210
211=cut
212
213sub actions {
214 AnyEvent::WebDriver::Actions->new (wd => $_[0])
215}
216
201=back 217=back
202 218
203=head2 SIMPLIFIED API 219=head2 SIMPLIFIED API
204 220
205This section documents the simplified API, which is really just a very 221This section documents the simplified API, which is really just a very
407 423
408=item $handles = $wd->switch_to_frame ($frame) 424=item $handles = $wd->switch_to_frame ($frame)
409 425
410Switch to the given frame identified by C<$frame>, which must be either 426Switch to the given frame identified by C<$frame>, which must be either
411C<undef> to go back to the top-level browsing context, an integer to 427C<undef> to go back to the top-level browsing context, an integer to
412select the nth subframe, or an element object (as e.g. returned by the 428select the nth subframe, or an element object.
413C<element_object> method.
414 429
415=cut 430=cut
416 431
417sub switch_to_frame_ { 432sub switch_to_frame_ {
418 $_[0]->post_ (frame => { id => "$_[1]" }, $_[2]); 433 $_[0]->post_ (frame => { id => "$_[1]" }, $_[2]);
479 494
480=over 495=over
481 496
482=cut 497=cut
483 498
484=item $element_id = $wd->find_element ($location_strategy, $selector) 499=item $element = $wd->find_element ($location_strategy, $selector)
485 500
486Finds the first element specified by the given selector and returns its 501Finds the first element specified by the given selector and returns its
487web element ID (the strong, not the object from the protocol). Raises an 502element object. Raises an error when no element was found.
488error when no element was found.
489 503
490 $element = $wd->find_element ("css selector" => "body a"); 504 $element = $wd->find_element ("css selector" => "body a");
491 $element = $wd->find_element ("link text" => "Click Here For Porn"); 505 $element = $wd->find_element ("link text" => "Click Here For Porn");
492 $element = $wd->find_element ("partial link text" => "orn"); 506 $element = $wd->find_element ("partial link text" => "orn");
493 $element = $wd->find_element ("tag name" => "input"); 507 $element = $wd->find_element ("tag name" => "input");
494 $element = $wd->find_element ("xpath" => '//input[@type="text"]'); 508 $element = $wd->find_element ("xpath" => '//input[@type="text"]');
495 => e.g. "decddca8-5986-4e1d-8c93-efe952505a5f" 509 => e.g. { "element-6066-11e4-a52e-4f735466cecf" => "decddca8-5986-4e1d-8c93-efe952505a5f" }
496 510
497=item $element_ids = $wd->find_elements ($location_strategy, $selector) 511=item $elements = $wd->find_elements ($location_strategy, $selector)
498 512
499As above, but returns an arrayref of all found element IDs. 513As above, but returns an arrayref of all found element objects.
500 514
501=item $element_id = $wd->find_element_from_element ($element_id, $location_strategy, $selector) 515=item $element = $wd->find_element_from_element ($element, $location_strategy, $selector)
502 516
503Like C<find_element>, but looks only inside the specified C<$element>. 517Like C<find_element>, but looks only inside the specified C<$element>.
504 518
505=item $element_ids = $wd->find_elements_from_element ($element_id, $location_strategy, $selector) 519=item $elements = $wd->find_elements_from_element ($element, $location_strategy, $selector)
506 520
507Like C<find_elements>, but looks only inside the specified C<$element>. 521Like C<find_elements>, but looks only inside the specified C<$element>.
508 522
509 my $head = $wd->find_element ("tag name" => "head"); 523 my $head = $wd->find_element ("tag name" => "head");
510 my $links = $wd->find_elements_from_element ($head, "tag name", "link"); 524 my $links = $wd->find_elements_from_element ($head, "tag name", "link");
511 525
512=item $element_id = $wd->get_active_element 526=item $element = $wd->get_active_element
513 527
514Returns the active element. 528Returns the active element.
515 529
516=cut 530=cut
517 531
518sub find_element_ { 532sub find_element_ {
519 my $cb = pop;
520 $_[0]->post_ (element => { using => "$_[1]", value => "$_[2]" }, sub { 533 $_[0]->post_ (element => { using => "$_[1]", value => "$_[2]" }, $_[3]);
521 $cb->($_[0], $_[0] ne "200" ? $_[1] : $_[1]{$WEB_ELEMENT_IDENTIFIER})
522 });
523} 534}
524 535
525sub find_elements_ { 536sub find_elements_ {
526 my $cb = pop;
527 $_[0]->post_ (elements => { using => "$_[1]", value => "$_[2]" }, sub { 537 $_[0]->post_ (elements => { using => "$_[1]", value => "$_[2]" }, $_[3]);
528 $cb->($_[0], $_[0] ne "200" ? $_[1] : [ map $_->{$WEB_ELEMENT_IDENTIFIER}, @{$_[1]} ]);
529 });
530} 538}
531 539
532sub find_element_from_element_ { 540sub find_element_from_element_ {
533 my $cb = pop;
534 $_[0]->post_ ("element/$_[1]/element" => { using => "$_[2]", value => "$_[3]" }, sub { 541 $_[0]->post_ ("element/$_[1]/element" => { using => "$_[2]", value => "$_[3]" }, $_[4]);
535 $cb->($_[0], $_[0] ne "200" ? $_[1] : $_[1]{$WEB_ELEMENT_IDENTIFIER})
536 });
537} 542}
538 543
539sub find_elements_from_element_ { 544sub find_elements_from_element_ {
540 my $cb = pop;
541 $_[0]->post_ ("element/$_[1]/elements" => { using => "$_[2]", value => "$_[3]" }, sub { 545 $_[0]->post_ ("element/$_[1]/elements" => { using => "$_[2]", value => "$_[3]" }, $_[4]);
542 $cb->($_[0], $_[0] ne "200" ? $_[1] : [ map $_->{$WEB_ELEMENT_IDENTIFIER}, @{$_[1]} ]);
543 });
544} 546}
545 547
546sub get_active_element_ { 548sub get_active_element_ {
547 my $cb = pop;
548 $_[0]->get_ ("element/active" => sub { 549 $_[0]->get_ ("element/active" => $_[1]);
549 $cb->($_[0], $_[0] ne "200" ? $_[1] : $_[1]{$WEB_ELEMENT_IDENTIFIER})
550 });
551} 550}
552 551
553=back 552=back
554 553
555=head3 ELEMENT STATE 554=head3 ELEMENT STATE
560 559
561=item $bool = $wd->is_element_selected 560=item $bool = $wd->is_element_selected
562 561
563Returns whether the given input or option element is selected or not. 562Returns whether the given input or option element is selected or not.
564 563
565=item $string = $wd->get_element_attribute ($element_id, $name) 564=item $string = $wd->get_element_attribute ($element, $name)
566 565
567Returns the value of the given attribute. 566Returns the value of the given attribute.
568 567
569=item $string = $wd->get_element_property ($element_id, $name) 568=item $string = $wd->get_element_property ($element, $name)
570 569
571Returns the value of the given property. 570Returns the value of the given property.
572 571
573=item $string = $wd->get_element_css_value ($element_id, $name) 572=item $string = $wd->get_element_css_value ($element, $name)
574 573
575Returns the value of the given CSS value. 574Returns the value of the given CSS value.
576 575
577=item $string = $wd->get_element_text ($element_id) 576=item $string = $wd->get_element_text ($element)
578 577
579Returns the (rendered) text content of the given element. 578Returns the (rendered) text content of the given element.
580 579
581=item $string = $wd->get_element_tag_name ($element_id) 580=item $string = $wd->get_element_tag_name ($element)
582 581
583Returns the tag of the given element. 582Returns the tag of the given element.
584 583
585=item $rect = $wd->get_element_rect ($element_id) 584=item $rect = $wd->get_element_rect ($element)
586 585
587Returns the element rect(angle) of the given element. 586Returns the element rect(angle) of the given element.
588 587
589=item $bool = $wd->is_element_enabled 588=item $bool = $wd->is_element_enabled
590 589
591Returns whether the element is enabled or not. 590Returns whether the element is enabled or not.
592 591
593=cut 592=cut
594 593
595sub is_element_selected_ { 594sub is_element_selected_ {
596 $_[0]->get_ ("element/$_[1]/selected" => $_[2]); 595 $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/selected" => $_[2]);
597} 596}
598 597
599sub get_element_attribute_ { 598sub get_element_attribute_ {
600 $_[0]->get_ ("element/$_[1]/attribute/$_[2]" => $_[3]); 599 $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/attribute/$_[2]" => $_[3]);
601} 600}
602 601
603sub get_element_property_ { 602sub get_element_property_ {
604 $_[0]->get_ ("element/$_[1]/property/$_[2]" => $_[3]); 603 $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/property/$_[2]" => $_[3]);
605} 604}
606 605
607sub get_element_css_value_ { 606sub get_element_css_value_ {
608 $_[0]->get_ ("element/$_[1]/css/$_[2]" => $_[3]); 607 $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/css/$_[2]" => $_[3]);
609} 608}
610 609
611sub get_element_text_ { 610sub get_element_text_ {
612 $_[0]->get_ ("element/$_[1]/text" => $_[2]); 611 $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/text" => $_[2]);
613} 612}
614 613
615sub get_element_tag_name_ { 614sub get_element_tag_name_ {
616 $_[0]->get_ ("element/$_[1]/name" => $_[2]); 615 $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/name" => $_[2]);
617} 616}
618 617
619sub get_element_rect_ { 618sub get_element_rect_ {
620 $_[0]->get_ ("element/$_[1]/rect" => $_[2]); 619 $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/rect" => $_[2]);
621} 620}
622 621
623sub is_element_enabled_ { 622sub is_element_enabled_ {
624 $_[0]->get_ ("element/$_[1]/enabled" => $_[2]); 623 $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/enabled" => $_[2]);
625} 624}
626 625
627=back 626=back
628 627
629=head3 ELEMENT INTERACTION 628=head3 ELEMENT INTERACTION
630 629
631=over 630=over
632 631
633=cut 632=cut
634 633
635=item $wd->element_click ($element_id) 634=item $wd->element_click ($element)
636 635
637Clicks the given element. 636Clicks the given element.
638 637
639=item $wd->element_clear ($element_id) 638=item $wd->element_clear ($element)
640 639
641Clear the contents of the given element. 640Clear the contents of the given element.
642 641
643=item $wd->element_send_keys ($element_id, $text) 642=item $wd->element_send_keys ($element, $text)
644 643
645Sends the given text as key events to the given element. 644Sends the given text as key events to the given element.
646 645
647=cut 646=cut
648 647
649sub element_click_ { 648sub element_click_ {
650 $_[0]->post_ ("element/$_[1]/click" => undef, $_[2]); 649 $_[0]->post_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/click" => undef, $_[2]);
651} 650}
652 651
653sub element_clear_ { 652sub element_clear_ {
654 $_[0]->post_ ("element/$_[1]/clear" => undef, $_[2]); 653 $_[0]->post_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/clear" => undef, $_[2]);
655} 654}
656 655
657sub element_send_keys_ { 656sub element_send_keys_ {
658 $_[0]->post_ ("element/$_[1]/value" => { text => "$_[2]" }, $_[3]); 657 $_[0]->post_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/value" => { text => "$_[2]" }, $_[3]);
659} 658}
660 659
661=back 660=back
662 661
663=head3 DOCUMENT HANDLING 662=head3 DOCUMENT HANDLING
763=cut 762=cut
764 763
765=item $wd->perform_actions ($actions) 764=item $wd->perform_actions ($actions)
766 765
767Perform the given actions (an arrayref of action specifications simulating 766Perform the given actions (an arrayref of action specifications simulating
768user activity). For further details, read the spec. 767user activity, or an C<AnyEvent::WebDriver::Actions> object). For further
768details, read the spec or the section L<ACTION LISTS>, below.
769 769
770An example to get you started: 770An example to get you started (see the next example for a mostly
771equivalent example using the C<AnyEvent::WebDriver::Actions> helper API):
771 772
772 $wd->navigate_to ("https://duckduckgo.com/html"); 773 $wd->navigate_to ("https://duckduckgo.com/html");
773 $wd->set_timeouts ({ implicit => 10000 });
774 my $input = $wd->find_element ("css selector", 'input[type="text"]'); 774 my $input = $wd->find_element ("css selector", 'input[type="text"]');
775 $wd->perform_actions ([ 775 $wd->perform_actions ([
776 { 776 {
777 id => "myfatfinger", 777 id => "myfatfinger",
778 type => "pointer", 778 type => "pointer",
779 pointerType => "touch", 779 pointerType => "touch",
780 actions => [ 780 actions => [
781 { type => "pointerMove", duration => 100, origin => $wd->element_object ($input), x => 40, y => 5 }, 781 { type => "pointerMove", duration => 100, origin => $input, x => 40, y => 5 },
782 { type => "pointerDown", button => 1 }, 782 { type => "pointerDown", button => 1 },
783 { type => "pause", duration => 40 }, 783 { type => "pause", duration => 40 },
784 { type => "pointerUp", button => 1 }, 784 { type => "pointerUp", button => 1 },
785 ], 785 ],
786 }, 786 },
806 { type => "pause", duration => 5000 }, 806 { type => "pause", duration => 5000 },
807 ], 807 ],
808 }, 808 },
809 ]); 809 ]);
810 810
811And here is essentially the same (except for fewer pauses) example as
812above, using the much simpler C<AnyEvent::WebDriver::Actions> API. Note
813that the pointer up and key down event happen concurrently in this
814example:
815
816 $wd->navigate_to ("https://duckduckgo.com/html");
817 my $input = $wd->find_element ("css selector", 'input[type="text"]');
818 $wd->actions
819 ->move ($input, 40, 5, "touch1")
820 ->click;
821 ->key ("a");
822 ->key ("b");
823 ->pause (2000);
824 ->key ("\x{E007}")
825 ->pause (5000);
826 ->perform;
827
811=item $wd->release_actions 828=item $wd->release_actions
812 829
813Release all keys and pointer buttons currently depressed. 830Release all keys and pointer buttons currently depressed.
814 831
815=cut 832=cut
816 833
817sub perform_actions_ { 834sub perform_actions_ {
835 if (UNIVERSAL::isa $_[1], AnyEvent::WebDriver::Actions::) {
836 my ($actions, $duration) = $_[1]->compile;
837 local $_[0]{timeout} = $_[0]{timeout} + $duration * 1e-3;
838 $_[0]->post_ (actions => { actions => $actions }, $_[2]);
839 } else {
818 $_[0]->post_ (actions => { actions => $_[1] }, $_[2]); 840 $_[0]->post_ (actions => { actions => $_[1] }, $_[2]);
841 }
819} 842}
820 843
821sub release_actions_ { 844sub release_actions_ {
822 $_[0]->delete_ (actions => $_[1]); 845 $_[0]->delete_ (actions => $_[1]);
823} 846}
875 898
876=item $wd->take_screenshot 899=item $wd->take_screenshot
877 900
878Create a screenshot, returning it as a PNG image in a C<data:> URL. 901Create a screenshot, returning it as a PNG image in a C<data:> URL.
879 902
880=item $wd->take_element_screenshot ($element_id) 903=item $wd->take_element_screenshot ($element)
881 904
882Accept a simple dialog, if present. 905Accept a simple dialog, if present.
883 906
884=cut 907=cut
885 908
886sub take_screenshot_ { 909sub take_screenshot_ {
887 $_[0]->get_ (screenshot => $_[1]); 910 $_[0]->get_ (screenshot => $_[1]);
888} 911}
889 912
890sub take_element_screenshot_ { 913sub take_element_screenshot_ {
891 $_[0]->get_ ("element/$_[1]/screenshot" => $_[2]); 914 $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/screenshot" => $_[2]);
892} 915}
893 916
894=back 917=back
895 918
896=head2 HELPER METHODS 919=head2 ACTION LISTS
897 920
921Action lists can be quite complicated. Or at least it took a while for
922me to twist my head around them. Basically, an action list consists of a
923number of sources representing devices (such as a finger, a mouse, a pen
924or a keyboard) and a list of actions for each source.
925
926An action can be a key press, a pointer move or a pause (time
927delay). Actions from different sources can happen "at the same time",
928while actions from a single source are executed in order.
929
930While you can provide an action list manually, it is (hopefully) less
931cumbersome to use the API described in this section to create them.
932
933The basic process of creating and performing actions is to create a new
934action list, adding action sources, followed by adding actions. Finally
935you would C<perform> those actions on the WebDriver.
936
937Virtual time progresses as long as you add actions to the same event
938source. Adding events to different sources are considered to happen
939concurrently. If you want to force time to progress, you can do this using
940a call to C<< ->pause (0) >>.
941
942Most methods here are designed to chain, i.e. they return the web actions
943object, to simplify multiple calls.
944
945For example, to simulate a mouse click to an input element, followed by
946entering some text and pressing enter, you can use this:
947
948 $wd->actions
949 ->click (1, 100)
950 ->type ("some text")
951 ->key ("Enter")
952 ->perform;
953
954By default, keyboard and mouse input sources are provided. You can create
955your own sources and use them when adding events. The above example could
956be more verbosely written like this:
957
958 $wd->actions
959 ->click (1, 100, "mouse")
960 ->type ("some text")
961 ->key ("Enter")
962 ->perform;
963
964
965
966#TODO verboser example
967
968When you specify the event source expliticly it will switch the current
969"focus" for this class of device (all keyboards are in one class, all
970pointer-like devices such as mice/fingers/pens are in one class), so you
971don't have to specify the source for subsequent actions.
972
973When you use the sources C<keyboard>, C<mouse>, C<touch1>..C<touch3>,
974C<pen> without defining them, then a suitable default source will be
975created for them.
976
898=over 977=over 4
899 978
900=cut 979=cut
901 980
902=item $object = AnyEvent::WebDriver->element_object ($element_id) 981package AnyEvent::WebDriver::Actions;
903 982
904=item $object = $wd->element_object ($element_id) 983=item $al = new AnyEvent::WebDriver::Actions
905 984
906Encoding element IDs in data structures is done by representing them as an 985Create a new empty action list object. More often you would use the C<<
907object with a special key and the element ID as value. This helper method 986$sel->action_list >> method to create one that is already associated with
908does this for you. 987a given web driver.
909 988
910=cut 989=cut
911 990
912sub element_object { 991sub new {
913 +{ $WEB_ELEMENT_IDENTIFIER => $_[1] } 992 my ($class, %kv) = @_;
993
994 $kv{last_kbd} = "keyboard";
995 $kv{last_ptr} = "mouse";
996
997 bless \%kv, $class
998}
999
1000=item $al = $al->source ($id, $type, key => value...)
1001
1002The first time you call this with a givne ID, this defines the event
1003source using the extra parameters. Subsequent calls merely switch the
1004current source for its event class.
1005
1006It's not an error to define built-in sources (such as C<keyboard> or
1007C<touch1>) differently then the defaults.
1008
1009Example: define a new touch device called C<fatfinger>.
1010
1011 $al->source (fatfinger => "pointer", pointerType => "touch");
1012
1013Example: switchdefine a new touch device called C<fatfinger>.
1014
1015 $al->source (fatfinger => "pointer", pointerType => "touch");
1016
1017=cut
1018
1019sub _default_source {
1020 my ($source) = @_;
1021
1022 $source eq "keyboard" ? { actions => [], id => $source, type => "key" }
1023 : $source eq "mouse" ? { actions => [], id => $source, type => "pointer", pointerType => "mouse" }
1024 : $source eq "touch" ? { actions => [], id => $source, type => "pointer", pointerType => "touch" }
1025 : $source eq "pen" ? { actions => [], id => $source, type => "pointer", pointerType => "pen" }
1026 : Carp::croak "AnyEvent::WebDriver::Actions: event source '$source' not defined"
1027}
1028
1029my %source_class = (
1030 key => "kbd",
1031 pointer => "ptr",
1032);
1033
1034sub source {
1035 my ($self, $id, $type, %kv) = @_;
1036
1037 if (defined $type) {
1038 !exists $self->{source}{$id}
1039 or Carp::croak "AnyEvent::WebDriver::Actions: source '$id' already defined";
1040
1041 $kv{id} = $id;
1042 $kv{type} = $type;
1043 $kv{actions} = [];
1044
1045 $self->{source}{$id} = \%kv;
1046 }
1047
1048 my $source = $self->{source}{$id} ||= _default_source $id;
1049
1050 my $last = $source_class{$source->{type}} // "xxx";
1051
1052 $self->{"last_$last"} = $id;
1053
1054 $self
1055}
1056
1057sub _add {
1058 my ($self, $source, $sourcetype, $type, %kv) = @_;
1059
1060 my $last = \$self->{"last_$sourcetype"};
1061
1062 $source
1063 ? ($$last = $source)
1064 : ($source = $$last);
1065
1066 my $source = $self->{source}{$source} ||= _default_source $source;
1067
1068 my $al = $source->{actions};
1069
1070 push @$al, { type => "pause" }
1071 while @$al < $self->{tick} - 1;
1072
1073 $kv{type} = $type;
1074
1075 push @{ $source->{actions} }, \%kv;
1076
1077 $self->{tick_duration} = $kv{duration}
1078 if $kv{duration} > $self->{tick_duration};
1079
1080 if ($self->{tick} != @$al) {
1081 $self->{tick} = @$al;
1082 $self->{duration} += delete $self->{tick_duration};
1083 }
1084
1085 $self
1086}
1087
1088=item $al = $al->pause ($duration)
1089
1090Creates a pause with the given duration. Makes sure that time progresses
1091in any case, even when C<$duration> is C<0>.
1092
1093=cut
1094
1095sub pause {
1096 my ($self, $duration) = @_;
1097
1098 $self->{tick_duration} = $duration
1099 if $duration > $self->{tick_duration};
1100
1101 $self->{duration} += delete $self->{tick_duration};
1102
1103 # find the source with the longest list
1104
1105 for my $source (values %{ $self->{source} }) {
1106 if (@{ $source->{actions} } == $self->{tick}) {
1107 # this source is one of the longest
1108
1109 # create a pause event only if $duration is non-zero...
1110 push @{ $source->{actions} }, { type => "pause", duration => $duration*1 }
1111 if $duration;
1112
1113 # ... but advance time in any case
1114 ++$self->{tick};
1115
1116 return $self;
1117 }
1118 }
1119
1120 # no event sources are longest. so advance time in any case
1121 ++$self->{tick};
1122
1123 Carp::croak "AnyEvent::WebDriver::Actions: multiple pause calls in a row not (yet) supported"
1124 if $duration;
1125
1126 $self
1127}
1128
1129=item $al = $al->pointer_down ($button, $source)
1130
1131=item $al = $al->pointer_up ($button, $source)
1132
1133Press or release the given button. C<$button> defaults to C<1>.
1134
1135=item $al = $al->click ($button, $source)
1136
1137Convenience function that creates a button press and release action
1138without any delay between them. C<$button> defaults to C<1>.
1139
1140=item $al = $al->doubleclick ($button, $source)
1141
1142Convenience function that creates two button press and release action
1143pairs in a row, with no unnecessary delay between them. C<$button>
1144defaults to C<1>.
1145
1146=cut
1147
1148sub pointer_down {
1149 my ($self, $button, $source) = @_;
1150
1151 $self->_add ($source, ptr => pointerDown => button => ($button // 1)*1)
1152}
1153
1154sub pointer_up {
1155 my ($self, $button, $source) = @_;
1156
1157 $self->_add ($source, ptr => pointerUp => button => ($button // 1)*1)
1158}
1159
1160sub click {
1161 my ($self, $button, $source) = @_;
1162
1163 $self
1164 ->pointer_down ($button, $source)
1165 ->pointer_up ($button)
1166}
1167
1168sub doubleclick {
1169 my ($self, $button, $source) = @_;
1170
1171 $self
1172 ->click ($button, $source)
1173 ->click ($button)
1174}
1175
1176=item $al = $al->move ($button, $origin, $x, $y, $duration, $source)
1177
1178Moves a pointer to the given position, relative to origin (either
1179"viewport", "pointer" or an element object.
1180
1181=cut
1182
1183sub move {
1184 my ($self, $origin, $x, $y, $duration, $source) = @_;
1185
1186 $self->_add ($source, ptr => pointerMove =>
1187 origin => $origin, x => $x*1, y => $y*1, duration => $duration*1)
1188}
1189
1190=item $al = $al->keyDown ($key, $source)
1191
1192=item $al = $al->keyUp ($key, $source)
1193
1194Press or release the given key.
1195
1196=cut
1197
1198sub key_down {
1199 my ($self, $key, $source) = @_;
1200
1201 $self->_add ($source, kbd => keyDown => value => $key)
1202}
1203
1204sub key_up {
1205 my ($self, $key, $source) = @_;
1206
1207 $self->_add ($source, kbd => keyUp => value => $key)
1208}
1209
1210sub key {
1211 my ($self, $key, $source) = @_;
1212
1213 $self
1214 ->key_down ($key, $source)
1215 ->key_up ($key)
1216}
1217
1218=item $al->perform ($wd)
1219
1220Finaluses and compiles the list, if not done yet, and calls C<<
1221$wd->perform >> with it.
1222
1223If C<$wd> is undef, and the action list was created using the C<<
1224$wd->actions >> method, then perform it against that WebDriver object.
1225
1226There is no underscore variant - call the C<perform_actions_> method with
1227the action object instead.
1228
1229=item $al->perform_release ($wd)
1230
1231Exactly like C<perform>, but additionally call C<release_actions>
1232afterwards.
1233
1234=cut
1235
1236sub perform {
1237 my ($self, $wd) = @_;
1238
1239 ($wd //= $self->{wd})->perform_actions ($self)
1240}
1241
1242sub perform_release {
1243 my ($self, $wd) = @_;
1244
1245 ($wd //= $self->{wd})->perform_actions ($self);
1246 $wd->release_actions;
1247}
1248
1249=item ($actions, $duration) = $al->compile
1250
1251Finalises and compiles the list, if not done yet, and returns an actions
1252object suitable for calls to C<< $wd->perform_actions >>. When called in
1253list context, additionally returns the total duration of the action list.
1254
1255Since building large action lists can take nontrivial amounts of time,
1256it can make sense to build an action list only once and then perform it
1257multiple times.
1258
1259Actions must not be added after compiling a list.
1260
1261=cut
1262
1263sub compile {
1264 my ($self) = @_;
1265
1266 $self->{duration} += delete $self->{tick_duration};
1267
1268 delete $self->{tick};
1269 delete $self->{last_kbd};
1270 delete $self->{last_ptr};
1271
1272 $self->{actions} ||= [values %{ delete $self->{source} }];
1273
1274 wantarray
1275 ? ($self->{actions}, $self->{duration})
1276 : $self->{actions}
914} 1277}
915 1278
916=back 1279=back
917 1280
918=head2 EVENT BASED API 1281=head2 EVENT BASED API
1017 1380
1018Example: implement C<execute_script>, which needs some parameters: 1381Example: implement C<execute_script>, which needs some parameters:
1019 1382
1020 $results = $wd->post ("execute/sync" => { script => "$javascript", args => [] }); 1383 $results = $wd->post ("execute/sync" => { script => "$javascript", args => [] });
1021 1384
1022Example: call C<find_elements> to find all C<IMG> elements, stripping the 1385Example: call C<find_elements> to find all C<IMG> elements:
1023returned element objects to only return the element ID strings:
1024 1386
1025 my $elems = $wd->post (elements => { using => "css selector", value => "img" }); 1387 $elems = $wd->post (elements => { using => "css selector", value => "img" });
1026
1027 # yes, the W3C found an interesting way around the typelessness of JSON
1028 $_ = $_->{"element-6066-11e4-a52e-4f735466cecf"}
1029 for @$elems;
1030 1388
1031=cut 1389=cut
1032 1390
1033=head1 HISTORY 1391=head1 HISTORY
1034 1392

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines