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