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