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