ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-WebDriver/WebDriver.pm
Revision: 1.47
Committed: Fri Sep 27 03:31:02 2019 UTC (4 years, 8 months ago) by root
Branch: MAIN
CVS Tags: rel-1_01
Changes since 1.46: +1 -1 lines
Log Message:
1.01

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