ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-WebDriver/WebDriver.pm
Revision: 1.57
Committed: Fri Mar 27 19:11:30 2020 UTC (4 years, 2 months ago) by root
Branch: MAIN
Changes since 1.56: +1 -1 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 = "AyEvent::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 Note: The W3C WebDriver specification also mentions a "scroll" parameter,
1111 but it is impossible to pass parameters for this command, so it is
1112 czrrently left out.
1113
1114 =cut
1115
1116 sub take_screenshot_ {
1117 my $cb = pop; push @_, sub { $cb->($_[0], _decode_base64 $_[1]) };
1118 $_[0]->get_ (screenshot => $_[1]);
1119 }
1120
1121 sub take_element_screenshot_ {
1122 my $cb = pop; push @_, sub { $cb->($_[0], _decode_base64 $_[1]) };
1123 $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/screenshot" => $_[2]);
1124 }
1125
1126 =back
1127
1128 =head3 PRINT
1129
1130 =over
1131
1132 =cut
1133
1134 =item $wd->print_page (key => value...)
1135
1136 Create a printed version of the document, returning it as a PDF document
1137 encoded as base64. See C<take_screenshot> for an example on how to decode
1138 and save such a string.
1139
1140 This command takes a lot of optional parameters, see L<the print
1141 section|https://www.w3.org/TR/webdriver2/#print> of the WebDriver
1142 specification for details.
1143
1144 This command is taken from a draft document, so it might change in the
1145 future.
1146
1147 =cut
1148
1149 sub print_page {
1150 my $cb = pop; push @_, sub { $cb->($_[0], _decode_base64 $_[1]) };
1151 $_[0]->post_ (print => { @_ });
1152 }
1153
1154 =head2 ACTION LISTS
1155
1156 Action lists can be quite complicated. Or at least it took a while for
1157 me to twist my head around them. Basically, an action list consists of a
1158 number of sources representing devices (such as a finger, a mouse, a pen
1159 or a keyboard) and a list of actions for each source, in a timeline.
1160
1161 An action can be a key press, a pointer move or a pause (time delay).
1162
1163 While you can provide these action lists manually, it is (hopefully) less
1164 cumbersome to use the API described in this section to create them.
1165
1166 The basic process of creating and performing actions is to create a new
1167 action list, adding action sources, followed by adding actions. Finally
1168 you would C<perform> those actions on the WebDriver.
1169
1170 Most methods here are designed to chain, i.e. they return the web actions
1171 object, to simplify multiple calls.
1172
1173 Also, while actions from different sources can happen "at the same time"
1174 in the WebDriver protocol, this class by default ensures that actions will
1175 execute in the order specified.
1176
1177 For example, to simulate a mouse click to an input element, followed by
1178 entering some text and pressing enter, you can use this:
1179
1180 $wd->actions
1181 ->click (0, 100)
1182 ->type ("some text")
1183 ->key ("{Enter}")
1184 ->perform;
1185
1186 By default, C<keyboard> and C<mouse> input sources are provided and
1187 used. You can create your own sources and use them when adding events. The
1188 above example could be more verbosely written like this:
1189
1190 $wd->actions
1191 ->source ("mouse", "pointer", pointerType => "mouse")
1192 ->source ("kbd", "key")
1193 ->click (0, 100, "mouse")
1194 ->type ("some text", "kbd")
1195 ->key ("{Enter}", "kbd")
1196 ->perform;
1197
1198 When you specify the event source explicitly it will switch the current
1199 "focus" for this class of device (all keyboards are in one class, all
1200 pointer-like devices such as mice/fingers/pens are in one class), so you
1201 don't have to specify the source for subsequent actions that are on the
1202 same class.
1203
1204 When you use the sources C<keyboard>, C<mouse>, C<touch1>..C<touch3>,
1205 C<pen> without defining them, then a suitable default source will be
1206 created for them.
1207
1208 =over 4
1209
1210 =cut
1211
1212 package AnyEvent::WebDriver::Actions;
1213
1214 =item $al = new AnyEvent::WebDriver::Actions
1215
1216 Create a new empty action list object. More often you would use the C<<
1217 $wd->action_list >> method to create one that is already associated with
1218 a given web driver.
1219
1220 =cut
1221
1222 sub new {
1223 my ($class, %kv) = @_;
1224
1225 $kv{last_kbd} = "keyboard";
1226 $kv{last_ptr} = "mouse";
1227
1228 bless \%kv, $class
1229 }
1230
1231 =item $al = $al->source ($id, $type, key => value...)
1232
1233 The first time you call this with a given ID, this defines the event
1234 source using the extra parameters. Subsequent calls merely switch the
1235 current source for its event class.
1236
1237 It's not an error to define built-in sources (such as C<keyboard> or
1238 C<touch1>) differently then the defaults.
1239
1240 Example: define a new touch device called C<fatfinger>.
1241
1242 $al->source (fatfinger => "pointer", pointerType => "touch");
1243
1244 Example: define a new touch device called C<fatfinger>.
1245
1246 $al->source (fatfinger => "pointer", pointerType => "touch");
1247
1248 Example: switch default keyboard source to C<kbd1>, assuming it is of C<key> class.
1249
1250 $al->source ("kbd1");
1251
1252 =cut
1253
1254 sub _default_source($) {
1255 my ($source) = @_;
1256
1257 $source eq "keyboard" ? { actions => [], id => $source, type => "key" }
1258 : $source eq "mouse" ? { actions => [], id => $source, type => "pointer", pointerType => "mouse" }
1259 : $source eq "touch" ? { actions => [], id => $source, type => "pointer", pointerType => "touch" }
1260 : $source eq "pen" ? { actions => [], id => $source, type => "pointer", pointerType => "pen" }
1261 : Carp::croak "AnyEvent::WebDriver::Actions: event source '$source' not defined"
1262 }
1263
1264 my %source_class = (
1265 key => "kbd",
1266 pointer => "ptr",
1267 );
1268
1269 sub source {
1270 my ($self, $id, $type, %kv) = @_;
1271
1272 if (defined $type) {
1273 !exists $self->{source}{$id}
1274 or Carp::croak "AnyEvent::WebDriver::Actions: source '$id' already defined";
1275
1276 $kv{id} = $id;
1277 $kv{type} = $type;
1278 $kv{actions} = [];
1279
1280 $self->{source}{$id} = \%kv;
1281 }
1282
1283 my $source = $self->{source}{$id} ||= _default_source $id;
1284
1285 my $last = $source_class{$source->{type}} // "xxx";
1286
1287 $self->{"last_$last"} = $id;
1288
1289 $self
1290 }
1291
1292 sub _add {
1293 my ($self, $source, $sourcetype, $type, %kv) = @_;
1294
1295 my $last = \$self->{"last_$sourcetype"};
1296
1297 $source
1298 ? ($$last = $source)
1299 : ($source = $$last);
1300
1301 my $source = $self->{source}{$source} ||= _default_source $source;
1302
1303 my $al = $source->{actions};
1304
1305 push @$al, { type => "pause" }
1306 while @$al < $self->{tick}; # -1 == allow concurrent actions
1307
1308 $kv{type} = $type;
1309
1310 push @{ $source->{actions} }, \%kv;
1311
1312 $self->{tick_duration} = $kv{duration}
1313 if $kv{duration} > $self->{tick_duration};
1314
1315 if ($self->{tick} != @$al) {
1316 $self->{tick} = @$al;
1317 $self->{duration} += delete $self->{tick_duration};
1318 }
1319
1320 $self
1321 }
1322
1323 =item $al = $al->pause ($duration)
1324
1325 Creates a pause with the given duration. Makes sure that time progresses
1326 in any case, even when C<$duration> is C<0>.
1327
1328 =cut
1329
1330 sub pause {
1331 my ($self, $duration) = @_;
1332
1333 $self->{tick_duration} = $duration
1334 if $duration > $self->{tick_duration};
1335
1336 $self->{duration} += delete $self->{tick_duration};
1337
1338 # find the source with the longest list
1339
1340 for my $source (values %{ $self->{source} }) {
1341 if (@{ $source->{actions} } == $self->{tick}) {
1342 # this source is one of the longest
1343
1344 # create a pause event only if $duration is non-zero...
1345 push @{ $source->{actions} }, { type => "pause", duration => $duration*1 }
1346 if $duration;
1347
1348 # ... but advance time in any case
1349 ++$self->{tick};
1350
1351 return $self;
1352 }
1353 }
1354
1355 # no event sources are longest. so advance time in any case
1356 ++$self->{tick};
1357
1358 Carp::croak "AnyEvent::WebDriver::Actions: multiple pause calls in a row not (yet) supported"
1359 if $duration;
1360
1361 $self
1362 }
1363
1364 =item $al = $al->pointer_down ($button, $source)
1365
1366 =item $al = $al->pointer_up ($button, $source)
1367
1368 Press or release the given button. C<$button> defaults to C<0>.
1369
1370 =item $al = $al->click ($button, $source)
1371
1372 Convenience function that creates a button press and release action
1373 without any delay between them. C<$button> defaults to C<0>.
1374
1375 =item $al = $al->doubleclick ($button, $source)
1376
1377 Convenience function that creates two button press and release action
1378 pairs in a row, with no unnecessary delay between them. C<$button>
1379 defaults to C<0>.
1380
1381 =cut
1382
1383 sub pointer_down {
1384 my ($self, $button, $source) = @_;
1385
1386 $self->_add ($source, ptr => pointerDown => button => ($button // 0)*1)
1387 }
1388
1389 sub pointer_up {
1390 my ($self, $button, $source) = @_;
1391
1392 $self->_add ($source, ptr => pointerUp => button => ($button // 0)*1)
1393 }
1394
1395 sub click {
1396 my ($self, $button, $source) = @_;
1397
1398 $self
1399 ->pointer_down ($button, $source)
1400 ->pointer_up ($button)
1401 }
1402
1403 sub doubleclick {
1404 my ($self, $button, $source) = @_;
1405
1406 $self
1407 ->click ($button, $source)
1408 ->click ($button)
1409 }
1410
1411 =item $al = $al->move ($origin, $x, $y, $duration, $source)
1412
1413 Moves a pointer to the given position, relative to origin (either
1414 "viewport", "pointer" or an element object. The coordinates will be
1415 truncated to integer values.
1416
1417 =cut
1418
1419 sub move {
1420 my ($self, $origin, $x, $y, $duration, $source) = @_;
1421
1422 $self->_add ($source, ptr => pointerMove =>
1423 origin => $origin, x => int $x*1, y => int $y*1, duration => $duration*1)
1424 }
1425
1426 =item $al = $al->cancel ($source)
1427
1428 Executes a pointer cancel action.
1429
1430 =cut
1431
1432 sub cancel {
1433 my ($self, $source) = @_;
1434
1435 $self->_add ($source, ptr => "pointerCancel")
1436 }
1437
1438 =item $al = $al->key_down ($key, $source)
1439
1440 =item $al = $al->key_up ($key, $source)
1441
1442 Press or release the given key.
1443
1444 =item $al = $al->key ($key, $source)
1445
1446 Peess and release the given key in one go, without unnecessary delay.
1447
1448 A special syntax, C<{keyname}> can be used for special keys -
1449 all the special key names from L<the second table in section
1450 17.4.2|https://www.w3.org/TR/webdriver1/#keyboard-actions> of the
1451 WebDriver recommendation can be used - prefix with C<Shift-Space>. to get
1452 the shifted version, as in C<Shift-
1453
1454 Example: press and release "a".
1455
1456 $al->key ("a");
1457
1458 Example: press and release the "Enter" key:
1459
1460 $al->key ("\x{e007}");
1461
1462 Example: press and release the "enter" key using the special key name syntax:
1463
1464 $al->key ("{Enter}");
1465
1466 =item $al = $al->type ($string, $source)
1467
1468 Convenience method to simulate a series of key press and release events
1469 for the keys in C<$string>, one pair per extended unicode grapheme
1470 cluster. There is no syntax for special keys, everything will be typed
1471 "as-is" if possible.
1472
1473 =cut
1474
1475 # copy&paste from the spec via browser, with added MetaLeft/MetaRight aliases
1476 our $SPECIAL_KEY = <<'EOF';
1477 "`" "~" "Backquote"
1478 "\" "|" "Backslash"
1479 "\uE003" "Backspace"
1480 "[" "{" "BracketLeft"
1481 "]" "}" "BracketRight"
1482 "," "<" "Comma"
1483 "0" ")" "Digit0"
1484 "1" "!" "Digit1"
1485 "2" "@" "Digit2"
1486 "3" "#" "Digit3"
1487 "4" "$" "Digit4"
1488 "5" "%" "Digit5"
1489 "6" "^" "Digit6"
1490 "7" "&" "Digit7"
1491 "8" "*" "Digit8"
1492 "9" "(" "Digit9"
1493 "=" "+" "Equal"
1494 "<" ">" "IntlBackslash"
1495 "a" "A" "KeyA"
1496 "b" "B" "KeyB"
1497 "c" "C" "KeyC"
1498 "d" "D" "KeyD"
1499 "e" "E" "KeyE"
1500 "f" "F" "KeyF"
1501 "g" "G" "KeyG"
1502 "h" "H" "KeyH"
1503 "i" "I" "KeyI"
1504 "j" "J" "KeyJ"
1505 "k" "K" "KeyK"
1506 "l" "L" "KeyL"
1507 "m" "M" "KeyM"
1508 "n" "N" "KeyN"
1509 "o" "O" "KeyO"
1510 "p" "P" "KeyP"
1511 "q" "Q" "KeyQ"
1512 "r" "R" "KeyR"
1513 "s" "S" "KeyS"
1514 "t" "T" "KeyT"
1515 "u" "U" "KeyU"
1516 "v" "V" "KeyV"
1517 "w" "W" "KeyW"
1518 "x" "X" "KeyX"
1519 "y" "Y" "KeyY"
1520 "z" "Z" "KeyZ"
1521 "-" "_" "Minus"
1522 "." ">"." "Period"
1523 "'" """ "Quote"
1524 ";" ":" "Semicolon"
1525 "/" "?" "Slash"
1526 "\uE00A" "AltLeft"
1527 "\uE052" "AltRight"
1528 "\uE009" "ControlLeft"
1529 "\uE051" "ControlRight"
1530 "\uE006" "Enter"
1531 "\uE03D" "OSLeft"
1532 "\uE053" "OSRight"
1533 "\uE008" "ShiftLeft"
1534 "\uE050" "ShiftRight"
1535 " " "\uE00D" "Space"
1536 "\uE004" "Tab"
1537 "\uE017" "Delete"
1538 "\uE010" "End"
1539 "\uE002" "Help"
1540 "\uE011" "Home"
1541 "\uE016" "Insert"
1542 "\uE00F" "PageDown"
1543 "\uE00E" "PageUp"
1544 "\uE015" "ArrowDown"
1545 "\uE012" "ArrowLeft"
1546 "\uE014" "ArrowRight"
1547 "\uE013" "ArrowUp"
1548 "\uE00C" "Escape"
1549 "\uE031" "F1"
1550 "\uE032" "F2"
1551 "\uE033" "F3"
1552 "\uE034" "F4"
1553 "\uE035" "F5"
1554 "\uE036" "F6"
1555 "\uE037" "F7"
1556 "\uE038" "F8"
1557 "\uE039" "F9"
1558 "\uE03A" "F10"
1559 "\uE03B" "F11"
1560 "\uE03C" "F12"
1561 "\uE01A" "\uE05C" "Numpad0"
1562 "\uE01B" "\uE056" "Numpad1"
1563 "\uE01C" "\uE05B" "Numpad2"
1564 "\uE01D" "\uE055" "Numpad3"
1565 "\uE01E" "\uE058" "Numpad4"
1566 "\uE01F" "Numpad5"
1567 "\uE020" "\uE05A" "Numpad6"
1568 "\uE021" "\uE057" "Numpad7"
1569 "\uE022" "\uE059" "Numpad8"
1570 "\uE023" "\uE054" "Numpad9"
1571 "\uE025" "NumpadAdd"
1572 "\uE026" "NumpadComma"
1573 "\uE028" "\uE05D" "NumpadDecimal"
1574 "\uE029" "NumpadDivide"
1575 "\uE007" "NumpadEnter"
1576 "\uE024" "NumpadMultiply"
1577 "\uE027" "NumpadSubtract"
1578
1579 "\uE03D" "MetaLeft"
1580 "\uE053" "MetaRight"
1581 EOF
1582
1583 our %SPECIAL_KEY;
1584
1585 sub _special_key($) {
1586 # parse first time
1587 %SPECIAL_KEY || do {
1588 for (split /\n/, $SPECIAL_KEY) {
1589 s/"//g or next;
1590 my ($k, $s, $name) = split /\t/;
1591
1592 # unescape \uXXXX, convert string to codepoint
1593 $_ = /^\\u/ ? hex substr $_, 2 : ord
1594 for $k, $s;
1595
1596 $SPECIAL_KEY{$name} = $k;
1597 $SPECIAL_KEY{"Shift-$name"} = $s if $s;
1598
1599 }
1600
1601 undef $SPECIAL_KEY; # save memory
1602 };
1603
1604 exists $SPECIAL_KEY{$_[0]}
1605 ? chr $SPECIAL_KEY{$_[0]}
1606 : Carp::croak "AnyEvent::WebDriver::Actions: special key '$1' not known"
1607 }
1608
1609 sub _kv($) {
1610 $_[0] =~ /^\{(.*)\}$/s
1611 ? _special_key $1
1612 : $_[0]
1613 }
1614
1615 sub key_down {
1616 my ($self, $key, $source) = @_;
1617
1618 $self->_add ($source, kbd => keyDown => value => _kv $key)
1619 }
1620
1621 sub key_up {
1622 my ($self, $key, $source) = @_;
1623
1624 $self->_add ($source, kbd => keyUp => value => _kv $key)
1625 }
1626
1627 sub key {
1628 my ($self, $key, $source) = @_;
1629
1630 $self
1631 ->key_down ($key, $source)
1632 ->key_up ($key)
1633 }
1634
1635 sub type {
1636 my ($self, $string, $source) = @_;
1637
1638 $self->key ($_, $source)
1639 for $string =~ /(\X)/g;
1640
1641 $self
1642 }
1643
1644 =item $al->perform ($wd)
1645
1646 Finalises and compiles the list, if not done yet, and calls C<<
1647 $wd->perform >> with it.
1648
1649 If C<$wd> is undef, and the action list was created using the C<<
1650 $wd->actions >> method, then perform it against that WebDriver object.
1651
1652 There is no underscore variant - call the C<perform_actions_> method with
1653 the action object instead.
1654
1655 =item $al->perform_release ($wd)
1656
1657 Exactly like C<perform>, but additionally call C<release_actions>
1658 afterwards.
1659
1660 =cut
1661
1662 sub perform {
1663 my ($self, $wd) = @_;
1664
1665 ($wd //= $self->{wd})->perform_actions ($self)
1666 }
1667
1668 sub perform_release {
1669 my ($self, $wd) = @_;
1670
1671 ($wd //= $self->{wd})->perform_actions ($self);
1672 $wd->release_actions;
1673 }
1674
1675 =item ($actions, $duration) = $al->compile
1676
1677 Finalises and compiles the list, if not done yet, and returns an actions
1678 object suitable for calls to C<< $wd->perform_actions >>. When called in
1679 list context, additionally returns the total duration of the action list.
1680
1681 Since building large action lists can take nontrivial amounts of time,
1682 it can make sense to build an action list only once and then perform it
1683 multiple times.
1684
1685 No additional actions must be added after compiling an action list.
1686
1687 =cut
1688
1689 sub compile {
1690 my ($self) = @_;
1691
1692 $self->{duration} += delete $self->{tick_duration};
1693
1694 delete $self->{tick};
1695 delete $self->{last_kbd};
1696 delete $self->{last_ptr};
1697
1698 $self->{actions} ||= [values %{ delete $self->{source} }];
1699
1700 wantarray
1701 ? ($self->{actions}, $self->{duration})
1702 : $self->{actions}
1703 }
1704
1705 =back
1706
1707 =head2 EVENT BASED API
1708
1709 This module wouldn't be a good AnyEvent citizen if it didn't have a true
1710 event-based API.
1711
1712 In fact, the simplified API, as documented above, is emulated via the
1713 event-based API and an C<AUTOLOAD> function that automatically provides
1714 blocking wrappers around the callback-based API.
1715
1716 Every method documented in the L<SIMPLIFIED API> section has an equivalent
1717 event-based method that is formed by appending a underscore (C<_>) to the
1718 method name, and appending a callback to the argument list (mnemonic: the
1719 underscore indicates the "the action is not yet finished" after the call
1720 returns).
1721
1722 For example, instead of a blocking calls to C<new_session>, C<navigate_to>
1723 and C<back>, you can make a callback-based ones:
1724
1725 my $cv = AE::cv;
1726
1727 $wd->new_session ({}, sub {
1728 my ($status, $value) = @_,
1729
1730 die "error $value->{error}" if $status ne "200";
1731
1732 $wd->navigate_to_ ("http://www.nethype.de", sub {
1733
1734 $wd->back_ (sub {
1735 print "all done\n";
1736 $cv->send;
1737 });
1738
1739 });
1740 });
1741
1742 $cv->recv;
1743
1744 While the blocking methods C<croak> on errors, the callback-based ones all
1745 pass two values to the callback, C<$status> and C<$res>, where C<$status>
1746 is the HTTP status code (200 for successful requests, typically 4xx or
1747 5xx for errors), and C<$res> is the value of the C<value> key in the JSON
1748 response object.
1749
1750 Other than that, the underscore variants and the blocking variants are
1751 identical.
1752
1753 =head2 LOW LEVEL API
1754
1755 All the simplified API methods are very thin wrappers around WebDriver
1756 commands of the same name. They are all implemented in terms of the
1757 low-level methods (C<req>, C<get>, C<post> and C<delete>), which exist
1758 in blocking and callback-based variants (C<req_>, C<get_>, C<post_> and
1759 C<delete_>).
1760
1761 Examples are after the function descriptions.
1762
1763 =over
1764
1765 =item $wd->req_ ($method, $uri, $body, $cb->($status, $value))
1766
1767 =item $value = $wd->req ($method, $uri, $body)
1768
1769 Appends the C<$uri> to the C<endpoint/session/{sessionid}/> URL and makes
1770 a HTTP C<$method> request (C<GET>, C<POST> etc.). C<POST> requests can
1771 provide a UTF-8-encoded JSON text as HTTP request body, or the empty
1772 string to indicate no body is used.
1773
1774 For the callback version, the callback gets passed the HTTP status code
1775 (200 for every successful request), and the value of the C<value> key in
1776 the JSON response object as second argument.
1777
1778 =item $wd->get_ ($uri, $cb->($status, $value))
1779
1780 =item $value = $wd->get ($uri)
1781
1782 Simply a call to C<req_> with C<$method> set to C<GET> and an empty body.
1783
1784 =item $wd->post_ ($uri, $data, $cb->($status, $value))
1785
1786 =item $value = $wd->post ($uri, $data)
1787
1788 Simply a call to C<req_> with C<$method> set to C<POST> - if C<$body> is
1789 C<undef>, then an empty object is send, otherwise, C<$data> must be a
1790 valid request object, which gets encoded into JSON for you.
1791
1792 =item $wd->delete_ ($uri, $cb->($status, $value))
1793
1794 =item $value = $wd->delete ($uri)
1795
1796 Simply a call to C<req_> with C<$method> set to C<DELETE> and an empty body.
1797
1798 =cut
1799
1800 =back
1801
1802 Example: implement C<get_all_cookies>, which is a simple C<GET> request
1803 without any parameters:
1804
1805 $cookies = $wd->get ("cookie");
1806
1807 Example: implement C<execute_script>, which needs some parameters:
1808
1809 $results = $wd->post ("execute/sync" => { script => "$javascript", args => [] });
1810
1811 Example: call C<find_elements> to find all C<IMG> elements:
1812
1813 $elems = $wd->post (elements => { using => "css selector", value => "img" });
1814
1815 =cut
1816
1817 =head1 HISTORY
1818
1819 This module was unintentionally created (it started inside some quickly
1820 hacked-together script) simply because I couldn't get the existing
1821 C<Selenium::Remote::Driver> module to work reliably, ever, despite
1822 multiple attempts over the years and trying to report multiple bugs, which
1823 have been completely ignored. It's also not event-based, so, yeah...
1824
1825 =head1 AUTHOR
1826
1827 Marc Lehmann <schmorp@schmorp.de>
1828 http://anyevent.schmorp.de
1829
1830 =cut
1831
1832 1
1833