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