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