ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-WebDriver/WebDriver.pm
Revision: 1.5
Committed: Tue Aug 28 23:10:30 2018 UTC (5 years, 9 months ago) by root
Branch: MAIN
Changes since 1.4: +2 -3 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     On success, C<< $wd->{sid} >> is set to the session id, and C<<
232     $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     Queries the czurrent page URL as set by C<navigate_to>.
314    
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     Switch to the given frame.
408    
409     =cut
410    
411     sub switch_to_frame_ {
412     $_[0]->post_ (frame => { id => "$_[1]" }, $_[2]);
413     }
414    
415     =item $handles = $wd->switch_to_parent_frame
416    
417     Switch to the parent frame.
418    
419     =cut
420    
421     sub switch_to_parent_frame_ {
422     $_[0]->post_ ("frame/parent" => undef, $_[1]);
423     }
424    
425     =item $rect = $wd->get_window_rect
426    
427     Return the current window rect, e.g.:
428    
429     $rect = $wd->get_window_rect
430 root 1.5 => { height => 1040, width => 540, x => 0, y => 0 }
431 root 1.1
432     =item $wd->set_window_rect ($rect)
433    
434     Sets the window rect.
435    
436     =cut
437    
438     sub get_window_rect_ {
439     $_[0]->get_ ("window/rect" => $_[1]);
440     }
441    
442     sub set_window_rect_ {
443     $_[0]->post_ ("window/rect" => $_[1], $_[2]);
444     }
445    
446     =item $wd->maximize_window
447    
448     =item $wd->minimize_window
449    
450     =item $wd->fullscreen_window
451    
452 root 1.3 Changes the window size by either maximising, minimising or making it
453 root 1.1 fullscreen. In my experience, this might timeout if no window manager is
454     running.
455    
456     =cut
457    
458     sub maximize_window_ {
459     $_[0]->post_ ("window/maximize" => undef, $_[1]);
460     }
461    
462     sub minimize_window_ {
463     $_[0]->post_ ("window/minimize" => undef, $_[1]);
464     }
465    
466     sub fullscreen_window_ {
467     $_[0]->post_ ("window/fullscreen" => undef, $_[1]);
468     }
469    
470     =back
471    
472     =head3 ELEMENT RETRIEVAL
473    
474     =over
475    
476     =cut
477    
478     =item $element_id = $wd->find_element ($location_strategy, $selector)
479    
480     Finds the first element specified by the given selector and returns its
481     web element id (the strong, not the object from the protocol). Raises an
482     error when no element was found.
483    
484     $element = $wd->find_element ("css selector" => "body a");
485     $element = $wd->find_element ("link text" => "Click Here For Porn");
486     $element = $wd->find_element ("partial link text" => "orn");
487     $element = $wd->find_element ("tag name" => "input");
488     $element = $wd->find_element ("xpath" => '//input[@type="text"]');
489     # "decddca8-5986-4e1d-8c93-efe952505a5f"
490    
491     =item $element_ids = $wd->find_elements ($location_strategy, $selector)
492    
493     As above, but returns an arrayref of all found element IDs.
494    
495     =item $element_id = $wd->find_element_from_element ($element_id, $location_strategy, $selector)
496    
497     Like C<find_element>, but looks only inside the specified C<$element>.
498    
499     =item $element_ids = $wd->find_elements_from_element ($element_id, $location_strategy, $selector)
500    
501     Like C<find_elements>, but looks only inside the specified C<$element>.
502    
503     my $head = $wd->find_element ("tag name" => "head");
504     my $links = $wd->find_elements_from_element ($head, "tag name", "link");
505    
506     =item $element_id = $wd->get_active_element
507    
508     Returns the active element.
509    
510     =cut
511    
512     sub find_element_ {
513     my $cb = pop;
514     $_[0]->post_ (element => { using => "$_[1]", value => "$_[2]" }, sub {
515     $cb->($_[0], $_[0] ne "200" ? $_[1] : $_[1]{$WEB_ELEMENT_IDENTIFIER})
516     });
517     }
518    
519     sub find_elements_ {
520     my $cb = pop;
521     $_[0]->post_ (elements => { using => "$_[1]", value => "$_[2]" }, sub {
522     $cb->($_[0], $_[0] ne "200" ? $_[1] : [ map $_->{$WEB_ELEMENT_IDENTIFIER}, @{$_[1]} ]);
523     });
524     }
525    
526     sub find_element_from_element_ {
527     my $cb = pop;
528     $_[0]->post_ ("element/$_[1]/element" => { using => "$_[2]", value => "$_[3]" }, sub {
529     $cb->($_[0], $_[0] ne "200" ? $_[1] : $_[1]{$WEB_ELEMENT_IDENTIFIER})
530     });
531     }
532    
533     sub find_elements_from_element_ {
534     my $cb = pop;
535     $_[0]->post_ ("element/$_[1]/elements" => { using => "$_[2]", value => "$_[3]" }, sub {
536     $cb->($_[0], $_[0] ne "200" ? $_[1] : [ map $_->{$WEB_ELEMENT_IDENTIFIER}, @{$_[1]} ]);
537     });
538     }
539    
540     sub get_active_element_ {
541     my $cb = pop;
542     $_[0]->get_ ("element/active" => sub {
543     $cb->($_[0], $_[0] ne "200" ? $_[1] : $_[1]{$WEB_ELEMENT_IDENTIFIER})
544     });
545     }
546    
547     =back
548    
549     =head3 ELEMENT STATE
550    
551     =over
552    
553     =cut
554    
555     =item $bool = $wd->is_element_selected
556    
557     Returns whether the given input or option element is selected or not.
558    
559     =item $string = $wd->get_element_attribute ($element_id, $name)
560    
561     Returns the value of the given attribute.
562    
563     =item $string = $wd->get_element_property ($element_id, $name)
564    
565     Returns the value of the given property.
566    
567     =item $string = $wd->get_element_css_value ($element_id, $name)
568    
569     Returns the value of the given css value.
570    
571     =item $string = $wd->get_element_text ($element_id)
572    
573     Returns the (rendered) text content of the given element.
574    
575     =item $string = $wd->get_element_tag_name ($element_id)
576    
577     Returns the tag of the given element.
578    
579     =item $rect = $wd->get_element_rect ($element_id)
580    
581     Returns the element rect of the given element.
582    
583     =item $bool = $wd->is_element_enabled
584    
585     Returns whether the element is enabled or not.
586    
587     =cut
588    
589     sub is_element_selected_ {
590     $_[0]->get_ ("element/$_[1]/selected" => $_[2]);
591     }
592    
593     sub get_element_attribute_ {
594     $_[0]->get_ ("element/$_[1]/attribute/$_[2]" => $_[3]);
595     }
596    
597     sub get_element_property_ {
598     $_[0]->get_ ("element/$_[1]/property/$_[2]" => $_[3]);
599     }
600    
601     sub get_element_css_value_ {
602     $_[0]->get_ ("element/$_[1]/css/$_[2]" => $_[3]);
603     }
604    
605     sub get_element_text_ {
606     $_[0]->get_ ("element/$_[1]/text" => $_[2]);
607     }
608    
609     sub get_element_tag_name_ {
610     $_[0]->get_ ("element/$_[1]/name" => $_[2]);
611     }
612    
613     sub get_element_rect_ {
614     $_[0]->get_ ("element/$_[1]/rect" => $_[2]);
615     }
616    
617     sub is_element_enabled_ {
618     $_[0]->get_ ("element/$_[1]/enabled" => $_[2]);
619     }
620    
621     =back
622    
623     =head3 ELEMENT INTERACTION
624    
625     =over
626    
627     =cut
628    
629     =item $wd->element_click ($element_id)
630    
631     Clicks the given element.
632    
633     =item $wd->element_clear ($element_id)
634    
635     Clear the contents of the given element.
636    
637     =item $wd->element_send_keys ($element_id, $text)
638    
639     Sends the given text as key events to the given element.
640    
641     =cut
642    
643     sub element_click_ {
644     $_[0]->post_ ("element/$_[1]/click" => undef, $_[2]);
645     }
646    
647     sub element_clear_ {
648     $_[0]->post_ ("element/$_[1]/clear" => undef, $_[2]);
649     }
650    
651     sub element_send_keys_ {
652     $_[0]->post_ ("element/$_[1]/value" => { text => "$_[2]" }, $_[3]);
653     }
654    
655     =back
656    
657     =head3 DOCUMENT HANDLING
658    
659     =over
660    
661     =cut
662    
663     =item $source = $wd->get_page_source
664    
665     Returns the (HTML/XML) page source of the current document.
666    
667     =item $results = $wd->execute_script ($javascript, $args)
668    
669     Synchronously execute the given script with given arguments and return its
670     results (C<$args> can be C<undef> if no arguments are wanted/needed).
671    
672     $ten = $wd->execute_script ("return arguments[0]+arguments[1]", [3, 7]);
673    
674     =item $results = $wd->execute_async_script ($javascript, $args)
675    
676     Similar to C<execute_script>, but doesn't wait for script to return, but
677     instead waits for the script to call its last argument, which is added to
678     C<$args> automatically.
679    
680     $twenty = $wd->execute_async_script ("arguments[0](20)", undef);
681    
682     =cut
683    
684     sub get_page_source_ {
685     $_[0]->get_ (source => $_[1]);
686     }
687    
688     sub execute_script_ {
689     $_[0]->post_ ("execute/sync" => { script => "$_[1]", args => $_[2] || [] }, $_[3]);
690     }
691    
692     sub execute_async_script_ {
693     $_[0]->post_ ("execute/async" => { script => "$_[1]", args => $_[2] || [] }, $_[3]);
694     }
695    
696     =back
697    
698     =head3 COOKIES
699    
700     =over
701    
702     =cut
703    
704     =item $cookies = $wd->get_all_cookies
705    
706     Returns all cookies, as an arrayref of hashrefs.
707    
708     # google surely sets a lot of cookies without my consent
709     $wd->navigate_to ("http://google.com");
710     use Data::Dump;
711     ddx $wd->get_all_cookies;
712    
713     =item $cookie = $wd->get_named_cookie ($name)
714    
715     Returns a single cookie as a hashref.
716    
717     =item $wd->add_cookie ($cookie)
718    
719     Adds the given cookie hashref.
720    
721     =item $wd->delete_cookie ($name)
722    
723     Delete the named cookie.
724    
725     =item $wd->delete_all_cookies
726    
727     Delete all cookies.
728    
729     =cut
730    
731     sub get_all_cookies_ {
732     $_[0]->get_ (cookie => $_[1]);
733     }
734    
735     sub get_named_cookie_ {
736     $_[0]->get_ ("cookie/$_[1]" => $_[2]);
737     }
738    
739     sub add_cookie_ {
740     $_[0]->post_ (cookie => { cookie => $_[1] }, $_[2]);
741     }
742    
743     sub delete_cookie_ {
744     $_[0]->delete_ ("cookie/$_[1]" => $_[2]);
745     }
746    
747     sub delete_all_cookies_ {
748     $_[0]->delete_ (cookie => $_[2]);
749     }
750    
751     =back
752    
753     =head3 ACTIONS
754    
755     =over
756    
757     =cut
758    
759     =item $wd->perform_actions ($actions)
760    
761     Perform the given actions (an arrayref of action specifications simulating
762     user activity). For further details, read the spec.
763    
764     An example to get you started:
765    
766     $wd->navigate_to ("https://duckduckgo.com/html");
767     $wd->set_timeouts ({ implicit => 10000 });
768     my $input = $wd->find_element ("css selector", 'input[type="text"]');
769     $wd->perform_actions ([
770     {
771     id => "myfatfinger",
772     type => "pointer",
773     pointerType => "touch",
774     actions => [
775     { type => "pointerMove", duration => 100, origin => $wd->element_object ($input), x => 40, y => 5 },
776     { type => "pointerDown", button => 1 },
777     { type => "pause", duration => 40 },
778     { type => "pointerUp", button => 1 },
779     ],
780     },
781     {
782     id => "mykeyboard",
783     type => "key",
784     actions => [
785     { type => "pause" },
786     { type => "pause" },
787     { type => "pause" },
788     { type => "pause" },
789     { type => "keyDown", value => "a" },
790     { type => "pause", duration => 100 },
791     { type => "keyUp", value => "a" },
792     { type => "pause", duration => 100 },
793     { type => "keyDown", value => "b" },
794     { type => "pause", duration => 100 },
795     { type => "keyUp", value => "b" },
796     { type => "pause", duration => 2000 },
797     { type => "keyDown", value => "\x{E007}" }, # enter
798     { type => "pause", duration => 100 },
799     { type => "keyUp", value => "\x{E007}" }, # enter
800     { type => "pause", duration => 5000 },
801     ],
802     },
803     ]);
804    
805     =item $wd->release_actions
806    
807     Release all keys and pointer buttons currently depressed.
808    
809     =cut
810    
811     sub perform_actions_ {
812     $_[0]->post_ (actions => { actions => $_[1] }, $_[2]);
813     }
814    
815     sub release_actions_ {
816     $_[0]->delete_ (actions => $_[1]);
817     }
818    
819     =back
820    
821     =head3 USER PROMPTS
822    
823     =over
824    
825     =cut
826    
827     =item $wd->dismiss_alert
828    
829     Dismiss a simple dialog, if present.
830    
831     =item $wd->accept_alert
832    
833     Accept a simple dialog, if present.
834    
835     =item $text = $wd->get_alert_text
836    
837     Returns the text of any simple dialog.
838    
839     =item $text = $wd->send_alert_text
840    
841     Fills in the user prompt with the given text.
842    
843    
844     =cut
845    
846     sub dismiss_alert_ {
847     $_[0]->post_ ("alert/dismiss" => undef, $_[1]);
848     }
849    
850     sub accept_alert_ {
851     $_[0]->post_ ("alert/accept" => undef, $_[1]);
852     }
853    
854     sub get_alert_text_ {
855     $_[0]->get_ ("alert/text" => $_[1]);
856     }
857    
858     sub send_alert_text_ {
859     $_[0]->post_ ("alert/text" => { text => "$_[1]" }, $_[2]);
860     }
861    
862     =back
863    
864     =head3 SCREEN CAPTURE
865    
866     =over
867    
868     =cut
869    
870     =item $wd->take_screenshot
871    
872     Create a screenshot, returning it as a PNG image in a data url.
873    
874     =item $wd->take_element_screenshot ($element_id)
875    
876     Accept a simple dialog, if present.
877    
878     =cut
879    
880     sub take_screenshot_ {
881     $_[0]->get_ (screenshot => $_[1]);
882     }
883    
884     sub take_element_screenshot_ {
885     $_[0]->get_ ("element/$_[1]/screenshot" => $_[2]);
886     }
887    
888     =back
889    
890     =head2 HELPER METHODS
891    
892     =over
893    
894     =cut
895    
896     =item $object = $wd->element_object ($element_id)
897    
898     Encoding element ids in data structures is done by represetning them as an
899     object with a special key and the element id as value. This helper method
900     does this for you.
901    
902     =cut
903    
904     sub element_object {
905     +{ $WEB_ELEMENT_IDENTIFIER => $_[1] }
906     }
907    
908     =back
909    
910     =head2 EVENT BASED API
911    
912     This module wouldn't be a good AnyEvent citizen if it didn't have a true
913     event-based API.
914    
915     In fact, the simplified API, as documented above, is emulated via the
916     event-based API and an C<AUTOLOAD> function that automatically provides
917     blocking wrappers around the callback-based API.
918    
919     Every method documented in the L<SIMPLIFIED API> section has an equivalent
920     event-based method that is formed by appending a underscore (C<_>) to the
921     method name, and appending a callback to the argument list (mnemonic: the
922     underscore indicates the "the action is not yet finished" after the call
923     returns).
924    
925     For example, instead of a blocking calls to C<new_session>, C<navigate_to>
926     and C<back>, you can make a callback-based ones:
927    
928     my $cv = AE::cv;
929    
930     $wd->new_session ({}, sub {
931     my ($status, $value) = @_,
932    
933     die "error $value->{error}" if $status ne "200";
934    
935     $wd->navigate_to_ ("http://www.nethype.de", sub {
936    
937     $wd->back_ (sub {
938     print "all done\n";
939     $cv->send;
940     });
941    
942     });
943     });
944    
945     $cv->recv;
946    
947     While the blocking methods C<croak> on errors, the callback-based ones all
948     pass two values to the callback, C<$status> and C<$res>, where C<$status>
949     is the HTTP status code (200 for successful requests, typically 4xx ot
950     5xx for errors), and C<$res> is the value of the C<value> key in the JSON
951     response object.
952    
953     Other than that, the underscore variants and the blocking variants are
954     identical.
955    
956     =head2 LOW LEVEL API
957    
958     All the simplfiied API methods are very thin wrappers around WebDriver
959     commands of the same name. Theyx are all implemented in terms of the
960     low-level methods (C<req>, C<get>, C<post> and C<delete>), which exists
961     in blocking and callback-based variants (C<req_>, C<get_>, C<post_> and
962     C<delete_>).
963    
964     Examples are after the function descriptions.
965    
966     =over
967    
968     =item $wd->req_ ($method, $uri, $body, $cb->($status, $value))
969    
970     =item $value = $wd->req ($method, $uri, $body)
971    
972     Appends the C<$uri> to the C<endpoint/session/{sessionid}/> URL and makes
973     a HTTP C<$method> request (C<GET>, C<POST> etc.). C<POST> requests can
974     provide a UTF-8-encoded JSON text as HTTP request body, or the empty
975     string to indicate no body is used.
976    
977     For the callback version, the callback gets passed the HTTP status code
978     (200 for every successful request), and the value of the C<value> key in
979     the JSON response object as second argument.
980    
981     =item $wd->get_ ($uri, $cb->($status, $value))
982    
983     =item $value = $wd->get ($uri)
984    
985     Simply a call to C<req_> with C<$method> set to C<GET> and an empty body.
986    
987     =item $wd->post_ ($uri, $data, $cb->($status, $value))
988    
989     =item $value = $wd->post ($uri, $data)
990    
991     Simply a call to C<req_> with C<$method> set to C<POST> - if C<$body> is
992     C<undef>, then an empty object is send, otherwise, C<$data> must be a
993     valid request object, which gets encoded into JSON for you.
994    
995     =item $wd->delete_ ($uri, $cb->($status, $value))
996    
997     =item $value = $wd->delete ($uri)
998    
999     Simply a call to C<req_> with C<$method> set to C<DELETE> and an empty body.
1000    
1001     =cut
1002    
1003     =back
1004    
1005     Example: implement C<get_all_cookies>, which is a simple C<GET> request
1006     without any parameters:
1007    
1008     $cookies = $wd->get ("cookie");
1009    
1010     Example: implement C<execute_script>, which needs some parameters:
1011    
1012     $results = $wd->post ("execute/sync" => { script => "$javascript", args => [] });
1013    
1014     Example: call C<find_elements> to find all C<IMG> elements, stripping the
1015     returned element objects to only return the element ID strings:
1016    
1017     my $elems = $wd->post (elements => { using => "css selector", value => "img" });
1018    
1019     # yes, the W3C found an interetsing way around the typelessness of JSON
1020     $_ = $_->{"element-6066-11e4-a52e-4f735466cecf"}
1021     for @$elems;
1022    
1023     =cut
1024    
1025     =head1 HISTORY
1026    
1027 root 1.2 This module was unintentionally created (it started inside some quickly
1028     hacked-together script) simply because I couldn't get the existing
1029 root 1.1 C<Selenium::Remote::Driver> module to work, ever, despite multiple
1030     attempts over the years and trying to report multiple bugs, which have
1031     been completely ignored. It's also not event-based, so, yeah...
1032    
1033     =head1 AUTHOR
1034    
1035     Marc Lehmann <schmorp@schmorp.de>
1036     http://anyevent.schmorp.de
1037    
1038     =cut
1039    
1040     1
1041