ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-WebDriver/WebDriver.pm
Revision: 1.2
Committed: Tue Aug 28 22:59:53 2018 UTC (5 years, 9 months ago) by root
Branch: MAIN
CVS Tags: rel-0_0
Changes since 1.1: +2 -1 lines
Log Message:
0.0

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