File Coverage

blib/lib/AnyEvent/WebDriver.pm
Criterion Covered Total %
statement 12 204 5.8
branch 0 58 0.0
condition 0 29 0.0
subroutine 4 91 4.4
pod 9 62 14.5
total 25 444 5.6


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