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