File Coverage

blib/lib/AnyEvent/WebDriver.pm
Criterion Covered Total %
statement 12 208 5.7
branch 0 60 0.0
condition 0 34 0.0
subroutine 4 91 4.4
pod 9 62 14.5
total 25 455 5.4


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