ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-WebDriver/WebDriver.pm
Revision: 1.42
Committed: Fri Sep 20 20:15:58 2019 UTC (4 years, 8 months ago) by root
Branch: MAIN
CVS Tags: rel-1_0
Changes since 1.41: +1 -1 lines
Log Message:
1.0

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