ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-WebDriver/WebDriver.pm
Revision: 1.60
Committed: Wed Dec 16 16:44:49 2020 UTC (3 years, 5 months ago) by root
Branch: MAIN
CVS Tags: HEAD
Changes since 1.59: +2 -2 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.1 =head1 NAME
2    
3     AnyEvent::WebDriver - control browsers using the W3C WebDriver protocol
4    
5     =head1 SYNOPSIS
6    
7 root 1.41 # start geckodriver(chromedriver or any other webdriver via the shell
8     $ geckodriver -b myfirefox/firefox --log trace --port 4444
9     # chromedriver --port=4444
10 root 1.1
11     # then use it
12     use AnyEvent::WebDriver;
13    
14     # create a new webdriver object
15     my $wd = new AnyEvent::WebDriver;
16    
17     # create a new session with default capabilities.
18     $wd->new_session ({});
19    
20     $wd->navigate_to ("https://duckduckgo.com/html");
21 root 1.30 my $searchbox = $wd->find_element (css => 'input[type="text"]');
22 root 1.1
23     $wd->element_send_keys ($searchbox => "free software");
24 root 1.30 $wd->element_click ($wd->find_element (css => 'input[type="submit"]'));
25 root 1.1
26 root 1.28 # session gets autodeleted by default, so wait a bit
27 root 1.1 sleep 10;
28    
29 root 1.30 # this is an example of an action sequence
30     $wd->actions
31     ->move ($wd->find_element (...), 40, 5)
32     ->click
33     ->type ("some text")
34     ->key ("{Enter}")
35     ->perform;
36    
37 root 1.1 =head1 DESCRIPTION
38    
39 root 1.39 This module aims to implement the L<W3C
40     WebDriver|https://www.w3.org/TR/webdriver1/> specification which is the
41 root 1.38 standardised equivalent to the Selenium WebDriver API, which in turn aims
42 root 1.1 at remotely controlling web browsers such as Firefox or Chromium.
43    
44 root 1.30 One of the design goals of this module was to stay very close to the
45     language and words used in the WebDriver specification itself, so to make
46     most of this module, or, in fact, to make any reasonable use of this
47     module, you would need to refer to the W3C WebDriver recommendation, which
48     can be found L<here|https://www.w3.org/TR/webdriver1/>:
49 root 1.1
50 root 1.11 https://www.w3.org/TR/webdriver1/
51 root 1.1
52 root 1.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 root 1.59 $msg = "AnyEvent::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.58 Compatibility note: As of chrome version 80, I found that the screenshot
1111     scaling is often wrong (the screenshot is much smaller than the element
1112     normally displays) unless chrome runs in headless mode. The spec does
1113     allow for any form of scaling, so this is not strictly a bug in chrome,
1114     but of course it diminishes trhe screenshot functionality.
1115 root 1.50
1116 root 1.1 =cut
1117    
1118     sub take_screenshot_ {
1119 root 1.51 my $cb = pop; push @_, sub { $cb->($_[0], _decode_base64 $_[1]) };
1120 root 1.1 $_[0]->get_ (screenshot => $_[1]);
1121     }
1122    
1123     sub take_element_screenshot_ {
1124 root 1.51 my $cb = pop; push @_, sub { $cb->($_[0], _decode_base64 $_[1]) };
1125 root 1.12 $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/screenshot" => $_[2]);
1126 root 1.1 }
1127    
1128     =back
1129    
1130 root 1.51 =head3 PRINT
1131    
1132     =over
1133    
1134     =cut
1135    
1136     =item $wd->print_page (key => value...)
1137    
1138     Create a printed version of the document, returning it as a PDF document
1139     encoded as base64. See C<take_screenshot> for an example on how to decode
1140     and save such a string.
1141    
1142     This command takes a lot of optional parameters, see L<the print
1143     section|https://www.w3.org/TR/webdriver2/#print> of the WebDriver
1144     specification for details.
1145    
1146     This command is taken from a draft document, so it might change in the
1147     future.
1148    
1149     =cut
1150    
1151     sub print_page {
1152     my $cb = pop; push @_, sub { $cb->($_[0], _decode_base64 $_[1]) };
1153     $_[0]->post_ (print => { @_ });
1154     }
1155    
1156 root 1.12 =head2 ACTION LISTS
1157    
1158     Action lists can be quite complicated. Or at least it took a while for
1159     me to twist my head around them. Basically, an action list consists of a
1160     number of sources representing devices (such as a finger, a mouse, a pen
1161 root 1.51 or a keyboard) and a list of actions for each source, in a timeline.
1162 root 1.12
1163 root 1.36 An action can be a key press, a pointer move or a pause (time delay).
1164 root 1.12
1165 root 1.36 While you can provide these action lists manually, it is (hopefully) less
1166 root 1.12 cumbersome to use the API described in this section to create them.
1167    
1168     The basic process of creating and performing actions is to create a new
1169     action list, adding action sources, followed by adding actions. Finally
1170     you would C<perform> those actions on the WebDriver.
1171    
1172     Most methods here are designed to chain, i.e. they return the web actions
1173     object, to simplify multiple calls.
1174    
1175 root 1.36 Also, while actions from different sources can happen "at the same time"
1176 root 1.51 in the WebDriver protocol, this class by default ensures that actions will
1177     execute in the order specified.
1178 root 1.36
1179 root 1.12 For example, to simulate a mouse click to an input element, followed by
1180     entering some text and pressing enter, you can use this:
1181    
1182     $wd->actions
1183 root 1.36 ->click (0, 100)
1184 root 1.12 ->type ("some text")
1185 root 1.13 ->key ("{Enter}")
1186 root 1.12 ->perform;
1187    
1188 root 1.51 By default, C<keyboard> and C<mouse> input sources are provided and
1189     used. You can create your own sources and use them when adding events. The
1190     above example could be more verbosely written like this:
1191 root 1.12
1192     $wd->actions
1193 root 1.26 ->source ("mouse", "pointer", pointerType => "mouse")
1194     ->source ("kbd", "key")
1195 root 1.36 ->click (0, 100, "mouse")
1196 root 1.26 ->type ("some text", "kbd")
1197     ->key ("{Enter}", "kbd")
1198 root 1.12 ->perform;
1199    
1200 root 1.21 When you specify the event source explicitly it will switch the current
1201 root 1.12 "focus" for this class of device (all keyboards are in one class, all
1202     pointer-like devices such as mice/fingers/pens are in one class), so you
1203 root 1.51 don't have to specify the source for subsequent actions that are on the
1204     same class.
1205 root 1.12
1206     When you use the sources C<keyboard>, C<mouse>, C<touch1>..C<touch3>,
1207     C<pen> without defining them, then a suitable default source will be
1208     created for them.
1209    
1210     =over 4
1211    
1212     =cut
1213    
1214     package AnyEvent::WebDriver::Actions;
1215    
1216     =item $al = new AnyEvent::WebDriver::Actions
1217    
1218     Create a new empty action list object. More often you would use the C<<
1219 root 1.13 $wd->action_list >> method to create one that is already associated with
1220 root 1.12 a given web driver.
1221    
1222     =cut
1223    
1224     sub new {
1225     my ($class, %kv) = @_;
1226    
1227     $kv{last_kbd} = "keyboard";
1228     $kv{last_ptr} = "mouse";
1229    
1230     bless \%kv, $class
1231     }
1232    
1233     =item $al = $al->source ($id, $type, key => value...)
1234    
1235 root 1.21 The first time you call this with a given ID, this defines the event
1236 root 1.12 source using the extra parameters. Subsequent calls merely switch the
1237     current source for its event class.
1238    
1239     It's not an error to define built-in sources (such as C<keyboard> or
1240     C<touch1>) differently then the defaults.
1241    
1242     Example: define a new touch device called C<fatfinger>.
1243    
1244     $al->source (fatfinger => "pointer", pointerType => "touch");
1245    
1246 root 1.21 Example: define a new touch device called C<fatfinger>.
1247 root 1.12
1248     $al->source (fatfinger => "pointer", pointerType => "touch");
1249    
1250 root 1.21 Example: switch default keyboard source to C<kbd1>, assuming it is of C<key> class.
1251    
1252     $al->source ("kbd1");
1253    
1254 root 1.12 =cut
1255    
1256 root 1.13 sub _default_source($) {
1257 root 1.12 my ($source) = @_;
1258    
1259     $source eq "keyboard" ? { actions => [], id => $source, type => "key" }
1260     : $source eq "mouse" ? { actions => [], id => $source, type => "pointer", pointerType => "mouse" }
1261     : $source eq "touch" ? { actions => [], id => $source, type => "pointer", pointerType => "touch" }
1262     : $source eq "pen" ? { actions => [], id => $source, type => "pointer", pointerType => "pen" }
1263     : Carp::croak "AnyEvent::WebDriver::Actions: event source '$source' not defined"
1264     }
1265    
1266     my %source_class = (
1267     key => "kbd",
1268     pointer => "ptr",
1269     );
1270    
1271     sub source {
1272     my ($self, $id, $type, %kv) = @_;
1273    
1274     if (defined $type) {
1275     !exists $self->{source}{$id}
1276     or Carp::croak "AnyEvent::WebDriver::Actions: source '$id' already defined";
1277    
1278     $kv{id} = $id;
1279     $kv{type} = $type;
1280     $kv{actions} = [];
1281    
1282     $self->{source}{$id} = \%kv;
1283     }
1284    
1285     my $source = $self->{source}{$id} ||= _default_source $id;
1286    
1287     my $last = $source_class{$source->{type}} // "xxx";
1288    
1289     $self->{"last_$last"} = $id;
1290    
1291     $self
1292     }
1293    
1294     sub _add {
1295     my ($self, $source, $sourcetype, $type, %kv) = @_;
1296    
1297     my $last = \$self->{"last_$sourcetype"};
1298    
1299     $source
1300     ? ($$last = $source)
1301     : ($source = $$last);
1302    
1303     my $source = $self->{source}{$source} ||= _default_source $source;
1304    
1305     my $al = $source->{actions};
1306    
1307     push @$al, { type => "pause" }
1308 root 1.36 while @$al < $self->{tick}; # -1 == allow concurrent actions
1309 root 1.12
1310     $kv{type} = $type;
1311    
1312     push @{ $source->{actions} }, \%kv;
1313    
1314     $self->{tick_duration} = $kv{duration}
1315     if $kv{duration} > $self->{tick_duration};
1316    
1317     if ($self->{tick} != @$al) {
1318     $self->{tick} = @$al;
1319     $self->{duration} += delete $self->{tick_duration};
1320     }
1321    
1322     $self
1323     }
1324 root 1.1
1325 root 1.12 =item $al = $al->pause ($duration)
1326    
1327     Creates a pause with the given duration. Makes sure that time progresses
1328     in any case, even when C<$duration> is C<0>.
1329    
1330     =cut
1331    
1332     sub pause {
1333     my ($self, $duration) = @_;
1334    
1335     $self->{tick_duration} = $duration
1336     if $duration > $self->{tick_duration};
1337    
1338     $self->{duration} += delete $self->{tick_duration};
1339    
1340     # find the source with the longest list
1341    
1342     for my $source (values %{ $self->{source} }) {
1343     if (@{ $source->{actions} } == $self->{tick}) {
1344     # this source is one of the longest
1345    
1346     # create a pause event only if $duration is non-zero...
1347     push @{ $source->{actions} }, { type => "pause", duration => $duration*1 }
1348     if $duration;
1349    
1350     # ... but advance time in any case
1351     ++$self->{tick};
1352    
1353     return $self;
1354     }
1355     }
1356    
1357     # no event sources are longest. so advance time in any case
1358     ++$self->{tick};
1359    
1360     Carp::croak "AnyEvent::WebDriver::Actions: multiple pause calls in a row not (yet) supported"
1361     if $duration;
1362    
1363     $self
1364     }
1365    
1366     =item $al = $al->pointer_down ($button, $source)
1367    
1368     =item $al = $al->pointer_up ($button, $source)
1369    
1370 root 1.36 Press or release the given button. C<$button> defaults to C<0>.
1371 root 1.12
1372     =item $al = $al->click ($button, $source)
1373    
1374     Convenience function that creates a button press and release action
1375 root 1.36 without any delay between them. C<$button> defaults to C<0>.
1376 root 1.12
1377     =item $al = $al->doubleclick ($button, $source)
1378    
1379     Convenience function that creates two button press and release action
1380     pairs in a row, with no unnecessary delay between them. C<$button>
1381 root 1.36 defaults to C<0>.
1382 root 1.12
1383     =cut
1384    
1385     sub pointer_down {
1386     my ($self, $button, $source) = @_;
1387    
1388 root 1.36 $self->_add ($source, ptr => pointerDown => button => ($button // 0)*1)
1389 root 1.12 }
1390    
1391     sub pointer_up {
1392     my ($self, $button, $source) = @_;
1393    
1394 root 1.36 $self->_add ($source, ptr => pointerUp => button => ($button // 0)*1)
1395 root 1.12 }
1396    
1397     sub click {
1398     my ($self, $button, $source) = @_;
1399    
1400     $self
1401     ->pointer_down ($button, $source)
1402     ->pointer_up ($button)
1403     }
1404    
1405     sub doubleclick {
1406     my ($self, $button, $source) = @_;
1407    
1408     $self
1409     ->click ($button, $source)
1410     ->click ($button)
1411     }
1412    
1413 root 1.50 =item $al = $al->move ($origin, $x, $y, $duration, $source)
1414 root 1.12
1415     Moves a pointer to the given position, relative to origin (either
1416 root 1.50 "viewport", "pointer" or an element object. The coordinates will be
1417     truncated to integer values.
1418 root 1.12
1419     =cut
1420    
1421     sub move {
1422     my ($self, $origin, $x, $y, $duration, $source) = @_;
1423    
1424     $self->_add ($source, ptr => pointerMove =>
1425 root 1.50 origin => $origin, x => int $x*1, y => int $y*1, duration => $duration*1)
1426 root 1.12 }
1427    
1428 root 1.33 =item $al = $al->cancel ($source)
1429    
1430     Executes a pointer cancel action.
1431    
1432     =cut
1433    
1434     sub cancel {
1435     my ($self, $source) = @_;
1436    
1437     $self->_add ($source, ptr => "pointerCancel")
1438     }
1439    
1440 root 1.52 =item $al = $al->key_down ($key, $source)
1441 root 1.12
1442 root 1.52 =item $al = $al->key_up ($key, $source)
1443 root 1.12
1444     Press or release the given key.
1445    
1446 root 1.13 =item $al = $al->key ($key, $source)
1447    
1448 root 1.52 Peess and release the given key in one go, without unnecessary delay.
1449 root 1.13
1450 root 1.52 A special syntax, C<{keyname}> can be used for special keys -
1451     all the special key names from L<the second table in section
1452     17.4.2|https://www.w3.org/TR/webdriver1/#keyboard-actions> of the
1453     WebDriver recommendation can be used - prefix with C<Shift-Space>. to get
1454     the shifted version, as in C<Shift-
1455 root 1.13
1456     Example: press and release "a".
1457    
1458     $al->key ("a");
1459    
1460     Example: press and release the "Enter" key:
1461    
1462     $al->key ("\x{e007}");
1463    
1464     Example: press and release the "enter" key using the special key name syntax:
1465    
1466     $al->key ("{Enter}");
1467    
1468     =item $al = $al->type ($string, $source)
1469    
1470     Convenience method to simulate a series of key press and release events
1471 root 1.33 for the keys in C<$string>, one pair per extended unicode grapheme
1472     cluster. There is no syntax for special keys, everything will be typed
1473     "as-is" if possible.
1474 root 1.13
1475     =cut
1476    
1477 root 1.53 # copy&paste from the spec via browser, with added MetaLeft/MetaRight aliases
1478 root 1.52 our $SPECIAL_KEY = <<'EOF';
1479     "`" "~" "Backquote"
1480     "\" "|" "Backslash"
1481     "\uE003" "Backspace"
1482     "[" "{" "BracketLeft"
1483     "]" "}" "BracketRight"
1484     "," "<" "Comma"
1485     "0" ")" "Digit0"
1486     "1" "!" "Digit1"
1487     "2" "@" "Digit2"
1488     "3" "#" "Digit3"
1489     "4" "$" "Digit4"
1490     "5" "%" "Digit5"
1491     "6" "^" "Digit6"
1492     "7" "&" "Digit7"
1493     "8" "*" "Digit8"
1494     "9" "(" "Digit9"
1495     "=" "+" "Equal"
1496     "<" ">" "IntlBackslash"
1497     "a" "A" "KeyA"
1498     "b" "B" "KeyB"
1499     "c" "C" "KeyC"
1500     "d" "D" "KeyD"
1501     "e" "E" "KeyE"
1502     "f" "F" "KeyF"
1503     "g" "G" "KeyG"
1504     "h" "H" "KeyH"
1505     "i" "I" "KeyI"
1506     "j" "J" "KeyJ"
1507     "k" "K" "KeyK"
1508     "l" "L" "KeyL"
1509     "m" "M" "KeyM"
1510     "n" "N" "KeyN"
1511     "o" "O" "KeyO"
1512     "p" "P" "KeyP"
1513     "q" "Q" "KeyQ"
1514     "r" "R" "KeyR"
1515     "s" "S" "KeyS"
1516     "t" "T" "KeyT"
1517     "u" "U" "KeyU"
1518     "v" "V" "KeyV"
1519     "w" "W" "KeyW"
1520     "x" "X" "KeyX"
1521     "y" "Y" "KeyY"
1522     "z" "Z" "KeyZ"
1523     "-" "_" "Minus"
1524     "." ">"." "Period"
1525     "'" """ "Quote"
1526     ";" ":" "Semicolon"
1527     "/" "?" "Slash"
1528     "\uE00A" "AltLeft"
1529     "\uE052" "AltRight"
1530     "\uE009" "ControlLeft"
1531     "\uE051" "ControlRight"
1532     "\uE006" "Enter"
1533     "\uE03D" "OSLeft"
1534     "\uE053" "OSRight"
1535     "\uE008" "ShiftLeft"
1536     "\uE050" "ShiftRight"
1537     " " "\uE00D" "Space"
1538     "\uE004" "Tab"
1539     "\uE017" "Delete"
1540     "\uE010" "End"
1541     "\uE002" "Help"
1542     "\uE011" "Home"
1543     "\uE016" "Insert"
1544     "\uE00F" "PageDown"
1545     "\uE00E" "PageUp"
1546     "\uE015" "ArrowDown"
1547     "\uE012" "ArrowLeft"
1548     "\uE014" "ArrowRight"
1549     "\uE013" "ArrowUp"
1550     "\uE00C" "Escape"
1551     "\uE031" "F1"
1552     "\uE032" "F2"
1553     "\uE033" "F3"
1554     "\uE034" "F4"
1555     "\uE035" "F5"
1556     "\uE036" "F6"
1557     "\uE037" "F7"
1558     "\uE038" "F8"
1559     "\uE039" "F9"
1560     "\uE03A" "F10"
1561     "\uE03B" "F11"
1562     "\uE03C" "F12"
1563     "\uE01A" "\uE05C" "Numpad0"
1564     "\uE01B" "\uE056" "Numpad1"
1565     "\uE01C" "\uE05B" "Numpad2"
1566     "\uE01D" "\uE055" "Numpad3"
1567     "\uE01E" "\uE058" "Numpad4"
1568     "\uE01F" "Numpad5"
1569     "\uE020" "\uE05A" "Numpad6"
1570     "\uE021" "\uE057" "Numpad7"
1571     "\uE022" "\uE059" "Numpad8"
1572     "\uE023" "\uE054" "Numpad9"
1573     "\uE025" "NumpadAdd"
1574     "\uE026" "NumpadComma"
1575     "\uE028" "\uE05D" "NumpadDecimal"
1576     "\uE029" "NumpadDivide"
1577     "\uE007" "NumpadEnter"
1578     "\uE024" "NumpadMultiply"
1579     "\uE027" "NumpadSubtract"
1580    
1581     "\uE03D" "MetaLeft"
1582     "\uE053" "MetaRight"
1583     EOF
1584    
1585     our %SPECIAL_KEY;
1586    
1587     sub _special_key($) {
1588     # parse first time
1589     %SPECIAL_KEY || do {
1590     for (split /\n/, $SPECIAL_KEY) {
1591     s/"//g or next;
1592     my ($k, $s, $name) = split /\t/;
1593    
1594     # unescape \uXXXX, convert string to codepoint
1595     $_ = /^\\u/ ? hex substr $_, 2 : ord
1596     for $k, $s;
1597    
1598     $SPECIAL_KEY{$name} = $k;
1599     $SPECIAL_KEY{"Shift-$name"} = $s if $s;
1600    
1601     }
1602    
1603     undef $SPECIAL_KEY; # save memory
1604     };
1605    
1606     exists $SPECIAL_KEY{$_[0]}
1607     ? chr $SPECIAL_KEY{$_[0]}
1608     : Carp::croak "AnyEvent::WebDriver::Actions: special key '$1' not known"
1609     }
1610 root 1.13
1611     sub _kv($) {
1612     $_[0] =~ /^\{(.*)\}$/s
1613 root 1.52 ? _special_key $1
1614 root 1.13 : $_[0]
1615     }
1616 root 1.12
1617     sub key_down {
1618     my ($self, $key, $source) = @_;
1619    
1620 root 1.13 $self->_add ($source, kbd => keyDown => value => _kv $key)
1621 root 1.12 }
1622    
1623     sub key_up {
1624     my ($self, $key, $source) = @_;
1625    
1626 root 1.13 $self->_add ($source, kbd => keyUp => value => _kv $key)
1627 root 1.12 }
1628    
1629     sub key {
1630     my ($self, $key, $source) = @_;
1631    
1632     $self
1633     ->key_down ($key, $source)
1634     ->key_up ($key)
1635     }
1636    
1637 root 1.13 sub type {
1638     my ($self, $string, $source) = @_;
1639    
1640     $self->key ($_, $source)
1641     for $string =~ /(\X)/g;
1642    
1643     $self
1644     }
1645    
1646 root 1.12 =item $al->perform ($wd)
1647    
1648 root 1.23 Finalises and compiles the list, if not done yet, and calls C<<
1649 root 1.12 $wd->perform >> with it.
1650    
1651     If C<$wd> is undef, and the action list was created using the C<<
1652     $wd->actions >> method, then perform it against that WebDriver object.
1653    
1654     There is no underscore variant - call the C<perform_actions_> method with
1655     the action object instead.
1656    
1657     =item $al->perform_release ($wd)
1658    
1659     Exactly like C<perform>, but additionally call C<release_actions>
1660     afterwards.
1661 root 1.1
1662     =cut
1663    
1664 root 1.12 sub perform {
1665     my ($self, $wd) = @_;
1666    
1667     ($wd //= $self->{wd})->perform_actions ($self)
1668     }
1669 root 1.9
1670 root 1.12 sub perform_release {
1671     my ($self, $wd) = @_;
1672    
1673     ($wd //= $self->{wd})->perform_actions ($self);
1674     $wd->release_actions;
1675     }
1676 root 1.1
1677 root 1.12 =item ($actions, $duration) = $al->compile
1678    
1679     Finalises and compiles the list, if not done yet, and returns an actions
1680     object suitable for calls to C<< $wd->perform_actions >>. When called in
1681     list context, additionally returns the total duration of the action list.
1682    
1683     Since building large action lists can take nontrivial amounts of time,
1684     it can make sense to build an action list only once and then perform it
1685     multiple times.
1686    
1687 root 1.45 No additional actions must be added after compiling an action list.
1688 root 1.1
1689     =cut
1690    
1691 root 1.12 sub compile {
1692     my ($self) = @_;
1693    
1694     $self->{duration} += delete $self->{tick_duration};
1695    
1696     delete $self->{tick};
1697     delete $self->{last_kbd};
1698     delete $self->{last_ptr};
1699    
1700     $self->{actions} ||= [values %{ delete $self->{source} }];
1701    
1702     wantarray
1703     ? ($self->{actions}, $self->{duration})
1704     : $self->{actions}
1705 root 1.1 }
1706    
1707     =back
1708    
1709     =head2 EVENT BASED API
1710    
1711     This module wouldn't be a good AnyEvent citizen if it didn't have a true
1712     event-based API.
1713    
1714     In fact, the simplified API, as documented above, is emulated via the
1715     event-based API and an C<AUTOLOAD> function that automatically provides
1716     blocking wrappers around the callback-based API.
1717    
1718     Every method documented in the L<SIMPLIFIED API> section has an equivalent
1719     event-based method that is formed by appending a underscore (C<_>) to the
1720     method name, and appending a callback to the argument list (mnemonic: the
1721     underscore indicates the "the action is not yet finished" after the call
1722     returns).
1723    
1724     For example, instead of a blocking calls to C<new_session>, C<navigate_to>
1725     and C<back>, you can make a callback-based ones:
1726    
1727     my $cv = AE::cv;
1728    
1729 root 1.60 $wd->new_session_ ({}, sub {
1730     my ($status, $value) = @_;
1731 root 1.1
1732     die "error $value->{error}" if $status ne "200";
1733    
1734     $wd->navigate_to_ ("http://www.nethype.de", sub {
1735    
1736     $wd->back_ (sub {
1737     print "all done\n";
1738     $cv->send;
1739     });
1740    
1741     });
1742     });
1743    
1744     $cv->recv;
1745    
1746     While the blocking methods C<croak> on errors, the callback-based ones all
1747     pass two values to the callback, C<$status> and C<$res>, where C<$status>
1748 root 1.9 is the HTTP status code (200 for successful requests, typically 4xx or
1749 root 1.1 5xx for errors), and C<$res> is the value of the C<value> key in the JSON
1750     response object.
1751    
1752     Other than that, the underscore variants and the blocking variants are
1753     identical.
1754    
1755     =head2 LOW LEVEL API
1756    
1757 root 1.9 All the simplified API methods are very thin wrappers around WebDriver
1758     commands of the same name. They are all implemented in terms of the
1759 root 1.27 low-level methods (C<req>, C<get>, C<post> and C<delete>), which exist
1760 root 1.1 in blocking and callback-based variants (C<req_>, C<get_>, C<post_> and
1761     C<delete_>).
1762    
1763     Examples are after the function descriptions.
1764    
1765     =over
1766    
1767     =item $wd->req_ ($method, $uri, $body, $cb->($status, $value))
1768    
1769     =item $value = $wd->req ($method, $uri, $body)
1770    
1771     Appends the C<$uri> to the C<endpoint/session/{sessionid}/> URL and makes
1772     a HTTP C<$method> request (C<GET>, C<POST> etc.). C<POST> requests can
1773     provide a UTF-8-encoded JSON text as HTTP request body, or the empty
1774     string to indicate no body is used.
1775    
1776     For the callback version, the callback gets passed the HTTP status code
1777     (200 for every successful request), and the value of the C<value> key in
1778     the JSON response object as second argument.
1779    
1780     =item $wd->get_ ($uri, $cb->($status, $value))
1781    
1782     =item $value = $wd->get ($uri)
1783    
1784     Simply a call to C<req_> with C<$method> set to C<GET> and an empty body.
1785    
1786     =item $wd->post_ ($uri, $data, $cb->($status, $value))
1787    
1788     =item $value = $wd->post ($uri, $data)
1789    
1790     Simply a call to C<req_> with C<$method> set to C<POST> - if C<$body> is
1791     C<undef>, then an empty object is send, otherwise, C<$data> must be a
1792     valid request object, which gets encoded into JSON for you.
1793    
1794     =item $wd->delete_ ($uri, $cb->($status, $value))
1795    
1796     =item $value = $wd->delete ($uri)
1797    
1798     Simply a call to C<req_> with C<$method> set to C<DELETE> and an empty body.
1799    
1800     =cut
1801    
1802     =back
1803    
1804     Example: implement C<get_all_cookies>, which is a simple C<GET> request
1805     without any parameters:
1806    
1807     $cookies = $wd->get ("cookie");
1808    
1809     Example: implement C<execute_script>, which needs some parameters:
1810    
1811     $results = $wd->post ("execute/sync" => { script => "$javascript", args => [] });
1812    
1813 root 1.12 Example: call C<find_elements> to find all C<IMG> elements:
1814 root 1.1
1815 root 1.12 $elems = $wd->post (elements => { using => "css selector", value => "img" });
1816 root 1.1
1817     =cut
1818    
1819     =head1 HISTORY
1820    
1821 root 1.2 This module was unintentionally created (it started inside some quickly
1822     hacked-together script) simply because I couldn't get the existing
1823 root 1.41 C<Selenium::Remote::Driver> module to work reliably, ever, despite
1824     multiple attempts over the years and trying to report multiple bugs, which
1825     have been completely ignored. It's also not event-based, so, yeah...
1826 root 1.1
1827     =head1 AUTHOR
1828    
1829     Marc Lehmann <schmorp@schmorp.de>
1830     http://anyevent.schmorp.de
1831    
1832     =cut
1833    
1834     1
1835