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