ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-WebDriver/WebDriver.pm
Revision: 1.54
Committed: Fri Mar 27 18:08:23 2020 UTC (4 years, 3 months ago) by root
Branch: MAIN
Changes since 1.53: +5 -0 lines
Log Message:
*** empty log message ***

File Contents

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