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