ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-WebDriver/WebDriver.pm
Revision: 1.18
Committed: Wed Aug 29 06:39:10 2018 UTC (5 years, 10 months ago) by root
Branch: MAIN
Changes since 1.17: +9 -0 lines
Log Message:
*** empty log message ***

File Contents

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