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