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