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