ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-WebDriver/WebDriver.pm
Revision: 1.35
Committed: Mon Sep 3 20:57:43 2018 UTC (5 years, 9 months ago) by root
Branch: MAIN
Changes since 1.34: +3 -1 lines
Log Message:
*** empty log message ***

File Contents

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