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