ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-WebDriver/WebDriver.pm
Revision: 1.39
Committed: Tue Sep 4 01:48:09 2018 UTC (5 years, 9 months ago) by root
Branch: MAIN
Changes since 1.38: +2 -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 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 ->type ("some text")
33 ->key ("{Enter}")
34 ->perform;
35
36 =head1 DESCRIPTION
37
38 WARNING: BEFORE VERSION 1.0, API CHANGES ARE LIKELY.
39
40 This module aims to implement the L<W3C
41 WebDriver|https://www.w3.org/TR/webdriver1/> 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.91;
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>. Presumably, you can
799 embed modifiers using their unicode codepoints, but the specification is
800 less than clear to mein this area.
801
802 =cut
803
804 sub element_click_ {
805 $_[0]->post_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/click" => undef, $_[2]);
806 }
807
808 sub element_clear_ {
809 $_[0]->post_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/clear" => undef, $_[2]);
810 }
811
812 sub element_send_keys_ {
813 $_[0]->post_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/value" => { text => "$_[2]" }, $_[3]);
814 }
815
816 =back
817
818 =head3 DOCUMENT HANDLING
819
820 =over
821
822 =cut
823
824 =item $source = $wd->get_page_source
825
826 Returns the (HTML/XML) page source of the current document.
827
828 =item $results = $wd->execute_script ($javascript, $args)
829
830 Synchronously execute the given script with given arguments and return its
831 results (C<$args> can be C<undef> if no arguments are wanted/needed).
832
833 $ten = $wd->execute_script ("return arguments[0]+arguments[1]", [3, 7]);
834
835 =item $results = $wd->execute_async_script ($javascript, $args)
836
837 Similar to C<execute_script>, but doesn't wait for script to return, but
838 instead waits for the script to call its last argument, which is added to
839 C<$args> automatically.
840
841 $twenty = $wd->execute_async_script ("arguments[0](20)", undef);
842
843 =cut
844
845 sub get_page_source_ {
846 $_[0]->get_ (source => $_[1]);
847 }
848
849 sub execute_script_ {
850 $_[0]->post_ ("execute/sync" => { script => "$_[1]", args => $_[2] || [] }, $_[3]);
851 }
852
853 sub execute_async_script_ {
854 $_[0]->post_ ("execute/async" => { script => "$_[1]", args => $_[2] || [] }, $_[3]);
855 }
856
857 =back
858
859 =head3 COOKIES
860
861 =over
862
863 =cut
864
865 =item $cookies = $wd->get_all_cookies
866
867 Returns all cookies, as an arrayref of hashrefs.
868
869 # google surely sets a lot of cookies without my consent
870 $wd->navigate_to ("http://google.com");
871 use Data::Dump;
872 ddx $wd->get_all_cookies;
873
874 =item $cookie = $wd->get_named_cookie ($name)
875
876 Returns a single cookie as a hashref.
877
878 =item $wd->add_cookie ($cookie)
879
880 Adds the given cookie hashref.
881
882 =item $wd->delete_cookie ($name)
883
884 Delete the named cookie.
885
886 =item $wd->delete_all_cookies
887
888 Delete all cookies.
889
890 =cut
891
892 sub get_all_cookies_ {
893 $_[0]->get_ (cookie => $_[1]);
894 }
895
896 sub get_named_cookie_ {
897 $_[0]->get_ ("cookie/$_[1]" => $_[2]);
898 }
899
900 sub add_cookie_ {
901 $_[0]->post_ (cookie => { cookie => $_[1] }, $_[2]);
902 }
903
904 sub delete_cookie_ {
905 $_[0]->delete_ ("cookie/$_[1]" => $_[2]);
906 }
907
908 sub delete_all_cookies_ {
909 $_[0]->delete_ (cookie => $_[2]);
910 }
911
912 =back
913
914 =head3 ACTIONS
915
916 =over
917
918 =cut
919
920 =item $wd->perform_actions ($actions)
921
922 Perform the given actions (an arrayref of action specifications simulating
923 user activity, or an C<AnyEvent::WebDriver::Actions> object). For further
924 details, read the spec or the section L<ACTION LISTS>, below.
925
926 An example to get you started (see the next example for a mostly
927 equivalent example using the C<AnyEvent::WebDriver::Actions> helper API):
928
929 $wd->navigate_to ("https://duckduckgo.com/html");
930 my $input = $wd->find_element ("css selector", 'input[type="text"]');
931 $wd->perform_actions ([
932 {
933 id => "myfatfinger",
934 type => "pointer",
935 pointerType => "touch",
936 actions => [
937 { type => "pointerMove", duration => 100, origin => $input, x => 40, y => 5 },
938 { type => "pointerDown", button => 0 },
939 { type => "pause", duration => 40 },
940 { type => "pointerUp", button => 0 },
941 ],
942 },
943 {
944 id => "mykeyboard",
945 type => "key",
946 actions => [
947 { type => "pause" },
948 { type => "pause" },
949 { type => "pause" },
950 { type => "pause" },
951 { type => "keyDown", value => "a" },
952 { type => "pause", duration => 100 },
953 { type => "keyUp", value => "a" },
954 { type => "pause", duration => 100 },
955 { type => "keyDown", value => "b" },
956 { type => "pause", duration => 100 },
957 { type => "keyUp", value => "b" },
958 { type => "pause", duration => 2000 },
959 { type => "keyDown", value => "\x{E007}" }, # enter
960 { type => "pause", duration => 100 },
961 { type => "keyUp", value => "\x{E007}" }, # enter
962 { type => "pause", duration => 5000 },
963 ],
964 },
965 ]);
966
967 And here is essentially the same (except for fewer pauses) example as
968 above, using the much simpler C<AnyEvent::WebDriver::Actions> API:
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 ->key ("a")
976 ->key ("b")
977 ->pause (2000) # so you can watch leisurely
978 ->key ("{Enter}")
979 ->pause (5000) # so you can see the result
980 ->perform;
981
982 =item $wd->release_actions
983
984 Release all keys and pointer buttons currently depressed.
985
986 =cut
987
988 sub perform_actions_ {
989 if (UNIVERSAL::isa $_[1], AnyEvent::WebDriver::Actions::) {
990 my ($actions, $duration) = $_[1]->compile;
991 local $_[0]{timeout} = $_[0]{timeout} + $duration * 1e-3;
992 $_[0]->post_ (actions => { actions => $actions }, $_[2]);
993 } else {
994 $_[0]->post_ (actions => { actions => $_[1] }, $_[2]);
995 }
996 }
997
998 sub release_actions_ {
999 $_[0]->delete_ (actions => $_[1]);
1000 }
1001
1002 =back
1003
1004 =head3 USER PROMPTS
1005
1006 =over
1007
1008 =cut
1009
1010 =item $wd->dismiss_alert
1011
1012 Dismiss a simple dialog, if present.
1013
1014 =item $wd->accept_alert
1015
1016 Accept a simple dialog, if present.
1017
1018 =item $text = $wd->get_alert_text
1019
1020 Returns the text of any simple dialog.
1021
1022 =item $text = $wd->send_alert_text
1023
1024 Fills in the user prompt with the given text.
1025
1026
1027 =cut
1028
1029 sub dismiss_alert_ {
1030 $_[0]->post_ ("alert/dismiss" => undef, $_[1]);
1031 }
1032
1033 sub accept_alert_ {
1034 $_[0]->post_ ("alert/accept" => undef, $_[1]);
1035 }
1036
1037 sub get_alert_text_ {
1038 $_[0]->get_ ("alert/text" => $_[1]);
1039 }
1040
1041 sub send_alert_text_ {
1042 $_[0]->post_ ("alert/text" => { text => "$_[1]" }, $_[2]);
1043 }
1044
1045 =back
1046
1047 =head3 SCREEN CAPTURE
1048
1049 =over
1050
1051 =cut
1052
1053 =item $wd->take_screenshot
1054
1055 Create a screenshot, returning it as a PNG image in a C<data:> URL.
1056
1057 =item $wd->take_element_screenshot ($element)
1058
1059 Accept a simple dialog, if present.
1060
1061 =cut
1062
1063 sub take_screenshot_ {
1064 $_[0]->get_ (screenshot => $_[1]);
1065 }
1066
1067 sub take_element_screenshot_ {
1068 $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/screenshot" => $_[2]);
1069 }
1070
1071 =back
1072
1073 =head2 ACTION LISTS
1074
1075 Action lists can be quite complicated. Or at least it took a while for
1076 me to twist my head around them. Basically, an action list consists of a
1077 number of sources representing devices (such as a finger, a mouse, a pen
1078 or a keyboard) and a list of actions for each source.
1079
1080 An action can be a key press, a pointer move or a pause (time delay).
1081
1082 While you can provide these action lists manually, it is (hopefully) less
1083 cumbersome to use the API described in this section to create them.
1084
1085 The basic process of creating and performing actions is to create a new
1086 action list, adding action sources, followed by adding actions. Finally
1087 you would C<perform> those actions on the WebDriver.
1088
1089 Most methods here are designed to chain, i.e. they return the web actions
1090 object, to simplify multiple calls.
1091
1092 Also, while actions from different sources can happen "at the same time"
1093 in the WebDriver protocol, this class ensures that actions will execute in
1094 the order specified.
1095
1096 For example, to simulate a mouse click to an input element, followed by
1097 entering some text and pressing enter, you can use this:
1098
1099 $wd->actions
1100 ->click (0, 100)
1101 ->type ("some text")
1102 ->key ("{Enter}")
1103 ->perform;
1104
1105 By default, keyboard and mouse input sources are provided. You can create
1106 your own sources and use them when adding events. The above example could
1107 be more verbosely written like this:
1108
1109 $wd->actions
1110 ->source ("mouse", "pointer", pointerType => "mouse")
1111 ->source ("kbd", "key")
1112 ->click (0, 100, "mouse")
1113 ->type ("some text", "kbd")
1114 ->key ("{Enter}", "kbd")
1115 ->perform;
1116
1117 When you specify the event source explicitly it will switch the current
1118 "focus" for this class of device (all keyboards are in one class, all
1119 pointer-like devices such as mice/fingers/pens are in one class), so you
1120 don't have to specify the source for subsequent actions.
1121
1122 When you use the sources C<keyboard>, C<mouse>, C<touch1>..C<touch3>,
1123 C<pen> without defining them, then a suitable default source will be
1124 created for them.
1125
1126 =over 4
1127
1128 =cut
1129
1130 package AnyEvent::WebDriver::Actions;
1131
1132 =item $al = new AnyEvent::WebDriver::Actions
1133
1134 Create a new empty action list object. More often you would use the C<<
1135 $wd->action_list >> method to create one that is already associated with
1136 a given web driver.
1137
1138 =cut
1139
1140 sub new {
1141 my ($class, %kv) = @_;
1142
1143 $kv{last_kbd} = "keyboard";
1144 $kv{last_ptr} = "mouse";
1145
1146 bless \%kv, $class
1147 }
1148
1149 =item $al = $al->source ($id, $type, key => value...)
1150
1151 The first time you call this with a given ID, this defines the event
1152 source using the extra parameters. Subsequent calls merely switch the
1153 current source for its event class.
1154
1155 It's not an error to define built-in sources (such as C<keyboard> or
1156 C<touch1>) differently then the defaults.
1157
1158 Example: define a new touch device called C<fatfinger>.
1159
1160 $al->source (fatfinger => "pointer", pointerType => "touch");
1161
1162 Example: define a new touch device called C<fatfinger>.
1163
1164 $al->source (fatfinger => "pointer", pointerType => "touch");
1165
1166 Example: switch default keyboard source to C<kbd1>, assuming it is of C<key> class.
1167
1168 $al->source ("kbd1");
1169
1170 =cut
1171
1172 sub _default_source($) {
1173 my ($source) = @_;
1174
1175 $source eq "keyboard" ? { actions => [], id => $source, type => "key" }
1176 : $source eq "mouse" ? { actions => [], id => $source, type => "pointer", pointerType => "mouse" }
1177 : $source eq "touch" ? { actions => [], id => $source, type => "pointer", pointerType => "touch" }
1178 : $source eq "pen" ? { actions => [], id => $source, type => "pointer", pointerType => "pen" }
1179 : Carp::croak "AnyEvent::WebDriver::Actions: event source '$source' not defined"
1180 }
1181
1182 my %source_class = (
1183 key => "kbd",
1184 pointer => "ptr",
1185 );
1186
1187 sub source {
1188 my ($self, $id, $type, %kv) = @_;
1189
1190 if (defined $type) {
1191 !exists $self->{source}{$id}
1192 or Carp::croak "AnyEvent::WebDriver::Actions: source '$id' already defined";
1193
1194 $kv{id} = $id;
1195 $kv{type} = $type;
1196 $kv{actions} = [];
1197
1198 $self->{source}{$id} = \%kv;
1199 }
1200
1201 my $source = $self->{source}{$id} ||= _default_source $id;
1202
1203 my $last = $source_class{$source->{type}} // "xxx";
1204
1205 $self->{"last_$last"} = $id;
1206
1207 $self
1208 }
1209
1210 sub _add {
1211 my ($self, $source, $sourcetype, $type, %kv) = @_;
1212
1213 my $last = \$self->{"last_$sourcetype"};
1214
1215 $source
1216 ? ($$last = $source)
1217 : ($source = $$last);
1218
1219 my $source = $self->{source}{$source} ||= _default_source $source;
1220
1221 my $al = $source->{actions};
1222
1223 push @$al, { type => "pause" }
1224 while @$al < $self->{tick}; # -1 == allow concurrent actions
1225
1226 $kv{type} = $type;
1227
1228 push @{ $source->{actions} }, \%kv;
1229
1230 $self->{tick_duration} = $kv{duration}
1231 if $kv{duration} > $self->{tick_duration};
1232
1233 if ($self->{tick} != @$al) {
1234 $self->{tick} = @$al;
1235 $self->{duration} += delete $self->{tick_duration};
1236 }
1237
1238 $self
1239 }
1240
1241 =item $al = $al->pause ($duration)
1242
1243 Creates a pause with the given duration. Makes sure that time progresses
1244 in any case, even when C<$duration> is C<0>.
1245
1246 =cut
1247
1248 sub pause {
1249 my ($self, $duration) = @_;
1250
1251 $self->{tick_duration} = $duration
1252 if $duration > $self->{tick_duration};
1253
1254 $self->{duration} += delete $self->{tick_duration};
1255
1256 # find the source with the longest list
1257
1258 for my $source (values %{ $self->{source} }) {
1259 if (@{ $source->{actions} } == $self->{tick}) {
1260 # this source is one of the longest
1261
1262 # create a pause event only if $duration is non-zero...
1263 push @{ $source->{actions} }, { type => "pause", duration => $duration*1 }
1264 if $duration;
1265
1266 # ... but advance time in any case
1267 ++$self->{tick};
1268
1269 return $self;
1270 }
1271 }
1272
1273 # no event sources are longest. so advance time in any case
1274 ++$self->{tick};
1275
1276 Carp::croak "AnyEvent::WebDriver::Actions: multiple pause calls in a row not (yet) supported"
1277 if $duration;
1278
1279 $self
1280 }
1281
1282 =item $al = $al->pointer_down ($button, $source)
1283
1284 =item $al = $al->pointer_up ($button, $source)
1285
1286 Press or release the given button. C<$button> defaults to C<0>.
1287
1288 =item $al = $al->click ($button, $source)
1289
1290 Convenience function that creates a button press and release action
1291 without any delay between them. C<$button> defaults to C<0>.
1292
1293 =item $al = $al->doubleclick ($button, $source)
1294
1295 Convenience function that creates two button press and release action
1296 pairs in a row, with no unnecessary delay between them. C<$button>
1297 defaults to C<0>.
1298
1299 =cut
1300
1301 sub pointer_down {
1302 my ($self, $button, $source) = @_;
1303
1304 $self->_add ($source, ptr => pointerDown => button => ($button // 0)*1)
1305 }
1306
1307 sub pointer_up {
1308 my ($self, $button, $source) = @_;
1309
1310 $self->_add ($source, ptr => pointerUp => button => ($button // 0)*1)
1311 }
1312
1313 sub click {
1314 my ($self, $button, $source) = @_;
1315
1316 $self
1317 ->pointer_down ($button, $source)
1318 ->pointer_up ($button)
1319 }
1320
1321 sub doubleclick {
1322 my ($self, $button, $source) = @_;
1323
1324 $self
1325 ->click ($button, $source)
1326 ->click ($button)
1327 }
1328
1329 =item $al = $al->move ($button, $origin, $x, $y, $duration, $source)
1330
1331 Moves a pointer to the given position, relative to origin (either
1332 "viewport", "pointer" or an element object.
1333
1334 =cut
1335
1336 sub move {
1337 my ($self, $origin, $x, $y, $duration, $source) = @_;
1338
1339 $self->_add ($source, ptr => pointerMove =>
1340 origin => $origin, x => $x*1, y => $y*1, duration => $duration*1)
1341 }
1342
1343 =item $al = $al->cancel ($source)
1344
1345 Executes a pointer cancel action.
1346
1347 =cut
1348
1349 sub cancel {
1350 my ($self, $source) = @_;
1351
1352 $self->_add ($source, ptr => "pointerCancel")
1353 }
1354
1355 =item $al = $al->keyDown ($key, $source)
1356
1357 =item $al = $al->keyUp ($key, $source)
1358
1359 Press or release the given key.
1360
1361 =item $al = $al->key ($key, $source)
1362
1363 Peess and release the given key, without unnecessary delay.
1364
1365 A special syntax, C<{keyname}> can be used for special keys - all the special key names from
1366 L<section 17.4.2|https://www.w3.org/TR/webdriver1/#keyboard-actions> of the WebDriver recommendation
1367 can be used.
1368
1369 Example: press and release "a".
1370
1371 $al->key ("a");
1372
1373 Example: press and release the "Enter" key:
1374
1375 $al->key ("\x{e007}");
1376
1377 Example: press and release the "enter" key using the special key name syntax:
1378
1379 $al->key ("{Enter}");
1380
1381 =item $al = $al->type ($string, $source)
1382
1383 Convenience method to simulate a series of key press and release events
1384 for the keys in C<$string>, one pair per extended unicode grapheme
1385 cluster. There is no syntax for special keys, everything will be typed
1386 "as-is" if possible.
1387
1388 =cut
1389
1390 our %SPECIAL_KEY = (
1391 # "NULL" => \xE000,
1392 "Unidentified" => 0xE000,
1393 "Cancel" => 0xE001,
1394 "Help" => 0xE002,
1395 "Backspace" => 0xE003,
1396 "Tab" => 0xE004,
1397 "Clear" => 0xE005,
1398 "Return" => 0xE006,
1399 "Enter" => 0xE007,
1400 "Shift" => 0xE008,
1401 "Control" => 0xE009,
1402 "Alt" => 0xE00A,
1403 "Pause" => 0xE00B,
1404 "Escape" => 0xE00C,
1405 " " => 0xE00D,
1406 "PageUp" => 0xE00E,
1407 "PageDown" => 0xE00F,
1408 "End" => 0xE010,
1409 "Home" => 0xE011,
1410 "ArrowLeft" => 0xE012,
1411 "ArrowUp" => 0xE013,
1412 "ArrowRight" => 0xE014,
1413 "ArrowDown" => 0xE015,
1414 "Insert" => 0xE016,
1415 "Delete" => 0xE017,
1416 ";" => 0xE018,
1417 "=" => 0xE019,
1418 "0" => 0xE01A,
1419 "1" => 0xE01B,
1420 "2" => 0xE01C,
1421 "3" => 0xE01D,
1422 "4" => 0xE01E,
1423 "5" => 0xE01F,
1424 "6" => 0xE020,
1425 "7" => 0xE021,
1426 "8" => 0xE022,
1427 "9" => 0xE023,
1428 "*" => 0xE024,
1429 "+" => 0xE025,
1430 "," => 0xE026,
1431 "-" => 0xE027,
1432 "." => 0xE028,
1433 "/" => 0xE029,
1434 "F1" => 0xE031,
1435 "F2" => 0xE032,
1436 "F3" => 0xE033,
1437 "F4" => 0xE034,
1438 "F5" => 0xE035,
1439 "F6" => 0xE036,
1440 "F7" => 0xE037,
1441 "F8" => 0xE038,
1442 "F9" => 0xE039,
1443 "F10" => 0xE03A,
1444 "F11" => 0xE03B,
1445 "F12" => 0xE03C,
1446 "Meta" => 0xE03D,
1447 "ZenkakuHankaku" => 0xE040,
1448 "Shift" => 0xE050,
1449 "Control" => 0xE051,
1450 "Alt" => 0xE052,
1451 "Meta" => 0xE053,
1452 "PageUp" => 0xE054,
1453 "PageDown" => 0xE055,
1454 "End" => 0xE056,
1455 "Home" => 0xE057,
1456 "ArrowLeft" => 0xE058,
1457 "ArrowUp" => 0xE059,
1458 "ArrowRight" => 0xE05A,
1459 "ArrowDown" => 0xE05B,
1460 "Insert" => 0xE05C,
1461 "Delete" => 0xE05D,
1462 );
1463
1464 sub _kv($) {
1465 $_[0] =~ /^\{(.*)\}$/s
1466 ? (exists $SPECIAL_KEY{$1}
1467 ? chr $SPECIAL_KEY{$1}
1468 : Carp::croak "AnyEvent::WebDriver::Actions: special key '$1' not known")
1469 : $_[0]
1470 }
1471
1472 sub key_down {
1473 my ($self, $key, $source) = @_;
1474
1475 $self->_add ($source, kbd => keyDown => value => _kv $key)
1476 }
1477
1478 sub key_up {
1479 my ($self, $key, $source) = @_;
1480
1481 $self->_add ($source, kbd => keyUp => value => _kv $key)
1482 }
1483
1484 sub key {
1485 my ($self, $key, $source) = @_;
1486
1487 $self
1488 ->key_down ($key, $source)
1489 ->key_up ($key)
1490 }
1491
1492 sub type {
1493 my ($self, $string, $source) = @_;
1494
1495 $self->key ($_, $source)
1496 for $string =~ /(\X)/g;
1497
1498 $self
1499 }
1500
1501 =item $al->perform ($wd)
1502
1503 Finalises and compiles the list, if not done yet, and calls C<<
1504 $wd->perform >> with it.
1505
1506 If C<$wd> is undef, and the action list was created using the C<<
1507 $wd->actions >> method, then perform it against that WebDriver object.
1508
1509 There is no underscore variant - call the C<perform_actions_> method with
1510 the action object instead.
1511
1512 =item $al->perform_release ($wd)
1513
1514 Exactly like C<perform>, but additionally call C<release_actions>
1515 afterwards.
1516
1517 =cut
1518
1519 sub perform {
1520 my ($self, $wd) = @_;
1521
1522 ($wd //= $self->{wd})->perform_actions ($self)
1523 }
1524
1525 sub perform_release {
1526 my ($self, $wd) = @_;
1527
1528 ($wd //= $self->{wd})->perform_actions ($self);
1529 $wd->release_actions;
1530 }
1531
1532 =item ($actions, $duration) = $al->compile
1533
1534 Finalises and compiles the list, if not done yet, and returns an actions
1535 object suitable for calls to C<< $wd->perform_actions >>. When called in
1536 list context, additionally returns the total duration of the action list.
1537
1538 Since building large action lists can take nontrivial amounts of time,
1539 it can make sense to build an action list only once and then perform it
1540 multiple times.
1541
1542 Actions must not be added after compiling a list.
1543
1544 =cut
1545
1546 sub compile {
1547 my ($self) = @_;
1548
1549 $self->{duration} += delete $self->{tick_duration};
1550
1551 delete $self->{tick};
1552 delete $self->{last_kbd};
1553 delete $self->{last_ptr};
1554
1555 $self->{actions} ||= [values %{ delete $self->{source} }];
1556
1557 wantarray
1558 ? ($self->{actions}, $self->{duration})
1559 : $self->{actions}
1560 }
1561
1562 =back
1563
1564 =head2 EVENT BASED API
1565
1566 This module wouldn't be a good AnyEvent citizen if it didn't have a true
1567 event-based API.
1568
1569 In fact, the simplified API, as documented above, is emulated via the
1570 event-based API and an C<AUTOLOAD> function that automatically provides
1571 blocking wrappers around the callback-based API.
1572
1573 Every method documented in the L<SIMPLIFIED API> section has an equivalent
1574 event-based method that is formed by appending a underscore (C<_>) to the
1575 method name, and appending a callback to the argument list (mnemonic: the
1576 underscore indicates the "the action is not yet finished" after the call
1577 returns).
1578
1579 For example, instead of a blocking calls to C<new_session>, C<navigate_to>
1580 and C<back>, you can make a callback-based ones:
1581
1582 my $cv = AE::cv;
1583
1584 $wd->new_session ({}, sub {
1585 my ($status, $value) = @_,
1586
1587 die "error $value->{error}" if $status ne "200";
1588
1589 $wd->navigate_to_ ("http://www.nethype.de", sub {
1590
1591 $wd->back_ (sub {
1592 print "all done\n";
1593 $cv->send;
1594 });
1595
1596 });
1597 });
1598
1599 $cv->recv;
1600
1601 While the blocking methods C<croak> on errors, the callback-based ones all
1602 pass two values to the callback, C<$status> and C<$res>, where C<$status>
1603 is the HTTP status code (200 for successful requests, typically 4xx or
1604 5xx for errors), and C<$res> is the value of the C<value> key in the JSON
1605 response object.
1606
1607 Other than that, the underscore variants and the blocking variants are
1608 identical.
1609
1610 =head2 LOW LEVEL API
1611
1612 All the simplified API methods are very thin wrappers around WebDriver
1613 commands of the same name. They are all implemented in terms of the
1614 low-level methods (C<req>, C<get>, C<post> and C<delete>), which exist
1615 in blocking and callback-based variants (C<req_>, C<get_>, C<post_> and
1616 C<delete_>).
1617
1618 Examples are after the function descriptions.
1619
1620 =over
1621
1622 =item $wd->req_ ($method, $uri, $body, $cb->($status, $value))
1623
1624 =item $value = $wd->req ($method, $uri, $body)
1625
1626 Appends the C<$uri> to the C<endpoint/session/{sessionid}/> URL and makes
1627 a HTTP C<$method> request (C<GET>, C<POST> etc.). C<POST> requests can
1628 provide a UTF-8-encoded JSON text as HTTP request body, or the empty
1629 string to indicate no body is used.
1630
1631 For the callback version, the callback gets passed the HTTP status code
1632 (200 for every successful request), and the value of the C<value> key in
1633 the JSON response object as second argument.
1634
1635 =item $wd->get_ ($uri, $cb->($status, $value))
1636
1637 =item $value = $wd->get ($uri)
1638
1639 Simply a call to C<req_> with C<$method> set to C<GET> and an empty body.
1640
1641 =item $wd->post_ ($uri, $data, $cb->($status, $value))
1642
1643 =item $value = $wd->post ($uri, $data)
1644
1645 Simply a call to C<req_> with C<$method> set to C<POST> - if C<$body> is
1646 C<undef>, then an empty object is send, otherwise, C<$data> must be a
1647 valid request object, which gets encoded into JSON for you.
1648
1649 =item $wd->delete_ ($uri, $cb->($status, $value))
1650
1651 =item $value = $wd->delete ($uri)
1652
1653 Simply a call to C<req_> with C<$method> set to C<DELETE> and an empty body.
1654
1655 =cut
1656
1657 =back
1658
1659 Example: implement C<get_all_cookies>, which is a simple C<GET> request
1660 without any parameters:
1661
1662 $cookies = $wd->get ("cookie");
1663
1664 Example: implement C<execute_script>, which needs some parameters:
1665
1666 $results = $wd->post ("execute/sync" => { script => "$javascript", args => [] });
1667
1668 Example: call C<find_elements> to find all C<IMG> elements:
1669
1670 $elems = $wd->post (elements => { using => "css selector", value => "img" });
1671
1672 =cut
1673
1674 =head1 HISTORY
1675
1676 This module was unintentionally created (it started inside some quickly
1677 hacked-together script) simply because I couldn't get the existing
1678 C<Selenium::Remote::Driver> module to work, ever, despite multiple
1679 attempts over the years and trying to report multiple bugs, which have
1680 been completely ignored. It's also not event-based, so, yeah...
1681
1682 =head1 AUTHOR
1683
1684 Marc Lehmann <schmorp@schmorp.de>
1685 http://anyevent.schmorp.de
1686
1687 =cut
1688
1689 1
1690