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