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