ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-WebDriver/WebDriver.pm
Revision: 1.39
Committed: Tue Sep 4 01:48:09 2018 UTC (5 years, 9 months ago) by root
Branch: MAIN
Changes since 1.38: +2 -1 lines
Log Message:
*** empty log message ***

File Contents

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