ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-WebDriver/WebDriver.pm
Revision: 1.10
Committed: Tue Aug 28 23:33:10 2018 UTC (5 years, 9 months ago) by root
Branch: MAIN
CVS Tags: rel-0_1
Changes since 1.9: +2 -1 lines
Log Message:
0.1

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