ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-WebDriver/WebDriver.pm
Revision: 1.57
Committed: Fri Mar 27 19:11:30 2020 UTC (4 years, 3 months ago) by root
Branch: MAIN
Changes since 1.56: +1 -1 lines
Log Message:
*** empty log message ***

File Contents

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