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