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