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