ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-WebDriver/WebDriver.pm
Revision: 1.23
Committed: Wed Aug 29 08:16:32 2018 UTC (5 years, 9 months ago) by root
Branch: MAIN
Changes since 1.22: +6 -6 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.22 At the time of this writing, it was so brand new that I could only get
36     C<geckodriver> (For Firefox) to work, but that is expected to be fixed
37 root 1.11 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 root 1.23 presumably used for other applications, such as using the same session
230     from multiple processes and so on.
231 root 1.14
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.23 Simple example of creating a WebDriver object and a new session:
310 root 1.14
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 root 1.23 Return the current window rect(angle), e.g.:
544 root 1.1
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 root 1.23 Sets the window rect(angle).
551 root 1.1
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 root 1.21 When you specify the event source explicitly it will switch the current
1090 root 1.12 "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 root 1.21 The first time you call this with a given ID, this defines the event
1124 root 1.12 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 root 1.21 Example: define a new touch device called C<fatfinger>.
1135 root 1.12
1136     $al->source (fatfinger => "pointer", pointerType => "touch");
1137    
1138 root 1.21 Example: switch default keyboard source to C<kbd1>, assuming it is of C<key> class.
1139    
1140     $al->source ("kbd1");
1141    
1142 root 1.12 =cut
1143    
1144 root 1.13 sub _default_source($) {
1145 root 1.12 my ($source) = @_;
1146    
1147     $source eq "keyboard" ? { actions => [], id => $source, type => "key" }
1148     : $source eq "mouse" ? { actions => [], id => $source, type => "pointer", pointerType => "mouse" }
1149     : $source eq "touch" ? { actions => [], id => $source, type => "pointer", pointerType => "touch" }
1150     : $source eq "pen" ? { actions => [], id => $source, type => "pointer", pointerType => "pen" }
1151     : Carp::croak "AnyEvent::WebDriver::Actions: event source '$source' not defined"
1152     }
1153    
1154     my %source_class = (
1155     key => "kbd",
1156     pointer => "ptr",
1157     );
1158    
1159     sub source {
1160     my ($self, $id, $type, %kv) = @_;
1161    
1162     if (defined $type) {
1163     !exists $self->{source}{$id}
1164     or Carp::croak "AnyEvent::WebDriver::Actions: source '$id' already defined";
1165    
1166     $kv{id} = $id;
1167     $kv{type} = $type;
1168     $kv{actions} = [];
1169    
1170     $self->{source}{$id} = \%kv;
1171     }
1172    
1173     my $source = $self->{source}{$id} ||= _default_source $id;
1174    
1175     my $last = $source_class{$source->{type}} // "xxx";
1176    
1177     $self->{"last_$last"} = $id;
1178    
1179     $self
1180     }
1181    
1182     sub _add {
1183     my ($self, $source, $sourcetype, $type, %kv) = @_;
1184    
1185     my $last = \$self->{"last_$sourcetype"};
1186    
1187     $source
1188     ? ($$last = $source)
1189     : ($source = $$last);
1190    
1191     my $source = $self->{source}{$source} ||= _default_source $source;
1192    
1193     my $al = $source->{actions};
1194    
1195     push @$al, { type => "pause" }
1196     while @$al < $self->{tick} - 1;
1197    
1198     $kv{type} = $type;
1199    
1200     push @{ $source->{actions} }, \%kv;
1201    
1202     $self->{tick_duration} = $kv{duration}
1203     if $kv{duration} > $self->{tick_duration};
1204    
1205     if ($self->{tick} != @$al) {
1206     $self->{tick} = @$al;
1207     $self->{duration} += delete $self->{tick_duration};
1208     }
1209    
1210     $self
1211     }
1212 root 1.1
1213 root 1.12 =item $al = $al->pause ($duration)
1214    
1215     Creates a pause with the given duration. Makes sure that time progresses
1216     in any case, even when C<$duration> is C<0>.
1217    
1218     =cut
1219    
1220     sub pause {
1221     my ($self, $duration) = @_;
1222    
1223     $self->{tick_duration} = $duration
1224     if $duration > $self->{tick_duration};
1225    
1226     $self->{duration} += delete $self->{tick_duration};
1227    
1228     # find the source with the longest list
1229    
1230     for my $source (values %{ $self->{source} }) {
1231     if (@{ $source->{actions} } == $self->{tick}) {
1232     # this source is one of the longest
1233    
1234     # create a pause event only if $duration is non-zero...
1235     push @{ $source->{actions} }, { type => "pause", duration => $duration*1 }
1236     if $duration;
1237    
1238     # ... but advance time in any case
1239     ++$self->{tick};
1240    
1241     return $self;
1242     }
1243     }
1244    
1245     # no event sources are longest. so advance time in any case
1246     ++$self->{tick};
1247    
1248     Carp::croak "AnyEvent::WebDriver::Actions: multiple pause calls in a row not (yet) supported"
1249     if $duration;
1250    
1251     $self
1252     }
1253    
1254     =item $al = $al->pointer_down ($button, $source)
1255    
1256     =item $al = $al->pointer_up ($button, $source)
1257    
1258     Press or release the given button. C<$button> defaults to C<1>.
1259    
1260     =item $al = $al->click ($button, $source)
1261    
1262     Convenience function that creates a button press and release action
1263     without any delay between them. C<$button> defaults to C<1>.
1264    
1265     =item $al = $al->doubleclick ($button, $source)
1266    
1267     Convenience function that creates two button press and release action
1268     pairs in a row, with no unnecessary delay between them. C<$button>
1269     defaults to C<1>.
1270    
1271     =cut
1272    
1273     sub pointer_down {
1274     my ($self, $button, $source) = @_;
1275    
1276     $self->_add ($source, ptr => pointerDown => button => ($button // 1)*1)
1277     }
1278    
1279     sub pointer_up {
1280     my ($self, $button, $source) = @_;
1281    
1282     $self->_add ($source, ptr => pointerUp => button => ($button // 1)*1)
1283     }
1284    
1285     sub click {
1286     my ($self, $button, $source) = @_;
1287    
1288     $self
1289     ->pointer_down ($button, $source)
1290     ->pointer_up ($button)
1291     }
1292    
1293     sub doubleclick {
1294     my ($self, $button, $source) = @_;
1295    
1296     $self
1297     ->click ($button, $source)
1298     ->click ($button)
1299     }
1300    
1301     =item $al = $al->move ($button, $origin, $x, $y, $duration, $source)
1302    
1303     Moves a pointer to the given position, relative to origin (either
1304     "viewport", "pointer" or an element object.
1305    
1306     =cut
1307    
1308     sub move {
1309     my ($self, $origin, $x, $y, $duration, $source) = @_;
1310    
1311     $self->_add ($source, ptr => pointerMove =>
1312     origin => $origin, x => $x*1, y => $y*1, duration => $duration*1)
1313     }
1314    
1315     =item $al = $al->keyDown ($key, $source)
1316    
1317     =item $al = $al->keyUp ($key, $source)
1318    
1319     Press or release the given key.
1320    
1321 root 1.13 =item $al = $al->key ($key, $source)
1322    
1323     Peess and release the given key, without unnecessary delay.
1324    
1325     A special syntax, C<{keyname}> can be used for special keys - all the special key names from
1326     L<section 17.4.2|https://www.w3.org/TR/webdriver1/#keyboard-actions> of the WebDriver recommendation
1327     can be used.
1328    
1329     Example: press and release "a".
1330    
1331     $al->key ("a");
1332    
1333     Example: press and release the "Enter" key:
1334    
1335     $al->key ("\x{e007}");
1336    
1337     Example: press and release the "enter" key using the special key name syntax:
1338    
1339     $al->key ("{Enter}");
1340    
1341     =item $al = $al->type ($string, $source)
1342    
1343     Convenience method to simulate a series of key press and release events
1344     for the keys in C<$string>. There is no syntax for special keys,
1345     everything will be typed "as-is" if possible.
1346    
1347     =cut
1348    
1349     our %SPECIAL_KEY = (
1350     "Unidentified" => 0xE000,
1351     "Cancel" => 0xE001,
1352     "Help" => 0xE002,
1353     "Backspace" => 0xE003,
1354     "Tab" => 0xE004,
1355     "Clear" => 0xE005,
1356     "Return" => 0xE006,
1357     "Enter" => 0xE007,
1358     "Shift" => 0xE008,
1359     "Control" => 0xE009,
1360     "Alt" => 0xE00A,
1361     "Pause" => 0xE00B,
1362     "Escape" => 0xE00C,
1363     " " => 0xE00D,
1364     "PageUp" => 0xE00E,
1365     "PageDown" => 0xE00F,
1366     "End" => 0xE010,
1367     "Home" => 0xE011,
1368     "ArrowLeft" => 0xE012,
1369     "ArrowUp" => 0xE013,
1370     "ArrowRight" => 0xE014,
1371     "ArrowDown" => 0xE015,
1372     "Insert" => 0xE016,
1373     "Delete" => 0xE017,
1374     ";" => 0xE018,
1375     "=" => 0xE019,
1376     "0" => 0xE01A,
1377     "1" => 0xE01B,
1378     "2" => 0xE01C,
1379     "3" => 0xE01D,
1380     "4" => 0xE01E,
1381     "5" => 0xE01F,
1382     "6" => 0xE020,
1383     "7" => 0xE021,
1384     "8" => 0xE022,
1385     "9" => 0xE023,
1386     "*" => 0xE024,
1387     "+" => 0xE025,
1388     "," => 0xE026,
1389     "-" => 0xE027,
1390     "." => 0xE028,
1391     "/" => 0xE029,
1392     "F1" => 0xE031,
1393     "F2" => 0xE032,
1394     "F3" => 0xE033,
1395     "F4" => 0xE034,
1396     "F5" => 0xE035,
1397     "F6" => 0xE036,
1398     "F7" => 0xE037,
1399     "F8" => 0xE038,
1400     "F9" => 0xE039,
1401     "F10" => 0xE03A,
1402     "F11" => 0xE03B,
1403     "F12" => 0xE03C,
1404     "Meta" => 0xE03D,
1405     "ZenkakuHankaku" => 0xE040,
1406     "Shift" => 0xE050,
1407     "Control" => 0xE051,
1408     "Alt" => 0xE052,
1409     "Meta" => 0xE053,
1410     "PageUp" => 0xE054,
1411     "PageDown" => 0xE055,
1412     "End" => 0xE056,
1413     "Home" => 0xE057,
1414     "ArrowLeft" => 0xE058,
1415     "ArrowUp" => 0xE059,
1416     "ArrowRight" => 0xE05A,
1417     "ArrowDown" => 0xE05B,
1418     "Insert" => 0xE05C,
1419     "Delete" => 0xE05D,
1420     );
1421    
1422     sub _kv($) {
1423     $_[0] =~ /^\{(.*)\}$/s
1424     ? (exists $SPECIAL_KEY{$1}
1425     ? chr $SPECIAL_KEY{$1}
1426     : Carp::croak "AnyEvent::WebDriver::Actions: special key '$1' not known")
1427     : $_[0]
1428     }
1429 root 1.12
1430     sub key_down {
1431     my ($self, $key, $source) = @_;
1432    
1433 root 1.13 $self->_add ($source, kbd => keyDown => value => _kv $key)
1434 root 1.12 }
1435    
1436     sub key_up {
1437     my ($self, $key, $source) = @_;
1438    
1439 root 1.13 $self->_add ($source, kbd => keyUp => value => _kv $key)
1440 root 1.12 }
1441    
1442     sub key {
1443     my ($self, $key, $source) = @_;
1444    
1445     $self
1446     ->key_down ($key, $source)
1447     ->key_up ($key)
1448     }
1449    
1450 root 1.13 sub type {
1451     my ($self, $string, $source) = @_;
1452    
1453     $self->key ($_, $source)
1454     for $string =~ /(\X)/g;
1455    
1456     $self
1457     }
1458    
1459 root 1.12 =item $al->perform ($wd)
1460    
1461 root 1.23 Finalises and compiles the list, if not done yet, and calls C<<
1462 root 1.12 $wd->perform >> with it.
1463    
1464     If C<$wd> is undef, and the action list was created using the C<<
1465     $wd->actions >> method, then perform it against that WebDriver object.
1466    
1467     There is no underscore variant - call the C<perform_actions_> method with
1468     the action object instead.
1469    
1470     =item $al->perform_release ($wd)
1471    
1472     Exactly like C<perform>, but additionally call C<release_actions>
1473     afterwards.
1474 root 1.1
1475     =cut
1476    
1477 root 1.12 sub perform {
1478     my ($self, $wd) = @_;
1479    
1480     ($wd //= $self->{wd})->perform_actions ($self)
1481     }
1482 root 1.9
1483 root 1.12 sub perform_release {
1484     my ($self, $wd) = @_;
1485    
1486     ($wd //= $self->{wd})->perform_actions ($self);
1487     $wd->release_actions;
1488     }
1489 root 1.1
1490 root 1.12 =item ($actions, $duration) = $al->compile
1491    
1492     Finalises and compiles the list, if not done yet, and returns an actions
1493     object suitable for calls to C<< $wd->perform_actions >>. When called in
1494     list context, additionally returns the total duration of the action list.
1495    
1496     Since building large action lists can take nontrivial amounts of time,
1497     it can make sense to build an action list only once and then perform it
1498     multiple times.
1499    
1500     Actions must not be added after compiling a list.
1501 root 1.1
1502     =cut
1503    
1504 root 1.12 sub compile {
1505     my ($self) = @_;
1506    
1507     $self->{duration} += delete $self->{tick_duration};
1508    
1509     delete $self->{tick};
1510     delete $self->{last_kbd};
1511     delete $self->{last_ptr};
1512    
1513     $self->{actions} ||= [values %{ delete $self->{source} }];
1514    
1515     wantarray
1516     ? ($self->{actions}, $self->{duration})
1517     : $self->{actions}
1518 root 1.1 }
1519    
1520     =back
1521    
1522     =head2 EVENT BASED API
1523    
1524     This module wouldn't be a good AnyEvent citizen if it didn't have a true
1525     event-based API.
1526    
1527     In fact, the simplified API, as documented above, is emulated via the
1528     event-based API and an C<AUTOLOAD> function that automatically provides
1529     blocking wrappers around the callback-based API.
1530    
1531     Every method documented in the L<SIMPLIFIED API> section has an equivalent
1532     event-based method that is formed by appending a underscore (C<_>) to the
1533     method name, and appending a callback to the argument list (mnemonic: the
1534     underscore indicates the "the action is not yet finished" after the call
1535     returns).
1536    
1537     For example, instead of a blocking calls to C<new_session>, C<navigate_to>
1538     and C<back>, you can make a callback-based ones:
1539    
1540     my $cv = AE::cv;
1541    
1542     $wd->new_session ({}, sub {
1543     my ($status, $value) = @_,
1544    
1545     die "error $value->{error}" if $status ne "200";
1546    
1547     $wd->navigate_to_ ("http://www.nethype.de", sub {
1548    
1549     $wd->back_ (sub {
1550     print "all done\n";
1551     $cv->send;
1552     });
1553    
1554     });
1555     });
1556    
1557     $cv->recv;
1558    
1559     While the blocking methods C<croak> on errors, the callback-based ones all
1560     pass two values to the callback, C<$status> and C<$res>, where C<$status>
1561 root 1.9 is the HTTP status code (200 for successful requests, typically 4xx or
1562 root 1.1 5xx for errors), and C<$res> is the value of the C<value> key in the JSON
1563     response object.
1564    
1565     Other than that, the underscore variants and the blocking variants are
1566     identical.
1567    
1568     =head2 LOW LEVEL API
1569    
1570 root 1.9 All the simplified API methods are very thin wrappers around WebDriver
1571     commands of the same name. They are all implemented in terms of the
1572 root 1.1 low-level methods (C<req>, C<get>, C<post> and C<delete>), which exists
1573     in blocking and callback-based variants (C<req_>, C<get_>, C<post_> and
1574     C<delete_>).
1575    
1576     Examples are after the function descriptions.
1577    
1578     =over
1579    
1580     =item $wd->req_ ($method, $uri, $body, $cb->($status, $value))
1581    
1582     =item $value = $wd->req ($method, $uri, $body)
1583    
1584     Appends the C<$uri> to the C<endpoint/session/{sessionid}/> URL and makes
1585     a HTTP C<$method> request (C<GET>, C<POST> etc.). C<POST> requests can
1586     provide a UTF-8-encoded JSON text as HTTP request body, or the empty
1587     string to indicate no body is used.
1588    
1589     For the callback version, the callback gets passed the HTTP status code
1590     (200 for every successful request), and the value of the C<value> key in
1591     the JSON response object as second argument.
1592    
1593     =item $wd->get_ ($uri, $cb->($status, $value))
1594    
1595     =item $value = $wd->get ($uri)
1596    
1597     Simply a call to C<req_> with C<$method> set to C<GET> and an empty body.
1598    
1599     =item $wd->post_ ($uri, $data, $cb->($status, $value))
1600    
1601     =item $value = $wd->post ($uri, $data)
1602    
1603     Simply a call to C<req_> with C<$method> set to C<POST> - if C<$body> is
1604     C<undef>, then an empty object is send, otherwise, C<$data> must be a
1605     valid request object, which gets encoded into JSON for you.
1606    
1607     =item $wd->delete_ ($uri, $cb->($status, $value))
1608    
1609     =item $value = $wd->delete ($uri)
1610    
1611     Simply a call to C<req_> with C<$method> set to C<DELETE> and an empty body.
1612    
1613     =cut
1614    
1615     =back
1616    
1617     Example: implement C<get_all_cookies>, which is a simple C<GET> request
1618     without any parameters:
1619    
1620     $cookies = $wd->get ("cookie");
1621    
1622     Example: implement C<execute_script>, which needs some parameters:
1623    
1624     $results = $wd->post ("execute/sync" => { script => "$javascript", args => [] });
1625    
1626 root 1.12 Example: call C<find_elements> to find all C<IMG> elements:
1627 root 1.1
1628 root 1.12 $elems = $wd->post (elements => { using => "css selector", value => "img" });
1629 root 1.1
1630     =cut
1631    
1632     =head1 HISTORY
1633    
1634 root 1.2 This module was unintentionally created (it started inside some quickly
1635     hacked-together script) simply because I couldn't get the existing
1636 root 1.1 C<Selenium::Remote::Driver> module to work, ever, despite multiple
1637     attempts over the years and trying to report multiple bugs, which have
1638     been completely ignored. It's also not event-based, so, yeah...
1639    
1640     =head1 AUTHOR
1641    
1642     Marc Lehmann <schmorp@schmorp.de>
1643     http://anyevent.schmorp.de
1644    
1645     =cut
1646    
1647     1
1648