ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-WebDriver/WebDriver.pm
Revision: 1.17
Committed: Wed Aug 29 05:55:23 2018 UTC (5 years, 10 months ago) by root
Branch: MAIN
CVS Tags: rel-0_5
Changes since 1.16: +2 -1 lines
Log Message:
*** empty log message ***

File Contents

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