ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-WebDriver/WebDriver.pm
Revision: 1.32
Committed: Mon Sep 3 20:35:30 2018 UTC (5 years, 9 months ago) by root
Branch: MAIN
Changes since 1.31: +1 -1 lines
Log Message:
*** empty log message ***

File Contents

# Content
1 =head1 NAME
2
3 AnyEvent::WebDriver - control browsers using the W3C WebDriver protocol
4
5 =head1 SYNOPSIS
6
7 # start geckodriver 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).
576
577 =cut
578
579 sub get_window_rect_ {
580 $_[0]->get_ ("window/rect" => $_[1]);
581 }
582
583 sub set_window_rect_ {
584 $_[0]->post_ ("window/rect" => $_[1], $_[2]);
585 }
586
587 =item $wd->maximize_window
588
589 =item $wd->minimize_window
590
591 =item $wd->fullscreen_window
592
593 Changes the window size by either maximising, minimising or making it
594 fullscreen. In my experience, this will timeout if no window manager is
595 running.
596
597 =cut
598
599 sub maximize_window_ {
600 $_[0]->post_ ("window/maximize" => undef, $_[1]);
601 }
602
603 sub minimize_window_ {
604 $_[0]->post_ ("window/minimize" => undef, $_[1]);
605 }
606
607 sub fullscreen_window_ {
608 $_[0]->post_ ("window/fullscreen" => undef, $_[1]);
609 }
610
611 =back
612
613 =head3 ELEMENT RETRIEVAL
614
615 To reduce typing and memory strain, the element finding functions accept
616 some shorter and hopefully easier to remember aliases for the standard
617 locator strategy values, as follows:
618
619 Alias Locator Strategy
620 css css selector
621 link link text
622 substr partial link text
623 tag tag name
624
625 =over
626
627 =cut
628
629 our %USING = (
630 css => "css selector",
631 link => "link text",
632 substr => "partial link text",
633 tag => "tag name",
634 );
635
636 sub _using($) {
637 using => $USING{$_[0]} // "$_[0]"
638 }
639
640 =item $element = $wd->find_element ($locator_strategy, $selector)
641
642 Finds the first element specified by the given selector and returns its
643 element object. Raises an error when no element was found.
644
645 Examples showing all standard locator strategies:
646
647 $element = $wd->find_element ("css selector" => "body a");
648 $element = $wd->find_element ("link text" => "Click Here For Porn");
649 $element = $wd->find_element ("partial link text" => "orn");
650 $element = $wd->find_element ("tag name" => "input");
651 $element = $wd->find_element ("xpath" => '//input[@type="text"]');
652 => e.g. { "element-6066-11e4-a52e-4f735466cecf" => "decddca8-5986-4e1d-8c93-efe952505a5f" }
653
654 Same examples using aliases provided by this module:
655
656 $element = $wd->find_element (css => "body a");
657 $element = $wd->find_element (link => "Click Here For Porn");
658 $element = $wd->find_element (substr => "orn");
659 $element = $wd->find_element (tag => "input");
660
661 =item $elements = $wd->find_elements ($locator_strategy, $selector)
662
663 As above, but returns an arrayref of all found element objects.
664
665 =item $element = $wd->find_element_from_element ($element, $locator_strategy, $selector)
666
667 Like C<find_element>, but looks only inside the specified C<$element>.
668
669 =item $elements = $wd->find_elements_from_element ($element, $locator_strategy, $selector)
670
671 Like C<find_elements>, but looks only inside the specified C<$element>.
672
673 my $head = $wd->find_element ("tag name" => "head");
674 my $links = $wd->find_elements_from_element ($head, "tag name", "link");
675
676 =item $element = $wd->get_active_element
677
678 Returns the active element.
679
680 =cut
681
682 sub find_element_ {
683 $_[0]->post_ (element => { _using $_[1], value => "$_[2]" }, $_[3]);
684 }
685
686 sub find_elements_ {
687 $_[0]->post_ (elements => { _using $_[1], value => "$_[2]" }, $_[3]);
688 }
689
690 sub find_element_from_element_ {
691 $_[0]->post_ ("element/$_[1]/element" => { _using $_[2], value => "$_[3]" }, $_[4]);
692 }
693
694 sub find_elements_from_element_ {
695 $_[0]->post_ ("element/$_[1]/elements" => { _using $_[2], value => "$_[3]" }, $_[4]);
696 }
697
698 sub get_active_element_ {
699 $_[0]->get_ ("element/active" => $_[1]);
700 }
701
702 =back
703
704 =head3 ELEMENT STATE
705
706 =over
707
708 =cut
709
710 =item $bool = $wd->is_element_selected
711
712 Returns whether the given input or option element is selected or not.
713
714 =item $string = $wd->get_element_attribute ($element, $name)
715
716 Returns the value of the given attribute.
717
718 =item $string = $wd->get_element_property ($element, $name)
719
720 Returns the value of the given property.
721
722 =item $string = $wd->get_element_css_value ($element, $name)
723
724 Returns the value of the given CSS value.
725
726 =item $string = $wd->get_element_text ($element)
727
728 Returns the (rendered) text content of the given element.
729
730 =item $string = $wd->get_element_tag_name ($element)
731
732 Returns the tag of the given element.
733
734 =item $rect = $wd->get_element_rect ($element)
735
736 Returns the element rect(angle) of the given element.
737
738 =item $bool = $wd->is_element_enabled
739
740 Returns whether the element is enabled or not.
741
742 =cut
743
744 sub is_element_selected_ {
745 $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/selected" => $_[2]);
746 }
747
748 sub get_element_attribute_ {
749 $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/attribute/$_[2]" => $_[3]);
750 }
751
752 sub get_element_property_ {
753 $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/property/$_[2]" => $_[3]);
754 }
755
756 sub get_element_css_value_ {
757 $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/css/$_[2]" => $_[3]);
758 }
759
760 sub get_element_text_ {
761 $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/text" => $_[2]);
762 }
763
764 sub get_element_tag_name_ {
765 $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/name" => $_[2]);
766 }
767
768 sub get_element_rect_ {
769 $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/rect" => $_[2]);
770 }
771
772 sub is_element_enabled_ {
773 $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/enabled" => $_[2]);
774 }
775
776 =back
777
778 =head3 ELEMENT INTERACTION
779
780 =over
781
782 =cut
783
784 =item $wd->element_click ($element)
785
786 Clicks the given element.
787
788 =item $wd->element_clear ($element)
789
790 Clear the contents of the given element.
791
792 =item $wd->element_send_keys ($element, $text)
793
794 Sends the given text as key events to the given element.
795
796 =cut
797
798 sub element_click_ {
799 $_[0]->post_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/click" => undef, $_[2]);
800 }
801
802 sub element_clear_ {
803 $_[0]->post_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/clear" => undef, $_[2]);
804 }
805
806 sub element_send_keys_ {
807 $_[0]->post_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/value" => { text => "$_[2]" }, $_[3]);
808 }
809
810 =back
811
812 =head3 DOCUMENT HANDLING
813
814 =over
815
816 =cut
817
818 =item $source = $wd->get_page_source
819
820 Returns the (HTML/XML) page source of the current document.
821
822 =item $results = $wd->execute_script ($javascript, $args)
823
824 Synchronously execute the given script with given arguments and return its
825 results (C<$args> can be C<undef> if no arguments are wanted/needed).
826
827 $ten = $wd->execute_script ("return arguments[0]+arguments[1]", [3, 7]);
828
829 =item $results = $wd->execute_async_script ($javascript, $args)
830
831 Similar to C<execute_script>, but doesn't wait for script to return, but
832 instead waits for the script to call its last argument, which is added to
833 C<$args> automatically.
834
835 $twenty = $wd->execute_async_script ("arguments[0](20)", undef);
836
837 =cut
838
839 sub get_page_source_ {
840 $_[0]->get_ (source => $_[1]);
841 }
842
843 sub execute_script_ {
844 $_[0]->post_ ("execute/sync" => { script => "$_[1]", args => $_[2] || [] }, $_[3]);
845 }
846
847 sub execute_async_script_ {
848 $_[0]->post_ ("execute/async" => { script => "$_[1]", args => $_[2] || [] }, $_[3]);
849 }
850
851 =back
852
853 =head3 COOKIES
854
855 =over
856
857 =cut
858
859 =item $cookies = $wd->get_all_cookies
860
861 Returns all cookies, as an arrayref of hashrefs.
862
863 # google surely sets a lot of cookies without my consent
864 $wd->navigate_to ("http://google.com");
865 use Data::Dump;
866 ddx $wd->get_all_cookies;
867
868 =item $cookie = $wd->get_named_cookie ($name)
869
870 Returns a single cookie as a hashref.
871
872 =item $wd->add_cookie ($cookie)
873
874 Adds the given cookie hashref.
875
876 =item $wd->delete_cookie ($name)
877
878 Delete the named cookie.
879
880 =item $wd->delete_all_cookies
881
882 Delete all cookies.
883
884 =cut
885
886 sub get_all_cookies_ {
887 $_[0]->get_ (cookie => $_[1]);
888 }
889
890 sub get_named_cookie_ {
891 $_[0]->get_ ("cookie/$_[1]" => $_[2]);
892 }
893
894 sub add_cookie_ {
895 $_[0]->post_ (cookie => { cookie => $_[1] }, $_[2]);
896 }
897
898 sub delete_cookie_ {
899 $_[0]->delete_ ("cookie/$_[1]" => $_[2]);
900 }
901
902 sub delete_all_cookies_ {
903 $_[0]->delete_ (cookie => $_[2]);
904 }
905
906 =back
907
908 =head3 ACTIONS
909
910 =over
911
912 =cut
913
914 =item $wd->perform_actions ($actions)
915
916 Perform the given actions (an arrayref of action specifications simulating
917 user activity, or an C<AnyEvent::WebDriver::Actions> object). For further
918 details, read the spec or the section L<ACTION LISTS>, below.
919
920 An example to get you started (see the next example for a mostly
921 equivalent example using the C<AnyEvent::WebDriver::Actions> helper API):
922
923 $wd->navigate_to ("https://duckduckgo.com/html");
924 my $input = $wd->find_element ("css selector", 'input[type="text"]');
925 $wd->perform_actions ([
926 {
927 id => "myfatfinger",
928 type => "pointer",
929 pointerType => "touch",
930 actions => [
931 { type => "pointerMove", duration => 100, origin => $input, x => 40, y => 5 },
932 { type => "pointerDown", button => 1 },
933 { type => "pause", duration => 40 },
934 { type => "pointerUp", button => 1 },
935 ],
936 },
937 {
938 id => "mykeyboard",
939 type => "key",
940 actions => [
941 { type => "pause" },
942 { type => "pause" },
943 { type => "pause" },
944 { type => "pause" },
945 { type => "keyDown", value => "a" },
946 { type => "pause", duration => 100 },
947 { type => "keyUp", value => "a" },
948 { type => "pause", duration => 100 },
949 { type => "keyDown", value => "b" },
950 { type => "pause", duration => 100 },
951 { type => "keyUp", value => "b" },
952 { type => "pause", duration => 2000 },
953 { type => "keyDown", value => "\x{E007}" }, # enter
954 { type => "pause", duration => 100 },
955 { type => "keyUp", value => "\x{E007}" }, # enter
956 { type => "pause", duration => 5000 },
957 ],
958 },
959 ]);
960
961 And here is essentially the same (except for fewer pauses) example as
962 above, using the much simpler C<AnyEvent::WebDriver::Actions> API. Note
963 that the pointer up and key down event happen concurrently in this
964 example:
965
966 $wd->navigate_to ("https://duckduckgo.com/html");
967 my $input = $wd->find_element ("css selector", 'input[type="text"]');
968 $wd->actions
969 ->move ($input, 40, 5, "touch1")
970 ->click
971 ->pause # to separate the click from the keypress
972 ->key ("a")
973 ->key ("b")
974 ->pause (2000) # so you can watch leisurely
975 ->key ("{Enter}")
976 ->pause (5000) # so you can see the result
977 ->perform;
978
979 =item $wd->release_actions
980
981 Release all keys and pointer buttons currently depressed.
982
983 =cut
984
985 sub perform_actions_ {
986 if (UNIVERSAL::isa $_[1], AnyEvent::WebDriver::Actions::) {
987 my ($actions, $duration) = $_[1]->compile;
988 local $_[0]{timeout} = $_[0]{timeout} + $duration * 1e-3;
989 $_[0]->post_ (actions => { actions => $actions }, $_[2]);
990 } else {
991 $_[0]->post_ (actions => { actions => $_[1] }, $_[2]);
992 }
993 }
994
995 sub release_actions_ {
996 $_[0]->delete_ (actions => $_[1]);
997 }
998
999 =back
1000
1001 =head3 USER PROMPTS
1002
1003 =over
1004
1005 =cut
1006
1007 =item $wd->dismiss_alert
1008
1009 Dismiss a simple dialog, if present.
1010
1011 =item $wd->accept_alert
1012
1013 Accept a simple dialog, if present.
1014
1015 =item $text = $wd->get_alert_text
1016
1017 Returns the text of any simple dialog.
1018
1019 =item $text = $wd->send_alert_text
1020
1021 Fills in the user prompt with the given text.
1022
1023
1024 =cut
1025
1026 sub dismiss_alert_ {
1027 $_[0]->post_ ("alert/dismiss" => undef, $_[1]);
1028 }
1029
1030 sub accept_alert_ {
1031 $_[0]->post_ ("alert/accept" => undef, $_[1]);
1032 }
1033
1034 sub get_alert_text_ {
1035 $_[0]->get_ ("alert/text" => $_[1]);
1036 }
1037
1038 sub send_alert_text_ {
1039 $_[0]->post_ ("alert/text" => { text => "$_[1]" }, $_[2]);
1040 }
1041
1042 =back
1043
1044 =head3 SCREEN CAPTURE
1045
1046 =over
1047
1048 =cut
1049
1050 =item $wd->take_screenshot
1051
1052 Create a screenshot, returning it as a PNG image in a C<data:> URL.
1053
1054 =item $wd->take_element_screenshot ($element)
1055
1056 Accept a simple dialog, if present.
1057
1058 =cut
1059
1060 sub take_screenshot_ {
1061 $_[0]->get_ (screenshot => $_[1]);
1062 }
1063
1064 sub take_element_screenshot_ {
1065 $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/screenshot" => $_[2]);
1066 }
1067
1068 =back
1069
1070 =head2 ACTION LISTS
1071
1072 Action lists can be quite complicated. Or at least it took a while for
1073 me to twist my head around them. Basically, an action list consists of a
1074 number of sources representing devices (such as a finger, a mouse, a pen
1075 or a keyboard) and a list of actions for each source.
1076
1077 An action can be a key press, a pointer move or a pause (time
1078 delay). Actions from different sources can happen "at the same time",
1079 while actions from a single source are executed in order.
1080
1081 While you can provide an action list manually, it is (hopefully) less
1082 cumbersome to use the API described in this section to create them.
1083
1084 The basic process of creating and performing actions is to create a new
1085 action list, adding action sources, followed by adding actions. Finally
1086 you would C<perform> those actions on the WebDriver.
1087
1088 Virtual time progresses as long as you add actions to the same event
1089 source. Adding events to different sources are considered to happen
1090 concurrently. If you want to force time to progress, you can do this using
1091 a call to C<< ->pause (0) >>.
1092
1093 Most methods here are designed to chain, i.e. they return the web actions
1094 object, to simplify multiple calls.
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 (1, 100)
1101 ->pause # to separate the click from keypressed
1102 ->type ("some text")
1103 ->key ("{Enter}")
1104 ->perform;
1105
1106 By default, keyboard and mouse input sources are provided. You can create
1107 your own sources and use them when adding events. The above example could
1108 be more verbosely written like this:
1109
1110 $wd->actions
1111 ->source ("mouse", "pointer", pointerType => "mouse")
1112 ->source ("kbd", "key")
1113 ->click (1, 100, "mouse")
1114 ->pause
1115 ->type ("some text", "kbd")
1116 ->key ("{Enter}", "kbd")
1117 ->perform;
1118
1119 When you specify the event source explicitly it will switch the current
1120 "focus" for this class of device (all keyboards are in one class, all
1121 pointer-like devices such as mice/fingers/pens are in one class), so you
1122 don't have to specify the source for subsequent actions.
1123
1124 When you use the sources C<keyboard>, C<mouse>, C<touch1>..C<touch3>,
1125 C<pen> without defining them, then a suitable default source will be
1126 created for them.
1127
1128 =over 4
1129
1130 =cut
1131
1132 package AnyEvent::WebDriver::Actions;
1133
1134 =item $al = new AnyEvent::WebDriver::Actions
1135
1136 Create a new empty action list object. More often you would use the C<<
1137 $wd->action_list >> method to create one that is already associated with
1138 a given web driver.
1139
1140 =cut
1141
1142 sub new {
1143 my ($class, %kv) = @_;
1144
1145 $kv{last_kbd} = "keyboard";
1146 $kv{last_ptr} = "mouse";
1147
1148 bless \%kv, $class
1149 }
1150
1151 =item $al = $al->source ($id, $type, key => value...)
1152
1153 The first time you call this with a given ID, this defines the event
1154 source using the extra parameters. Subsequent calls merely switch the
1155 current source for its event class.
1156
1157 It's not an error to define built-in sources (such as C<keyboard> or
1158 C<touch1>) differently then the defaults.
1159
1160 Example: define a new touch device called C<fatfinger>.
1161
1162 $al->source (fatfinger => "pointer", pointerType => "touch");
1163
1164 Example: define a new touch device called C<fatfinger>.
1165
1166 $al->source (fatfinger => "pointer", pointerType => "touch");
1167
1168 Example: switch default keyboard source to C<kbd1>, assuming it is of C<key> class.
1169
1170 $al->source ("kbd1");
1171
1172 =cut
1173
1174 sub _default_source($) {
1175 my ($source) = @_;
1176
1177 $source eq "keyboard" ? { actions => [], id => $source, type => "key" }
1178 : $source eq "mouse" ? { actions => [], id => $source, type => "pointer", pointerType => "mouse" }
1179 : $source eq "touch" ? { actions => [], id => $source, type => "pointer", pointerType => "touch" }
1180 : $source eq "pen" ? { actions => [], id => $source, type => "pointer", pointerType => "pen" }
1181 : Carp::croak "AnyEvent::WebDriver::Actions: event source '$source' not defined"
1182 }
1183
1184 my %source_class = (
1185 key => "kbd",
1186 pointer => "ptr",
1187 );
1188
1189 sub source {
1190 my ($self, $id, $type, %kv) = @_;
1191
1192 if (defined $type) {
1193 !exists $self->{source}{$id}
1194 or Carp::croak "AnyEvent::WebDriver::Actions: source '$id' already defined";
1195
1196 $kv{id} = $id;
1197 $kv{type} = $type;
1198 $kv{actions} = [];
1199
1200 $self->{source}{$id} = \%kv;
1201 }
1202
1203 my $source = $self->{source}{$id} ||= _default_source $id;
1204
1205 my $last = $source_class{$source->{type}} // "xxx";
1206
1207 $self->{"last_$last"} = $id;
1208
1209 $self
1210 }
1211
1212 sub _add {
1213 my ($self, $source, $sourcetype, $type, %kv) = @_;
1214
1215 my $last = \$self->{"last_$sourcetype"};
1216
1217 $source
1218 ? ($$last = $source)
1219 : ($source = $$last);
1220
1221 my $source = $self->{source}{$source} ||= _default_source $source;
1222
1223 my $al = $source->{actions};
1224
1225 push @$al, { type => "pause" }
1226 while @$al < $self->{tick} - 1;
1227
1228 $kv{type} = $type;
1229
1230 push @{ $source->{actions} }, \%kv;
1231
1232 $self->{tick_duration} = $kv{duration}
1233 if $kv{duration} > $self->{tick_duration};
1234
1235 if ($self->{tick} != @$al) {
1236 $self->{tick} = @$al;
1237 $self->{duration} += delete $self->{tick_duration};
1238 }
1239
1240 $self
1241 }
1242
1243 =item $al = $al->pause ($duration)
1244
1245 Creates a pause with the given duration. Makes sure that time progresses
1246 in any case, even when C<$duration> is C<0>.
1247
1248 =cut
1249
1250 sub pause {
1251 my ($self, $duration) = @_;
1252
1253 $self->{tick_duration} = $duration
1254 if $duration > $self->{tick_duration};
1255
1256 $self->{duration} += delete $self->{tick_duration};
1257
1258 # find the source with the longest list
1259
1260 for my $source (values %{ $self->{source} }) {
1261 if (@{ $source->{actions} } == $self->{tick}) {
1262 # this source is one of the longest
1263
1264 # create a pause event only if $duration is non-zero...
1265 push @{ $source->{actions} }, { type => "pause", duration => $duration*1 }
1266 if $duration;
1267
1268 # ... but advance time in any case
1269 ++$self->{tick};
1270
1271 return $self;
1272 }
1273 }
1274
1275 # no event sources are longest. so advance time in any case
1276 ++$self->{tick};
1277
1278 Carp::croak "AnyEvent::WebDriver::Actions: multiple pause calls in a row not (yet) supported"
1279 if $duration;
1280
1281 $self
1282 }
1283
1284 =item $al = $al->pointer_down ($button, $source)
1285
1286 =item $al = $al->pointer_up ($button, $source)
1287
1288 Press or release the given button. C<$button> defaults to C<1>.
1289
1290 =item $al = $al->click ($button, $source)
1291
1292 Convenience function that creates a button press and release action
1293 without any delay between them. C<$button> defaults to C<1>.
1294
1295 =item $al = $al->doubleclick ($button, $source)
1296
1297 Convenience function that creates two button press and release action
1298 pairs in a row, with no unnecessary delay between them. C<$button>
1299 defaults to C<1>.
1300
1301 =cut
1302
1303 sub pointer_down {
1304 my ($self, $button, $source) = @_;
1305
1306 $self->_add ($source, ptr => pointerDown => button => ($button // 1)*1)
1307 }
1308
1309 sub pointer_up {
1310 my ($self, $button, $source) = @_;
1311
1312 $self->_add ($source, ptr => pointerUp => button => ($button // 1)*1)
1313 }
1314
1315 sub click {
1316 my ($self, $button, $source) = @_;
1317
1318 $self
1319 ->pointer_down ($button, $source)
1320 ->pointer_up ($button)
1321 }
1322
1323 sub doubleclick {
1324 my ($self, $button, $source) = @_;
1325
1326 $self
1327 ->click ($button, $source)
1328 ->click ($button)
1329 }
1330
1331 =item $al = $al->move ($button, $origin, $x, $y, $duration, $source)
1332
1333 Moves a pointer to the given position, relative to origin (either
1334 "viewport", "pointer" or an element object.
1335
1336 =cut
1337
1338 sub move {
1339 my ($self, $origin, $x, $y, $duration, $source) = @_;
1340
1341 $self->_add ($source, ptr => pointerMove =>
1342 origin => $origin, x => $x*1, y => $y*1, duration => $duration*1)
1343 }
1344
1345 =item $al = $al->keyDown ($key, $source)
1346
1347 =item $al = $al->keyUp ($key, $source)
1348
1349 Press or release the given key.
1350
1351 =item $al = $al->key ($key, $source)
1352
1353 Peess and release the given key, without unnecessary delay.
1354
1355 A special syntax, C<{keyname}> can be used for special keys - all the special key names from
1356 L<section 17.4.2|https://www.w3.org/TR/webdriver1/#keyboard-actions> of the WebDriver recommendation
1357 can be used.
1358
1359 Example: press and release "a".
1360
1361 $al->key ("a");
1362
1363 Example: press and release the "Enter" key:
1364
1365 $al->key ("\x{e007}");
1366
1367 Example: press and release the "enter" key using the special key name syntax:
1368
1369 $al->key ("{Enter}");
1370
1371 =item $al = $al->type ($string, $source)
1372
1373 Convenience method to simulate a series of key press and release events
1374 for the keys in C<$string>. There is no syntax for special keys,
1375 everything will be typed "as-is" if possible.
1376
1377 =cut
1378
1379 our %SPECIAL_KEY = (
1380 "Unidentified" => 0xE000,
1381 "Cancel" => 0xE001,
1382 "Help" => 0xE002,
1383 "Backspace" => 0xE003,
1384 "Tab" => 0xE004,
1385 "Clear" => 0xE005,
1386 "Return" => 0xE006,
1387 "Enter" => 0xE007,
1388 "Shift" => 0xE008,
1389 "Control" => 0xE009,
1390 "Alt" => 0xE00A,
1391 "Pause" => 0xE00B,
1392 "Escape" => 0xE00C,
1393 " " => 0xE00D,
1394 "PageUp" => 0xE00E,
1395 "PageDown" => 0xE00F,
1396 "End" => 0xE010,
1397 "Home" => 0xE011,
1398 "ArrowLeft" => 0xE012,
1399 "ArrowUp" => 0xE013,
1400 "ArrowRight" => 0xE014,
1401 "ArrowDown" => 0xE015,
1402 "Insert" => 0xE016,
1403 "Delete" => 0xE017,
1404 ";" => 0xE018,
1405 "=" => 0xE019,
1406 "0" => 0xE01A,
1407 "1" => 0xE01B,
1408 "2" => 0xE01C,
1409 "3" => 0xE01D,
1410 "4" => 0xE01E,
1411 "5" => 0xE01F,
1412 "6" => 0xE020,
1413 "7" => 0xE021,
1414 "8" => 0xE022,
1415 "9" => 0xE023,
1416 "*" => 0xE024,
1417 "+" => 0xE025,
1418 "," => 0xE026,
1419 "-" => 0xE027,
1420 "." => 0xE028,
1421 "/" => 0xE029,
1422 "F1" => 0xE031,
1423 "F2" => 0xE032,
1424 "F3" => 0xE033,
1425 "F4" => 0xE034,
1426 "F5" => 0xE035,
1427 "F6" => 0xE036,
1428 "F7" => 0xE037,
1429 "F8" => 0xE038,
1430 "F9" => 0xE039,
1431 "F10" => 0xE03A,
1432 "F11" => 0xE03B,
1433 "F12" => 0xE03C,
1434 "Meta" => 0xE03D,
1435 "ZenkakuHankaku" => 0xE040,
1436 "Shift" => 0xE050,
1437 "Control" => 0xE051,
1438 "Alt" => 0xE052,
1439 "Meta" => 0xE053,
1440 "PageUp" => 0xE054,
1441 "PageDown" => 0xE055,
1442 "End" => 0xE056,
1443 "Home" => 0xE057,
1444 "ArrowLeft" => 0xE058,
1445 "ArrowUp" => 0xE059,
1446 "ArrowRight" => 0xE05A,
1447 "ArrowDown" => 0xE05B,
1448 "Insert" => 0xE05C,
1449 "Delete" => 0xE05D,
1450 );
1451
1452 sub _kv($) {
1453 $_[0] =~ /^\{(.*)\}$/s
1454 ? (exists $SPECIAL_KEY{$1}
1455 ? chr $SPECIAL_KEY{$1}
1456 : Carp::croak "AnyEvent::WebDriver::Actions: special key '$1' not known")
1457 : $_[0]
1458 }
1459
1460 sub key_down {
1461 my ($self, $key, $source) = @_;
1462
1463 $self->_add ($source, kbd => keyDown => value => _kv $key)
1464 }
1465
1466 sub key_up {
1467 my ($self, $key, $source) = @_;
1468
1469 $self->_add ($source, kbd => keyUp => value => _kv $key)
1470 }
1471
1472 sub key {
1473 my ($self, $key, $source) = @_;
1474
1475 $self
1476 ->key_down ($key, $source)
1477 ->key_up ($key)
1478 }
1479
1480 sub type {
1481 my ($self, $string, $source) = @_;
1482
1483 $self->key ($_, $source)
1484 for $string =~ /(\X)/g;
1485
1486 $self
1487 }
1488
1489 =item $al->perform ($wd)
1490
1491 Finalises and compiles the list, if not done yet, and calls C<<
1492 $wd->perform >> with it.
1493
1494 If C<$wd> is undef, and the action list was created using the C<<
1495 $wd->actions >> method, then perform it against that WebDriver object.
1496
1497 There is no underscore variant - call the C<perform_actions_> method with
1498 the action object instead.
1499
1500 =item $al->perform_release ($wd)
1501
1502 Exactly like C<perform>, but additionally call C<release_actions>
1503 afterwards.
1504
1505 =cut
1506
1507 sub perform {
1508 my ($self, $wd) = @_;
1509
1510 ($wd //= $self->{wd})->perform_actions ($self)
1511 }
1512
1513 sub perform_release {
1514 my ($self, $wd) = @_;
1515
1516 ($wd //= $self->{wd})->perform_actions ($self);
1517 $wd->release_actions;
1518 }
1519
1520 =item ($actions, $duration) = $al->compile
1521
1522 Finalises and compiles the list, if not done yet, and returns an actions
1523 object suitable for calls to C<< $wd->perform_actions >>. When called in
1524 list context, additionally returns the total duration of the action list.
1525
1526 Since building large action lists can take nontrivial amounts of time,
1527 it can make sense to build an action list only once and then perform it
1528 multiple times.
1529
1530 Actions must not be added after compiling a list.
1531
1532 =cut
1533
1534 sub compile {
1535 my ($self) = @_;
1536
1537 $self->{duration} += delete $self->{tick_duration};
1538
1539 delete $self->{tick};
1540 delete $self->{last_kbd};
1541 delete $self->{last_ptr};
1542
1543 $self->{actions} ||= [values %{ delete $self->{source} }];
1544
1545 wantarray
1546 ? ($self->{actions}, $self->{duration})
1547 : $self->{actions}
1548 }
1549
1550 =back
1551
1552 =head2 EVENT BASED API
1553
1554 This module wouldn't be a good AnyEvent citizen if it didn't have a true
1555 event-based API.
1556
1557 In fact, the simplified API, as documented above, is emulated via the
1558 event-based API and an C<AUTOLOAD> function that automatically provides
1559 blocking wrappers around the callback-based API.
1560
1561 Every method documented in the L<SIMPLIFIED API> section has an equivalent
1562 event-based method that is formed by appending a underscore (C<_>) to the
1563 method name, and appending a callback to the argument list (mnemonic: the
1564 underscore indicates the "the action is not yet finished" after the call
1565 returns).
1566
1567 For example, instead of a blocking calls to C<new_session>, C<navigate_to>
1568 and C<back>, you can make a callback-based ones:
1569
1570 my $cv = AE::cv;
1571
1572 $wd->new_session ({}, sub {
1573 my ($status, $value) = @_,
1574
1575 die "error $value->{error}" if $status ne "200";
1576
1577 $wd->navigate_to_ ("http://www.nethype.de", sub {
1578
1579 $wd->back_ (sub {
1580 print "all done\n";
1581 $cv->send;
1582 });
1583
1584 });
1585 });
1586
1587 $cv->recv;
1588
1589 While the blocking methods C<croak> on errors, the callback-based ones all
1590 pass two values to the callback, C<$status> and C<$res>, where C<$status>
1591 is the HTTP status code (200 for successful requests, typically 4xx or
1592 5xx for errors), and C<$res> is the value of the C<value> key in the JSON
1593 response object.
1594
1595 Other than that, the underscore variants and the blocking variants are
1596 identical.
1597
1598 =head2 LOW LEVEL API
1599
1600 All the simplified API methods are very thin wrappers around WebDriver
1601 commands of the same name. They are all implemented in terms of the
1602 low-level methods (C<req>, C<get>, C<post> and C<delete>), which exist
1603 in blocking and callback-based variants (C<req_>, C<get_>, C<post_> and
1604 C<delete_>).
1605
1606 Examples are after the function descriptions.
1607
1608 =over
1609
1610 =item $wd->req_ ($method, $uri, $body, $cb->($status, $value))
1611
1612 =item $value = $wd->req ($method, $uri, $body)
1613
1614 Appends the C<$uri> to the C<endpoint/session/{sessionid}/> URL and makes
1615 a HTTP C<$method> request (C<GET>, C<POST> etc.). C<POST> requests can
1616 provide a UTF-8-encoded JSON text as HTTP request body, or the empty
1617 string to indicate no body is used.
1618
1619 For the callback version, the callback gets passed the HTTP status code
1620 (200 for every successful request), and the value of the C<value> key in
1621 the JSON response object as second argument.
1622
1623 =item $wd->get_ ($uri, $cb->($status, $value))
1624
1625 =item $value = $wd->get ($uri)
1626
1627 Simply a call to C<req_> with C<$method> set to C<GET> and an empty body.
1628
1629 =item $wd->post_ ($uri, $data, $cb->($status, $value))
1630
1631 =item $value = $wd->post ($uri, $data)
1632
1633 Simply a call to C<req_> with C<$method> set to C<POST> - if C<$body> is
1634 C<undef>, then an empty object is send, otherwise, C<$data> must be a
1635 valid request object, which gets encoded into JSON for you.
1636
1637 =item $wd->delete_ ($uri, $cb->($status, $value))
1638
1639 =item $value = $wd->delete ($uri)
1640
1641 Simply a call to C<req_> with C<$method> set to C<DELETE> and an empty body.
1642
1643 =cut
1644
1645 =back
1646
1647 Example: implement C<get_all_cookies>, which is a simple C<GET> request
1648 without any parameters:
1649
1650 $cookies = $wd->get ("cookie");
1651
1652 Example: implement C<execute_script>, which needs some parameters:
1653
1654 $results = $wd->post ("execute/sync" => { script => "$javascript", args => [] });
1655
1656 Example: call C<find_elements> to find all C<IMG> elements:
1657
1658 $elems = $wd->post (elements => { using => "css selector", value => "img" });
1659
1660 =cut
1661
1662 =head1 HISTORY
1663
1664 This module was unintentionally created (it started inside some quickly
1665 hacked-together script) simply because I couldn't get the existing
1666 C<Selenium::Remote::Driver> module to work, ever, despite multiple
1667 attempts over the years and trying to report multiple bugs, which have
1668 been completely ignored. It's also not event-based, so, yeah...
1669
1670 =head1 AUTHOR
1671
1672 Marc Lehmann <schmorp@schmorp.de>
1673 http://anyevent.schmorp.de
1674
1675 =cut
1676
1677 1
1678