File Coverage

blib/lib/AnyEvent/WebDriver.pm
Criterion Covered Total %
statement 12 229 5.2
branch 0 68 0.0
condition 0 34 0.0
subroutine 4 97 4.1
pod 10 63 15.8
total 26 491 5.3


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             AnyEvent::WebDriver - control browsers using the W3C WebDriver protocol
4              
5             =head1 SYNOPSIS
6              
7             # start geckodriver(chromedriver or any other webdriver via the shell
8             $ geckodriver -b myfirefox/firefox --log trace --port 4444
9             # chromedriver --port=4444
10              
11             # then use it
12             use AnyEvent::WebDriver;
13              
14             # create a new webdriver object
15             my $wd = new AnyEvent::WebDriver;
16              
17             # create a new session with default capabilities.
18             $wd->new_session ({});
19              
20             $wd->navigate_to ("https://duckduckgo.com/html");
21             my $searchbox = $wd->find_element (css => 'input[type="text"]');
22              
23             $wd->element_send_keys ($searchbox => "free software");
24             $wd->element_click ($wd->find_element (css => 'input[type="submit"]'));
25              
26             # session gets autodeleted by default, so wait a bit
27             sleep 10;
28              
29             # this is an example of an action sequence
30             $wd->actions
31             ->move ($wd->find_element (...), 40, 5)
32             ->click
33             ->type ("some text")
34             ->key ("{Enter}")
35             ->perform;
36              
37             =head1 DESCRIPTION
38              
39             This module aims to implement the L
40             WebDriver|https://www.w3.org/TR/webdriver1/> specification which is the
41             standardised equivalent to the Selenium WebDriver API, which in turn aims
42             at remotely controlling web browsers such as Firefox or Chromium.
43              
44             One of the design goals of this module was to stay very close to the
45             language and words used in the WebDriver specification itself, so to make
46             most of this module, or, in fact, to make any reasonable use of this
47             module, you would need to refer to the W3C WebDriver recommendation, which
48             can be found L:
49              
50             https://www.w3.org/TR/webdriver1/
51              
52             Mozilla's C has had webdriver support for a long time, while
53             C only has basic and mostly undocumented webdriver support
54             as of release 77.
55              
56             In Debian GNU/Linux, you can install the chromedriver for chromium
57             via the C package. Unfortunately, there is no
58             (working) package for geckodriver, but you can download it from
59             L.
60              
61             =head2 CONVENTIONS
62              
63             Unless otherwise stated, all delays and time differences in this module
64             are represented as an integer number of milliseconds, which is perhaps
65             surprising to users of my other modules but is what the WebDriver spec
66             uses.
67              
68             =cut
69              
70             package AnyEvent::WebDriver;
71              
72 1     1   1222 use common::sense;
  1         13  
  1         8  
73              
74 1     1   60 use Carp ();
  1         2  
  1         14  
75 1     1   1053 use AnyEvent ();
  1         5523  
  1         26  
76 1     1   658 use AnyEvent::HTTP ();
  1         31092  
  1         6399  
77              
78             our $VERSION = '1.2';
79              
80             our $WEB_ELEMENT_IDENTIFIER = "element-6066-11e4-a52e-4f735466cecf";
81             our $WEB_WINDOW_IDENTIFIER = "window-fcc6-11e5-b4f8-330a88ab9d7f";
82             our $WEB_FRAME_IDENTIFIER = "frame-075b-4da1-b6ba-e579c2d3230a";
83              
84             my $json = eval { require JSON::XS; JSON::XS:: } || do { require JSON::PP; JSON::PP:: };
85             $json = $json->new->utf8;
86              
87             $json->boolean_values (0, 1)
88             if $json->can ("boolean_values");
89              
90             sub _decode_base64 {
91 0     0     require MIME::Base64;
92              
93 0           MIME::Base64::decode_base64 (shift)
94             }
95              
96             sub req_ {
97 0     0 1   my ($self, $method, $ep, $body, $cb) = @_;
98              
99             AnyEvent::HTTP::http_request $method => "$self->{_ep}$ep",
100             body => $body,
101             $self->{persistent} ? (persistent => 1) : (),
102             $self->{proxy} eq "default" ? () : (proxy => $self->{proxy}),
103             timeout => $self->{timeout},
104             headers => { "content-type" => "application/json; charset=utf-8", "cache-control" => "no-cache" },
105             sub {
106 0     0     my ($res, $hdr) = @_;
107              
108 0           $res = eval { $json->decode ($res) };
  0            
109 0 0         $hdr->{Status} = 500 unless exists $res->{value};
110              
111 0           $cb->($hdr->{Status}, $res->{value});
112             }
113 0 0         ;
    0          
114             }
115              
116             sub get_ {
117 0     0 1   my ($self, $ep, $cb) = @_;
118              
119 0           $self->req_ (GET => $ep, undef, $cb)
120             }
121              
122             sub post_ {
123 0     0 1   my ($self, $ep, $data, $cb) = @_;
124              
125 0   0       $self->req_ (POST => $ep, $json->encode ($data || {}), $cb)
126             }
127              
128             sub delete_ {
129 0     0 1   my ($self, $ep, $cb) = @_;
130              
131 0           $self->req_ (DELETE => $ep, "", $cb)
132             }
133              
134             sub AUTOLOAD {
135 0     0     our $AUTOLOAD;
136              
137 0 0         $_[0]->isa (__PACKAGE__)
138             or Carp::croak "$AUTOLOAD: no such function";
139              
140 0           (my $name = $AUTOLOAD) =~ s/^.*://;
141              
142 0           my $name_ = "$name\_";
143              
144 0 0         defined &$name_
145             or Carp::croak "$AUTOLOAD: no such method";
146              
147 0           my $func_ = \&$name_;
148              
149             *$name = sub {
150 0     0     $func_->(@_, my $cv = AE::cv);
151 0           my ($status, $res) = $cv->recv;
152              
153 0 0         if ($status ne "200") {
154 0           my $msg;
155              
156 0 0         if (exists $res->{error}) {
157 0           $msg = "AyEvent::WebDriver: $res->{error}: $res->{message}";
158 0 0         $msg .= "\n$res->{stacktrace}caught at" if length $res->{stacktrace};
159             } else {
160 0           $msg = "AnyEvent::WebDriver: http status $status (wrong endpoint?), caught";
161             }
162              
163 0           Carp::croak $msg;
164             }
165              
166             $res
167 0           };
  0            
168              
169 0           goto &$name;
170             }
171              
172             =head2 WEBDRIVER OBJECTS
173              
174             =over
175              
176             =item new AnyEvent::WebDriver key => value...
177              
178             Create a new WebDriver object. Example for a remote WebDriver connection
179             (the only type supported at the moment):
180              
181             my $wd = new AnyEvent::WebDriver endpoint => "http://localhost:4444";
182              
183             Supported keys are:
184              
185             =over
186              
187             =item endpoint => $string
188              
189             For remote connections, the endpoint to connect to (defaults to C).
190              
191             =item proxy => $proxyspec
192              
193             The proxy to use (same as the C argument used by
194             L). The default is C, which disables proxies. To
195             use the system-provided proxy (e.g. C environment variable),
196             specify the string C.
197              
198             =item autodelete => $boolean
199              
200             If true (the default), then automatically execute C when
201             the WebDriver object is destroyed with an active session. If set to a
202             false value, then the session will continue to exist.
203              
204             Note that due to bugs in perl that are unlikely to get fixed,
205             C is likely ineffective during global destruction and might
206             even crash your process, so you should ensure objects go out of scope
207             before that, or explicitly call C, if you want the session
208             to be cleaned up.
209              
210             =item timeout => $seconds
211              
212             The HTTP timeout, in (fractional) seconds (default: C<300>). This timeout
213             is reset on any activity, so it is not an overall request timeout. Also,
214             individual requests might extend this timeout if they are known to take
215             longer.
216              
217             =item persistent => C<1> | C
218              
219             If true (the default) then persistent connections will be used for all
220             requests, which assumes you have a reasonably stable connection (such as
221             to C :) and that the WebDriver has a persistent timeout much
222             higher than what L uses.
223              
224             You can force connections to be closed for non-idempotent requests (the
225             safe default of L) by setting this to C.
226              
227             =back
228              
229             =cut
230              
231             sub new {
232 0     0 1   my ($class, %kv) = @_;
233              
234 0           bless {
235             endpoint => "http://localhost:4444",
236             proxy => undef,
237             persistent => 1,
238             autodelete => 1,
239             timeout => 300,
240             %kv,
241             }, $class
242             }
243              
244             sub DESTROY {
245 0     0     my ($self) = @_;
246              
247             $self->delete_session
248 0 0 0       if exists $self->{sid} && $self->{autodelete};
249             }
250              
251             =item $al = $wd->actions
252              
253             Creates an action list associated with this WebDriver. See L
254             LISTS>, below, for full details.
255              
256             =cut
257              
258             sub actions {
259 0     0 1   AnyEvent::WebDriver::Actions->new (wd => $_[0])
260             }
261              
262             =item $sessionstring = $wd->save_session
263              
264             Save the current session in a string so it can be restored load with
265             C. Note that only the session data itself is stored
266             (currently the session id and capabilities), not the endpoint information
267             itself.
268              
269             The main use of this function is in conjunction with disabled
270             C, to save a session to e.g., and restore it later. It could
271             presumably used for other applications, such as using the same session
272             from multiple processes and so on.
273              
274             =item $wd->load_session ($sessionstring)
275              
276             =item $wd->set_session ($sessionid, $capabilities)
277              
278             Starts using the given session, as identified by
279             C<$sessionid>. C<$capabilities> should be the original session
280             capabilities, although the current version of this module does not make
281             any use of it.
282              
283             The C<$sessionid> is stored in C<< $wd->{sid} >> (and could be fetched
284             form there for later use), while the capabilities are stored in C<<
285             $wd->{capabilities} >>.
286              
287             =cut
288              
289             sub save_session {
290 0     0 1   my ($self) = @_;
291              
292 0           $json->encode ([1, $self->{sid}, $self->{capabilities}]);
293             }
294              
295             sub load_session {
296 0     0 1   my ($self, $session) = @_;
297              
298 0           $session = $json->decode ($session);
299              
300 0 0         $session->[0] == 1
301             or Carp::croak "AnyEvent::WebDriver::load_session: session corrupted or from different version";
302              
303 0           $self->set_session ($session->[1], $session->[2]);
304             }
305              
306             sub set_session {
307 0     0 1   my ($self, $sid, $caps) = @_;
308              
309 0           $self->{sid} = $sid;
310 0           $self->{capabilities} = $caps;
311              
312 0           $self->{_ep} = "$self->{endpoint}/session/$self->{sid}/";
313             }
314              
315             =back
316              
317             =head2 SIMPLIFIED API
318              
319             This section documents the simplified API, which is really just a very
320             thin wrapper around the WebDriver protocol commands. They all block the
321             caller until the result is available (using L condvars), so must
322             not be called from an event loop callback - see L for an
323             alternative.
324              
325             The method names are pretty much taken directly from the W3C WebDriver
326             specification, e.g. the request documented in the "Get All Cookies"
327             section is implemented via the C method.
328              
329             The order is the same as in the WebDriver draft at the time of this
330             writing, and only minimal massaging is done to request parameters and
331             results.
332              
333             =head3 SESSIONS
334              
335             =over
336              
337             =cut
338              
339             =item $wd->new_session ({ key => value... })
340              
341             Try to connect to the WebDriver and initialize a new session with a
342             "new session" command, passing the given key-value pairs as value
343             (e.g. C).
344              
345             No session-dependent methods must be called before this function returns
346             successfully, and only one session can be created per WebDriver object.
347              
348             On success, C<< $wd->{sid} >> is set to the session ID, and C<<
349             $wd->{capabilities} >> is set to the returned capabilities.
350              
351             Simple example of creating a WebDriver object and a new session:
352              
353             my $wd = new AnyEvent::WebDriver endpoint => "http://localhost:4444";
354             $wd->new_session ({});
355              
356             Real-world example with capability negotiation:
357              
358             $wd->new_session ({
359             capabilities => {
360             alwaysMatch => {
361             pageLoadStrategy => "eager",
362             unhandledPromptBehavior => "dismiss",
363             # proxy => { proxyType => "manual", httpProxy => "1.2.3.4:56", sslProxy => "1.2.3.4:56" },
364             },
365             firstMatch => [
366             {
367             browserName => "firefox",
368             "moz:firefoxOptions" => {
369             binary => "firefox/firefox",
370             args => ["-devtools", "-headless"],
371             prefs => {
372             "dom.webnotifications.enabled" => \0,
373             "dom.push.enabled" => \0,
374             "dom.disable_beforeunload" => \1,
375             "browser.link.open_newwindow" => 3,
376             "browser.link.open_newwindow.restrictions" => 0,
377             "dom.popup_allowed_events" => "",
378             "dom.disable_open_during_load" => \1,
379             },
380             },
381             },
382             {
383             browserName => "chrome",
384             "goog:chromeOptions" => {
385             binary => "/bin/chromium",
386             args => ["--no-sandbox", "--headless"],
387             prefs => {
388             # ...
389             },
390             },
391             },
392             {
393             # generic fallback
394             },
395             ],
396              
397             },
398             });
399              
400             Firefox-specific capability documentation can be found L
401             MDN|https://developer.mozilla.org/en-US/docs/Web/WebDriver/Capabilities>,
402             Chrome-specific capability documentation might be found
403             L, but the latest
404             release at the time of this writing (chromedriver 77) has essentially
405             no documentation about webdriver capabilities (even MDN has better
406             documentation about chromwedriver!)
407              
408             If you have URLs for Safari/IE/Edge etc. capabilities, feel free to tell
409             me about them.
410              
411             =cut
412              
413             sub new_session_ {
414 0     0 0   my ($self, $kv, $cb) = @_;
415              
416 0   0       $kv->{capabilities} ||= {}; # required by protocol
417              
418 0           local $self->{_ep} = "$self->{endpoint}/";
419             $self->post_ (session => $kv, sub {
420 0     0     my ($status, $res) = @_;
421              
422             exists $res->{capabilities}
423 0 0         or $status = "500"; # blasted chromedriver
424              
425             $self->set_session ($res->{sessionId}, $res->{capabilities})
426 0 0         if $status eq "200";
427              
428 0           $cb->($status, $res);
429 0           });
430             }
431              
432             =item $wd->delete_session
433              
434             Deletes the session - the WebDriver object must not be used after this
435             call (except for calling this method).
436              
437             This method is always safe to call and will not do anything if there is no
438             active session.
439              
440             =cut
441              
442             sub delete_session_ {
443 0     0 0   my ($self, $cb) = @_;
444              
445 0           my $sid = delete $self->{sid};
446 0           delete $self->{capoabilities};
447              
448 0 0         return unless defined $sid;
449              
450 0           local $self->{_ep} = "$self->{endpoint}/session/$sid";
451 0           $self->delete_ ("" => $cb);
452             }
453              
454             =item $timeouts = $wd->get_timeouts
455              
456             Get the current timeouts, e.g.:
457              
458             my $timeouts = $wd->get_timeouts;
459             => { implicit => 0, pageLoad => 300000, script => 30000 }
460              
461             =item $wd->set_timeouts ($timeouts)
462              
463             Sets one or more timeouts, e.g.:
464              
465             $wd->set_timeouts ({ script => 60000 });
466              
467             =cut
468              
469             sub get_timeouts_ {
470 0     0 0   $_[0]->get_ (timeouts => $_[1], $_[2]);
471             }
472              
473             sub set_timeouts_ {
474 0     0 0   $_[0]->post_ (timeouts => $_[1], $_[2], $_[3]);
475             }
476              
477             =back
478              
479             =head3 NAVIGATION
480              
481             =over
482              
483             =cut
484              
485             =item $wd->navigate_to ($url)
486              
487             Navigates to the specified URL.
488              
489             =item $url = $wd->get_current_url
490              
491             Queries the current page URL as set by C.
492              
493             =cut
494              
495             sub navigate_to_ {
496 0     0 0   $_[0]->post_ (url => { url => "$_[1]" }, $_[2]);
497             }
498              
499             sub get_current_url_ {
500 0     0 0   $_[0]->get_ (url => $_[1])
501             }
502              
503             =item $wd->back
504              
505             The equivalent of pressing "back" in the browser.
506              
507             =item $wd->forward
508              
509             The equivalent of pressing "forward" in the browser.
510              
511             =item $wd->refresh
512              
513             The equivalent of pressing "refresh" in the browser.
514              
515             =cut
516              
517             sub back_ {
518 0     0 0   $_[0]->post_ (back => undef, $_[1]);
519             }
520              
521             sub forward_ {
522 0     0 0   $_[0]->post_ (forward => undef, $_[1]);
523             }
524              
525             sub refresh_ {
526 0     0 0   $_[0]->post_ (refresh => undef, $_[1]);
527             }
528              
529             =item $title = $wd->get_title
530              
531             Returns the current document title.
532              
533             =cut
534              
535             sub get_title_ {
536 0     0 0   $_[0]->get_ (title => $_[1]);
537             }
538              
539             =back
540              
541             =head3 COMMAND CONTEXTS
542              
543             =over
544              
545             =cut
546              
547             =item $handle = $wd->get_window_handle
548              
549             Returns the current window handle.
550              
551             =item $wd->close_window
552              
553             Closes the current browsing context.
554              
555             =item $wd->switch_to_window ($handle)
556              
557             Changes the current browsing context to the given window.
558              
559             =cut
560              
561             sub get_window_handle_ {
562 0     0 0   $_[0]->get_ (window => $_[1]);
563             }
564              
565             sub close_window_ {
566 0     0 0   $_[0]->delete_ (window => $_[1]);
567             }
568              
569             sub switch_to_window_ {
570 0     0 0   $_[0]->post_ (window => { handle => "$_[1]" }, $_[2]);
571             }
572              
573             =item $handles = $wd->get_window_handles
574              
575             Return the current window handles as an array-ref of handle IDs.
576              
577             =cut
578              
579             sub get_window_handles_ {
580 0     0 0   $_[0]->get_ ("window/handles" => $_[1]);
581             }
582              
583             =item $handles = $wd->switch_to_frame ($frame)
584              
585             Switch to the given frame identified by C<$frame>, which must be either
586             C to go back to the top-level browsing context, an integer to
587             select the nth subframe, or an element object.
588              
589             =cut
590              
591             sub switch_to_frame_ {
592 0     0 0   $_[0]->post_ (frame => { id => "$_[1]" }, $_[2]);
593             }
594              
595             =item $handles = $wd->switch_to_parent_frame
596              
597             Switch to the parent frame.
598              
599             =cut
600              
601             sub switch_to_parent_frame_ {
602 0     0 0   $_[0]->post_ ("frame/parent" => undef, $_[1]);
603             }
604              
605             =item $rect = $wd->get_window_rect
606              
607             Return the current window rect(angle), e.g.:
608              
609             $rect = $wd->get_window_rect
610             => { height => 1040, width => 540, x => 0, y => 0 }
611              
612             =item $wd->set_window_rect ($rect)
613              
614             Sets the window rect(angle), e.g.:
615              
616             $wd->set_window_rect ({ width => 780, height => 560 });
617             $wd->set_window_rect ({ x => 0, y => 0, width => 780, height => 560 });
618              
619             =cut
620              
621             sub get_window_rect_ {
622 0     0 0   $_[0]->get_ ("window/rect" => $_[1]);
623             }
624              
625             sub set_window_rect_ {
626 0     0 0   $_[0]->post_ ("window/rect" => $_[1], $_[2]);
627             }
628              
629             =item $wd->maximize_window
630              
631             =item $wd->minimize_window
632              
633             =item $wd->fullscreen_window
634              
635             Changes the window size by either maximising, minimising or making it
636             fullscreen. In my experience, this will timeout if no window manager is
637             running.
638              
639             =cut
640              
641             sub maximize_window_ {
642 0     0 0   $_[0]->post_ ("window/maximize" => undef, $_[1]);
643             }
644              
645             sub minimize_window_ {
646 0     0 0   $_[0]->post_ ("window/minimize" => undef, $_[1]);
647             }
648              
649             sub fullscreen_window_ {
650 0     0 0   $_[0]->post_ ("window/fullscreen" => undef, $_[1]);
651             }
652              
653             =back
654              
655             =head3 ELEMENT RETRIEVAL
656              
657             To reduce typing and memory strain, the element finding functions accept
658             some shorter and hopefully easier to remember aliases for the standard
659             locator strategy values, as follows:
660              
661             Alias Locator Strategy
662             css css selector
663             link link text
664             substr partial link text
665             tag tag name
666              
667             =over
668              
669             =cut
670              
671             our %USING = (
672             css => "css selector",
673             link => "link text",
674             substr => "partial link text",
675             tag => "tag name",
676             );
677              
678             sub _using($) {
679 0   0 0     using => $USING{$_[0]} // "$_[0]"
680             }
681              
682             =item $element = $wd->find_element ($locator_strategy, $selector)
683              
684             Finds the first element specified by the given selector and returns its
685             element object. Raises an error when no element was found.
686              
687             Examples showing all standard locator strategies:
688              
689             $element = $wd->find_element ("css selector" => "body a");
690             $element = $wd->find_element ("link text" => "Click Here For Porn");
691             $element = $wd->find_element ("partial link text" => "orn");
692             $element = $wd->find_element ("tag name" => "input");
693             $element = $wd->find_element ("xpath" => '//input[@type="text"]');
694             => e.g. { "element-6066-11e4-a52e-4f735466cecf" => "decddca8-5986-4e1d-8c93-efe952505a5f" }
695              
696             Same examples using aliases provided by this module:
697              
698             $element = $wd->find_element (css => "body a");
699             $element = $wd->find_element (link => "Click Here For Porn");
700             $element = $wd->find_element (substr => "orn");
701             $element = $wd->find_element (tag => "input");
702              
703             =item $elements = $wd->find_elements ($locator_strategy, $selector)
704              
705             As above, but returns an arrayref of all found element objects.
706              
707             =item $element = $wd->find_element_from_element ($element, $locator_strategy, $selector)
708              
709             Like C, but looks only inside the specified C<$element>.
710              
711             =item $elements = $wd->find_elements_from_element ($element, $locator_strategy, $selector)
712              
713             Like C, but looks only inside the specified C<$element>.
714              
715             my $head = $wd->find_element ("tag name" => "head");
716             my $links = $wd->find_elements_from_element ($head, "tag name", "link");
717              
718             =item $element = $wd->get_active_element
719              
720             Returns the active element.
721              
722             =cut
723              
724             sub find_element_ {
725 0     0 0   $_[0]->post_ (element => { _using $_[1], value => "$_[2]" }, $_[3]);
726             }
727              
728             sub find_elements_ {
729 0     0 0   $_[0]->post_ (elements => { _using $_[1], value => "$_[2]" }, $_[3]);
730             }
731              
732             sub find_element_from_element_ {
733 0     0 0   $_[0]->post_ ("element/$_[1]/element" => { _using $_[2], value => "$_[3]" }, $_[4]);
734             }
735              
736             sub find_elements_from_element_ {
737 0     0 0   $_[0]->post_ ("element/$_[1]/elements" => { _using $_[2], value => "$_[3]" }, $_[4]);
738             }
739              
740             sub get_active_element_ {
741 0     0 0   $_[0]->get_ ("element/active" => $_[1]);
742             }
743              
744             =back
745              
746             =head3 ELEMENT STATE
747              
748             =over
749              
750             =cut
751              
752             =item $bool = $wd->is_element_selected
753              
754             Returns whether the given input or option element is selected or not.
755              
756             =item $string = $wd->get_element_attribute ($element, $name)
757              
758             Returns the value of the given attribute.
759              
760             =item $string = $wd->get_element_property ($element, $name)
761              
762             Returns the value of the given property.
763              
764             =item $string = $wd->get_element_css_value ($element, $name)
765              
766             Returns the value of the given CSS value.
767              
768             =item $string = $wd->get_element_text ($element)
769              
770             Returns the (rendered) text content of the given element.
771              
772             =item $string = $wd->get_element_tag_name ($element)
773              
774             Returns the tag of the given element.
775              
776             =item $rect = $wd->get_element_rect ($element)
777              
778             Returns the element rect(angle) of the given element.
779              
780             =item $bool = $wd->is_element_enabled
781              
782             Returns whether the element is enabled or not.
783              
784             =cut
785              
786             sub is_element_selected_ {
787 0     0 0   $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/selected" => $_[2]);
788             }
789              
790             sub get_element_attribute_ {
791 0     0 0   $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/attribute/$_[2]" => $_[3]);
792             }
793              
794             sub get_element_property_ {
795 0     0 0   $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/property/$_[2]" => $_[3]);
796             }
797              
798             sub get_element_css_value_ {
799 0     0 0   $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/css/$_[2]" => $_[3]);
800             }
801              
802             sub get_element_text_ {
803 0     0 0   $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/text" => $_[2]);
804             }
805              
806             sub get_element_tag_name_ {
807 0     0 0   $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/name" => $_[2]);
808             }
809              
810             sub get_element_rect_ {
811 0     0 0   $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/rect" => $_[2]);
812             }
813              
814             sub is_element_enabled_ {
815 0     0 0   $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/enabled" => $_[2]);
816             }
817              
818             =back
819              
820             =head3 ELEMENT INTERACTION
821              
822             =over
823              
824             =cut
825              
826             =item $wd->element_click ($element)
827              
828             Clicks the given element.
829              
830             =item $wd->element_clear ($element)
831              
832             Clear the contents of the given element.
833              
834             =item $wd->element_send_keys ($element, $text)
835              
836             Sends the given text as key events to the given element. Key input state
837             can be cleared by embedding C<\x{e000}> in C<$text>. Presumably, you can
838             embed modifiers using their unicode codepoints, but the specification is
839             less than clear to mein this area.
840              
841             =cut
842              
843             sub element_click_ {
844 0     0 0   $_[0]->post_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/click" => undef, $_[2]);
845             }
846              
847             sub element_clear_ {
848 0     0 0   $_[0]->post_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/clear" => undef, $_[2]);
849             }
850              
851             sub element_send_keys_ {
852 0     0 0   $_[0]->post_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/value" => { text => "$_[2]" }, $_[3]);
853             }
854              
855             =back
856              
857             =head3 DOCUMENT HANDLING
858              
859             =over
860              
861             =cut
862              
863             =item $source = $wd->get_page_source
864              
865             Returns the (HTML/XML) page source of the current document.
866              
867             =item $results = $wd->execute_script ($javascript, $args)
868              
869             Synchronously execute the given script with given arguments and return its
870             results (C<$args> can be C if no arguments are wanted/needed).
871              
872             $ten = $wd->execute_script ("return arguments[0]+arguments[1]", [3, 7]);
873              
874             =item $results = $wd->execute_async_script ($javascript, $args)
875              
876             Similar to C, but doesn't wait for script to return, but
877             instead waits for the script to call its last argument, which is added to
878             C<$args> automatically.
879              
880             $twenty = $wd->execute_async_script ("arguments[0](20)", undef);
881              
882             =cut
883              
884             sub get_page_source_ {
885 0     0 0   $_[0]->get_ (source => $_[1]);
886             }
887              
888             sub execute_script_ {
889 0   0 0 0   $_[0]->post_ ("execute/sync" => { script => "$_[1]", args => $_[2] || [] }, $_[3]);
890             }
891              
892             sub execute_async_script_ {
893 0   0 0 0   $_[0]->post_ ("execute/async" => { script => "$_[1]", args => $_[2] || [] }, $_[3]);
894             }
895              
896             =back
897              
898             =head3 COOKIES
899              
900             =over
901              
902             =cut
903              
904             =item $cookies = $wd->get_all_cookies
905              
906             Returns all cookies, as an arrayref of hashrefs.
907              
908             # google surely sets a lot of cookies without my consent
909             $wd->navigate_to ("http://google.com");
910             use Data::Dump;
911             ddx $wd->get_all_cookies;
912              
913             =item $cookie = $wd->get_named_cookie ($name)
914              
915             Returns a single cookie as a hashref.
916              
917             =item $wd->add_cookie ($cookie)
918              
919             Adds the given cookie hashref.
920              
921             =item $wd->delete_cookie ($name)
922              
923             Delete the named cookie.
924              
925             =item $wd->delete_all_cookies
926              
927             Delete all cookies.
928              
929             =cut
930              
931             sub get_all_cookies_ {
932 0     0 0   $_[0]->get_ (cookie => $_[1]);
933             }
934              
935             sub get_named_cookie_ {
936 0     0 0   $_[0]->get_ ("cookie/$_[1]" => $_[2]);
937             }
938              
939             sub add_cookie_ {
940 0     0 0   $_[0]->post_ (cookie => { cookie => $_[1] }, $_[2]);
941             }
942              
943             sub delete_cookie_ {
944 0     0 0   $_[0]->delete_ ("cookie/$_[1]" => $_[2]);
945             }
946              
947             sub delete_all_cookies_ {
948 0     0 0   $_[0]->delete_ (cookie => $_[2]);
949             }
950              
951             =back
952              
953             =head3 ACTIONS
954              
955             =over
956              
957             =cut
958              
959             =item $wd->perform_actions ($actions)
960              
961             Perform the given actions (an arrayref of action specifications simulating
962             user activity, or an C object). For further
963             details, read the spec or the section L, below.
964              
965             An example to get you started (see the next example for a mostly
966             equivalent example using the C helper API):
967              
968             $wd->navigate_to ("https://duckduckgo.com/html");
969             my $input = $wd->find_element ("css selector", 'input[type="text"]');
970             $wd->perform_actions ([
971             {
972             id => "myfatfinger",
973             type => "pointer",
974             pointerType => "touch",
975             actions => [
976             { type => "pointerMove", duration => 100, origin => $input, x => 40, y => 5 },
977             { type => "pointerDown", button => 0 },
978             { type => "pause", duration => 40 },
979             { type => "pointerUp", button => 0 },
980             ],
981             },
982             {
983             id => "mykeyboard",
984             type => "key",
985             actions => [
986             { type => "pause" },
987             { type => "pause" },
988             { type => "pause" },
989             { type => "pause" },
990             { type => "keyDown", value => "a" },
991             { type => "pause", duration => 100 },
992             { type => "keyUp", value => "a" },
993             { type => "pause", duration => 100 },
994             { type => "keyDown", value => "b" },
995             { type => "pause", duration => 100 },
996             { type => "keyUp", value => "b" },
997             { type => "pause", duration => 2000 },
998             { type => "keyDown", value => "\x{E007}" }, # enter
999             { type => "pause", duration => 100 },
1000             { type => "keyUp", value => "\x{E007}" }, # enter
1001             { type => "pause", duration => 5000 },
1002             ],
1003             },
1004             ]);
1005              
1006             And here is essentially the same (except for fewer pauses) example as
1007             above, using the much simpler C API:
1008              
1009             $wd->navigate_to ("https://duckduckgo.com/html");
1010             my $input = $wd->find_element ("css selector", 'input[type="text"]');
1011             $wd->actions
1012             ->move ($input, 40, 5, "touch1")
1013             ->click
1014             ->key ("a")
1015             ->key ("b")
1016             ->pause (2000) # so you can watch leisurely
1017             ->key ("{Enter}")
1018             ->pause (5000) # so you can see the result
1019             ->perform;
1020              
1021             =item $wd->release_actions
1022              
1023             Release all keys and pointer buttons currently depressed.
1024              
1025             =cut
1026              
1027             sub perform_actions_ {
1028 0 0   0 0   if (UNIVERSAL::isa $_[1], AnyEvent::WebDriver::Actions::) {
1029 0           my ($actions, $duration) = $_[1]->compile;
1030 0           local $_[0]{timeout} = $_[0]{timeout} + $duration * 1e-3;
1031 0           $_[0]->post_ (actions => { actions => $actions }, $_[2]);
1032             } else {
1033 0           $_[0]->post_ (actions => { actions => $_[1] }, $_[2]);
1034             }
1035             }
1036              
1037             sub release_actions_ {
1038 0     0 0   $_[0]->delete_ (actions => $_[1]);
1039             }
1040              
1041             =back
1042              
1043             =head3 USER PROMPTS
1044              
1045             =over
1046              
1047             =cut
1048              
1049             =item $wd->dismiss_alert
1050              
1051             Dismiss a simple dialog, if present.
1052              
1053             =item $wd->accept_alert
1054              
1055             Accept a simple dialog, if present.
1056              
1057             =item $text = $wd->get_alert_text
1058              
1059             Returns the text of any simple dialog.
1060              
1061             =item $text = $wd->send_alert_text
1062              
1063             Fills in the user prompt with the given text.
1064              
1065              
1066             =cut
1067              
1068             sub dismiss_alert_ {
1069 0     0 0   $_[0]->post_ ("alert/dismiss" => undef, $_[1]);
1070             }
1071              
1072             sub accept_alert_ {
1073 0     0 0   $_[0]->post_ ("alert/accept" => undef, $_[1]);
1074             }
1075              
1076             sub get_alert_text_ {
1077 0     0 0   $_[0]->get_ ("alert/text" => $_[1]);
1078             }
1079              
1080             sub send_alert_text_ {
1081 0     0 0   $_[0]->post_ ("alert/text" => { text => "$_[1]" }, $_[2]);
1082             }
1083              
1084             =back
1085              
1086             =head3 SCREEN CAPTURE
1087              
1088             =over
1089              
1090             =cut
1091              
1092             =item $wd->take_screenshot
1093              
1094             Create a screenshot, returning it as a PNG image. To decode and save, you
1095             could do something like:
1096              
1097             use MIME::Base64 ();
1098              
1099             my $screenshot = $wd->take_screenshot;
1100              
1101             open my $fh, ">", "screenshot.png" or die "screenshot.png: $!\n";
1102              
1103             syswrite $fh, MIME::Base64::decode_base64 $screenshot;
1104              
1105             =item $wd->take_element_screenshot ($element)
1106              
1107             Similar to C, but only takes a screenshot of the bounding
1108             box of a single element.
1109              
1110             Compatibility note: As of chrome version 80, I found that the screenshot
1111             scaling is often wrong (the screenshot is much smaller than the element
1112             normally displays) unless chrome runs in headless mode. The spec does
1113             allow for any form of scaling, so this is not strictly a bug in chrome,
1114             but of course it diminishes trhe screenshot functionality.
1115              
1116             =cut
1117              
1118             sub take_screenshot_ {
1119 0     0 0   my $cb = pop; push @_, sub { $cb->($_[0], _decode_base64 $_[1]) };
  0     0      
  0            
1120 0           $_[0]->get_ (screenshot => $_[1]);
1121             }
1122              
1123             sub take_element_screenshot_ {
1124 0     0 0   my $cb = pop; push @_, sub { $cb->($_[0], _decode_base64 $_[1]) };
  0     0      
  0            
1125 0           $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/screenshot" => $_[2]);
1126             }
1127              
1128             =back
1129              
1130             =head3 PRINT
1131              
1132             =over
1133              
1134             =cut
1135              
1136             =item $wd->print_page (key => value...)
1137              
1138             Create a printed version of the document, returning it as a PDF document
1139             encoded as base64. See C for an example on how to decode
1140             and save such a string.
1141              
1142             This command takes a lot of optional parameters, see L
1143             section|https://www.w3.org/TR/webdriver2/#print> of the WebDriver
1144             specification for details.
1145              
1146             This command is taken from a draft document, so it might change in the
1147             future.
1148              
1149             =cut
1150              
1151             sub print_page {
1152 0     0 1   my $cb = pop; push @_, sub { $cb->($_[0], _decode_base64 $_[1]) };
  0     0      
  0            
1153 0           $_[0]->post_ (print => { @_ });
1154             }
1155              
1156             =head2 ACTION LISTS
1157              
1158             Action lists can be quite complicated. Or at least it took a while for
1159             me to twist my head around them. Basically, an action list consists of a
1160             number of sources representing devices (such as a finger, a mouse, a pen
1161             or a keyboard) and a list of actions for each source, in a timeline.
1162              
1163             An action can be a key press, a pointer move or a pause (time delay).
1164              
1165             While you can provide these action lists manually, it is (hopefully) less
1166             cumbersome to use the API described in this section to create them.
1167              
1168             The basic process of creating and performing actions is to create a new
1169             action list, adding action sources, followed by adding actions. Finally
1170             you would C those actions on the WebDriver.
1171              
1172             Most methods here are designed to chain, i.e. they return the web actions
1173             object, to simplify multiple calls.
1174              
1175             Also, while actions from different sources can happen "at the same time"
1176             in the WebDriver protocol, this class by default ensures that actions will
1177             execute in the order specified.
1178              
1179             For example, to simulate a mouse click to an input element, followed by
1180             entering some text and pressing enter, you can use this:
1181              
1182             $wd->actions
1183             ->click (0, 100)
1184             ->type ("some text")
1185             ->key ("{Enter}")
1186             ->perform;
1187              
1188             By default, C and C input sources are provided and
1189             used. You can create your own sources and use them when adding events. The
1190             above example could be more verbosely written like this:
1191              
1192             $wd->actions
1193             ->source ("mouse", "pointer", pointerType => "mouse")
1194             ->source ("kbd", "key")
1195             ->click (0, 100, "mouse")
1196             ->type ("some text", "kbd")
1197             ->key ("{Enter}", "kbd")
1198             ->perform;
1199              
1200             When you specify the event source explicitly it will switch the current
1201             "focus" for this class of device (all keyboards are in one class, all
1202             pointer-like devices such as mice/fingers/pens are in one class), so you
1203             don't have to specify the source for subsequent actions that are on the
1204             same class.
1205              
1206             When you use the sources C, C, C..C,
1207             C without defining them, then a suitable default source will be
1208             created for them.
1209              
1210             =over 4
1211              
1212             =cut
1213              
1214             package AnyEvent::WebDriver::Actions;
1215              
1216             =item $al = new AnyEvent::WebDriver::Actions
1217              
1218             Create a new empty action list object. More often you would use the C<<
1219             $wd->action_list >> method to create one that is already associated with
1220             a given web driver.
1221              
1222             =cut
1223              
1224             sub new {
1225 0     0     my ($class, %kv) = @_;
1226              
1227 0           $kv{last_kbd} = "keyboard";
1228 0           $kv{last_ptr} = "mouse";
1229              
1230 0           bless \%kv, $class
1231             }
1232              
1233             =item $al = $al->source ($id, $type, key => value...)
1234              
1235             The first time you call this with a given ID, this defines the event
1236             source using the extra parameters. Subsequent calls merely switch the
1237             current source for its event class.
1238              
1239             It's not an error to define built-in sources (such as C or
1240             C) differently then the defaults.
1241              
1242             Example: define a new touch device called C.
1243              
1244             $al->source (fatfinger => "pointer", pointerType => "touch");
1245              
1246             Example: define a new touch device called C.
1247              
1248             $al->source (fatfinger => "pointer", pointerType => "touch");
1249              
1250             Example: switch default keyboard source to C, assuming it is of C class.
1251              
1252             $al->source ("kbd1");
1253              
1254             =cut
1255              
1256             sub _default_source($) {
1257 0     0     my ($source) = @_;
1258              
1259 0 0         $source eq "keyboard" ? { actions => [], id => $source, type => "key" }
    0          
    0          
    0          
1260             : $source eq "mouse" ? { actions => [], id => $source, type => "pointer", pointerType => "mouse" }
1261             : $source eq "touch" ? { actions => [], id => $source, type => "pointer", pointerType => "touch" }
1262             : $source eq "pen" ? { actions => [], id => $source, type => "pointer", pointerType => "pen" }
1263             : Carp::croak "AnyEvent::WebDriver::Actions: event source '$source' not defined"
1264             }
1265              
1266             my %source_class = (
1267             key => "kbd",
1268             pointer => "ptr",
1269             );
1270              
1271             sub source {
1272 0     0     my ($self, $id, $type, %kv) = @_;
1273              
1274 0 0         if (defined $type) {
1275 0 0         !exists $self->{source}{$id}
1276             or Carp::croak "AnyEvent::WebDriver::Actions: source '$id' already defined";
1277              
1278 0           $kv{id} = $id;
1279 0           $kv{type} = $type;
1280 0           $kv{actions} = [];
1281              
1282 0           $self->{source}{$id} = \%kv;
1283             }
1284              
1285 0   0       my $source = $self->{source}{$id} ||= _default_source $id;
1286              
1287 0   0       my $last = $source_class{$source->{type}} // "xxx";
1288              
1289 0           $self->{"last_$last"} = $id;
1290              
1291 0           $self
1292             }
1293              
1294             sub _add {
1295 0     0     my ($self, $source, $sourcetype, $type, %kv) = @_;
1296              
1297 0           my $last = \$self->{"last_$sourcetype"};
1298              
1299 0 0         $source
1300             ? ($$last = $source)
1301             : ($source = $$last);
1302              
1303 0   0       my $source = $self->{source}{$source} ||= _default_source $source;
1304              
1305 0           my $al = $source->{actions};
1306              
1307             push @$al, { type => "pause" }
1308 0           while @$al < $self->{tick}; # -1 == allow concurrent actions
1309              
1310 0           $kv{type} = $type;
1311              
1312 0           push @{ $source->{actions} }, \%kv;
  0            
1313              
1314             $self->{tick_duration} = $kv{duration}
1315 0 0         if $kv{duration} > $self->{tick_duration};
1316              
1317 0 0         if ($self->{tick} != @$al) {
1318 0           $self->{tick} = @$al;
1319 0           $self->{duration} += delete $self->{tick_duration};
1320             }
1321              
1322             $self
1323 0           }
1324              
1325             =item $al = $al->pause ($duration)
1326              
1327             Creates a pause with the given duration. Makes sure that time progresses
1328             in any case, even when C<$duration> is C<0>.
1329              
1330             =cut
1331              
1332             sub pause {
1333 0     0     my ($self, $duration) = @_;
1334              
1335             $self->{tick_duration} = $duration
1336 0 0         if $duration > $self->{tick_duration};
1337              
1338 0           $self->{duration} += delete $self->{tick_duration};
1339              
1340             # find the source with the longest list
1341              
1342 0           for my $source (values %{ $self->{source} }) {
  0            
1343 0 0         if (@{ $source->{actions} } == $self->{tick}) {
  0            
1344             # this source is one of the longest
1345              
1346             # create a pause event only if $duration is non-zero...
1347 0 0         push @{ $source->{actions} }, { type => "pause", duration => $duration*1 }
  0            
1348             if $duration;
1349              
1350             # ... but advance time in any case
1351 0           ++$self->{tick};
1352              
1353 0           return $self;
1354             }
1355             }
1356              
1357             # no event sources are longest. so advance time in any case
1358 0           ++$self->{tick};
1359              
1360 0 0         Carp::croak "AnyEvent::WebDriver::Actions: multiple pause calls in a row not (yet) supported"
1361             if $duration;
1362              
1363 0           $self
1364             }
1365              
1366             =item $al = $al->pointer_down ($button, $source)
1367              
1368             =item $al = $al->pointer_up ($button, $source)
1369              
1370             Press or release the given button. C<$button> defaults to C<0>.
1371              
1372             =item $al = $al->click ($button, $source)
1373              
1374             Convenience function that creates a button press and release action
1375             without any delay between them. C<$button> defaults to C<0>.
1376              
1377             =item $al = $al->doubleclick ($button, $source)
1378              
1379             Convenience function that creates two button press and release action
1380             pairs in a row, with no unnecessary delay between them. C<$button>
1381             defaults to C<0>.
1382              
1383             =cut
1384              
1385             sub pointer_down {
1386 0     0     my ($self, $button, $source) = @_;
1387              
1388 0   0       $self->_add ($source, ptr => pointerDown => button => ($button // 0)*1)
1389             }
1390              
1391             sub pointer_up {
1392 0     0     my ($self, $button, $source) = @_;
1393              
1394 0   0       $self->_add ($source, ptr => pointerUp => button => ($button // 0)*1)
1395             }
1396              
1397             sub click {
1398 0     0     my ($self, $button, $source) = @_;
1399              
1400 0           $self
1401             ->pointer_down ($button, $source)
1402             ->pointer_up ($button)
1403             }
1404              
1405             sub doubleclick {
1406 0     0     my ($self, $button, $source) = @_;
1407              
1408 0           $self
1409             ->click ($button, $source)
1410             ->click ($button)
1411             }
1412              
1413             =item $al = $al->move ($origin, $x, $y, $duration, $source)
1414              
1415             Moves a pointer to the given position, relative to origin (either
1416             "viewport", "pointer" or an element object. The coordinates will be
1417             truncated to integer values.
1418              
1419             =cut
1420              
1421             sub move {
1422 0     0     my ($self, $origin, $x, $y, $duration, $source) = @_;
1423              
1424 0           $self->_add ($source, ptr => pointerMove =>
1425             origin => $origin, x => int $x*1, y => int $y*1, duration => $duration*1)
1426             }
1427              
1428             =item $al = $al->cancel ($source)
1429              
1430             Executes a pointer cancel action.
1431              
1432             =cut
1433              
1434             sub cancel {
1435 0     0     my ($self, $source) = @_;
1436              
1437 0           $self->_add ($source, ptr => "pointerCancel")
1438             }
1439              
1440             =item $al = $al->key_down ($key, $source)
1441              
1442             =item $al = $al->key_up ($key, $source)
1443              
1444             Press or release the given key.
1445              
1446             =item $al = $al->key ($key, $source)
1447              
1448             Peess and release the given key in one go, without unnecessary delay.
1449              
1450             A special syntax, C<{keyname}> can be used for special keys -
1451             all the special key names from L
1452             17.4.2|https://www.w3.org/TR/webdriver1/#keyboard-actions> of the
1453             WebDriver recommendation can be used - prefix with C. to get
1454             the shifted version, as in C
1455              
1456             Example: press and release "a".
1457              
1458             $al->key ("a");
1459              
1460             Example: press and release the "Enter" key:
1461              
1462             $al->key ("\x{e007}");
1463              
1464             Example: press and release the "enter" key using the special key name syntax:
1465              
1466             $al->key ("{Enter}");
1467              
1468             =item $al = $al->type ($string, $source)
1469              
1470             Convenience method to simulate a series of key press and release events
1471             for the keys in C<$string>, one pair per extended unicode grapheme
1472             cluster. There is no syntax for special keys, everything will be typed
1473             "as-is" if possible.
1474              
1475             =cut
1476              
1477             # copy&paste from the spec via browser, with added MetaLeft/MetaRight aliases
1478             our $SPECIAL_KEY = <<'EOF';
1479             "`" "~" "Backquote"
1480             "\" "|" "Backslash"
1481             "\uE003" "Backspace"
1482             "[" "{" "BracketLeft"
1483             "]" "}" "BracketRight"
1484             "," "<" "Comma"
1485             "0" ")" "Digit0"
1486             "1" "!" "Digit1"
1487             "2" "@" "Digit2"
1488             "3" "#" "Digit3"
1489             "4" "$" "Digit4"
1490             "5" "%" "Digit5"
1491             "6" "^" "Digit6"
1492             "7" "&" "Digit7"
1493             "8" "*" "Digit8"
1494             "9" "(" "Digit9"
1495             "=" "+" "Equal"
1496             "<" ">" "IntlBackslash"
1497             "a" "A" "KeyA"
1498             "b" "B" "KeyB"
1499             "c" "C" "KeyC"
1500             "d" "D" "KeyD"
1501             "e" "E" "KeyE"
1502             "f" "F" "KeyF"
1503             "g" "G" "KeyG"
1504             "h" "H" "KeyH"
1505             "i" "I" "KeyI"
1506             "j" "J" "KeyJ"
1507             "k" "K" "KeyK"
1508             "l" "L" "KeyL"
1509             "m" "M" "KeyM"
1510             "n" "N" "KeyN"
1511             "o" "O" "KeyO"
1512             "p" "P" "KeyP"
1513             "q" "Q" "KeyQ"
1514             "r" "R" "KeyR"
1515             "s" "S" "KeyS"
1516             "t" "T" "KeyT"
1517             "u" "U" "KeyU"
1518             "v" "V" "KeyV"
1519             "w" "W" "KeyW"
1520             "x" "X" "KeyX"
1521             "y" "Y" "KeyY"
1522             "z" "Z" "KeyZ"
1523             "-" "_" "Minus"
1524             "." ">"." "Period"
1525             "'" """ "Quote"
1526             ";" ":" "Semicolon"
1527             "/" "?" "Slash"
1528             "\uE00A" "AltLeft"
1529             "\uE052" "AltRight"
1530             "\uE009" "ControlLeft"
1531             "\uE051" "ControlRight"
1532             "\uE006" "Enter"
1533             "\uE03D" "OSLeft"
1534             "\uE053" "OSRight"
1535             "\uE008" "ShiftLeft"
1536             "\uE050" "ShiftRight"
1537             " " "\uE00D" "Space"
1538             "\uE004" "Tab"
1539             "\uE017" "Delete"
1540             "\uE010" "End"
1541             "\uE002" "Help"
1542             "\uE011" "Home"
1543             "\uE016" "Insert"
1544             "\uE00F" "PageDown"
1545             "\uE00E" "PageUp"
1546             "\uE015" "ArrowDown"
1547             "\uE012" "ArrowLeft"
1548             "\uE014" "ArrowRight"
1549             "\uE013" "ArrowUp"
1550             "\uE00C" "Escape"
1551             "\uE031" "F1"
1552             "\uE032" "F2"
1553             "\uE033" "F3"
1554             "\uE034" "F4"
1555             "\uE035" "F5"
1556             "\uE036" "F6"
1557             "\uE037" "F7"
1558             "\uE038" "F8"
1559             "\uE039" "F9"
1560             "\uE03A" "F10"
1561             "\uE03B" "F11"
1562             "\uE03C" "F12"
1563             "\uE01A" "\uE05C" "Numpad0"
1564             "\uE01B" "\uE056" "Numpad1"
1565             "\uE01C" "\uE05B" "Numpad2"
1566             "\uE01D" "\uE055" "Numpad3"
1567             "\uE01E" "\uE058" "Numpad4"
1568             "\uE01F" "Numpad5"
1569             "\uE020" "\uE05A" "Numpad6"
1570             "\uE021" "\uE057" "Numpad7"
1571             "\uE022" "\uE059" "Numpad8"
1572             "\uE023" "\uE054" "Numpad9"
1573             "\uE025" "NumpadAdd"
1574             "\uE026" "NumpadComma"
1575             "\uE028" "\uE05D" "NumpadDecimal"
1576             "\uE029" "NumpadDivide"
1577             "\uE007" "NumpadEnter"
1578             "\uE024" "NumpadMultiply"
1579             "\uE027" "NumpadSubtract"
1580              
1581             "\uE03D" "MetaLeft"
1582             "\uE053" "MetaRight"
1583             EOF
1584              
1585             our %SPECIAL_KEY;
1586              
1587             sub _special_key($) {
1588             # parse first time
1589 0 0   0     %SPECIAL_KEY || do {
1590 0           for (split /\n/, $SPECIAL_KEY) {
1591 0 0         s/"//g or next;
1592 0           my ($k, $s, $name) = split /\t/;
1593              
1594             # unescape \uXXXX, convert string to codepoint
1595             $_ = /^\\u/ ? hex substr $_, 2 : ord
1596 0 0         for $k, $s;
1597              
1598 0           $SPECIAL_KEY{$name} = $k;
1599 0 0         $SPECIAL_KEY{"Shift-$name"} = $s if $s;
1600              
1601             }
1602              
1603 0           undef $SPECIAL_KEY; # save memory
1604             };
1605              
1606             exists $SPECIAL_KEY{$_[0]}
1607 0 0         ? chr $SPECIAL_KEY{$_[0]}
1608             : Carp::croak "AnyEvent::WebDriver::Actions: special key '$1' not known"
1609             }
1610              
1611             sub _kv($) {
1612 0 0   0     $_[0] =~ /^\{(.*)\}$/s
1613             ? _special_key $1
1614             : $_[0]
1615             }
1616              
1617             sub key_down {
1618 0     0     my ($self, $key, $source) = @_;
1619              
1620 0           $self->_add ($source, kbd => keyDown => value => _kv $key)
1621             }
1622              
1623             sub key_up {
1624 0     0     my ($self, $key, $source) = @_;
1625              
1626 0           $self->_add ($source, kbd => keyUp => value => _kv $key)
1627             }
1628              
1629             sub key {
1630 0     0     my ($self, $key, $source) = @_;
1631              
1632 0           $self
1633             ->key_down ($key, $source)
1634             ->key_up ($key)
1635             }
1636              
1637             sub type {
1638 0     0     my ($self, $string, $source) = @_;
1639              
1640             $self->key ($_, $source)
1641 0           for $string =~ /(\X)/g;
1642              
1643 0           $self
1644             }
1645              
1646             =item $al->perform ($wd)
1647              
1648             Finalises and compiles the list, if not done yet, and calls C<<
1649             $wd->perform >> with it.
1650              
1651             If C<$wd> is undef, and the action list was created using the C<<
1652             $wd->actions >> method, then perform it against that WebDriver object.
1653              
1654             There is no underscore variant - call the C method with
1655             the action object instead.
1656              
1657             =item $al->perform_release ($wd)
1658              
1659             Exactly like C, but additionally call C
1660             afterwards.
1661              
1662             =cut
1663              
1664             sub perform {
1665 0     0     my ($self, $wd) = @_;
1666              
1667 0   0       ($wd //= $self->{wd})->perform_actions ($self)
1668             }
1669              
1670             sub perform_release {
1671 0     0     my ($self, $wd) = @_;
1672              
1673 0   0       ($wd //= $self->{wd})->perform_actions ($self);
1674 0           $wd->release_actions;
1675             }
1676              
1677             =item ($actions, $duration) = $al->compile
1678              
1679             Finalises and compiles the list, if not done yet, and returns an actions
1680             object suitable for calls to C<< $wd->perform_actions >>. When called in
1681             list context, additionally returns the total duration of the action list.
1682              
1683             Since building large action lists can take nontrivial amounts of time,
1684             it can make sense to build an action list only once and then perform it
1685             multiple times.
1686              
1687             No additional actions must be added after compiling an action list.
1688              
1689             =cut
1690              
1691             sub compile {
1692 0     0     my ($self) = @_;
1693              
1694 0           $self->{duration} += delete $self->{tick_duration};
1695              
1696 0           delete $self->{tick};
1697 0           delete $self->{last_kbd};
1698 0           delete $self->{last_ptr};
1699              
1700 0   0       $self->{actions} ||= [values %{ delete $self->{source} }];
  0            
1701              
1702             wantarray
1703             ? ($self->{actions}, $self->{duration})
1704             : $self->{actions}
1705 0 0         }
1706              
1707             =back
1708              
1709             =head2 EVENT BASED API
1710              
1711             This module wouldn't be a good AnyEvent citizen if it didn't have a true
1712             event-based API.
1713              
1714             In fact, the simplified API, as documented above, is emulated via the
1715             event-based API and an C function that automatically provides
1716             blocking wrappers around the callback-based API.
1717              
1718             Every method documented in the L section has an equivalent
1719             event-based method that is formed by appending a underscore (C<_>) to the
1720             method name, and appending a callback to the argument list (mnemonic: the
1721             underscore indicates the "the action is not yet finished" after the call
1722             returns).
1723              
1724             For example, instead of a blocking calls to C, C
1725             and C, you can make a callback-based ones:
1726              
1727             my $cv = AE::cv;
1728              
1729             $wd->new_session ({}, sub {
1730             my ($status, $value) = @_,
1731              
1732             die "error $value->{error}" if $status ne "200";
1733              
1734             $wd->navigate_to_ ("http://www.nethype.de", sub {
1735              
1736             $wd->back_ (sub {
1737             print "all done\n";
1738             $cv->send;
1739             });
1740              
1741             });
1742             });
1743              
1744             $cv->recv;
1745              
1746             While the blocking methods C on errors, the callback-based ones all
1747             pass two values to the callback, C<$status> and C<$res>, where C<$status>
1748             is the HTTP status code (200 for successful requests, typically 4xx or
1749             5xx for errors), and C<$res> is the value of the C key in the JSON
1750             response object.
1751              
1752             Other than that, the underscore variants and the blocking variants are
1753             identical.
1754              
1755             =head2 LOW LEVEL API
1756              
1757             All the simplified API methods are very thin wrappers around WebDriver
1758             commands of the same name. They are all implemented in terms of the
1759             low-level methods (C, C, C and C), which exist
1760             in blocking and callback-based variants (C, C, C and
1761             C).
1762              
1763             Examples are after the function descriptions.
1764              
1765             =over
1766              
1767             =item $wd->req_ ($method, $uri, $body, $cb->($status, $value))
1768              
1769             =item $value = $wd->req ($method, $uri, $body)
1770              
1771             Appends the C<$uri> to the C URL and makes
1772             a HTTP C<$method> request (C, C etc.). C requests can
1773             provide a UTF-8-encoded JSON text as HTTP request body, or the empty
1774             string to indicate no body is used.
1775              
1776             For the callback version, the callback gets passed the HTTP status code
1777             (200 for every successful request), and the value of the C key in
1778             the JSON response object as second argument.
1779              
1780             =item $wd->get_ ($uri, $cb->($status, $value))
1781              
1782             =item $value = $wd->get ($uri)
1783              
1784             Simply a call to C with C<$method> set to C and an empty body.
1785              
1786             =item $wd->post_ ($uri, $data, $cb->($status, $value))
1787              
1788             =item $value = $wd->post ($uri, $data)
1789              
1790             Simply a call to C with C<$method> set to C - if C<$body> is
1791             C, then an empty object is send, otherwise, C<$data> must be a
1792             valid request object, which gets encoded into JSON for you.
1793              
1794             =item $wd->delete_ ($uri, $cb->($status, $value))
1795              
1796             =item $value = $wd->delete ($uri)
1797              
1798             Simply a call to C with C<$method> set to C and an empty body.
1799              
1800             =cut
1801              
1802             =back
1803              
1804             Example: implement C, which is a simple C request
1805             without any parameters:
1806              
1807             $cookies = $wd->get ("cookie");
1808              
1809             Example: implement C, which needs some parameters:
1810              
1811             $results = $wd->post ("execute/sync" => { script => "$javascript", args => [] });
1812              
1813             Example: call C to find all C elements:
1814              
1815             $elems = $wd->post (elements => { using => "css selector", value => "img" });
1816              
1817             =cut
1818              
1819             =head1 HISTORY
1820              
1821             This module was unintentionally created (it started inside some quickly
1822             hacked-together script) simply because I couldn't get the existing
1823             C module to work reliably, ever, despite
1824             multiple attempts over the years and trying to report multiple bugs, which
1825             have been completely ignored. It's also not event-based, so, yeah...
1826              
1827             =head1 AUTHOR
1828              
1829             Marc Lehmann
1830             http://anyevent.schmorp.de
1831              
1832             =cut
1833              
1834             1
1835