ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-WebDriver/WebDriver.pm
Revision: 1.35
Committed: Mon Sep 3 20:57:43 2018 UTC (5 years, 8 months ago) by root
Branch: MAIN
Changes since 1.34: +3 -1 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.1 =head1 NAME
2    
3     AnyEvent::WebDriver - control browsers using the W3C WebDriver protocol
4    
5     =head1 SYNOPSIS
6    
7     # start geckodriver or any other w3c-compatible webdriver via the shell
8     $ geckdriver -b myfirefox/firefox --log trace --port 4444
9    
10     # then use it
11     use AnyEvent::WebDriver;
12    
13     # create a new webdriver object
14     my $wd = new AnyEvent::WebDriver;
15    
16     # create a new session with default capabilities.
17     $wd->new_session ({});
18    
19     $wd->navigate_to ("https://duckduckgo.com/html");
20 root 1.30 my $searchbox = $wd->find_element (css => 'input[type="text"]');
21 root 1.1
22     $wd->element_send_keys ($searchbox => "free software");
23 root 1.30 $wd->element_click ($wd->find_element (css => 'input[type="submit"]'));
24 root 1.1
25 root 1.28 # session gets autodeleted by default, so wait a bit
26 root 1.1 sleep 10;
27    
28 root 1.30 # this is an example of an action sequence
29     $wd->actions
30     ->move ($wd->find_element (...), 40, 5)
31     ->click
32     ->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 root 1.35 can be cleared by embedding C<\x{e000}> in C<$text>. Presumably, you can
799     embed modifiers using their unicode codepoints, but the specification is
800     less than clear to mein this area.
801 root 1.1
802     =cut
803    
804     sub element_click_ {
805 root 1.12 $_[0]->post_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/click" => undef, $_[2]);
806 root 1.1 }
807    
808     sub element_clear_ {
809 root 1.12 $_[0]->post_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/clear" => undef, $_[2]);
810 root 1.1 }
811    
812     sub element_send_keys_ {
813 root 1.12 $_[0]->post_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/value" => { text => "$_[2]" }, $_[3]);
814 root 1.1 }
815    
816     =back
817    
818     =head3 DOCUMENT HANDLING
819    
820     =over
821    
822     =cut
823    
824     =item $source = $wd->get_page_source
825    
826     Returns the (HTML/XML) page source of the current document.
827    
828     =item $results = $wd->execute_script ($javascript, $args)
829    
830     Synchronously execute the given script with given arguments and return its
831     results (C<$args> can be C<undef> if no arguments are wanted/needed).
832    
833     $ten = $wd->execute_script ("return arguments[0]+arguments[1]", [3, 7]);
834    
835     =item $results = $wd->execute_async_script ($javascript, $args)
836    
837     Similar to C<execute_script>, but doesn't wait for script to return, but
838     instead waits for the script to call its last argument, which is added to
839     C<$args> automatically.
840    
841     $twenty = $wd->execute_async_script ("arguments[0](20)", undef);
842    
843     =cut
844    
845     sub get_page_source_ {
846     $_[0]->get_ (source => $_[1]);
847     }
848    
849     sub execute_script_ {
850     $_[0]->post_ ("execute/sync" => { script => "$_[1]", args => $_[2] || [] }, $_[3]);
851     }
852    
853     sub execute_async_script_ {
854     $_[0]->post_ ("execute/async" => { script => "$_[1]", args => $_[2] || [] }, $_[3]);
855     }
856    
857     =back
858    
859     =head3 COOKIES
860    
861     =over
862    
863     =cut
864    
865     =item $cookies = $wd->get_all_cookies
866    
867     Returns all cookies, as an arrayref of hashrefs.
868    
869     # google surely sets a lot of cookies without my consent
870     $wd->navigate_to ("http://google.com");
871     use Data::Dump;
872     ddx $wd->get_all_cookies;
873    
874     =item $cookie = $wd->get_named_cookie ($name)
875    
876     Returns a single cookie as a hashref.
877    
878     =item $wd->add_cookie ($cookie)
879    
880     Adds the given cookie hashref.
881    
882     =item $wd->delete_cookie ($name)
883    
884     Delete the named cookie.
885    
886     =item $wd->delete_all_cookies
887    
888     Delete all cookies.
889    
890     =cut
891    
892     sub get_all_cookies_ {
893     $_[0]->get_ (cookie => $_[1]);
894     }
895    
896     sub get_named_cookie_ {
897     $_[0]->get_ ("cookie/$_[1]" => $_[2]);
898     }
899    
900     sub add_cookie_ {
901     $_[0]->post_ (cookie => { cookie => $_[1] }, $_[2]);
902     }
903    
904     sub delete_cookie_ {
905     $_[0]->delete_ ("cookie/$_[1]" => $_[2]);
906     }
907    
908     sub delete_all_cookies_ {
909     $_[0]->delete_ (cookie => $_[2]);
910     }
911    
912     =back
913    
914     =head3 ACTIONS
915    
916     =over
917    
918     =cut
919    
920     =item $wd->perform_actions ($actions)
921    
922     Perform the given actions (an arrayref of action specifications simulating
923 root 1.12 user activity, or an C<AnyEvent::WebDriver::Actions> object). For further
924     details, read the spec or the section L<ACTION LISTS>, below.
925 root 1.1
926 root 1.12 An example to get you started (see the next example for a mostly
927     equivalent example using the C<AnyEvent::WebDriver::Actions> helper API):
928 root 1.1
929     $wd->navigate_to ("https://duckduckgo.com/html");
930     my $input = $wd->find_element ("css selector", 'input[type="text"]');
931     $wd->perform_actions ([
932     {
933     id => "myfatfinger",
934     type => "pointer",
935     pointerType => "touch",
936     actions => [
937 root 1.12 { type => "pointerMove", duration => 100, origin => $input, x => 40, y => 5 },
938 root 1.1 { type => "pointerDown", button => 1 },
939     { type => "pause", duration => 40 },
940     { type => "pointerUp", button => 1 },
941     ],
942     },
943     {
944     id => "mykeyboard",
945     type => "key",
946     actions => [
947     { type => "pause" },
948     { type => "pause" },
949     { type => "pause" },
950     { type => "pause" },
951     { type => "keyDown", value => "a" },
952     { type => "pause", duration => 100 },
953     { type => "keyUp", value => "a" },
954     { type => "pause", duration => 100 },
955     { type => "keyDown", value => "b" },
956     { type => "pause", duration => 100 },
957     { type => "keyUp", value => "b" },
958     { type => "pause", duration => 2000 },
959     { type => "keyDown", value => "\x{E007}" }, # enter
960     { type => "pause", duration => 100 },
961     { type => "keyUp", value => "\x{E007}" }, # enter
962     { type => "pause", duration => 5000 },
963     ],
964     },
965     ]);
966    
967 root 1.12 And here is essentially the same (except for fewer pauses) example as
968     above, using the much simpler C<AnyEvent::WebDriver::Actions> API. Note
969     that the pointer up and key down event happen concurrently in this
970     example:
971    
972     $wd->navigate_to ("https://duckduckgo.com/html");
973     my $input = $wd->find_element ("css selector", 'input[type="text"]');
974     $wd->actions
975     ->move ($input, 40, 5, "touch1")
976 root 1.20 ->click
977 root 1.29 ->pause # to separate the click from the keypress
978 root 1.20 ->key ("a")
979     ->key ("b")
980 root 1.29 ->pause (2000) # so you can watch leisurely
981     ->key ("{Enter}")
982     ->pause (5000) # so you can see the result
983 root 1.12 ->perform;
984    
985 root 1.1 =item $wd->release_actions
986    
987     Release all keys and pointer buttons currently depressed.
988    
989     =cut
990    
991     sub perform_actions_ {
992 root 1.12 if (UNIVERSAL::isa $_[1], AnyEvent::WebDriver::Actions::) {
993     my ($actions, $duration) = $_[1]->compile;
994     local $_[0]{timeout} = $_[0]{timeout} + $duration * 1e-3;
995     $_[0]->post_ (actions => { actions => $actions }, $_[2]);
996     } else {
997     $_[0]->post_ (actions => { actions => $_[1] }, $_[2]);
998     }
999 root 1.1 }
1000    
1001     sub release_actions_ {
1002     $_[0]->delete_ (actions => $_[1]);
1003     }
1004    
1005     =back
1006    
1007     =head3 USER PROMPTS
1008    
1009     =over
1010    
1011     =cut
1012    
1013     =item $wd->dismiss_alert
1014    
1015     Dismiss a simple dialog, if present.
1016    
1017     =item $wd->accept_alert
1018    
1019     Accept a simple dialog, if present.
1020    
1021     =item $text = $wd->get_alert_text
1022    
1023     Returns the text of any simple dialog.
1024    
1025     =item $text = $wd->send_alert_text
1026    
1027     Fills in the user prompt with the given text.
1028    
1029    
1030     =cut
1031    
1032     sub dismiss_alert_ {
1033     $_[0]->post_ ("alert/dismiss" => undef, $_[1]);
1034     }
1035    
1036     sub accept_alert_ {
1037     $_[0]->post_ ("alert/accept" => undef, $_[1]);
1038     }
1039    
1040     sub get_alert_text_ {
1041     $_[0]->get_ ("alert/text" => $_[1]);
1042     }
1043    
1044     sub send_alert_text_ {
1045     $_[0]->post_ ("alert/text" => { text => "$_[1]" }, $_[2]);
1046     }
1047    
1048     =back
1049    
1050     =head3 SCREEN CAPTURE
1051    
1052     =over
1053    
1054     =cut
1055    
1056     =item $wd->take_screenshot
1057    
1058 root 1.9 Create a screenshot, returning it as a PNG image in a C<data:> URL.
1059 root 1.1
1060 root 1.12 =item $wd->take_element_screenshot ($element)
1061 root 1.1
1062     Accept a simple dialog, if present.
1063    
1064     =cut
1065    
1066     sub take_screenshot_ {
1067     $_[0]->get_ (screenshot => $_[1]);
1068     }
1069    
1070     sub take_element_screenshot_ {
1071 root 1.12 $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/screenshot" => $_[2]);
1072 root 1.1 }
1073    
1074     =back
1075    
1076 root 1.12 =head2 ACTION LISTS
1077    
1078     Action lists can be quite complicated. Or at least it took a while for
1079     me to twist my head around them. Basically, an action list consists of a
1080     number of sources representing devices (such as a finger, a mouse, a pen
1081     or a keyboard) and a list of actions for each source.
1082    
1083     An action can be a key press, a pointer move or a pause (time
1084     delay). Actions from different sources can happen "at the same time",
1085     while actions from a single source are executed in order.
1086    
1087     While you can provide an action list manually, it is (hopefully) less
1088     cumbersome to use the API described in this section to create them.
1089    
1090     The basic process of creating and performing actions is to create a new
1091     action list, adding action sources, followed by adding actions. Finally
1092     you would C<perform> those actions on the WebDriver.
1093    
1094     Virtual time progresses as long as you add actions to the same event
1095     source. Adding events to different sources are considered to happen
1096     concurrently. If you want to force time to progress, you can do this using
1097     a call to C<< ->pause (0) >>.
1098    
1099     Most methods here are designed to chain, i.e. they return the web actions
1100     object, to simplify multiple calls.
1101    
1102     For example, to simulate a mouse click to an input element, followed by
1103     entering some text and pressing enter, you can use this:
1104    
1105     $wd->actions
1106     ->click (1, 100)
1107 root 1.29 ->pause # to separate the click from keypressed
1108 root 1.12 ->type ("some text")
1109 root 1.13 ->key ("{Enter}")
1110 root 1.12 ->perform;
1111    
1112     By default, keyboard and mouse input sources are provided. You can create
1113     your own sources and use them when adding events. The above example could
1114     be more verbosely written like this:
1115    
1116     $wd->actions
1117 root 1.26 ->source ("mouse", "pointer", pointerType => "mouse")
1118     ->source ("kbd", "key")
1119 root 1.12 ->click (1, 100, "mouse")
1120 root 1.29 ->pause
1121 root 1.26 ->type ("some text", "kbd")
1122     ->key ("{Enter}", "kbd")
1123 root 1.12 ->perform;
1124    
1125 root 1.21 When you specify the event source explicitly it will switch the current
1126 root 1.12 "focus" for this class of device (all keyboards are in one class, all
1127     pointer-like devices such as mice/fingers/pens are in one class), so you
1128     don't have to specify the source for subsequent actions.
1129    
1130     When you use the sources C<keyboard>, C<mouse>, C<touch1>..C<touch3>,
1131     C<pen> without defining them, then a suitable default source will be
1132     created for them.
1133    
1134     =over 4
1135    
1136     =cut
1137    
1138     package AnyEvent::WebDriver::Actions;
1139    
1140     =item $al = new AnyEvent::WebDriver::Actions
1141    
1142     Create a new empty action list object. More often you would use the C<<
1143 root 1.13 $wd->action_list >> method to create one that is already associated with
1144 root 1.12 a given web driver.
1145    
1146     =cut
1147    
1148     sub new {
1149     my ($class, %kv) = @_;
1150    
1151     $kv{last_kbd} = "keyboard";
1152     $kv{last_ptr} = "mouse";
1153    
1154     bless \%kv, $class
1155     }
1156    
1157     =item $al = $al->source ($id, $type, key => value...)
1158    
1159 root 1.21 The first time you call this with a given ID, this defines the event
1160 root 1.12 source using the extra parameters. Subsequent calls merely switch the
1161     current source for its event class.
1162    
1163     It's not an error to define built-in sources (such as C<keyboard> or
1164     C<touch1>) differently then the defaults.
1165    
1166     Example: define a new touch device called C<fatfinger>.
1167    
1168     $al->source (fatfinger => "pointer", pointerType => "touch");
1169    
1170 root 1.21 Example: define a new touch device called C<fatfinger>.
1171 root 1.12
1172     $al->source (fatfinger => "pointer", pointerType => "touch");
1173    
1174 root 1.21 Example: switch default keyboard source to C<kbd1>, assuming it is of C<key> class.
1175    
1176     $al->source ("kbd1");
1177    
1178 root 1.12 =cut
1179    
1180 root 1.13 sub _default_source($) {
1181 root 1.12 my ($source) = @_;
1182    
1183     $source eq "keyboard" ? { actions => [], id => $source, type => "key" }
1184     : $source eq "mouse" ? { actions => [], id => $source, type => "pointer", pointerType => "mouse" }
1185     : $source eq "touch" ? { actions => [], id => $source, type => "pointer", pointerType => "touch" }
1186     : $source eq "pen" ? { actions => [], id => $source, type => "pointer", pointerType => "pen" }
1187     : Carp::croak "AnyEvent::WebDriver::Actions: event source '$source' not defined"
1188     }
1189    
1190     my %source_class = (
1191     key => "kbd",
1192     pointer => "ptr",
1193     );
1194    
1195     sub source {
1196     my ($self, $id, $type, %kv) = @_;
1197    
1198     if (defined $type) {
1199     !exists $self->{source}{$id}
1200     or Carp::croak "AnyEvent::WebDriver::Actions: source '$id' already defined";
1201    
1202     $kv{id} = $id;
1203     $kv{type} = $type;
1204     $kv{actions} = [];
1205    
1206     $self->{source}{$id} = \%kv;
1207     }
1208    
1209     my $source = $self->{source}{$id} ||= _default_source $id;
1210    
1211     my $last = $source_class{$source->{type}} // "xxx";
1212    
1213     $self->{"last_$last"} = $id;
1214    
1215     $self
1216     }
1217    
1218     sub _add {
1219     my ($self, $source, $sourcetype, $type, %kv) = @_;
1220    
1221     my $last = \$self->{"last_$sourcetype"};
1222    
1223     $source
1224     ? ($$last = $source)
1225     : ($source = $$last);
1226    
1227     my $source = $self->{source}{$source} ||= _default_source $source;
1228    
1229     my $al = $source->{actions};
1230    
1231     push @$al, { type => "pause" }
1232     while @$al < $self->{tick} - 1;
1233    
1234     $kv{type} = $type;
1235    
1236     push @{ $source->{actions} }, \%kv;
1237    
1238     $self->{tick_duration} = $kv{duration}
1239     if $kv{duration} > $self->{tick_duration};
1240    
1241     if ($self->{tick} != @$al) {
1242     $self->{tick} = @$al;
1243     $self->{duration} += delete $self->{tick_duration};
1244     }
1245    
1246     $self
1247     }
1248 root 1.1
1249 root 1.12 =item $al = $al->pause ($duration)
1250    
1251     Creates a pause with the given duration. Makes sure that time progresses
1252     in any case, even when C<$duration> is C<0>.
1253    
1254     =cut
1255    
1256     sub pause {
1257     my ($self, $duration) = @_;
1258    
1259     $self->{tick_duration} = $duration
1260     if $duration > $self->{tick_duration};
1261    
1262     $self->{duration} += delete $self->{tick_duration};
1263    
1264     # find the source with the longest list
1265    
1266     for my $source (values %{ $self->{source} }) {
1267     if (@{ $source->{actions} } == $self->{tick}) {
1268     # this source is one of the longest
1269    
1270     # create a pause event only if $duration is non-zero...
1271     push @{ $source->{actions} }, { type => "pause", duration => $duration*1 }
1272     if $duration;
1273    
1274     # ... but advance time in any case
1275     ++$self->{tick};
1276    
1277     return $self;
1278     }
1279     }
1280    
1281     # no event sources are longest. so advance time in any case
1282     ++$self->{tick};
1283    
1284     Carp::croak "AnyEvent::WebDriver::Actions: multiple pause calls in a row not (yet) supported"
1285     if $duration;
1286    
1287     $self
1288     }
1289    
1290     =item $al = $al->pointer_down ($button, $source)
1291    
1292     =item $al = $al->pointer_up ($button, $source)
1293    
1294     Press or release the given button. C<$button> defaults to C<1>.
1295    
1296     =item $al = $al->click ($button, $source)
1297    
1298     Convenience function that creates a button press and release action
1299     without any delay between them. C<$button> defaults to C<1>.
1300    
1301     =item $al = $al->doubleclick ($button, $source)
1302    
1303     Convenience function that creates two button press and release action
1304     pairs in a row, with no unnecessary delay between them. C<$button>
1305     defaults to C<1>.
1306    
1307     =cut
1308    
1309     sub pointer_down {
1310     my ($self, $button, $source) = @_;
1311    
1312     $self->_add ($source, ptr => pointerDown => button => ($button // 1)*1)
1313     }
1314    
1315     sub pointer_up {
1316     my ($self, $button, $source) = @_;
1317    
1318     $self->_add ($source, ptr => pointerUp => button => ($button // 1)*1)
1319     }
1320    
1321     sub click {
1322     my ($self, $button, $source) = @_;
1323    
1324     $self
1325     ->pointer_down ($button, $source)
1326     ->pointer_up ($button)
1327     }
1328    
1329     sub doubleclick {
1330     my ($self, $button, $source) = @_;
1331    
1332     $self
1333     ->click ($button, $source)
1334     ->click ($button)
1335     }
1336    
1337     =item $al = $al->move ($button, $origin, $x, $y, $duration, $source)
1338    
1339     Moves a pointer to the given position, relative to origin (either
1340     "viewport", "pointer" or an element object.
1341    
1342     =cut
1343    
1344     sub move {
1345     my ($self, $origin, $x, $y, $duration, $source) = @_;
1346    
1347     $self->_add ($source, ptr => pointerMove =>
1348     origin => $origin, x => $x*1, y => $y*1, duration => $duration*1)
1349     }
1350    
1351 root 1.33 =item $al = $al->cancel ($source)
1352    
1353     Executes a pointer cancel action.
1354    
1355     =cut
1356    
1357     sub cancel {
1358     my ($self, $source) = @_;
1359    
1360     $self->_add ($source, ptr => "pointerCancel")
1361     }
1362    
1363 root 1.12 =item $al = $al->keyDown ($key, $source)
1364    
1365     =item $al = $al->keyUp ($key, $source)
1366    
1367     Press or release the given key.
1368    
1369 root 1.13 =item $al = $al->key ($key, $source)
1370    
1371     Peess and release the given key, without unnecessary delay.
1372    
1373     A special syntax, C<{keyname}> can be used for special keys - all the special key names from
1374     L<section 17.4.2|https://www.w3.org/TR/webdriver1/#keyboard-actions> of the WebDriver recommendation
1375     can be used.
1376    
1377     Example: press and release "a".
1378    
1379     $al->key ("a");
1380    
1381     Example: press and release the "Enter" key:
1382    
1383     $al->key ("\x{e007}");
1384    
1385     Example: press and release the "enter" key using the special key name syntax:
1386    
1387     $al->key ("{Enter}");
1388    
1389     =item $al = $al->type ($string, $source)
1390    
1391     Convenience method to simulate a series of key press and release events
1392 root 1.33 for the keys in C<$string>, one pair per extended unicode grapheme
1393     cluster. There is no syntax for special keys, everything will be typed
1394     "as-is" if possible.
1395 root 1.13
1396     =cut
1397    
1398     our %SPECIAL_KEY = (
1399     "Unidentified" => 0xE000,
1400     "Cancel" => 0xE001,
1401     "Help" => 0xE002,
1402     "Backspace" => 0xE003,
1403     "Tab" => 0xE004,
1404     "Clear" => 0xE005,
1405     "Return" => 0xE006,
1406     "Enter" => 0xE007,
1407     "Shift" => 0xE008,
1408     "Control" => 0xE009,
1409     "Alt" => 0xE00A,
1410     "Pause" => 0xE00B,
1411     "Escape" => 0xE00C,
1412     " " => 0xE00D,
1413     "PageUp" => 0xE00E,
1414     "PageDown" => 0xE00F,
1415     "End" => 0xE010,
1416     "Home" => 0xE011,
1417     "ArrowLeft" => 0xE012,
1418     "ArrowUp" => 0xE013,
1419     "ArrowRight" => 0xE014,
1420     "ArrowDown" => 0xE015,
1421     "Insert" => 0xE016,
1422     "Delete" => 0xE017,
1423     ";" => 0xE018,
1424     "=" => 0xE019,
1425     "0" => 0xE01A,
1426     "1" => 0xE01B,
1427     "2" => 0xE01C,
1428     "3" => 0xE01D,
1429     "4" => 0xE01E,
1430     "5" => 0xE01F,
1431     "6" => 0xE020,
1432     "7" => 0xE021,
1433     "8" => 0xE022,
1434     "9" => 0xE023,
1435     "*" => 0xE024,
1436     "+" => 0xE025,
1437     "," => 0xE026,
1438     "-" => 0xE027,
1439     "." => 0xE028,
1440     "/" => 0xE029,
1441     "F1" => 0xE031,
1442     "F2" => 0xE032,
1443     "F3" => 0xE033,
1444     "F4" => 0xE034,
1445     "F5" => 0xE035,
1446     "F6" => 0xE036,
1447     "F7" => 0xE037,
1448     "F8" => 0xE038,
1449     "F9" => 0xE039,
1450     "F10" => 0xE03A,
1451     "F11" => 0xE03B,
1452     "F12" => 0xE03C,
1453     "Meta" => 0xE03D,
1454     "ZenkakuHankaku" => 0xE040,
1455     "Shift" => 0xE050,
1456     "Control" => 0xE051,
1457     "Alt" => 0xE052,
1458     "Meta" => 0xE053,
1459     "PageUp" => 0xE054,
1460     "PageDown" => 0xE055,
1461     "End" => 0xE056,
1462     "Home" => 0xE057,
1463     "ArrowLeft" => 0xE058,
1464     "ArrowUp" => 0xE059,
1465     "ArrowRight" => 0xE05A,
1466     "ArrowDown" => 0xE05B,
1467     "Insert" => 0xE05C,
1468     "Delete" => 0xE05D,
1469     );
1470    
1471     sub _kv($) {
1472     $_[0] =~ /^\{(.*)\}$/s
1473     ? (exists $SPECIAL_KEY{$1}
1474     ? chr $SPECIAL_KEY{$1}
1475     : Carp::croak "AnyEvent::WebDriver::Actions: special key '$1' not known")
1476     : $_[0]
1477     }
1478 root 1.12
1479     sub key_down {
1480     my ($self, $key, $source) = @_;
1481    
1482 root 1.13 $self->_add ($source, kbd => keyDown => value => _kv $key)
1483 root 1.12 }
1484    
1485     sub key_up {
1486     my ($self, $key, $source) = @_;
1487    
1488 root 1.13 $self->_add ($source, kbd => keyUp => value => _kv $key)
1489 root 1.12 }
1490    
1491     sub key {
1492     my ($self, $key, $source) = @_;
1493    
1494     $self
1495     ->key_down ($key, $source)
1496     ->key_up ($key)
1497     }
1498    
1499 root 1.13 sub type {
1500     my ($self, $string, $source) = @_;
1501    
1502     $self->key ($_, $source)
1503     for $string =~ /(\X)/g;
1504    
1505     $self
1506     }
1507    
1508 root 1.12 =item $al->perform ($wd)
1509    
1510 root 1.23 Finalises and compiles the list, if not done yet, and calls C<<
1511 root 1.12 $wd->perform >> with it.
1512    
1513     If C<$wd> is undef, and the action list was created using the C<<
1514     $wd->actions >> method, then perform it against that WebDriver object.
1515    
1516     There is no underscore variant - call the C<perform_actions_> method with
1517     the action object instead.
1518    
1519     =item $al->perform_release ($wd)
1520    
1521     Exactly like C<perform>, but additionally call C<release_actions>
1522     afterwards.
1523 root 1.1
1524     =cut
1525    
1526 root 1.12 sub perform {
1527     my ($self, $wd) = @_;
1528    
1529     ($wd //= $self->{wd})->perform_actions ($self)
1530     }
1531 root 1.9
1532 root 1.12 sub perform_release {
1533     my ($self, $wd) = @_;
1534    
1535     ($wd //= $self->{wd})->perform_actions ($self);
1536     $wd->release_actions;
1537     }
1538 root 1.1
1539 root 1.12 =item ($actions, $duration) = $al->compile
1540    
1541     Finalises and compiles the list, if not done yet, and returns an actions
1542     object suitable for calls to C<< $wd->perform_actions >>. When called in
1543     list context, additionally returns the total duration of the action list.
1544    
1545     Since building large action lists can take nontrivial amounts of time,
1546     it can make sense to build an action list only once and then perform it
1547     multiple times.
1548    
1549     Actions must not be added after compiling a list.
1550 root 1.1
1551     =cut
1552    
1553 root 1.12 sub compile {
1554     my ($self) = @_;
1555    
1556     $self->{duration} += delete $self->{tick_duration};
1557    
1558     delete $self->{tick};
1559     delete $self->{last_kbd};
1560     delete $self->{last_ptr};
1561    
1562     $self->{actions} ||= [values %{ delete $self->{source} }];
1563    
1564     wantarray
1565     ? ($self->{actions}, $self->{duration})
1566     : $self->{actions}
1567 root 1.1 }
1568    
1569     =back
1570    
1571     =head2 EVENT BASED API
1572    
1573     This module wouldn't be a good AnyEvent citizen if it didn't have a true
1574     event-based API.
1575    
1576     In fact, the simplified API, as documented above, is emulated via the
1577     event-based API and an C<AUTOLOAD> function that automatically provides
1578     blocking wrappers around the callback-based API.
1579    
1580     Every method documented in the L<SIMPLIFIED API> section has an equivalent
1581     event-based method that is formed by appending a underscore (C<_>) to the
1582     method name, and appending a callback to the argument list (mnemonic: the
1583     underscore indicates the "the action is not yet finished" after the call
1584     returns).
1585    
1586     For example, instead of a blocking calls to C<new_session>, C<navigate_to>
1587     and C<back>, you can make a callback-based ones:
1588    
1589     my $cv = AE::cv;
1590    
1591     $wd->new_session ({}, sub {
1592     my ($status, $value) = @_,
1593    
1594     die "error $value->{error}" if $status ne "200";
1595    
1596     $wd->navigate_to_ ("http://www.nethype.de", sub {
1597    
1598     $wd->back_ (sub {
1599     print "all done\n";
1600     $cv->send;
1601     });
1602    
1603     });
1604     });
1605    
1606     $cv->recv;
1607    
1608     While the blocking methods C<croak> on errors, the callback-based ones all
1609     pass two values to the callback, C<$status> and C<$res>, where C<$status>
1610 root 1.9 is the HTTP status code (200 for successful requests, typically 4xx or
1611 root 1.1 5xx for errors), and C<$res> is the value of the C<value> key in the JSON
1612     response object.
1613    
1614     Other than that, the underscore variants and the blocking variants are
1615     identical.
1616    
1617     =head2 LOW LEVEL API
1618    
1619 root 1.9 All the simplified API methods are very thin wrappers around WebDriver
1620     commands of the same name. They are all implemented in terms of the
1621 root 1.27 low-level methods (C<req>, C<get>, C<post> and C<delete>), which exist
1622 root 1.1 in blocking and callback-based variants (C<req_>, C<get_>, C<post_> and
1623     C<delete_>).
1624    
1625     Examples are after the function descriptions.
1626    
1627     =over
1628    
1629     =item $wd->req_ ($method, $uri, $body, $cb->($status, $value))
1630    
1631     =item $value = $wd->req ($method, $uri, $body)
1632    
1633     Appends the C<$uri> to the C<endpoint/session/{sessionid}/> URL and makes
1634     a HTTP C<$method> request (C<GET>, C<POST> etc.). C<POST> requests can
1635     provide a UTF-8-encoded JSON text as HTTP request body, or the empty
1636     string to indicate no body is used.
1637    
1638     For the callback version, the callback gets passed the HTTP status code
1639     (200 for every successful request), and the value of the C<value> key in
1640     the JSON response object as second argument.
1641    
1642     =item $wd->get_ ($uri, $cb->($status, $value))
1643    
1644     =item $value = $wd->get ($uri)
1645    
1646     Simply a call to C<req_> with C<$method> set to C<GET> and an empty body.
1647    
1648     =item $wd->post_ ($uri, $data, $cb->($status, $value))
1649    
1650     =item $value = $wd->post ($uri, $data)
1651    
1652     Simply a call to C<req_> with C<$method> set to C<POST> - if C<$body> is
1653     C<undef>, then an empty object is send, otherwise, C<$data> must be a
1654     valid request object, which gets encoded into JSON for you.
1655    
1656     =item $wd->delete_ ($uri, $cb->($status, $value))
1657    
1658     =item $value = $wd->delete ($uri)
1659    
1660     Simply a call to C<req_> with C<$method> set to C<DELETE> and an empty body.
1661    
1662     =cut
1663    
1664     =back
1665    
1666     Example: implement C<get_all_cookies>, which is a simple C<GET> request
1667     without any parameters:
1668    
1669     $cookies = $wd->get ("cookie");
1670    
1671     Example: implement C<execute_script>, which needs some parameters:
1672    
1673     $results = $wd->post ("execute/sync" => { script => "$javascript", args => [] });
1674    
1675 root 1.12 Example: call C<find_elements> to find all C<IMG> elements:
1676 root 1.1
1677 root 1.12 $elems = $wd->post (elements => { using => "css selector", value => "img" });
1678 root 1.1
1679     =cut
1680    
1681     =head1 HISTORY
1682    
1683 root 1.2 This module was unintentionally created (it started inside some quickly
1684     hacked-together script) simply because I couldn't get the existing
1685 root 1.1 C<Selenium::Remote::Driver> module to work, ever, despite multiple
1686     attempts over the years and trying to report multiple bugs, which have
1687     been completely ignored. It's also not event-based, so, yeah...
1688    
1689     =head1 AUTHOR
1690    
1691     Marc Lehmann <schmorp@schmorp.de>
1692     http://anyevent.schmorp.de
1693    
1694     =cut
1695    
1696     1
1697