ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-WebDriver/WebDriver.pm
Revision: 1.26
Committed: Fri Aug 31 02:44:31 2018 UTC (5 years, 9 months ago) by root
Branch: MAIN
CVS Tags: rel-0_9
Changes since 1.25: +5 -3 lines
Log Message:
0.9

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