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