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