ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-WebDriver/WebDriver.pm
Revision: 1.14
Committed: Wed Aug 29 04:54:21 2018 UTC (5 years, 9 months ago) by root
Branch: MAIN
Changes since 1.13: +98 -11 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 root 1.14 WARNING: THE API IS NOT GUARANTEED TO BE STABLE UNTIL VERSION 1.0.
30    
31 root 1.1 This module aims to implement the W3C WebDriver specification which is the
32     standardised equivalent to the Selenium WebDriver API., which in turn aims
33     at remotely controlling web browsers such as Firefox or Chromium.
34    
35 root 1.11 At the time of this writing, it was so brand new that I ciould only get
36     C<geckodriver> (For Firefox) to work, but that is expected to be fioxed
37     very soon indeed.
38 root 1.1
39     To make most of this module, or, in fact, to make any reasonable use of
40 root 1.11 this module, you would need to refer to the W3C WebDriver recommendation,
41     which can be found L<here|https://www.w3.org/TR/webdriver1/>:
42 root 1.1
43 root 1.11 https://www.w3.org/TR/webdriver1/
44 root 1.1
45 root 1.12 =head2 CONVENTIONS
46    
47     Unless otherwise stated, all delays and time differences in this module
48     are represented as an integer number of milliseconds.
49    
50 root 1.1 =cut
51    
52     package AnyEvent::WebDriver;
53    
54     use common::sense;
55    
56     use Carp ();
57 root 1.10 use AnyEvent ();
58 root 1.1 use AnyEvent::HTTP ();
59    
60 root 1.12 our $VERSION = 0.2;
61 root 1.1
62     our $WEB_ELEMENT_IDENTIFIER = "element-6066-11e4-a52e-4f735466cecf";
63    
64 root 1.14 my $json = eval { require JSON::XS; JSON::XS:: } || do { require JSON::PP; JSON::PP:: };
65     $json = $json->new->utf8;
66 root 1.9
67     $json->boolean_values (0, 1)
68     if $json->can ("boolean_values");
69 root 1.1
70     sub req_ {
71 root 1.9 my ($self, $method, $ep, $body, $cb) = @_;
72 root 1.1
73 root 1.9 AnyEvent::HTTP::http_request $method => "$self->{_ep}$ep",
74 root 1.1 body => $body,
75 root 1.3 timeout => $self->{timeout},
76 root 1.1 headers => { "content-type" => "application/json; charset=utf-8", "cache-control" => "no-cache" },
77 root 1.9 ($self->{proxy} eq "default" ? () : (proxy => $self->{proxy})),
78 root 1.1 sub {
79     my ($res, $hdr) = @_;
80    
81     $res = eval { $json->decode ($res) };
82     $hdr->{Status} = 500 unless exists $res->{value};
83    
84     $cb->($hdr->{Status}, $res->{value});
85     }
86     ;
87     }
88    
89     sub get_ {
90 root 1.9 my ($self, $ep, $cb) = @_;
91 root 1.1
92 root 1.9 $self->req_ (GET => $ep, undef, $cb)
93 root 1.1 }
94    
95     sub post_ {
96 root 1.9 my ($self, $ep, $data, $cb) = @_;
97 root 1.1
98 root 1.9 $self->req_ (POST => $ep, $json->encode ($data || {}), $cb)
99 root 1.1 }
100    
101     sub delete_ {
102 root 1.9 my ($self, $ep, $cb) = @_;
103 root 1.1
104 root 1.9 $self->req_ (DELETE => $ep, "", $cb)
105 root 1.1 }
106    
107     sub AUTOLOAD {
108     our $AUTOLOAD;
109    
110     $_[0]->isa (__PACKAGE__)
111     or Carp::croak "$AUTOLOAD: no such function";
112    
113     (my $name = $AUTOLOAD) =~ s/^.*://;
114    
115     my $name_ = "$name\_";
116    
117     defined &$name_
118 root 1.14 or Carp::croak "$AUTOLOAD: no such method";
119 root 1.1
120     my $func_ = \&$name_;
121    
122     *$name = sub {
123     $func_->(@_, my $cv = AE::cv);
124     my ($status, $res) = $cv->recv;
125    
126     if ($status ne "200") {
127     my $msg;
128    
129     if (exists $res->{error}) {
130     $msg = "AyEvent::WebDriver: $res->{error}: $res->{message}";
131     $msg .= "\n$res->{stacktrace}" if length $res->{stacktrace};
132     } else {
133     $msg = "AnyEvent::WebDriver: http status $status (wrong endpoint?), caught";
134     }
135    
136     Carp::croak $msg;
137     }
138    
139     $res
140     };
141    
142     goto &$name;
143     }
144    
145 root 1.12 =head2 WEBDRIVER OBJECTS
146 root 1.1
147     =over
148    
149     =item new AnyEvent::WebDriver key => value...
150    
151 root 1.9 Create a new WebDriver object. Example for a remote WebDriver connection
152 root 1.1 (the only type supported at the moment):
153    
154     my $wd = new AnyEvent::WebDriver host => "localhost", port => 4444;
155    
156     Supported keys are:
157    
158     =over
159    
160     =item endpoint => $string
161    
162     For remote connections, the endpoint to connect to (defaults to C<http://localhost:4444>).
163    
164     =item proxy => $proxyspec
165    
166     The proxy to use (same as the C<proxy> argument used by
167     L<AnyEvent::HTTP>). The default is C<undef>, which disables proxies. To
168     use the system-provided proxy (e.g. C<http_proxy> environment variable),
169     specify a value of C<default>.
170    
171     =item autodelete => $boolean
172    
173     If true (the default), then automatically execute C<delete_session> when
174     the WebDriver object is destroyed with an active session. IF set to a
175     false value, then the session will continue to exist.
176    
177 root 1.3 =item timeout => $seconds
178    
179     The HTTP timeout, in (fractional) seconds (default: C<300>, but this will
180     likely drastically reduce). This timeout is reset on any activity, so it
181 root 1.9 is not an overall request timeout. Also, individual requests might extend
182 root 1.3 this timeout if they are known to take longer.
183    
184 root 1.1 =back
185    
186     =cut
187    
188     sub new {
189     my ($class, %kv) = @_;
190    
191     bless {
192     endpoint => "http://localhost:4444",
193     proxy => undef,
194     autodelete => 1,
195 root 1.3 timeout => 300,
196 root 1.1 %kv,
197     }, $class
198     }
199    
200     sub DESTROY {
201 root 1.9 my ($self) = @_;
202 root 1.1
203 root 1.9 $self->delete_session
204     if exists $self->{sid};
205 root 1.1 }
206    
207 root 1.12 =item $al = $wd->actions
208    
209     Creates an action list associated with this WebDriver. See L<ACTION
210     LISTS>, below, for full details.
211    
212     =cut
213    
214     sub actions {
215     AnyEvent::WebDriver::Actions->new (wd => $_[0])
216     }
217    
218 root 1.14 =item $sessionstring = $wd->save_session
219    
220     Save the current session in a string so it can be restored load with
221     C<load_session>. Note that only the session data itself is stored
222     (currently the session id and capabilities), not the endpoint information
223     itself.
224    
225     The main use of this function is in conjunction with disabled
226     C<autodelete>, to save a session to e.g., and restore it later. It could
227     presumably used for other applications, suhc as using the same sssion from
228     multiple processes and so on.
229    
230     =item $wd->load_session ($sessionstring)
231    
232     =item $wd->set_session ($sessionid, $capabilities)
233    
234     Starts using the given session, as identified by
235     C<$sessionid>. C<$capabilities> should be the original session
236     capabilities, although the current version of this module does not make
237     any use of it.
238    
239     The C<$sessionid> is stored in C<< $wd->{sid} >> (and could be fetched
240     form there for later use), while the capabilities are stored in C<<
241     $wd->{capabilities} >>.
242    
243     =cut
244    
245     sub save_session {
246     my ($self) = @_;
247    
248     $json->encode ([1, $self->{sid}, $self->{capabilities}]);
249     }
250    
251     sub load_session {
252     my ($self, $session) = @_;
253    
254     $session = $json->decode ($session);
255    
256     $session->[0] == 1
257     or Carp::croak "AnyEvent::WebDriver::load_session: session corrupted or from different version";
258    
259     $self->set_session ($session->[1], $session->[2]);
260     }
261    
262     sub set_session {
263     my ($self, $sid, $caps) = @_;
264    
265     $self->{sid} = $sid;
266     $self->{capabilities} = $caps;
267    
268     $self->{_ep} = "$self->{endpoint}/session/$self->{sid}/";
269     }
270    
271 root 1.1 =back
272    
273     =head2 SIMPLIFIED API
274    
275     This section documents the simplified API, which is really just a very
276     thin wrapper around the WebDriver protocol commands. They all block (using
277     L<AnyEvent> condvars) the caller until the result is available, so must
278     not be called from an event loop callback - see L<EVENT BASED API> for an
279     alternative.
280    
281 root 1.9 The method names are pretty much taken directly from the W3C WebDriver
282 root 1.1 specification, e.g. the request documented in the "Get All Cookies"
283     section is implemented via the C<get_all_cookies> method.
284    
285 root 1.9 The order is the same as in the WebDriver draft at the time of this
286 root 1.1 writing, and only minimal massaging is done to request parameters and
287     results.
288    
289     =head3 SESSIONS
290    
291     =over
292    
293     =cut
294    
295     =item $wd->new_session ({ key => value... })
296    
297 root 1.9 Try to connect to the WebDriver and initialize a new session with a
298     "new session" command, passing the given key-value pairs as value
299 root 1.1 (e.g. C<capabilities>).
300    
301     No session-dependent methods must be called before this function returns
302 root 1.9 successfully, and only one session can be created per WebDriver object.
303 root 1.1
304 root 1.7 On success, C<< $wd->{sid} >> is set to the session ID, and C<<
305 root 1.1 $wd->{capabilities} >> is set to the returned capabilities.
306    
307 root 1.14 Simple example of creatring a WebDriver object and a new session:
308    
309 root 1.4 my $wd = new AnyEvent::Selenium endpoint => "http://localhost:4545";
310 root 1.14 $wd->new_session ({});
311    
312     Real-world example with capability negotiation:
313 root 1.1
314     $wd->new_session ({
315     capabilities => {
316 root 1.14 alwaysMatch => {
317     pageLoadStrategy => "eager",
318     unhandledPromptBehavior => "dismiss",
319     },
320     firstMatch => [
321     {
322     browserName => "firefox",
323     "moz:firefoxOptions" => {
324     binary => "firefox/firefox",
325     args => ["-devtools"],
326     prefs => {
327     "dom.webnotifications.enabled" => \0,
328     },
329     },
330     },
331     {
332     # generic fallback
333     },
334     ],
335    
336     },
337 root 1.1 });
338    
339 root 1.14 Firefox-specific capability documentation can be found L<on
340     MDN|https://developer.mozilla.org/en-US/docs/Web/WebDriver/Capabilities>,
341     Chrome-specific capability documentation might be found
342     L<here|http://chromedriver.chromium.org/capabilities>, but the latest
343     release at the time of this writing has effectively no WebDriver support
344     at all, and canary releases are not freely downloadable.
345    
346     If you have URLs for Safari/IE/Edge etc. capabilities, feel free to tell
347     me about them.
348    
349 root 1.1 =cut
350    
351     sub new_session_ {
352 root 1.9 my ($self, $kv, $cb) = @_;
353 root 1.1
354 root 1.9 local $self->{_ep} = "$self->{endpoint}/";
355     $self->post_ (session => $kv, sub {
356 root 1.1 my ($status, $res) = @_;
357    
358 root 1.14 exists $res->{capabilities}
359     or $status = "500"; # blasted chromedriver
360 root 1.1
361 root 1.14 $self->set_session ($res->{sessionId}, $res->{capabilities});
362     if $status eq "200";
363 root 1.1
364     $cb->($status, $res);
365     });
366     }
367    
368     =item $wd->delete_session
369    
370     Deletes the session - the WebDriver object must not be used after this
371     call.
372    
373     =cut
374    
375     sub delete_session_ {
376 root 1.9 my ($self, $cb) = @_;
377 root 1.1
378 root 1.9 local $self->{_ep} = "$self->{endpoint}/session/$self->{sid}";
379     $self->delete_ ("" => $cb);
380 root 1.1 }
381    
382     =item $timeouts = $wd->get_timeouts
383    
384     Get the current timeouts, e.g.:
385    
386     my $timeouts = $wd->get_timeouts;
387 root 1.5 => { implicit => 0, pageLoad => 300000, script => 30000 }
388 root 1.1
389     =item $wd->set_timeouts ($timeouts)
390    
391     Sets one or more timeouts, e.g.:
392    
393     $wd->set_timeouts ({ script => 60000 });
394    
395     =cut
396    
397     sub get_timeouts_ {
398     $_[0]->get_ (timeouts => $_[1], $_[2]);
399     }
400    
401     sub set_timeouts_ {
402     $_[0]->post_ (timeouts => $_[1], $_[2], $_[3]);
403     }
404    
405     =back
406    
407     =head3 NAVIGATION
408    
409     =over
410    
411     =cut
412    
413     =item $wd->navigate_to ($url)
414    
415     Navigates to the specified URL.
416    
417     =item $url = $wd->get_current_url
418    
419 root 1.8 Queries the current page URL as set by C<navigate_to>.
420 root 1.1
421     =cut
422    
423     sub navigate_to_ {
424     $_[0]->post_ (url => { url => "$_[1]" }, $_[2]);
425     }
426    
427     sub get_current_url_ {
428     $_[0]->get_ (url => $_[1])
429     }
430    
431     =item $wd->back
432    
433     The equivalent of pressing "back" in the browser.
434    
435     =item $wd->forward
436    
437     The equivalent of pressing "forward" in the browser.
438    
439     =item $wd->refresh
440    
441     The equivalent of pressing "refresh" in the browser.
442    
443     =cut
444    
445     sub back_ {
446     $_[0]->post_ (back => undef, $_[1]);
447     }
448    
449     sub forward_ {
450     $_[0]->post_ (forward => undef, $_[1]);
451     }
452    
453     sub refresh_ {
454     $_[0]->post_ (refresh => undef, $_[1]);
455     }
456    
457     =item $title = $wd->get_title
458    
459     Returns the current document title.
460    
461     =cut
462    
463     sub get_title_ {
464     $_[0]->get_ (title => $_[1]);
465     }
466    
467     =back
468    
469     =head3 COMMAND CONTEXTS
470    
471     =over
472    
473     =cut
474    
475     =item $handle = $wd->get_window_handle
476    
477     Returns the current window handle.
478    
479     =item $wd->close_window
480    
481     Closes the current browsing context.
482    
483     =item $wd->switch_to_window ($handle)
484    
485     Changes the current browsing context to the given window.
486    
487     =cut
488    
489     sub get_window_handle_ {
490     $_[0]->get_ (window => $_[1]);
491     }
492    
493     sub close_window_ {
494     $_[0]->delete_ (window => $_[1]);
495     }
496    
497     sub switch_to_window_ {
498     $_[0]->post_ (window => "$_[1]", $_[2]);
499     }
500    
501     =item $handles = $wd->get_window_handles
502    
503     Return the current window handles as an array-ref of handle IDs.
504    
505     =cut
506    
507     sub get_window_handles_ {
508     $_[0]->get_ ("window/handles" => $_[1]);
509     }
510    
511     =item $handles = $wd->switch_to_frame ($frame)
512    
513 root 1.8 Switch to the given frame identified by C<$frame>, which must be either
514     C<undef> to go back to the top-level browsing context, an integer to
515 root 1.12 select the nth subframe, or an element object.
516 root 1.1
517     =cut
518    
519     sub switch_to_frame_ {
520     $_[0]->post_ (frame => { id => "$_[1]" }, $_[2]);
521     }
522    
523     =item $handles = $wd->switch_to_parent_frame
524    
525     Switch to the parent frame.
526    
527     =cut
528    
529     sub switch_to_parent_frame_ {
530     $_[0]->post_ ("frame/parent" => undef, $_[1]);
531     }
532    
533     =item $rect = $wd->get_window_rect
534    
535     Return the current window rect, e.g.:
536    
537     $rect = $wd->get_window_rect
538 root 1.5 => { height => 1040, width => 540, x => 0, y => 0 }
539 root 1.1
540     =item $wd->set_window_rect ($rect)
541    
542     Sets the window rect.
543    
544     =cut
545    
546     sub get_window_rect_ {
547     $_[0]->get_ ("window/rect" => $_[1]);
548     }
549    
550     sub set_window_rect_ {
551     $_[0]->post_ ("window/rect" => $_[1], $_[2]);
552     }
553    
554     =item $wd->maximize_window
555    
556     =item $wd->minimize_window
557    
558     =item $wd->fullscreen_window
559    
560 root 1.3 Changes the window size by either maximising, minimising or making it
561 root 1.9 fullscreen. In my experience, this will timeout if no window manager is
562 root 1.1 running.
563    
564     =cut
565    
566     sub maximize_window_ {
567     $_[0]->post_ ("window/maximize" => undef, $_[1]);
568     }
569    
570     sub minimize_window_ {
571     $_[0]->post_ ("window/minimize" => undef, $_[1]);
572     }
573    
574     sub fullscreen_window_ {
575     $_[0]->post_ ("window/fullscreen" => undef, $_[1]);
576     }
577    
578     =back
579    
580     =head3 ELEMENT RETRIEVAL
581    
582 root 1.13 To reduce typing and memory strain, the element finding functions accept
583     some shorter and hopefully easier to remember aliases for the standard
584     locator strategy values, as follows:
585    
586     Alias Locator Strategy
587     css css selector
588     link link text
589     substr partial link text
590     tag tag name
591    
592 root 1.1 =over
593    
594     =cut
595    
596 root 1.13 our %USING = (
597     css => "css selector",
598     link => "link text",
599     substr => "partial link text",
600     tag => "tag name",
601     );
602    
603     sub _using($) {
604     using => $USING{$_[0]} // "$_[0]"
605     }
606    
607     =item $element = $wd->find_element ($locator_strategy, $selector)
608 root 1.1
609     Finds the first element specified by the given selector and returns its
610 root 1.12 element object. Raises an error when no element was found.
611 root 1.1
612     $element = $wd->find_element ("css selector" => "body a");
613     $element = $wd->find_element ("link text" => "Click Here For Porn");
614     $element = $wd->find_element ("partial link text" => "orn");
615     $element = $wd->find_element ("tag name" => "input");
616     $element = $wd->find_element ("xpath" => '//input[@type="text"]');
617 root 1.12 => e.g. { "element-6066-11e4-a52e-4f735466cecf" => "decddca8-5986-4e1d-8c93-efe952505a5f" }
618 root 1.1
619 root 1.13 =item $elements = $wd->find_elements ($locator_strategy, $selector)
620 root 1.1
621 root 1.12 As above, but returns an arrayref of all found element objects.
622 root 1.1
623 root 1.13 =item $element = $wd->find_element_from_element ($element, $locator_strategy, $selector)
624 root 1.1
625     Like C<find_element>, but looks only inside the specified C<$element>.
626    
627 root 1.13 =item $elements = $wd->find_elements_from_element ($element, $locator_strategy, $selector)
628 root 1.1
629     Like C<find_elements>, but looks only inside the specified C<$element>.
630    
631     my $head = $wd->find_element ("tag name" => "head");
632     my $links = $wd->find_elements_from_element ($head, "tag name", "link");
633    
634 root 1.12 =item $element = $wd->get_active_element
635 root 1.1
636     Returns the active element.
637    
638     =cut
639    
640     sub find_element_ {
641 root 1.13 $_[0]->post_ (element => { _using $_[1], value => "$_[2]" }, $_[3]);
642 root 1.1 }
643    
644     sub find_elements_ {
645 root 1.13 $_[0]->post_ (elements => { _using $_[1], value => "$_[2]" }, $_[3]);
646 root 1.1 }
647    
648     sub find_element_from_element_ {
649 root 1.13 $_[0]->post_ ("element/$_[1]/element" => { _using $_[2], value => "$_[3]" }, $_[4]);
650 root 1.1 }
651    
652     sub find_elements_from_element_ {
653 root 1.13 $_[0]->post_ ("element/$_[1]/elements" => { _using $_[2], value => "$_[3]" }, $_[4]);
654 root 1.1 }
655    
656     sub get_active_element_ {
657 root 1.12 $_[0]->get_ ("element/active" => $_[1]);
658 root 1.1 }
659    
660     =back
661    
662     =head3 ELEMENT STATE
663    
664     =over
665    
666     =cut
667    
668     =item $bool = $wd->is_element_selected
669    
670     Returns whether the given input or option element is selected or not.
671    
672 root 1.12 =item $string = $wd->get_element_attribute ($element, $name)
673 root 1.1
674     Returns the value of the given attribute.
675    
676 root 1.12 =item $string = $wd->get_element_property ($element, $name)
677 root 1.1
678     Returns the value of the given property.
679    
680 root 1.12 =item $string = $wd->get_element_css_value ($element, $name)
681 root 1.1
682 root 1.9 Returns the value of the given CSS value.
683 root 1.1
684 root 1.12 =item $string = $wd->get_element_text ($element)
685 root 1.1
686     Returns the (rendered) text content of the given element.
687    
688 root 1.12 =item $string = $wd->get_element_tag_name ($element)
689 root 1.1
690     Returns the tag of the given element.
691    
692 root 1.12 =item $rect = $wd->get_element_rect ($element)
693 root 1.1
694 root 1.9 Returns the element rect(angle) of the given element.
695 root 1.1
696     =item $bool = $wd->is_element_enabled
697    
698     Returns whether the element is enabled or not.
699    
700     =cut
701    
702     sub is_element_selected_ {
703 root 1.12 $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/selected" => $_[2]);
704 root 1.1 }
705    
706     sub get_element_attribute_ {
707 root 1.12 $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/attribute/$_[2]" => $_[3]);
708 root 1.1 }
709    
710     sub get_element_property_ {
711 root 1.12 $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/property/$_[2]" => $_[3]);
712 root 1.1 }
713    
714     sub get_element_css_value_ {
715 root 1.12 $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/css/$_[2]" => $_[3]);
716 root 1.1 }
717    
718     sub get_element_text_ {
719 root 1.12 $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/text" => $_[2]);
720 root 1.1 }
721    
722     sub get_element_tag_name_ {
723 root 1.12 $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/name" => $_[2]);
724 root 1.1 }
725    
726     sub get_element_rect_ {
727 root 1.12 $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/rect" => $_[2]);
728 root 1.1 }
729    
730     sub is_element_enabled_ {
731 root 1.12 $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/enabled" => $_[2]);
732 root 1.1 }
733    
734     =back
735    
736     =head3 ELEMENT INTERACTION
737    
738     =over
739    
740     =cut
741    
742 root 1.12 =item $wd->element_click ($element)
743 root 1.1
744     Clicks the given element.
745    
746 root 1.12 =item $wd->element_clear ($element)
747 root 1.1
748     Clear the contents of the given element.
749    
750 root 1.12 =item $wd->element_send_keys ($element, $text)
751 root 1.1
752     Sends the given text as key events to the given element.
753    
754     =cut
755    
756     sub element_click_ {
757 root 1.12 $_[0]->post_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/click" => undef, $_[2]);
758 root 1.1 }
759    
760     sub element_clear_ {
761 root 1.12 $_[0]->post_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/clear" => undef, $_[2]);
762 root 1.1 }
763    
764     sub element_send_keys_ {
765 root 1.12 $_[0]->post_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/value" => { text => "$_[2]" }, $_[3]);
766 root 1.1 }
767    
768     =back
769    
770     =head3 DOCUMENT HANDLING
771    
772     =over
773    
774     =cut
775    
776     =item $source = $wd->get_page_source
777    
778     Returns the (HTML/XML) page source of the current document.
779    
780     =item $results = $wd->execute_script ($javascript, $args)
781    
782     Synchronously execute the given script with given arguments and return its
783     results (C<$args> can be C<undef> if no arguments are wanted/needed).
784    
785     $ten = $wd->execute_script ("return arguments[0]+arguments[1]", [3, 7]);
786    
787     =item $results = $wd->execute_async_script ($javascript, $args)
788    
789     Similar to C<execute_script>, but doesn't wait for script to return, but
790     instead waits for the script to call its last argument, which is added to
791     C<$args> automatically.
792    
793     $twenty = $wd->execute_async_script ("arguments[0](20)", undef);
794    
795     =cut
796    
797     sub get_page_source_ {
798     $_[0]->get_ (source => $_[1]);
799     }
800    
801     sub execute_script_ {
802     $_[0]->post_ ("execute/sync" => { script => "$_[1]", args => $_[2] || [] }, $_[3]);
803     }
804    
805     sub execute_async_script_ {
806     $_[0]->post_ ("execute/async" => { script => "$_[1]", args => $_[2] || [] }, $_[3]);
807     }
808    
809     =back
810    
811     =head3 COOKIES
812    
813     =over
814    
815     =cut
816    
817     =item $cookies = $wd->get_all_cookies
818    
819     Returns all cookies, as an arrayref of hashrefs.
820    
821     # google surely sets a lot of cookies without my consent
822     $wd->navigate_to ("http://google.com");
823     use Data::Dump;
824     ddx $wd->get_all_cookies;
825    
826     =item $cookie = $wd->get_named_cookie ($name)
827    
828     Returns a single cookie as a hashref.
829    
830     =item $wd->add_cookie ($cookie)
831    
832     Adds the given cookie hashref.
833    
834     =item $wd->delete_cookie ($name)
835    
836     Delete the named cookie.
837    
838     =item $wd->delete_all_cookies
839    
840     Delete all cookies.
841    
842     =cut
843    
844     sub get_all_cookies_ {
845     $_[0]->get_ (cookie => $_[1]);
846     }
847    
848     sub get_named_cookie_ {
849     $_[0]->get_ ("cookie/$_[1]" => $_[2]);
850     }
851    
852     sub add_cookie_ {
853     $_[0]->post_ (cookie => { cookie => $_[1] }, $_[2]);
854     }
855    
856     sub delete_cookie_ {
857     $_[0]->delete_ ("cookie/$_[1]" => $_[2]);
858     }
859    
860     sub delete_all_cookies_ {
861     $_[0]->delete_ (cookie => $_[2]);
862     }
863    
864     =back
865    
866     =head3 ACTIONS
867    
868     =over
869    
870     =cut
871    
872     =item $wd->perform_actions ($actions)
873    
874     Perform the given actions (an arrayref of action specifications simulating
875 root 1.12 user activity, or an C<AnyEvent::WebDriver::Actions> object). For further
876     details, read the spec or the section L<ACTION LISTS>, below.
877 root 1.1
878 root 1.12 An example to get you started (see the next example for a mostly
879     equivalent example using the C<AnyEvent::WebDriver::Actions> helper API):
880 root 1.1
881     $wd->navigate_to ("https://duckduckgo.com/html");
882     my $input = $wd->find_element ("css selector", 'input[type="text"]');
883     $wd->perform_actions ([
884     {
885     id => "myfatfinger",
886     type => "pointer",
887     pointerType => "touch",
888     actions => [
889 root 1.12 { type => "pointerMove", duration => 100, origin => $input, x => 40, y => 5 },
890 root 1.1 { type => "pointerDown", button => 1 },
891     { type => "pause", duration => 40 },
892     { type => "pointerUp", button => 1 },
893     ],
894     },
895     {
896     id => "mykeyboard",
897     type => "key",
898     actions => [
899     { type => "pause" },
900     { type => "pause" },
901     { type => "pause" },
902     { type => "pause" },
903     { type => "keyDown", value => "a" },
904     { type => "pause", duration => 100 },
905     { type => "keyUp", value => "a" },
906     { type => "pause", duration => 100 },
907     { type => "keyDown", value => "b" },
908     { type => "pause", duration => 100 },
909     { type => "keyUp", value => "b" },
910     { type => "pause", duration => 2000 },
911     { type => "keyDown", value => "\x{E007}" }, # enter
912     { type => "pause", duration => 100 },
913     { type => "keyUp", value => "\x{E007}" }, # enter
914     { type => "pause", duration => 5000 },
915     ],
916     },
917     ]);
918    
919 root 1.12 And here is essentially the same (except for fewer pauses) example as
920     above, using the much simpler C<AnyEvent::WebDriver::Actions> API. Note
921     that the pointer up and key down event happen concurrently in this
922     example:
923    
924     $wd->navigate_to ("https://duckduckgo.com/html");
925     my $input = $wd->find_element ("css selector", 'input[type="text"]');
926     $wd->actions
927     ->move ($input, 40, 5, "touch1")
928     ->click;
929     ->key ("a");
930     ->key ("b");
931     ->pause (2000);
932     ->key ("\x{E007}")
933     ->pause (5000);
934     ->perform;
935    
936 root 1.1 =item $wd->release_actions
937    
938     Release all keys and pointer buttons currently depressed.
939    
940     =cut
941    
942     sub perform_actions_ {
943 root 1.12 if (UNIVERSAL::isa $_[1], AnyEvent::WebDriver::Actions::) {
944     my ($actions, $duration) = $_[1]->compile;
945     local $_[0]{timeout} = $_[0]{timeout} + $duration * 1e-3;
946     $_[0]->post_ (actions => { actions => $actions }, $_[2]);
947     } else {
948     $_[0]->post_ (actions => { actions => $_[1] }, $_[2]);
949     }
950 root 1.1 }
951    
952     sub release_actions_ {
953     $_[0]->delete_ (actions => $_[1]);
954     }
955    
956     =back
957    
958     =head3 USER PROMPTS
959    
960     =over
961    
962     =cut
963    
964     =item $wd->dismiss_alert
965    
966     Dismiss a simple dialog, if present.
967    
968     =item $wd->accept_alert
969    
970     Accept a simple dialog, if present.
971    
972     =item $text = $wd->get_alert_text
973    
974     Returns the text of any simple dialog.
975    
976     =item $text = $wd->send_alert_text
977    
978     Fills in the user prompt with the given text.
979    
980    
981     =cut
982    
983     sub dismiss_alert_ {
984     $_[0]->post_ ("alert/dismiss" => undef, $_[1]);
985     }
986    
987     sub accept_alert_ {
988     $_[0]->post_ ("alert/accept" => undef, $_[1]);
989     }
990    
991     sub get_alert_text_ {
992     $_[0]->get_ ("alert/text" => $_[1]);
993     }
994    
995     sub send_alert_text_ {
996     $_[0]->post_ ("alert/text" => { text => "$_[1]" }, $_[2]);
997     }
998    
999     =back
1000    
1001     =head3 SCREEN CAPTURE
1002    
1003     =over
1004    
1005     =cut
1006    
1007     =item $wd->take_screenshot
1008    
1009 root 1.9 Create a screenshot, returning it as a PNG image in a C<data:> URL.
1010 root 1.1
1011 root 1.12 =item $wd->take_element_screenshot ($element)
1012 root 1.1
1013     Accept a simple dialog, if present.
1014    
1015     =cut
1016    
1017     sub take_screenshot_ {
1018     $_[0]->get_ (screenshot => $_[1]);
1019     }
1020    
1021     sub take_element_screenshot_ {
1022 root 1.12 $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/screenshot" => $_[2]);
1023 root 1.1 }
1024    
1025     =back
1026    
1027 root 1.12 =head2 ACTION LISTS
1028    
1029     Action lists can be quite complicated. Or at least it took a while for
1030     me to twist my head around them. Basically, an action list consists of a
1031     number of sources representing devices (such as a finger, a mouse, a pen
1032     or a keyboard) and a list of actions for each source.
1033    
1034     An action can be a key press, a pointer move or a pause (time
1035     delay). Actions from different sources can happen "at the same time",
1036     while actions from a single source are executed in order.
1037    
1038     While you can provide an action list manually, it is (hopefully) less
1039     cumbersome to use the API described in this section to create them.
1040    
1041     The basic process of creating and performing actions is to create a new
1042     action list, adding action sources, followed by adding actions. Finally
1043     you would C<perform> those actions on the WebDriver.
1044    
1045     Virtual time progresses as long as you add actions to the same event
1046     source. Adding events to different sources are considered to happen
1047     concurrently. If you want to force time to progress, you can do this using
1048     a call to C<< ->pause (0) >>.
1049    
1050     Most methods here are designed to chain, i.e. they return the web actions
1051     object, to simplify multiple calls.
1052    
1053     For example, to simulate a mouse click to an input element, followed by
1054     entering some text and pressing enter, you can use this:
1055    
1056     $wd->actions
1057     ->click (1, 100)
1058     ->type ("some text")
1059 root 1.13 ->key ("{Enter}")
1060 root 1.12 ->perform;
1061    
1062     By default, keyboard and mouse input sources are provided. You can create
1063     your own sources and use them when adding events. The above example could
1064     be more verbosely written like this:
1065    
1066     $wd->actions
1067     ->click (1, 100, "mouse")
1068     ->type ("some text")
1069 root 1.13 ->key ("{Enter}")
1070 root 1.12 ->perform;
1071    
1072     When you specify the event source expliticly it will switch the current
1073     "focus" for this class of device (all keyboards are in one class, all
1074     pointer-like devices such as mice/fingers/pens are in one class), so you
1075     don't have to specify the source for subsequent actions.
1076    
1077     When you use the sources C<keyboard>, C<mouse>, C<touch1>..C<touch3>,
1078     C<pen> without defining them, then a suitable default source will be
1079     created for them.
1080    
1081     =over 4
1082    
1083     =cut
1084    
1085     package AnyEvent::WebDriver::Actions;
1086    
1087     =item $al = new AnyEvent::WebDriver::Actions
1088    
1089     Create a new empty action list object. More often you would use the C<<
1090 root 1.13 $wd->action_list >> method to create one that is already associated with
1091 root 1.12 a given web driver.
1092    
1093     =cut
1094    
1095     sub new {
1096     my ($class, %kv) = @_;
1097    
1098     $kv{last_kbd} = "keyboard";
1099     $kv{last_ptr} = "mouse";
1100    
1101     bless \%kv, $class
1102     }
1103    
1104     =item $al = $al->source ($id, $type, key => value...)
1105    
1106     The first time you call this with a givne ID, this defines the event
1107     source using the extra parameters. Subsequent calls merely switch the
1108     current source for its event class.
1109    
1110     It's not an error to define built-in sources (such as C<keyboard> or
1111     C<touch1>) differently then the defaults.
1112    
1113     Example: define a new touch device called C<fatfinger>.
1114    
1115     $al->source (fatfinger => "pointer", pointerType => "touch");
1116    
1117     Example: switchdefine a new touch device called C<fatfinger>.
1118    
1119     $al->source (fatfinger => "pointer", pointerType => "touch");
1120    
1121     =cut
1122    
1123 root 1.13 sub _default_source($) {
1124 root 1.12 my ($source) = @_;
1125    
1126     $source eq "keyboard" ? { actions => [], id => $source, type => "key" }
1127     : $source eq "mouse" ? { actions => [], id => $source, type => "pointer", pointerType => "mouse" }
1128     : $source eq "touch" ? { actions => [], id => $source, type => "pointer", pointerType => "touch" }
1129     : $source eq "pen" ? { actions => [], id => $source, type => "pointer", pointerType => "pen" }
1130     : Carp::croak "AnyEvent::WebDriver::Actions: event source '$source' not defined"
1131     }
1132    
1133     my %source_class = (
1134     key => "kbd",
1135     pointer => "ptr",
1136     );
1137    
1138     sub source {
1139     my ($self, $id, $type, %kv) = @_;
1140    
1141     if (defined $type) {
1142     !exists $self->{source}{$id}
1143     or Carp::croak "AnyEvent::WebDriver::Actions: source '$id' already defined";
1144    
1145     $kv{id} = $id;
1146     $kv{type} = $type;
1147     $kv{actions} = [];
1148    
1149     $self->{source}{$id} = \%kv;
1150     }
1151    
1152     my $source = $self->{source}{$id} ||= _default_source $id;
1153    
1154     my $last = $source_class{$source->{type}} // "xxx";
1155    
1156     $self->{"last_$last"} = $id;
1157    
1158     $self
1159     }
1160    
1161     sub _add {
1162     my ($self, $source, $sourcetype, $type, %kv) = @_;
1163    
1164     my $last = \$self->{"last_$sourcetype"};
1165    
1166     $source
1167     ? ($$last = $source)
1168     : ($source = $$last);
1169    
1170     my $source = $self->{source}{$source} ||= _default_source $source;
1171    
1172     my $al = $source->{actions};
1173    
1174     push @$al, { type => "pause" }
1175     while @$al < $self->{tick} - 1;
1176    
1177     $kv{type} = $type;
1178    
1179     push @{ $source->{actions} }, \%kv;
1180    
1181     $self->{tick_duration} = $kv{duration}
1182     if $kv{duration} > $self->{tick_duration};
1183    
1184     if ($self->{tick} != @$al) {
1185     $self->{tick} = @$al;
1186     $self->{duration} += delete $self->{tick_duration};
1187     }
1188    
1189     $self
1190     }
1191 root 1.1
1192 root 1.12 =item $al = $al->pause ($duration)
1193    
1194     Creates a pause with the given duration. Makes sure that time progresses
1195     in any case, even when C<$duration> is C<0>.
1196    
1197     =cut
1198    
1199     sub pause {
1200     my ($self, $duration) = @_;
1201    
1202     $self->{tick_duration} = $duration
1203     if $duration > $self->{tick_duration};
1204    
1205     $self->{duration} += delete $self->{tick_duration};
1206    
1207     # find the source with the longest list
1208    
1209     for my $source (values %{ $self->{source} }) {
1210     if (@{ $source->{actions} } == $self->{tick}) {
1211     # this source is one of the longest
1212    
1213     # create a pause event only if $duration is non-zero...
1214     push @{ $source->{actions} }, { type => "pause", duration => $duration*1 }
1215     if $duration;
1216    
1217     # ... but advance time in any case
1218     ++$self->{tick};
1219    
1220     return $self;
1221     }
1222     }
1223    
1224     # no event sources are longest. so advance time in any case
1225     ++$self->{tick};
1226    
1227     Carp::croak "AnyEvent::WebDriver::Actions: multiple pause calls in a row not (yet) supported"
1228     if $duration;
1229    
1230     $self
1231     }
1232    
1233     =item $al = $al->pointer_down ($button, $source)
1234    
1235     =item $al = $al->pointer_up ($button, $source)
1236    
1237     Press or release the given button. C<$button> defaults to C<1>.
1238    
1239     =item $al = $al->click ($button, $source)
1240    
1241     Convenience function that creates a button press and release action
1242     without any delay between them. C<$button> defaults to C<1>.
1243    
1244     =item $al = $al->doubleclick ($button, $source)
1245    
1246     Convenience function that creates two button press and release action
1247     pairs in a row, with no unnecessary delay between them. C<$button>
1248     defaults to C<1>.
1249    
1250     =cut
1251    
1252     sub pointer_down {
1253     my ($self, $button, $source) = @_;
1254    
1255     $self->_add ($source, ptr => pointerDown => button => ($button // 1)*1)
1256     }
1257    
1258     sub pointer_up {
1259     my ($self, $button, $source) = @_;
1260    
1261     $self->_add ($source, ptr => pointerUp => button => ($button // 1)*1)
1262     }
1263    
1264     sub click {
1265     my ($self, $button, $source) = @_;
1266    
1267     $self
1268     ->pointer_down ($button, $source)
1269     ->pointer_up ($button)
1270     }
1271    
1272     sub doubleclick {
1273     my ($self, $button, $source) = @_;
1274    
1275     $self
1276     ->click ($button, $source)
1277     ->click ($button)
1278     }
1279    
1280     =item $al = $al->move ($button, $origin, $x, $y, $duration, $source)
1281    
1282     Moves a pointer to the given position, relative to origin (either
1283     "viewport", "pointer" or an element object.
1284    
1285     =cut
1286    
1287     sub move {
1288     my ($self, $origin, $x, $y, $duration, $source) = @_;
1289    
1290     $self->_add ($source, ptr => pointerMove =>
1291     origin => $origin, x => $x*1, y => $y*1, duration => $duration*1)
1292     }
1293    
1294     =item $al = $al->keyDown ($key, $source)
1295    
1296     =item $al = $al->keyUp ($key, $source)
1297    
1298     Press or release the given key.
1299    
1300 root 1.13 =item $al = $al->key ($key, $source)
1301    
1302     Peess and release the given key, without unnecessary delay.
1303    
1304     A special syntax, C<{keyname}> can be used for special keys - all the special key names from
1305     L<section 17.4.2|https://www.w3.org/TR/webdriver1/#keyboard-actions> of the WebDriver recommendation
1306     can be used.
1307    
1308     Example: press and release "a".
1309    
1310     $al->key ("a");
1311    
1312     Example: press and release the "Enter" key:
1313    
1314     $al->key ("\x{e007}");
1315    
1316     Example: press and release the "enter" key using the special key name syntax:
1317    
1318     $al->key ("{Enter}");
1319    
1320     =item $al = $al->type ($string, $source)
1321    
1322     Convenience method to simulate a series of key press and release events
1323     for the keys in C<$string>. There is no syntax for special keys,
1324     everything will be typed "as-is" if possible.
1325    
1326     =cut
1327    
1328     our %SPECIAL_KEY = (
1329     "Unidentified" => 0xE000,
1330     "Cancel" => 0xE001,
1331     "Help" => 0xE002,
1332     "Backspace" => 0xE003,
1333     "Tab" => 0xE004,
1334     "Clear" => 0xE005,
1335     "Return" => 0xE006,
1336     "Enter" => 0xE007,
1337     "Shift" => 0xE008,
1338     "Control" => 0xE009,
1339     "Alt" => 0xE00A,
1340     "Pause" => 0xE00B,
1341     "Escape" => 0xE00C,
1342     " " => 0xE00D,
1343     "PageUp" => 0xE00E,
1344     "PageDown" => 0xE00F,
1345     "End" => 0xE010,
1346     "Home" => 0xE011,
1347     "ArrowLeft" => 0xE012,
1348     "ArrowUp" => 0xE013,
1349     "ArrowRight" => 0xE014,
1350     "ArrowDown" => 0xE015,
1351     "Insert" => 0xE016,
1352     "Delete" => 0xE017,
1353     ";" => 0xE018,
1354     "=" => 0xE019,
1355     "0" => 0xE01A,
1356     "1" => 0xE01B,
1357     "2" => 0xE01C,
1358     "3" => 0xE01D,
1359     "4" => 0xE01E,
1360     "5" => 0xE01F,
1361     "6" => 0xE020,
1362     "7" => 0xE021,
1363     "8" => 0xE022,
1364     "9" => 0xE023,
1365     "*" => 0xE024,
1366     "+" => 0xE025,
1367     "," => 0xE026,
1368     "-" => 0xE027,
1369     "." => 0xE028,
1370     "/" => 0xE029,
1371     "F1" => 0xE031,
1372     "F2" => 0xE032,
1373     "F3" => 0xE033,
1374     "F4" => 0xE034,
1375     "F5" => 0xE035,
1376     "F6" => 0xE036,
1377     "F7" => 0xE037,
1378     "F8" => 0xE038,
1379     "F9" => 0xE039,
1380     "F10" => 0xE03A,
1381     "F11" => 0xE03B,
1382     "F12" => 0xE03C,
1383     "Meta" => 0xE03D,
1384     "ZenkakuHankaku" => 0xE040,
1385     "Shift" => 0xE050,
1386     "Control" => 0xE051,
1387     "Alt" => 0xE052,
1388     "Meta" => 0xE053,
1389     "PageUp" => 0xE054,
1390     "PageDown" => 0xE055,
1391     "End" => 0xE056,
1392     "Home" => 0xE057,
1393     "ArrowLeft" => 0xE058,
1394     "ArrowUp" => 0xE059,
1395     "ArrowRight" => 0xE05A,
1396     "ArrowDown" => 0xE05B,
1397     "Insert" => 0xE05C,
1398     "Delete" => 0xE05D,
1399     );
1400    
1401     sub _kv($) {
1402     $_[0] =~ /^\{(.*)\}$/s
1403     ? (exists $SPECIAL_KEY{$1}
1404     ? chr $SPECIAL_KEY{$1}
1405     : Carp::croak "AnyEvent::WebDriver::Actions: special key '$1' not known")
1406     : $_[0]
1407     }
1408 root 1.12
1409     sub key_down {
1410     my ($self, $key, $source) = @_;
1411    
1412 root 1.13 $self->_add ($source, kbd => keyDown => value => _kv $key)
1413 root 1.12 }
1414    
1415     sub key_up {
1416     my ($self, $key, $source) = @_;
1417    
1418 root 1.13 $self->_add ($source, kbd => keyUp => value => _kv $key)
1419 root 1.12 }
1420    
1421     sub key {
1422     my ($self, $key, $source) = @_;
1423    
1424     $self
1425     ->key_down ($key, $source)
1426     ->key_up ($key)
1427     }
1428    
1429 root 1.13 sub type {
1430     my ($self, $string, $source) = @_;
1431    
1432     $self->key ($_, $source)
1433     for $string =~ /(\X)/g;
1434    
1435     $self
1436     }
1437    
1438 root 1.12 =item $al->perform ($wd)
1439    
1440     Finaluses and compiles the list, if not done yet, and calls C<<
1441     $wd->perform >> with it.
1442    
1443     If C<$wd> is undef, and the action list was created using the C<<
1444     $wd->actions >> method, then perform it against that WebDriver object.
1445    
1446     There is no underscore variant - call the C<perform_actions_> method with
1447     the action object instead.
1448    
1449     =item $al->perform_release ($wd)
1450    
1451     Exactly like C<perform>, but additionally call C<release_actions>
1452     afterwards.
1453 root 1.1
1454     =cut
1455    
1456 root 1.12 sub perform {
1457     my ($self, $wd) = @_;
1458    
1459     ($wd //= $self->{wd})->perform_actions ($self)
1460     }
1461 root 1.9
1462 root 1.12 sub perform_release {
1463     my ($self, $wd) = @_;
1464    
1465     ($wd //= $self->{wd})->perform_actions ($self);
1466     $wd->release_actions;
1467     }
1468 root 1.1
1469 root 1.12 =item ($actions, $duration) = $al->compile
1470    
1471     Finalises and compiles the list, if not done yet, and returns an actions
1472     object suitable for calls to C<< $wd->perform_actions >>. When called in
1473     list context, additionally returns the total duration of the action list.
1474    
1475     Since building large action lists can take nontrivial amounts of time,
1476     it can make sense to build an action list only once and then perform it
1477     multiple times.
1478    
1479     Actions must not be added after compiling a list.
1480 root 1.1
1481     =cut
1482    
1483 root 1.12 sub compile {
1484     my ($self) = @_;
1485    
1486     $self->{duration} += delete $self->{tick_duration};
1487    
1488     delete $self->{tick};
1489     delete $self->{last_kbd};
1490     delete $self->{last_ptr};
1491    
1492     $self->{actions} ||= [values %{ delete $self->{source} }];
1493    
1494     wantarray
1495     ? ($self->{actions}, $self->{duration})
1496     : $self->{actions}
1497 root 1.1 }
1498    
1499     =back
1500    
1501     =head2 EVENT BASED API
1502    
1503     This module wouldn't be a good AnyEvent citizen if it didn't have a true
1504     event-based API.
1505    
1506     In fact, the simplified API, as documented above, is emulated via the
1507     event-based API and an C<AUTOLOAD> function that automatically provides
1508     blocking wrappers around the callback-based API.
1509    
1510     Every method documented in the L<SIMPLIFIED API> section has an equivalent
1511     event-based method that is formed by appending a underscore (C<_>) to the
1512     method name, and appending a callback to the argument list (mnemonic: the
1513     underscore indicates the "the action is not yet finished" after the call
1514     returns).
1515    
1516     For example, instead of a blocking calls to C<new_session>, C<navigate_to>
1517     and C<back>, you can make a callback-based ones:
1518    
1519     my $cv = AE::cv;
1520    
1521     $wd->new_session ({}, sub {
1522     my ($status, $value) = @_,
1523    
1524     die "error $value->{error}" if $status ne "200";
1525    
1526     $wd->navigate_to_ ("http://www.nethype.de", sub {
1527    
1528     $wd->back_ (sub {
1529     print "all done\n";
1530     $cv->send;
1531     });
1532    
1533     });
1534     });
1535    
1536     $cv->recv;
1537    
1538     While the blocking methods C<croak> on errors, the callback-based ones all
1539     pass two values to the callback, C<$status> and C<$res>, where C<$status>
1540 root 1.9 is the HTTP status code (200 for successful requests, typically 4xx or
1541 root 1.1 5xx for errors), and C<$res> is the value of the C<value> key in the JSON
1542     response object.
1543    
1544     Other than that, the underscore variants and the blocking variants are
1545     identical.
1546    
1547     =head2 LOW LEVEL API
1548    
1549 root 1.9 All the simplified API methods are very thin wrappers around WebDriver
1550     commands of the same name. They are all implemented in terms of the
1551 root 1.1 low-level methods (C<req>, C<get>, C<post> and C<delete>), which exists
1552     in blocking and callback-based variants (C<req_>, C<get_>, C<post_> and
1553     C<delete_>).
1554    
1555     Examples are after the function descriptions.
1556    
1557     =over
1558    
1559     =item $wd->req_ ($method, $uri, $body, $cb->($status, $value))
1560    
1561     =item $value = $wd->req ($method, $uri, $body)
1562    
1563     Appends the C<$uri> to the C<endpoint/session/{sessionid}/> URL and makes
1564     a HTTP C<$method> request (C<GET>, C<POST> etc.). C<POST> requests can
1565     provide a UTF-8-encoded JSON text as HTTP request body, or the empty
1566     string to indicate no body is used.
1567    
1568     For the callback version, the callback gets passed the HTTP status code
1569     (200 for every successful request), and the value of the C<value> key in
1570     the JSON response object as second argument.
1571    
1572     =item $wd->get_ ($uri, $cb->($status, $value))
1573    
1574     =item $value = $wd->get ($uri)
1575    
1576     Simply a call to C<req_> with C<$method> set to C<GET> and an empty body.
1577    
1578     =item $wd->post_ ($uri, $data, $cb->($status, $value))
1579    
1580     =item $value = $wd->post ($uri, $data)
1581    
1582     Simply a call to C<req_> with C<$method> set to C<POST> - if C<$body> is
1583     C<undef>, then an empty object is send, otherwise, C<$data> must be a
1584     valid request object, which gets encoded into JSON for you.
1585    
1586     =item $wd->delete_ ($uri, $cb->($status, $value))
1587    
1588     =item $value = $wd->delete ($uri)
1589    
1590     Simply a call to C<req_> with C<$method> set to C<DELETE> and an empty body.
1591    
1592     =cut
1593    
1594     =back
1595    
1596     Example: implement C<get_all_cookies>, which is a simple C<GET> request
1597     without any parameters:
1598    
1599     $cookies = $wd->get ("cookie");
1600    
1601     Example: implement C<execute_script>, which needs some parameters:
1602    
1603     $results = $wd->post ("execute/sync" => { script => "$javascript", args => [] });
1604    
1605 root 1.12 Example: call C<find_elements> to find all C<IMG> elements:
1606 root 1.1
1607 root 1.12 $elems = $wd->post (elements => { using => "css selector", value => "img" });
1608 root 1.1
1609     =cut
1610    
1611     =head1 HISTORY
1612    
1613 root 1.2 This module was unintentionally created (it started inside some quickly
1614     hacked-together script) simply because I couldn't get the existing
1615 root 1.1 C<Selenium::Remote::Driver> module to work, ever, despite multiple
1616     attempts over the years and trying to report multiple bugs, which have
1617     been completely ignored. It's also not event-based, so, yeah...
1618    
1619     =head1 AUTHOR
1620    
1621     Marc Lehmann <schmorp@schmorp.de>
1622     http://anyevent.schmorp.de
1623    
1624     =cut
1625    
1626     1
1627