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