ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-WebDriver/WebDriver.pm
Revision: 1.32
Committed: Mon Sep 3 20:35:30 2018 UTC (5 years, 9 months ago) by root
Branch: MAIN
Changes since 1.31: +1 -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     ->pause # to separate the click from the keypress
33     ->type ("some text")
34     ->key ("{Enter}")
35     ->perform;
36    
37 root 1.1 =head1 DESCRIPTION
38    
39 root 1.31 WARNING: BEFORE VERSION 1.0, API CHANGES ARE LIKELY.
40 root 1.14
41 root 1.1 This module aims to implement the W3C WebDriver specification which is the
42     standardised equivalent to the Selenium WebDriver API., which in turn aims
43     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.26 our $VERSION = 0.9;
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.23 Sets the window rect(angle).
576 root 1.1
577     =cut
578    
579     sub get_window_rect_ {
580     $_[0]->get_ ("window/rect" => $_[1]);
581     }
582    
583     sub set_window_rect_ {
584     $_[0]->post_ ("window/rect" => $_[1], $_[2]);
585     }
586    
587     =item $wd->maximize_window
588    
589     =item $wd->minimize_window
590    
591     =item $wd->fullscreen_window
592    
593 root 1.3 Changes the window size by either maximising, minimising or making it
594 root 1.9 fullscreen. In my experience, this will timeout if no window manager is
595 root 1.1 running.
596    
597     =cut
598    
599     sub maximize_window_ {
600     $_[0]->post_ ("window/maximize" => undef, $_[1]);
601     }
602    
603     sub minimize_window_ {
604     $_[0]->post_ ("window/minimize" => undef, $_[1]);
605     }
606    
607     sub fullscreen_window_ {
608     $_[0]->post_ ("window/fullscreen" => undef, $_[1]);
609     }
610    
611     =back
612    
613     =head3 ELEMENT RETRIEVAL
614    
615 root 1.13 To reduce typing and memory strain, the element finding functions accept
616     some shorter and hopefully easier to remember aliases for the standard
617     locator strategy values, as follows:
618    
619     Alias Locator Strategy
620     css css selector
621     link link text
622     substr partial link text
623     tag tag name
624    
625 root 1.1 =over
626    
627     =cut
628    
629 root 1.13 our %USING = (
630     css => "css selector",
631     link => "link text",
632     substr => "partial link text",
633     tag => "tag name",
634     );
635    
636     sub _using($) {
637     using => $USING{$_[0]} // "$_[0]"
638     }
639    
640     =item $element = $wd->find_element ($locator_strategy, $selector)
641 root 1.1
642     Finds the first element specified by the given selector and returns its
643 root 1.12 element object. Raises an error when no element was found.
644 root 1.1
645 root 1.18 Examples showing all standard locator strategies:
646    
647 root 1.1 $element = $wd->find_element ("css selector" => "body a");
648     $element = $wd->find_element ("link text" => "Click Here For Porn");
649     $element = $wd->find_element ("partial link text" => "orn");
650     $element = $wd->find_element ("tag name" => "input");
651     $element = $wd->find_element ("xpath" => '//input[@type="text"]');
652 root 1.12 => e.g. { "element-6066-11e4-a52e-4f735466cecf" => "decddca8-5986-4e1d-8c93-efe952505a5f" }
653 root 1.1
654 root 1.18 Same examples using aliases provided by this module:
655    
656     $element = $wd->find_element (css => "body a");
657     $element = $wd->find_element (link => "Click Here For Porn");
658     $element = $wd->find_element (substr => "orn");
659     $element = $wd->find_element (tag => "input");
660    
661 root 1.13 =item $elements = $wd->find_elements ($locator_strategy, $selector)
662 root 1.1
663 root 1.12 As above, but returns an arrayref of all found element objects.
664 root 1.1
665 root 1.13 =item $element = $wd->find_element_from_element ($element, $locator_strategy, $selector)
666 root 1.1
667     Like C<find_element>, but looks only inside the specified C<$element>.
668    
669 root 1.13 =item $elements = $wd->find_elements_from_element ($element, $locator_strategy, $selector)
670 root 1.1
671     Like C<find_elements>, but looks only inside the specified C<$element>.
672    
673     my $head = $wd->find_element ("tag name" => "head");
674     my $links = $wd->find_elements_from_element ($head, "tag name", "link");
675    
676 root 1.12 =item $element = $wd->get_active_element
677 root 1.1
678     Returns the active element.
679    
680     =cut
681    
682     sub find_element_ {
683 root 1.13 $_[0]->post_ (element => { _using $_[1], value => "$_[2]" }, $_[3]);
684 root 1.1 }
685    
686     sub find_elements_ {
687 root 1.13 $_[0]->post_ (elements => { _using $_[1], value => "$_[2]" }, $_[3]);
688 root 1.1 }
689    
690     sub find_element_from_element_ {
691 root 1.13 $_[0]->post_ ("element/$_[1]/element" => { _using $_[2], value => "$_[3]" }, $_[4]);
692 root 1.1 }
693    
694     sub find_elements_from_element_ {
695 root 1.13 $_[0]->post_ ("element/$_[1]/elements" => { _using $_[2], value => "$_[3]" }, $_[4]);
696 root 1.1 }
697    
698     sub get_active_element_ {
699 root 1.12 $_[0]->get_ ("element/active" => $_[1]);
700 root 1.1 }
701    
702     =back
703    
704     =head3 ELEMENT STATE
705    
706     =over
707    
708     =cut
709    
710     =item $bool = $wd->is_element_selected
711    
712     Returns whether the given input or option element is selected or not.
713    
714 root 1.12 =item $string = $wd->get_element_attribute ($element, $name)
715 root 1.1
716     Returns the value of the given attribute.
717    
718 root 1.12 =item $string = $wd->get_element_property ($element, $name)
719 root 1.1
720     Returns the value of the given property.
721    
722 root 1.12 =item $string = $wd->get_element_css_value ($element, $name)
723 root 1.1
724 root 1.9 Returns the value of the given CSS value.
725 root 1.1
726 root 1.12 =item $string = $wd->get_element_text ($element)
727 root 1.1
728     Returns the (rendered) text content of the given element.
729    
730 root 1.12 =item $string = $wd->get_element_tag_name ($element)
731 root 1.1
732     Returns the tag of the given element.
733    
734 root 1.12 =item $rect = $wd->get_element_rect ($element)
735 root 1.1
736 root 1.9 Returns the element rect(angle) of the given element.
737 root 1.1
738     =item $bool = $wd->is_element_enabled
739    
740     Returns whether the element is enabled or not.
741    
742     =cut
743    
744     sub is_element_selected_ {
745 root 1.12 $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/selected" => $_[2]);
746 root 1.1 }
747    
748     sub get_element_attribute_ {
749 root 1.12 $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/attribute/$_[2]" => $_[3]);
750 root 1.1 }
751    
752     sub get_element_property_ {
753 root 1.12 $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/property/$_[2]" => $_[3]);
754 root 1.1 }
755    
756     sub get_element_css_value_ {
757 root 1.12 $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/css/$_[2]" => $_[3]);
758 root 1.1 }
759    
760     sub get_element_text_ {
761 root 1.12 $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/text" => $_[2]);
762 root 1.1 }
763    
764     sub get_element_tag_name_ {
765 root 1.12 $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/name" => $_[2]);
766 root 1.1 }
767    
768     sub get_element_rect_ {
769 root 1.12 $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/rect" => $_[2]);
770 root 1.1 }
771    
772     sub is_element_enabled_ {
773 root 1.12 $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/enabled" => $_[2]);
774 root 1.1 }
775    
776     =back
777    
778     =head3 ELEMENT INTERACTION
779    
780     =over
781    
782     =cut
783    
784 root 1.12 =item $wd->element_click ($element)
785 root 1.1
786     Clicks the given element.
787    
788 root 1.12 =item $wd->element_clear ($element)
789 root 1.1
790     Clear the contents of the given element.
791    
792 root 1.12 =item $wd->element_send_keys ($element, $text)
793 root 1.1
794     Sends the given text as key events to the given element.
795    
796     =cut
797    
798     sub element_click_ {
799 root 1.12 $_[0]->post_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/click" => undef, $_[2]);
800 root 1.1 }
801    
802     sub element_clear_ {
803 root 1.12 $_[0]->post_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/clear" => undef, $_[2]);
804 root 1.1 }
805    
806     sub element_send_keys_ {
807 root 1.12 $_[0]->post_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/value" => { text => "$_[2]" }, $_[3]);
808 root 1.1 }
809    
810     =back
811    
812     =head3 DOCUMENT HANDLING
813    
814     =over
815    
816     =cut
817    
818     =item $source = $wd->get_page_source
819    
820     Returns the (HTML/XML) page source of the current document.
821    
822     =item $results = $wd->execute_script ($javascript, $args)
823    
824     Synchronously execute the given script with given arguments and return its
825     results (C<$args> can be C<undef> if no arguments are wanted/needed).
826    
827     $ten = $wd->execute_script ("return arguments[0]+arguments[1]", [3, 7]);
828    
829     =item $results = $wd->execute_async_script ($javascript, $args)
830    
831     Similar to C<execute_script>, but doesn't wait for script to return, but
832     instead waits for the script to call its last argument, which is added to
833     C<$args> automatically.
834    
835     $twenty = $wd->execute_async_script ("arguments[0](20)", undef);
836    
837     =cut
838    
839     sub get_page_source_ {
840     $_[0]->get_ (source => $_[1]);
841     }
842    
843     sub execute_script_ {
844     $_[0]->post_ ("execute/sync" => { script => "$_[1]", args => $_[2] || [] }, $_[3]);
845     }
846    
847     sub execute_async_script_ {
848     $_[0]->post_ ("execute/async" => { script => "$_[1]", args => $_[2] || [] }, $_[3]);
849     }
850    
851     =back
852    
853     =head3 COOKIES
854    
855     =over
856    
857     =cut
858    
859     =item $cookies = $wd->get_all_cookies
860    
861     Returns all cookies, as an arrayref of hashrefs.
862    
863     # google surely sets a lot of cookies without my consent
864     $wd->navigate_to ("http://google.com");
865     use Data::Dump;
866     ddx $wd->get_all_cookies;
867    
868     =item $cookie = $wd->get_named_cookie ($name)
869    
870     Returns a single cookie as a hashref.
871    
872     =item $wd->add_cookie ($cookie)
873    
874     Adds the given cookie hashref.
875    
876     =item $wd->delete_cookie ($name)
877    
878     Delete the named cookie.
879    
880     =item $wd->delete_all_cookies
881    
882     Delete all cookies.
883    
884     =cut
885    
886     sub get_all_cookies_ {
887     $_[0]->get_ (cookie => $_[1]);
888     }
889    
890     sub get_named_cookie_ {
891     $_[0]->get_ ("cookie/$_[1]" => $_[2]);
892     }
893    
894     sub add_cookie_ {
895     $_[0]->post_ (cookie => { cookie => $_[1] }, $_[2]);
896     }
897    
898     sub delete_cookie_ {
899     $_[0]->delete_ ("cookie/$_[1]" => $_[2]);
900     }
901    
902     sub delete_all_cookies_ {
903     $_[0]->delete_ (cookie => $_[2]);
904     }
905    
906     =back
907    
908     =head3 ACTIONS
909    
910     =over
911    
912     =cut
913    
914     =item $wd->perform_actions ($actions)
915    
916     Perform the given actions (an arrayref of action specifications simulating
917 root 1.12 user activity, or an C<AnyEvent::WebDriver::Actions> object). For further
918     details, read the spec or the section L<ACTION LISTS>, below.
919 root 1.1
920 root 1.12 An example to get you started (see the next example for a mostly
921     equivalent example using the C<AnyEvent::WebDriver::Actions> helper API):
922 root 1.1
923     $wd->navigate_to ("https://duckduckgo.com/html");
924     my $input = $wd->find_element ("css selector", 'input[type="text"]');
925     $wd->perform_actions ([
926     {
927     id => "myfatfinger",
928     type => "pointer",
929     pointerType => "touch",
930     actions => [
931 root 1.12 { type => "pointerMove", duration => 100, origin => $input, x => 40, y => 5 },
932 root 1.1 { type => "pointerDown", button => 1 },
933     { type => "pause", duration => 40 },
934     { type => "pointerUp", button => 1 },
935     ],
936     },
937     {
938     id => "mykeyboard",
939     type => "key",
940     actions => [
941     { type => "pause" },
942     { type => "pause" },
943     { type => "pause" },
944     { type => "pause" },
945     { type => "keyDown", value => "a" },
946     { type => "pause", duration => 100 },
947     { type => "keyUp", value => "a" },
948     { type => "pause", duration => 100 },
949     { type => "keyDown", value => "b" },
950     { type => "pause", duration => 100 },
951     { type => "keyUp", value => "b" },
952     { type => "pause", duration => 2000 },
953     { type => "keyDown", value => "\x{E007}" }, # enter
954     { type => "pause", duration => 100 },
955     { type => "keyUp", value => "\x{E007}" }, # enter
956     { type => "pause", duration => 5000 },
957     ],
958     },
959     ]);
960    
961 root 1.12 And here is essentially the same (except for fewer pauses) example as
962     above, using the much simpler C<AnyEvent::WebDriver::Actions> API. Note
963     that the pointer up and key down event happen concurrently in this
964     example:
965    
966     $wd->navigate_to ("https://duckduckgo.com/html");
967     my $input = $wd->find_element ("css selector", 'input[type="text"]');
968     $wd->actions
969     ->move ($input, 40, 5, "touch1")
970 root 1.20 ->click
971 root 1.29 ->pause # to separate the click from the keypress
972 root 1.20 ->key ("a")
973     ->key ("b")
974 root 1.29 ->pause (2000) # so you can watch leisurely
975     ->key ("{Enter}")
976     ->pause (5000) # so you can see the result
977 root 1.12 ->perform;
978    
979 root 1.1 =item $wd->release_actions
980    
981     Release all keys and pointer buttons currently depressed.
982    
983     =cut
984    
985     sub perform_actions_ {
986 root 1.12 if (UNIVERSAL::isa $_[1], AnyEvent::WebDriver::Actions::) {
987     my ($actions, $duration) = $_[1]->compile;
988     local $_[0]{timeout} = $_[0]{timeout} + $duration * 1e-3;
989     $_[0]->post_ (actions => { actions => $actions }, $_[2]);
990     } else {
991     $_[0]->post_ (actions => { actions => $_[1] }, $_[2]);
992     }
993 root 1.1 }
994    
995     sub release_actions_ {
996     $_[0]->delete_ (actions => $_[1]);
997     }
998    
999     =back
1000    
1001     =head3 USER PROMPTS
1002    
1003     =over
1004    
1005     =cut
1006    
1007     =item $wd->dismiss_alert
1008    
1009     Dismiss a simple dialog, if present.
1010    
1011     =item $wd->accept_alert
1012    
1013     Accept a simple dialog, if present.
1014    
1015     =item $text = $wd->get_alert_text
1016    
1017     Returns the text of any simple dialog.
1018    
1019     =item $text = $wd->send_alert_text
1020    
1021     Fills in the user prompt with the given text.
1022    
1023    
1024     =cut
1025    
1026     sub dismiss_alert_ {
1027     $_[0]->post_ ("alert/dismiss" => undef, $_[1]);
1028     }
1029    
1030     sub accept_alert_ {
1031     $_[0]->post_ ("alert/accept" => undef, $_[1]);
1032     }
1033    
1034     sub get_alert_text_ {
1035     $_[0]->get_ ("alert/text" => $_[1]);
1036     }
1037    
1038     sub send_alert_text_ {
1039     $_[0]->post_ ("alert/text" => { text => "$_[1]" }, $_[2]);
1040     }
1041    
1042     =back
1043    
1044     =head3 SCREEN CAPTURE
1045    
1046     =over
1047    
1048     =cut
1049    
1050     =item $wd->take_screenshot
1051    
1052 root 1.9 Create a screenshot, returning it as a PNG image in a C<data:> URL.
1053 root 1.1
1054 root 1.12 =item $wd->take_element_screenshot ($element)
1055 root 1.1
1056     Accept a simple dialog, if present.
1057    
1058     =cut
1059    
1060     sub take_screenshot_ {
1061     $_[0]->get_ (screenshot => $_[1]);
1062     }
1063    
1064     sub take_element_screenshot_ {
1065 root 1.12 $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/screenshot" => $_[2]);
1066 root 1.1 }
1067    
1068     =back
1069    
1070 root 1.12 =head2 ACTION LISTS
1071    
1072     Action lists can be quite complicated. Or at least it took a while for
1073     me to twist my head around them. Basically, an action list consists of a
1074     number of sources representing devices (such as a finger, a mouse, a pen
1075     or a keyboard) and a list of actions for each source.
1076    
1077     An action can be a key press, a pointer move or a pause (time
1078     delay). Actions from different sources can happen "at the same time",
1079     while actions from a single source are executed in order.
1080    
1081     While you can provide an action list manually, it is (hopefully) less
1082     cumbersome to use the API described in this section to create them.
1083    
1084     The basic process of creating and performing actions is to create a new
1085     action list, adding action sources, followed by adding actions. Finally
1086     you would C<perform> those actions on the WebDriver.
1087    
1088     Virtual time progresses as long as you add actions to the same event
1089     source. Adding events to different sources are considered to happen
1090     concurrently. If you want to force time to progress, you can do this using
1091     a call to C<< ->pause (0) >>.
1092    
1093     Most methods here are designed to chain, i.e. they return the web actions
1094     object, to simplify multiple calls.
1095    
1096     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     ->click (1, 100)
1101 root 1.29 ->pause # to separate the click from keypressed
1102 root 1.12 ->type ("some text")
1103 root 1.13 ->key ("{Enter}")
1104 root 1.12 ->perform;
1105    
1106     By default, keyboard and mouse input sources are provided. You can create
1107     your own sources and use them when adding events. The above example could
1108     be more verbosely written like this:
1109    
1110     $wd->actions
1111 root 1.26 ->source ("mouse", "pointer", pointerType => "mouse")
1112     ->source ("kbd", "key")
1113 root 1.12 ->click (1, 100, "mouse")
1114 root 1.29 ->pause
1115 root 1.26 ->type ("some text", "kbd")
1116     ->key ("{Enter}", "kbd")
1117 root 1.12 ->perform;
1118    
1119 root 1.21 When you specify the event source explicitly it will switch the current
1120 root 1.12 "focus" for this class of device (all keyboards are in one class, all
1121     pointer-like devices such as mice/fingers/pens are in one class), so you
1122     don't have to specify the source for subsequent actions.
1123    
1124     When you use the sources C<keyboard>, C<mouse>, C<touch1>..C<touch3>,
1125     C<pen> without defining them, then a suitable default source will be
1126     created for them.
1127    
1128     =over 4
1129    
1130     =cut
1131    
1132     package AnyEvent::WebDriver::Actions;
1133    
1134     =item $al = new AnyEvent::WebDriver::Actions
1135    
1136     Create a new empty action list object. More often you would use the C<<
1137 root 1.13 $wd->action_list >> method to create one that is already associated with
1138 root 1.12 a given web driver.
1139    
1140     =cut
1141    
1142     sub new {
1143     my ($class, %kv) = @_;
1144    
1145     $kv{last_kbd} = "keyboard";
1146     $kv{last_ptr} = "mouse";
1147    
1148     bless \%kv, $class
1149     }
1150    
1151     =item $al = $al->source ($id, $type, key => value...)
1152    
1153 root 1.21 The first time you call this with a given ID, this defines the event
1154 root 1.12 source using the extra parameters. Subsequent calls merely switch the
1155     current source for its event class.
1156    
1157     It's not an error to define built-in sources (such as C<keyboard> or
1158     C<touch1>) differently then the defaults.
1159    
1160     Example: define a new touch device called C<fatfinger>.
1161    
1162     $al->source (fatfinger => "pointer", pointerType => "touch");
1163    
1164 root 1.21 Example: define a new touch device called C<fatfinger>.
1165 root 1.12
1166     $al->source (fatfinger => "pointer", pointerType => "touch");
1167    
1168 root 1.21 Example: switch default keyboard source to C<kbd1>, assuming it is of C<key> class.
1169    
1170     $al->source ("kbd1");
1171    
1172 root 1.12 =cut
1173    
1174 root 1.13 sub _default_source($) {
1175 root 1.12 my ($source) = @_;
1176    
1177     $source eq "keyboard" ? { actions => [], id => $source, type => "key" }
1178     : $source eq "mouse" ? { actions => [], id => $source, type => "pointer", pointerType => "mouse" }
1179     : $source eq "touch" ? { actions => [], id => $source, type => "pointer", pointerType => "touch" }
1180     : $source eq "pen" ? { actions => [], id => $source, type => "pointer", pointerType => "pen" }
1181     : Carp::croak "AnyEvent::WebDriver::Actions: event source '$source' not defined"
1182     }
1183    
1184     my %source_class = (
1185     key => "kbd",
1186     pointer => "ptr",
1187     );
1188    
1189     sub source {
1190     my ($self, $id, $type, %kv) = @_;
1191    
1192     if (defined $type) {
1193     !exists $self->{source}{$id}
1194     or Carp::croak "AnyEvent::WebDriver::Actions: source '$id' already defined";
1195    
1196     $kv{id} = $id;
1197     $kv{type} = $type;
1198     $kv{actions} = [];
1199    
1200     $self->{source}{$id} = \%kv;
1201     }
1202    
1203     my $source = $self->{source}{$id} ||= _default_source $id;
1204    
1205     my $last = $source_class{$source->{type}} // "xxx";
1206    
1207     $self->{"last_$last"} = $id;
1208    
1209     $self
1210     }
1211    
1212     sub _add {
1213     my ($self, $source, $sourcetype, $type, %kv) = @_;
1214    
1215     my $last = \$self->{"last_$sourcetype"};
1216    
1217     $source
1218     ? ($$last = $source)
1219     : ($source = $$last);
1220    
1221     my $source = $self->{source}{$source} ||= _default_source $source;
1222    
1223     my $al = $source->{actions};
1224    
1225     push @$al, { type => "pause" }
1226     while @$al < $self->{tick} - 1;
1227    
1228     $kv{type} = $type;
1229    
1230     push @{ $source->{actions} }, \%kv;
1231    
1232     $self->{tick_duration} = $kv{duration}
1233     if $kv{duration} > $self->{tick_duration};
1234    
1235     if ($self->{tick} != @$al) {
1236     $self->{tick} = @$al;
1237     $self->{duration} += delete $self->{tick_duration};
1238     }
1239    
1240     $self
1241     }
1242 root 1.1
1243 root 1.12 =item $al = $al->pause ($duration)
1244    
1245     Creates a pause with the given duration. Makes sure that time progresses
1246     in any case, even when C<$duration> is C<0>.
1247    
1248     =cut
1249    
1250     sub pause {
1251     my ($self, $duration) = @_;
1252    
1253     $self->{tick_duration} = $duration
1254     if $duration > $self->{tick_duration};
1255    
1256     $self->{duration} += delete $self->{tick_duration};
1257    
1258     # find the source with the longest list
1259    
1260     for my $source (values %{ $self->{source} }) {
1261     if (@{ $source->{actions} } == $self->{tick}) {
1262     # this source is one of the longest
1263    
1264     # create a pause event only if $duration is non-zero...
1265     push @{ $source->{actions} }, { type => "pause", duration => $duration*1 }
1266     if $duration;
1267    
1268     # ... but advance time in any case
1269     ++$self->{tick};
1270    
1271     return $self;
1272     }
1273     }
1274    
1275     # no event sources are longest. so advance time in any case
1276     ++$self->{tick};
1277    
1278     Carp::croak "AnyEvent::WebDriver::Actions: multiple pause calls in a row not (yet) supported"
1279     if $duration;
1280    
1281     $self
1282     }
1283    
1284     =item $al = $al->pointer_down ($button, $source)
1285    
1286     =item $al = $al->pointer_up ($button, $source)
1287    
1288     Press or release the given button. C<$button> defaults to C<1>.
1289    
1290     =item $al = $al->click ($button, $source)
1291    
1292     Convenience function that creates a button press and release action
1293     without any delay between them. C<$button> defaults to C<1>.
1294    
1295     =item $al = $al->doubleclick ($button, $source)
1296    
1297     Convenience function that creates two button press and release action
1298     pairs in a row, with no unnecessary delay between them. C<$button>
1299     defaults to C<1>.
1300    
1301     =cut
1302    
1303     sub pointer_down {
1304     my ($self, $button, $source) = @_;
1305    
1306     $self->_add ($source, ptr => pointerDown => button => ($button // 1)*1)
1307     }
1308    
1309     sub pointer_up {
1310     my ($self, $button, $source) = @_;
1311    
1312     $self->_add ($source, ptr => pointerUp => button => ($button // 1)*1)
1313     }
1314    
1315     sub click {
1316     my ($self, $button, $source) = @_;
1317    
1318     $self
1319     ->pointer_down ($button, $source)
1320     ->pointer_up ($button)
1321     }
1322    
1323     sub doubleclick {
1324     my ($self, $button, $source) = @_;
1325    
1326     $self
1327     ->click ($button, $source)
1328     ->click ($button)
1329     }
1330    
1331     =item $al = $al->move ($button, $origin, $x, $y, $duration, $source)
1332    
1333     Moves a pointer to the given position, relative to origin (either
1334     "viewport", "pointer" or an element object.
1335    
1336     =cut
1337    
1338     sub move {
1339     my ($self, $origin, $x, $y, $duration, $source) = @_;
1340    
1341     $self->_add ($source, ptr => pointerMove =>
1342     origin => $origin, x => $x*1, y => $y*1, duration => $duration*1)
1343     }
1344    
1345     =item $al = $al->keyDown ($key, $source)
1346    
1347     =item $al = $al->keyUp ($key, $source)
1348    
1349     Press or release the given key.
1350    
1351 root 1.13 =item $al = $al->key ($key, $source)
1352    
1353     Peess and release the given key, without unnecessary delay.
1354    
1355     A special syntax, C<{keyname}> can be used for special keys - all the special key names from
1356     L<section 17.4.2|https://www.w3.org/TR/webdriver1/#keyboard-actions> of the WebDriver recommendation
1357     can be used.
1358    
1359     Example: press and release "a".
1360    
1361     $al->key ("a");
1362    
1363     Example: press and release the "Enter" key:
1364    
1365     $al->key ("\x{e007}");
1366    
1367     Example: press and release the "enter" key using the special key name syntax:
1368    
1369     $al->key ("{Enter}");
1370    
1371     =item $al = $al->type ($string, $source)
1372    
1373     Convenience method to simulate a series of key press and release events
1374     for the keys in C<$string>. There is no syntax for special keys,
1375     everything will be typed "as-is" if possible.
1376    
1377     =cut
1378    
1379     our %SPECIAL_KEY = (
1380     "Unidentified" => 0xE000,
1381     "Cancel" => 0xE001,
1382     "Help" => 0xE002,
1383     "Backspace" => 0xE003,
1384     "Tab" => 0xE004,
1385     "Clear" => 0xE005,
1386     "Return" => 0xE006,
1387     "Enter" => 0xE007,
1388     "Shift" => 0xE008,
1389     "Control" => 0xE009,
1390     "Alt" => 0xE00A,
1391     "Pause" => 0xE00B,
1392     "Escape" => 0xE00C,
1393     " " => 0xE00D,
1394     "PageUp" => 0xE00E,
1395     "PageDown" => 0xE00F,
1396     "End" => 0xE010,
1397     "Home" => 0xE011,
1398     "ArrowLeft" => 0xE012,
1399     "ArrowUp" => 0xE013,
1400     "ArrowRight" => 0xE014,
1401     "ArrowDown" => 0xE015,
1402     "Insert" => 0xE016,
1403     "Delete" => 0xE017,
1404     ";" => 0xE018,
1405     "=" => 0xE019,
1406     "0" => 0xE01A,
1407     "1" => 0xE01B,
1408     "2" => 0xE01C,
1409     "3" => 0xE01D,
1410     "4" => 0xE01E,
1411     "5" => 0xE01F,
1412     "6" => 0xE020,
1413     "7" => 0xE021,
1414     "8" => 0xE022,
1415     "9" => 0xE023,
1416     "*" => 0xE024,
1417     "+" => 0xE025,
1418     "," => 0xE026,
1419     "-" => 0xE027,
1420     "." => 0xE028,
1421     "/" => 0xE029,
1422     "F1" => 0xE031,
1423     "F2" => 0xE032,
1424     "F3" => 0xE033,
1425     "F4" => 0xE034,
1426     "F5" => 0xE035,
1427     "F6" => 0xE036,
1428     "F7" => 0xE037,
1429     "F8" => 0xE038,
1430     "F9" => 0xE039,
1431     "F10" => 0xE03A,
1432     "F11" => 0xE03B,
1433     "F12" => 0xE03C,
1434     "Meta" => 0xE03D,
1435     "ZenkakuHankaku" => 0xE040,
1436     "Shift" => 0xE050,
1437     "Control" => 0xE051,
1438     "Alt" => 0xE052,
1439     "Meta" => 0xE053,
1440     "PageUp" => 0xE054,
1441     "PageDown" => 0xE055,
1442     "End" => 0xE056,
1443     "Home" => 0xE057,
1444     "ArrowLeft" => 0xE058,
1445     "ArrowUp" => 0xE059,
1446     "ArrowRight" => 0xE05A,
1447     "ArrowDown" => 0xE05B,
1448     "Insert" => 0xE05C,
1449     "Delete" => 0xE05D,
1450     );
1451    
1452     sub _kv($) {
1453     $_[0] =~ /^\{(.*)\}$/s
1454     ? (exists $SPECIAL_KEY{$1}
1455     ? chr $SPECIAL_KEY{$1}
1456     : Carp::croak "AnyEvent::WebDriver::Actions: special key '$1' not known")
1457     : $_[0]
1458     }
1459 root 1.12
1460     sub key_down {
1461     my ($self, $key, $source) = @_;
1462    
1463 root 1.13 $self->_add ($source, kbd => keyDown => value => _kv $key)
1464 root 1.12 }
1465    
1466     sub key_up {
1467     my ($self, $key, $source) = @_;
1468    
1469 root 1.13 $self->_add ($source, kbd => keyUp => value => _kv $key)
1470 root 1.12 }
1471    
1472     sub key {
1473     my ($self, $key, $source) = @_;
1474    
1475     $self
1476     ->key_down ($key, $source)
1477     ->key_up ($key)
1478     }
1479    
1480 root 1.13 sub type {
1481     my ($self, $string, $source) = @_;
1482    
1483     $self->key ($_, $source)
1484     for $string =~ /(\X)/g;
1485    
1486     $self
1487     }
1488    
1489 root 1.12 =item $al->perform ($wd)
1490    
1491 root 1.23 Finalises and compiles the list, if not done yet, and calls C<<
1492 root 1.12 $wd->perform >> with it.
1493    
1494     If C<$wd> is undef, and the action list was created using the C<<
1495     $wd->actions >> method, then perform it against that WebDriver object.
1496    
1497     There is no underscore variant - call the C<perform_actions_> method with
1498     the action object instead.
1499    
1500     =item $al->perform_release ($wd)
1501    
1502     Exactly like C<perform>, but additionally call C<release_actions>
1503     afterwards.
1504 root 1.1
1505     =cut
1506    
1507 root 1.12 sub perform {
1508     my ($self, $wd) = @_;
1509    
1510     ($wd //= $self->{wd})->perform_actions ($self)
1511     }
1512 root 1.9
1513 root 1.12 sub perform_release {
1514     my ($self, $wd) = @_;
1515    
1516     ($wd //= $self->{wd})->perform_actions ($self);
1517     $wd->release_actions;
1518     }
1519 root 1.1
1520 root 1.12 =item ($actions, $duration) = $al->compile
1521    
1522     Finalises and compiles the list, if not done yet, and returns an actions
1523     object suitable for calls to C<< $wd->perform_actions >>. When called in
1524     list context, additionally returns the total duration of the action list.
1525    
1526     Since building large action lists can take nontrivial amounts of time,
1527     it can make sense to build an action list only once and then perform it
1528     multiple times.
1529    
1530     Actions must not be added after compiling a list.
1531 root 1.1
1532     =cut
1533    
1534 root 1.12 sub compile {
1535     my ($self) = @_;
1536    
1537     $self->{duration} += delete $self->{tick_duration};
1538    
1539     delete $self->{tick};
1540     delete $self->{last_kbd};
1541     delete $self->{last_ptr};
1542    
1543     $self->{actions} ||= [values %{ delete $self->{source} }];
1544    
1545     wantarray
1546     ? ($self->{actions}, $self->{duration})
1547     : $self->{actions}
1548 root 1.1 }
1549    
1550     =back
1551    
1552     =head2 EVENT BASED API
1553    
1554     This module wouldn't be a good AnyEvent citizen if it didn't have a true
1555     event-based API.
1556    
1557     In fact, the simplified API, as documented above, is emulated via the
1558     event-based API and an C<AUTOLOAD> function that automatically provides
1559     blocking wrappers around the callback-based API.
1560    
1561     Every method documented in the L<SIMPLIFIED API> section has an equivalent
1562     event-based method that is formed by appending a underscore (C<_>) to the
1563     method name, and appending a callback to the argument list (mnemonic: the
1564     underscore indicates the "the action is not yet finished" after the call
1565     returns).
1566    
1567     For example, instead of a blocking calls to C<new_session>, C<navigate_to>
1568     and C<back>, you can make a callback-based ones:
1569    
1570     my $cv = AE::cv;
1571    
1572     $wd->new_session ({}, sub {
1573     my ($status, $value) = @_,
1574    
1575     die "error $value->{error}" if $status ne "200";
1576    
1577     $wd->navigate_to_ ("http://www.nethype.de", sub {
1578    
1579     $wd->back_ (sub {
1580     print "all done\n";
1581     $cv->send;
1582     });
1583    
1584     });
1585     });
1586    
1587     $cv->recv;
1588    
1589     While the blocking methods C<croak> on errors, the callback-based ones all
1590     pass two values to the callback, C<$status> and C<$res>, where C<$status>
1591 root 1.9 is the HTTP status code (200 for successful requests, typically 4xx or
1592 root 1.1 5xx for errors), and C<$res> is the value of the C<value> key in the JSON
1593     response object.
1594    
1595     Other than that, the underscore variants and the blocking variants are
1596     identical.
1597    
1598     =head2 LOW LEVEL API
1599    
1600 root 1.9 All the simplified API methods are very thin wrappers around WebDriver
1601     commands of the same name. They are all implemented in terms of the
1602 root 1.27 low-level methods (C<req>, C<get>, C<post> and C<delete>), which exist
1603 root 1.1 in blocking and callback-based variants (C<req_>, C<get_>, C<post_> and
1604     C<delete_>).
1605    
1606     Examples are after the function descriptions.
1607    
1608     =over
1609    
1610     =item $wd->req_ ($method, $uri, $body, $cb->($status, $value))
1611    
1612     =item $value = $wd->req ($method, $uri, $body)
1613    
1614     Appends the C<$uri> to the C<endpoint/session/{sessionid}/> URL and makes
1615     a HTTP C<$method> request (C<GET>, C<POST> etc.). C<POST> requests can
1616     provide a UTF-8-encoded JSON text as HTTP request body, or the empty
1617     string to indicate no body is used.
1618    
1619     For the callback version, the callback gets passed the HTTP status code
1620     (200 for every successful request), and the value of the C<value> key in
1621     the JSON response object as second argument.
1622    
1623     =item $wd->get_ ($uri, $cb->($status, $value))
1624    
1625     =item $value = $wd->get ($uri)
1626    
1627     Simply a call to C<req_> with C<$method> set to C<GET> and an empty body.
1628    
1629     =item $wd->post_ ($uri, $data, $cb->($status, $value))
1630    
1631     =item $value = $wd->post ($uri, $data)
1632    
1633     Simply a call to C<req_> with C<$method> set to C<POST> - if C<$body> is
1634     C<undef>, then an empty object is send, otherwise, C<$data> must be a
1635     valid request object, which gets encoded into JSON for you.
1636    
1637     =item $wd->delete_ ($uri, $cb->($status, $value))
1638    
1639     =item $value = $wd->delete ($uri)
1640    
1641     Simply a call to C<req_> with C<$method> set to C<DELETE> and an empty body.
1642    
1643     =cut
1644    
1645     =back
1646    
1647     Example: implement C<get_all_cookies>, which is a simple C<GET> request
1648     without any parameters:
1649    
1650     $cookies = $wd->get ("cookie");
1651    
1652     Example: implement C<execute_script>, which needs some parameters:
1653    
1654     $results = $wd->post ("execute/sync" => { script => "$javascript", args => [] });
1655    
1656 root 1.12 Example: call C<find_elements> to find all C<IMG> elements:
1657 root 1.1
1658 root 1.12 $elems = $wd->post (elements => { using => "css selector", value => "img" });
1659 root 1.1
1660     =cut
1661    
1662     =head1 HISTORY
1663    
1664 root 1.2 This module was unintentionally created (it started inside some quickly
1665     hacked-together script) simply because I couldn't get the existing
1666 root 1.1 C<Selenium::Remote::Driver> module to work, ever, despite multiple
1667     attempts over the years and trying to report multiple bugs, which have
1668     been completely ignored. It's also not event-based, so, yeah...
1669    
1670     =head1 AUTHOR
1671    
1672     Marc Lehmann <schmorp@schmorp.de>
1673     http://anyevent.schmorp.de
1674    
1675     =cut
1676    
1677     1
1678