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