ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-WebDriver/WebDriver.pm
Revision: 1.9
Committed: Tue Aug 28 23:30:45 2018 UTC (5 years, 9 months ago) by root
Branch: MAIN
Changes since 1.8: +42 -38 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.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 selector" => 'input[type="text"]');
21    
22     $wd->element_send_keys ($searchbox => "free software");
23     $wd->element_click ($wd->find_element ("css selector" => 'input[type="submit"]'));
24    
25     sleep 10;
26    
27     =head1 DESCRIPTION
28    
29     This module aims to implement the W3C WebDriver specification which is the
30     standardised equivalent to the Selenium WebDriver API., which in turn aims
31     at remotely controlling web browsers such as Firefox or Chromium.
32    
33     At the time of this writing, it was only available as a draft document, so
34     changes will be expected. Also, only F<geckodriver> did implement it, or
35     at least, most of it.
36    
37     To make most of this module, or, in fact, to make any reasonable use of
38     this module, you would need to refer tot he W3C WebDriver document, which
39     can be found L<here|https://w3c.github.io/webdriver/>:
40    
41     https://w3c.github.io/webdriver/
42    
43     =cut
44    
45     package AnyEvent::WebDriver;
46    
47     use common::sense;
48    
49     use Carp ();
50     use JSON::XS ();
51     use AnyEvent::HTTP ();
52    
53     our $VERSION = 0;
54    
55     our $WEB_ELEMENT_IDENTIFIER = "element-6066-11e4-a52e-4f735466cecf";
56    
57     my $json = JSON::XS->new
58 root 1.9 ->utf8;
59    
60     $json->boolean_values (0, 1)
61     if $json->can ("boolean_values");
62 root 1.1
63     sub req_ {
64 root 1.9 my ($self, $method, $ep, $body, $cb) = @_;
65 root 1.1
66 root 1.9 AnyEvent::HTTP::http_request $method => "$self->{_ep}$ep",
67 root 1.1 body => $body,
68 root 1.3 timeout => $self->{timeout},
69 root 1.1 headers => { "content-type" => "application/json; charset=utf-8", "cache-control" => "no-cache" },
70 root 1.9 ($self->{proxy} eq "default" ? () : (proxy => $self->{proxy})),
71 root 1.1 sub {
72     my ($res, $hdr) = @_;
73    
74     $res = eval { $json->decode ($res) };
75     $hdr->{Status} = 500 unless exists $res->{value};
76    
77     $cb->($hdr->{Status}, $res->{value});
78     }
79     ;
80     }
81    
82     sub get_ {
83 root 1.9 my ($self, $ep, $cb) = @_;
84 root 1.1
85 root 1.9 $self->req_ (GET => $ep, undef, $cb)
86 root 1.1 }
87    
88     sub post_ {
89 root 1.9 my ($self, $ep, $data, $cb) = @_;
90 root 1.1
91 root 1.9 $self->req_ (POST => $ep, $json->encode ($data || {}), $cb)
92 root 1.1 }
93    
94     sub delete_ {
95 root 1.9 my ($self, $ep, $cb) = @_;
96 root 1.1
97 root 1.9 $self->req_ (DELETE => $ep, "", $cb)
98 root 1.1 }
99    
100     sub AUTOLOAD {
101     our $AUTOLOAD;
102    
103     $_[0]->isa (__PACKAGE__)
104     or Carp::croak "$AUTOLOAD: no such function";
105    
106     (my $name = $AUTOLOAD) =~ s/^.*://;
107    
108     my $name_ = "$name\_";
109    
110     defined &$name_
111     or Carp::croak "AUTOLOAD: no such method";
112    
113     my $func_ = \&$name_;
114    
115     *$name = sub {
116     $func_->(@_, my $cv = AE::cv);
117     my ($status, $res) = $cv->recv;
118    
119     if ($status ne "200") {
120     my $msg;
121    
122     if (exists $res->{error}) {
123     $msg = "AyEvent::WebDriver: $res->{error}: $res->{message}";
124     $msg .= "\n$res->{stacktrace}" if length $res->{stacktrace};
125     } else {
126     $msg = "AnyEvent::WebDriver: http status $status (wrong endpoint?), caught";
127     }
128    
129     Carp::croak $msg;
130     }
131    
132     $res
133     };
134    
135     goto &$name;
136     }
137    
138     =head2 CREATING WEBDRIVER OBJECTS
139    
140     =over
141    
142     =item new AnyEvent::WebDriver key => value...
143    
144 root 1.9 Create a new WebDriver object. Example for a remote WebDriver connection
145 root 1.1 (the only type supported at the moment):
146    
147     my $wd = new AnyEvent::WebDriver host => "localhost", port => 4444;
148    
149     Supported keys are:
150    
151     =over
152    
153     =item endpoint => $string
154    
155     For remote connections, the endpoint to connect to (defaults to C<http://localhost:4444>).
156    
157     =item proxy => $proxyspec
158    
159     The proxy to use (same as the C<proxy> argument used by
160     L<AnyEvent::HTTP>). The default is C<undef>, which disables proxies. To
161     use the system-provided proxy (e.g. C<http_proxy> environment variable),
162     specify a value of C<default>.
163    
164     =item autodelete => $boolean
165    
166     If true (the default), then automatically execute C<delete_session> when
167     the WebDriver object is destroyed with an active session. IF set to a
168     false value, then the session will continue to exist.
169    
170 root 1.3 =item timeout => $seconds
171    
172     The HTTP timeout, in (fractional) seconds (default: C<300>, but this will
173     likely drastically reduce). This timeout is reset on any activity, so it
174 root 1.9 is not an overall request timeout. Also, individual requests might extend
175 root 1.3 this timeout if they are known to take longer.
176    
177 root 1.1 =back
178    
179     =cut
180    
181     sub new {
182     my ($class, %kv) = @_;
183    
184     bless {
185     endpoint => "http://localhost:4444",
186     proxy => undef,
187     autodelete => 1,
188 root 1.3 timeout => 300,
189 root 1.1 %kv,
190     }, $class
191     }
192    
193     sub DESTROY {
194 root 1.9 my ($self) = @_;
195 root 1.1
196 root 1.9 $self->delete_session
197     if exists $self->{sid};
198 root 1.1 }
199    
200     =back
201    
202     =head2 SIMPLIFIED API
203    
204     This section documents the simplified API, which is really just a very
205     thin wrapper around the WebDriver protocol commands. They all block (using
206     L<AnyEvent> condvars) the caller until the result is available, so must
207     not be called from an event loop callback - see L<EVENT BASED API> for an
208     alternative.
209    
210 root 1.9 The method names are pretty much taken directly from the W3C WebDriver
211 root 1.1 specification, e.g. the request documented in the "Get All Cookies"
212     section is implemented via the C<get_all_cookies> method.
213    
214 root 1.9 The order is the same as in the WebDriver draft at the time of this
215 root 1.1 writing, and only minimal massaging is done to request parameters and
216     results.
217    
218     =head3 SESSIONS
219    
220     =over
221    
222     =cut
223    
224     =item $wd->new_session ({ key => value... })
225    
226 root 1.9 Try to connect to the WebDriver and initialize a new session with a
227     "new session" command, passing the given key-value pairs as value
228 root 1.1 (e.g. C<capabilities>).
229    
230     No session-dependent methods must be called before this function returns
231 root 1.9 successfully, and only one session can be created per WebDriver object.
232 root 1.1
233 root 1.7 On success, C<< $wd->{sid} >> is set to the session ID, and C<<
234 root 1.1 $wd->{capabilities} >> is set to the returned capabilities.
235    
236 root 1.4 my $wd = new AnyEvent::Selenium endpoint => "http://localhost:4545";
237 root 1.1
238     $wd->new_session ({
239     capabilities => {
240     pageLoadStrategy => "normal",
241     }.
242     });
243    
244     =cut
245    
246     sub new_session_ {
247 root 1.9 my ($self, $kv, $cb) = @_;
248 root 1.1
249 root 1.9 local $self->{_ep} = "$self->{endpoint}/";
250     $self->post_ (session => $kv, sub {
251 root 1.1 my ($status, $res) = @_;
252    
253     if ($status eq "200") {
254 root 1.9 $self->{sid} = $res->{sessionId};
255     $self->{capabilities} = $res->{capabilities};
256 root 1.1
257 root 1.9 $self->{_ep} = "$self->{endpoint}/session/$self->{sid}/";
258 root 1.1 }
259    
260     $cb->($status, $res);
261     });
262     }
263    
264     =item $wd->delete_session
265    
266     Deletes the session - the WebDriver object must not be used after this
267     call.
268    
269     =cut
270    
271     sub delete_session_ {
272 root 1.9 my ($self, $cb) = @_;
273 root 1.1
274 root 1.9 local $self->{_ep} = "$self->{endpoint}/session/$self->{sid}";
275     $self->delete_ ("" => $cb);
276 root 1.1 }
277    
278     =item $timeouts = $wd->get_timeouts
279    
280     Get the current timeouts, e.g.:
281    
282     my $timeouts = $wd->get_timeouts;
283 root 1.5 => { implicit => 0, pageLoad => 300000, script => 30000 }
284 root 1.1
285     =item $wd->set_timeouts ($timeouts)
286    
287     Sets one or more timeouts, e.g.:
288    
289     $wd->set_timeouts ({ script => 60000 });
290    
291     =cut
292    
293     sub get_timeouts_ {
294     $_[0]->get_ (timeouts => $_[1], $_[2]);
295     }
296    
297     sub set_timeouts_ {
298     $_[0]->post_ (timeouts => $_[1], $_[2], $_[3]);
299     }
300    
301     =back
302    
303     =head3 NAVIGATION
304    
305     =over
306    
307     =cut
308    
309     =item $wd->navigate_to ($url)
310    
311     Navigates to the specified URL.
312    
313     =item $url = $wd->get_current_url
314    
315 root 1.8 Queries the current page URL as set by C<navigate_to>.
316 root 1.1
317     =cut
318    
319     sub navigate_to_ {
320     $_[0]->post_ (url => { url => "$_[1]" }, $_[2]);
321     }
322    
323     sub get_current_url_ {
324     $_[0]->get_ (url => $_[1])
325     }
326    
327     =item $wd->back
328    
329     The equivalent of pressing "back" in the browser.
330    
331     =item $wd->forward
332    
333     The equivalent of pressing "forward" in the browser.
334    
335     =item $wd->refresh
336    
337     The equivalent of pressing "refresh" in the browser.
338    
339     =cut
340    
341     sub back_ {
342     $_[0]->post_ (back => undef, $_[1]);
343     }
344    
345     sub forward_ {
346     $_[0]->post_ (forward => undef, $_[1]);
347     }
348    
349     sub refresh_ {
350     $_[0]->post_ (refresh => undef, $_[1]);
351     }
352    
353     =item $title = $wd->get_title
354    
355     Returns the current document title.
356    
357     =cut
358    
359     sub get_title_ {
360     $_[0]->get_ (title => $_[1]);
361     }
362    
363     =back
364    
365     =head3 COMMAND CONTEXTS
366    
367     =over
368    
369     =cut
370    
371     =item $handle = $wd->get_window_handle
372    
373     Returns the current window handle.
374    
375     =item $wd->close_window
376    
377     Closes the current browsing context.
378    
379     =item $wd->switch_to_window ($handle)
380    
381     Changes the current browsing context to the given window.
382    
383     =cut
384    
385     sub get_window_handle_ {
386     $_[0]->get_ (window => $_[1]);
387     }
388    
389     sub close_window_ {
390     $_[0]->delete_ (window => $_[1]);
391     }
392    
393     sub switch_to_window_ {
394     $_[0]->post_ (window => "$_[1]", $_[2]);
395     }
396    
397     =item $handles = $wd->get_window_handles
398    
399     Return the current window handles as an array-ref of handle IDs.
400    
401     =cut
402    
403     sub get_window_handles_ {
404     $_[0]->get_ ("window/handles" => $_[1]);
405     }
406    
407     =item $handles = $wd->switch_to_frame ($frame)
408    
409 root 1.8 Switch to the given frame identified by C<$frame>, which must be either
410     C<undef> to go back to the top-level browsing context, an integer to
411     select the nth subframe, or an element object (as e.g. returned by the
412     C<element_object> method.
413 root 1.1
414     =cut
415    
416     sub switch_to_frame_ {
417     $_[0]->post_ (frame => { id => "$_[1]" }, $_[2]);
418     }
419    
420     =item $handles = $wd->switch_to_parent_frame
421    
422     Switch to the parent frame.
423    
424     =cut
425    
426     sub switch_to_parent_frame_ {
427     $_[0]->post_ ("frame/parent" => undef, $_[1]);
428     }
429    
430     =item $rect = $wd->get_window_rect
431    
432     Return the current window rect, e.g.:
433    
434     $rect = $wd->get_window_rect
435 root 1.5 => { height => 1040, width => 540, x => 0, y => 0 }
436 root 1.1
437     =item $wd->set_window_rect ($rect)
438    
439     Sets the window rect.
440    
441     =cut
442    
443     sub get_window_rect_ {
444     $_[0]->get_ ("window/rect" => $_[1]);
445     }
446    
447     sub set_window_rect_ {
448     $_[0]->post_ ("window/rect" => $_[1], $_[2]);
449     }
450    
451     =item $wd->maximize_window
452    
453     =item $wd->minimize_window
454    
455     =item $wd->fullscreen_window
456    
457 root 1.3 Changes the window size by either maximising, minimising or making it
458 root 1.9 fullscreen. In my experience, this will timeout if no window manager is
459 root 1.1 running.
460    
461     =cut
462    
463     sub maximize_window_ {
464     $_[0]->post_ ("window/maximize" => undef, $_[1]);
465     }
466    
467     sub minimize_window_ {
468     $_[0]->post_ ("window/minimize" => undef, $_[1]);
469     }
470    
471     sub fullscreen_window_ {
472     $_[0]->post_ ("window/fullscreen" => undef, $_[1]);
473     }
474    
475     =back
476    
477     =head3 ELEMENT RETRIEVAL
478    
479     =over
480    
481     =cut
482    
483     =item $element_id = $wd->find_element ($location_strategy, $selector)
484    
485     Finds the first element specified by the given selector and returns its
486 root 1.7 web element ID (the strong, not the object from the protocol). Raises an
487 root 1.1 error when no element was found.
488    
489     $element = $wd->find_element ("css selector" => "body a");
490     $element = $wd->find_element ("link text" => "Click Here For Porn");
491     $element = $wd->find_element ("partial link text" => "orn");
492     $element = $wd->find_element ("tag name" => "input");
493     $element = $wd->find_element ("xpath" => '//input[@type="text"]');
494 root 1.6 => e.g. "decddca8-5986-4e1d-8c93-efe952505a5f"
495 root 1.1
496     =item $element_ids = $wd->find_elements ($location_strategy, $selector)
497    
498     As above, but returns an arrayref of all found element IDs.
499    
500     =item $element_id = $wd->find_element_from_element ($element_id, $location_strategy, $selector)
501    
502     Like C<find_element>, but looks only inside the specified C<$element>.
503    
504     =item $element_ids = $wd->find_elements_from_element ($element_id, $location_strategy, $selector)
505    
506     Like C<find_elements>, but looks only inside the specified C<$element>.
507    
508     my $head = $wd->find_element ("tag name" => "head");
509     my $links = $wd->find_elements_from_element ($head, "tag name", "link");
510    
511     =item $element_id = $wd->get_active_element
512    
513     Returns the active element.
514    
515     =cut
516    
517     sub find_element_ {
518     my $cb = pop;
519     $_[0]->post_ (element => { using => "$_[1]", value => "$_[2]" }, sub {
520     $cb->($_[0], $_[0] ne "200" ? $_[1] : $_[1]{$WEB_ELEMENT_IDENTIFIER})
521     });
522     }
523    
524     sub find_elements_ {
525     my $cb = pop;
526     $_[0]->post_ (elements => { using => "$_[1]", value => "$_[2]" }, sub {
527     $cb->($_[0], $_[0] ne "200" ? $_[1] : [ map $_->{$WEB_ELEMENT_IDENTIFIER}, @{$_[1]} ]);
528     });
529     }
530    
531     sub find_element_from_element_ {
532     my $cb = pop;
533     $_[0]->post_ ("element/$_[1]/element" => { using => "$_[2]", value => "$_[3]" }, sub {
534     $cb->($_[0], $_[0] ne "200" ? $_[1] : $_[1]{$WEB_ELEMENT_IDENTIFIER})
535     });
536     }
537    
538     sub find_elements_from_element_ {
539     my $cb = pop;
540     $_[0]->post_ ("element/$_[1]/elements" => { using => "$_[2]", value => "$_[3]" }, sub {
541     $cb->($_[0], $_[0] ne "200" ? $_[1] : [ map $_->{$WEB_ELEMENT_IDENTIFIER}, @{$_[1]} ]);
542     });
543     }
544    
545     sub get_active_element_ {
546     my $cb = pop;
547     $_[0]->get_ ("element/active" => sub {
548     $cb->($_[0], $_[0] ne "200" ? $_[1] : $_[1]{$WEB_ELEMENT_IDENTIFIER})
549     });
550     }
551    
552     =back
553    
554     =head3 ELEMENT STATE
555    
556     =over
557    
558     =cut
559    
560     =item $bool = $wd->is_element_selected
561    
562     Returns whether the given input or option element is selected or not.
563    
564     =item $string = $wd->get_element_attribute ($element_id, $name)
565    
566     Returns the value of the given attribute.
567    
568     =item $string = $wd->get_element_property ($element_id, $name)
569    
570     Returns the value of the given property.
571    
572     =item $string = $wd->get_element_css_value ($element_id, $name)
573    
574 root 1.9 Returns the value of the given CSS value.
575 root 1.1
576     =item $string = $wd->get_element_text ($element_id)
577    
578     Returns the (rendered) text content of the given element.
579    
580     =item $string = $wd->get_element_tag_name ($element_id)
581    
582     Returns the tag of the given element.
583    
584     =item $rect = $wd->get_element_rect ($element_id)
585    
586 root 1.9 Returns the element rect(angle) of the given element.
587 root 1.1
588     =item $bool = $wd->is_element_enabled
589    
590     Returns whether the element is enabled or not.
591    
592     =cut
593    
594     sub is_element_selected_ {
595     $_[0]->get_ ("element/$_[1]/selected" => $_[2]);
596     }
597    
598     sub get_element_attribute_ {
599     $_[0]->get_ ("element/$_[1]/attribute/$_[2]" => $_[3]);
600     }
601    
602     sub get_element_property_ {
603     $_[0]->get_ ("element/$_[1]/property/$_[2]" => $_[3]);
604     }
605    
606     sub get_element_css_value_ {
607     $_[0]->get_ ("element/$_[1]/css/$_[2]" => $_[3]);
608     }
609    
610     sub get_element_text_ {
611     $_[0]->get_ ("element/$_[1]/text" => $_[2]);
612     }
613    
614     sub get_element_tag_name_ {
615     $_[0]->get_ ("element/$_[1]/name" => $_[2]);
616     }
617    
618     sub get_element_rect_ {
619     $_[0]->get_ ("element/$_[1]/rect" => $_[2]);
620     }
621    
622     sub is_element_enabled_ {
623     $_[0]->get_ ("element/$_[1]/enabled" => $_[2]);
624     }
625    
626     =back
627    
628     =head3 ELEMENT INTERACTION
629    
630     =over
631    
632     =cut
633    
634     =item $wd->element_click ($element_id)
635    
636     Clicks the given element.
637    
638     =item $wd->element_clear ($element_id)
639    
640     Clear the contents of the given element.
641    
642     =item $wd->element_send_keys ($element_id, $text)
643    
644     Sends the given text as key events to the given element.
645    
646     =cut
647    
648     sub element_click_ {
649     $_[0]->post_ ("element/$_[1]/click" => undef, $_[2]);
650     }
651    
652     sub element_clear_ {
653     $_[0]->post_ ("element/$_[1]/clear" => undef, $_[2]);
654     }
655    
656     sub element_send_keys_ {
657     $_[0]->post_ ("element/$_[1]/value" => { text => "$_[2]" }, $_[3]);
658     }
659    
660     =back
661    
662     =head3 DOCUMENT HANDLING
663    
664     =over
665    
666     =cut
667    
668     =item $source = $wd->get_page_source
669    
670     Returns the (HTML/XML) page source of the current document.
671    
672     =item $results = $wd->execute_script ($javascript, $args)
673    
674     Synchronously execute the given script with given arguments and return its
675     results (C<$args> can be C<undef> if no arguments are wanted/needed).
676    
677     $ten = $wd->execute_script ("return arguments[0]+arguments[1]", [3, 7]);
678    
679     =item $results = $wd->execute_async_script ($javascript, $args)
680    
681     Similar to C<execute_script>, but doesn't wait for script to return, but
682     instead waits for the script to call its last argument, which is added to
683     C<$args> automatically.
684    
685     $twenty = $wd->execute_async_script ("arguments[0](20)", undef);
686    
687     =cut
688    
689     sub get_page_source_ {
690     $_[0]->get_ (source => $_[1]);
691     }
692    
693     sub execute_script_ {
694     $_[0]->post_ ("execute/sync" => { script => "$_[1]", args => $_[2] || [] }, $_[3]);
695     }
696    
697     sub execute_async_script_ {
698     $_[0]->post_ ("execute/async" => { script => "$_[1]", args => $_[2] || [] }, $_[3]);
699     }
700    
701     =back
702    
703     =head3 COOKIES
704    
705     =over
706    
707     =cut
708    
709     =item $cookies = $wd->get_all_cookies
710    
711     Returns all cookies, as an arrayref of hashrefs.
712    
713     # google surely sets a lot of cookies without my consent
714     $wd->navigate_to ("http://google.com");
715     use Data::Dump;
716     ddx $wd->get_all_cookies;
717    
718     =item $cookie = $wd->get_named_cookie ($name)
719    
720     Returns a single cookie as a hashref.
721    
722     =item $wd->add_cookie ($cookie)
723    
724     Adds the given cookie hashref.
725    
726     =item $wd->delete_cookie ($name)
727    
728     Delete the named cookie.
729    
730     =item $wd->delete_all_cookies
731    
732     Delete all cookies.
733    
734     =cut
735    
736     sub get_all_cookies_ {
737     $_[0]->get_ (cookie => $_[1]);
738     }
739    
740     sub get_named_cookie_ {
741     $_[0]->get_ ("cookie/$_[1]" => $_[2]);
742     }
743    
744     sub add_cookie_ {
745     $_[0]->post_ (cookie => { cookie => $_[1] }, $_[2]);
746     }
747    
748     sub delete_cookie_ {
749     $_[0]->delete_ ("cookie/$_[1]" => $_[2]);
750     }
751    
752     sub delete_all_cookies_ {
753     $_[0]->delete_ (cookie => $_[2]);
754     }
755    
756     =back
757    
758     =head3 ACTIONS
759    
760     =over
761    
762     =cut
763    
764     =item $wd->perform_actions ($actions)
765    
766     Perform the given actions (an arrayref of action specifications simulating
767     user activity). For further details, read the spec.
768    
769     An example to get you started:
770    
771     $wd->navigate_to ("https://duckduckgo.com/html");
772     $wd->set_timeouts ({ implicit => 10000 });
773     my $input = $wd->find_element ("css selector", 'input[type="text"]');
774     $wd->perform_actions ([
775     {
776     id => "myfatfinger",
777     type => "pointer",
778     pointerType => "touch",
779     actions => [
780     { type => "pointerMove", duration => 100, origin => $wd->element_object ($input), x => 40, y => 5 },
781     { type => "pointerDown", button => 1 },
782     { type => "pause", duration => 40 },
783     { type => "pointerUp", button => 1 },
784     ],
785     },
786     {
787     id => "mykeyboard",
788     type => "key",
789     actions => [
790     { type => "pause" },
791     { type => "pause" },
792     { type => "pause" },
793     { type => "pause" },
794     { type => "keyDown", value => "a" },
795     { type => "pause", duration => 100 },
796     { type => "keyUp", value => "a" },
797     { type => "pause", duration => 100 },
798     { type => "keyDown", value => "b" },
799     { type => "pause", duration => 100 },
800     { type => "keyUp", value => "b" },
801     { type => "pause", duration => 2000 },
802     { type => "keyDown", value => "\x{E007}" }, # enter
803     { type => "pause", duration => 100 },
804     { type => "keyUp", value => "\x{E007}" }, # enter
805     { type => "pause", duration => 5000 },
806     ],
807     },
808     ]);
809    
810     =item $wd->release_actions
811    
812     Release all keys and pointer buttons currently depressed.
813    
814     =cut
815    
816     sub perform_actions_ {
817     $_[0]->post_ (actions => { actions => $_[1] }, $_[2]);
818     }
819    
820     sub release_actions_ {
821     $_[0]->delete_ (actions => $_[1]);
822     }
823    
824     =back
825    
826     =head3 USER PROMPTS
827    
828     =over
829    
830     =cut
831    
832     =item $wd->dismiss_alert
833    
834     Dismiss a simple dialog, if present.
835    
836     =item $wd->accept_alert
837    
838     Accept a simple dialog, if present.
839    
840     =item $text = $wd->get_alert_text
841    
842     Returns the text of any simple dialog.
843    
844     =item $text = $wd->send_alert_text
845    
846     Fills in the user prompt with the given text.
847    
848    
849     =cut
850    
851     sub dismiss_alert_ {
852     $_[0]->post_ ("alert/dismiss" => undef, $_[1]);
853     }
854    
855     sub accept_alert_ {
856     $_[0]->post_ ("alert/accept" => undef, $_[1]);
857     }
858    
859     sub get_alert_text_ {
860     $_[0]->get_ ("alert/text" => $_[1]);
861     }
862    
863     sub send_alert_text_ {
864     $_[0]->post_ ("alert/text" => { text => "$_[1]" }, $_[2]);
865     }
866    
867     =back
868    
869     =head3 SCREEN CAPTURE
870    
871     =over
872    
873     =cut
874    
875     =item $wd->take_screenshot
876    
877 root 1.9 Create a screenshot, returning it as a PNG image in a C<data:> URL.
878 root 1.1
879     =item $wd->take_element_screenshot ($element_id)
880    
881     Accept a simple dialog, if present.
882    
883     =cut
884    
885     sub take_screenshot_ {
886     $_[0]->get_ (screenshot => $_[1]);
887     }
888    
889     sub take_element_screenshot_ {
890     $_[0]->get_ ("element/$_[1]/screenshot" => $_[2]);
891     }
892    
893     =back
894    
895     =head2 HELPER METHODS
896    
897     =over
898    
899     =cut
900    
901 root 1.9 =item $object = AnyEvent::WebDriver->element_object ($element_id)
902    
903 root 1.1 =item $object = $wd->element_object ($element_id)
904    
905 root 1.9 Encoding element IDs in data structures is done by representing them as an
906 root 1.7 object with a special key and the element ID as value. This helper method
907 root 1.1 does this for you.
908    
909     =cut
910    
911     sub element_object {
912     +{ $WEB_ELEMENT_IDENTIFIER => $_[1] }
913     }
914    
915     =back
916    
917     =head2 EVENT BASED API
918    
919     This module wouldn't be a good AnyEvent citizen if it didn't have a true
920     event-based API.
921    
922     In fact, the simplified API, as documented above, is emulated via the
923     event-based API and an C<AUTOLOAD> function that automatically provides
924     blocking wrappers around the callback-based API.
925    
926     Every method documented in the L<SIMPLIFIED API> section has an equivalent
927     event-based method that is formed by appending a underscore (C<_>) to the
928     method name, and appending a callback to the argument list (mnemonic: the
929     underscore indicates the "the action is not yet finished" after the call
930     returns).
931    
932     For example, instead of a blocking calls to C<new_session>, C<navigate_to>
933     and C<back>, you can make a callback-based ones:
934    
935     my $cv = AE::cv;
936    
937     $wd->new_session ({}, sub {
938     my ($status, $value) = @_,
939    
940     die "error $value->{error}" if $status ne "200";
941    
942     $wd->navigate_to_ ("http://www.nethype.de", sub {
943    
944     $wd->back_ (sub {
945     print "all done\n";
946     $cv->send;
947     });
948    
949     });
950     });
951    
952     $cv->recv;
953    
954     While the blocking methods C<croak> on errors, the callback-based ones all
955     pass two values to the callback, C<$status> and C<$res>, where C<$status>
956 root 1.9 is the HTTP status code (200 for successful requests, typically 4xx or
957 root 1.1 5xx for errors), and C<$res> is the value of the C<value> key in the JSON
958     response object.
959    
960     Other than that, the underscore variants and the blocking variants are
961     identical.
962    
963     =head2 LOW LEVEL API
964    
965 root 1.9 All the simplified API methods are very thin wrappers around WebDriver
966     commands of the same name. They are all implemented in terms of the
967 root 1.1 low-level methods (C<req>, C<get>, C<post> and C<delete>), which exists
968     in blocking and callback-based variants (C<req_>, C<get_>, C<post_> and
969     C<delete_>).
970    
971     Examples are after the function descriptions.
972    
973     =over
974    
975     =item $wd->req_ ($method, $uri, $body, $cb->($status, $value))
976    
977     =item $value = $wd->req ($method, $uri, $body)
978    
979     Appends the C<$uri> to the C<endpoint/session/{sessionid}/> URL and makes
980     a HTTP C<$method> request (C<GET>, C<POST> etc.). C<POST> requests can
981     provide a UTF-8-encoded JSON text as HTTP request body, or the empty
982     string to indicate no body is used.
983    
984     For the callback version, the callback gets passed the HTTP status code
985     (200 for every successful request), and the value of the C<value> key in
986     the JSON response object as second argument.
987    
988     =item $wd->get_ ($uri, $cb->($status, $value))
989    
990     =item $value = $wd->get ($uri)
991    
992     Simply a call to C<req_> with C<$method> set to C<GET> and an empty body.
993    
994     =item $wd->post_ ($uri, $data, $cb->($status, $value))
995    
996     =item $value = $wd->post ($uri, $data)
997    
998     Simply a call to C<req_> with C<$method> set to C<POST> - if C<$body> is
999     C<undef>, then an empty object is send, otherwise, C<$data> must be a
1000     valid request object, which gets encoded into JSON for you.
1001    
1002     =item $wd->delete_ ($uri, $cb->($status, $value))
1003    
1004     =item $value = $wd->delete ($uri)
1005    
1006     Simply a call to C<req_> with C<$method> set to C<DELETE> and an empty body.
1007    
1008     =cut
1009    
1010     =back
1011    
1012     Example: implement C<get_all_cookies>, which is a simple C<GET> request
1013     without any parameters:
1014    
1015     $cookies = $wd->get ("cookie");
1016    
1017     Example: implement C<execute_script>, which needs some parameters:
1018    
1019     $results = $wd->post ("execute/sync" => { script => "$javascript", args => [] });
1020    
1021     Example: call C<find_elements> to find all C<IMG> elements, stripping the
1022     returned element objects to only return the element ID strings:
1023    
1024     my $elems = $wd->post (elements => { using => "css selector", value => "img" });
1025    
1026 root 1.6 # yes, the W3C found an interesting way around the typelessness of JSON
1027 root 1.1 $_ = $_->{"element-6066-11e4-a52e-4f735466cecf"}
1028     for @$elems;
1029    
1030     =cut
1031    
1032     =head1 HISTORY
1033    
1034 root 1.2 This module was unintentionally created (it started inside some quickly
1035     hacked-together script) simply because I couldn't get the existing
1036 root 1.1 C<Selenium::Remote::Driver> module to work, ever, despite multiple
1037     attempts over the years and trying to report multiple bugs, which have
1038     been completely ignored. It's also not event-based, so, yeah...
1039    
1040     =head1 AUTHOR
1041    
1042     Marc Lehmann <schmorp@schmorp.de>
1043     http://anyevent.schmorp.de
1044    
1045     =cut
1046    
1047     1
1048