ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-WebDriver/WebDriver.pm
Revision: 1.38
Committed: Tue Sep 4 01:41:35 2018 UTC (5 years, 8 months ago) by root
Branch: MAIN
Changes since 1.37: +1 -1 lines
Log Message:
*** empty log message ***

File Contents

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