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