ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-WebDriver/WebDriver.pm
Revision: 1.61
Committed: Wed Jul 3 07:39:25 2024 UTC (2 years, 3 months ago) by root
Branch: MAIN
CVS Tags: HEAD
Changes since 1.60: +3 -1 lines
Log Message:
*** empty log message ***

File Contents

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