ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-WebDriver/WebDriver.pm
Revision: 1.8
Committed: Tue Aug 28 23:23:22 2018 UTC (5 years, 9 months ago) by root
Branch: MAIN
Changes since 1.7: +5 -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 or any other w3c-compatible webdriver via the shell
8 $ geckdriver -b myfirefox/firefox --log trace --port 4444
9
10 # then use it
11 use AnyEvent::WebDriver;
12
13 # create a new webdriver object
14 my $wd = new AnyEvent::WebDriver;
15
16 # create a new session with default capabilities.
17 $wd->new_session ({});
18
19 $wd->navigate_to ("https://duckduckgo.com/html");
20 my $searchbox = $wd->find_element ("css selector" => 'input[type="text"]');
21
22 $wd->element_send_keys ($searchbox => "free software");
23 $wd->element_click ($wd->find_element ("css selector" => 'input[type="submit"]'));
24
25 sleep 10;
26
27 =head1 DESCRIPTION
28
29 This module aims to implement the W3C WebDriver specification which is the
30 standardised equivalent to the Selenium WebDriver API., which in turn aims
31 at remotely controlling web browsers such as Firefox or Chromium.
32
33 At the time of this writing, it was only available as a draft document, so
34 changes will be expected. Also, only F<geckodriver> did implement it, or
35 at least, most of it.
36
37 To make most of this module, or, in fact, to make any reasonable use of
38 this module, you would need to refer tot he W3C WebDriver document, which
39 can be found L<here|https://w3c.github.io/webdriver/>:
40
41 https://w3c.github.io/webdriver/
42
43 =cut
44
45 package AnyEvent::WebDriver;
46
47 use common::sense;
48
49 use Carp ();
50 use JSON::XS ();
51 use AnyEvent::HTTP ();
52
53 our $VERSION = 0;
54
55 our $WEB_ELEMENT_IDENTIFIER = "element-6066-11e4-a52e-4f735466cecf";
56
57 my $json = JSON::XS->new
58 ->utf8
59 ->boolean_values (0, 1);
60
61 sub req_ {
62 my ($wdf, $method, $ep, $body, $cb) = @_;
63
64 AnyEvent::HTTP::http_request $method => "$wdf->{_ep}$ep",
65 body => $body,
66 timeout => $self->{timeout},
67 headers => { "content-type" => "application/json; charset=utf-8", "cache-control" => "no-cache" },
68 ($wdf->{proxy} eq "default" ? () : (proxy => $wdf->{proxy})),
69 sub {
70 my ($res, $hdr) = @_;
71
72 $res = eval { $json->decode ($res) };
73 $hdr->{Status} = 500 unless exists $res->{value};
74
75 $cb->($hdr->{Status}, $res->{value});
76 }
77 ;
78 }
79
80 sub get_ {
81 my ($wdf, $ep, $cb) = @_;
82
83 $wdf->req_ (GET => $ep, undef, $cb)
84 }
85
86 sub post_ {
87 my ($wdf, $ep, $data, $cb) = @_;
88
89 $wdf->req_ (POST => $ep, $json->encode ($data || {}), $cb)
90 }
91
92 sub delete_ {
93 my ($wdf, $ep, $cb) = @_;
94
95 $wdf->req_ (DELETE => $ep, "", $cb)
96 }
97
98 sub AUTOLOAD {
99 our $AUTOLOAD;
100
101 $_[0]->isa (__PACKAGE__)
102 or Carp::croak "$AUTOLOAD: no such function";
103
104 (my $name = $AUTOLOAD) =~ s/^.*://;
105
106 my $name_ = "$name\_";
107
108 defined &$name_
109 or Carp::croak "AUTOLOAD: no such method";
110
111 my $func_ = \&$name_;
112
113 *$name = sub {
114 $func_->(@_, my $cv = AE::cv);
115 my ($status, $res) = $cv->recv;
116
117 if ($status ne "200") {
118 my $msg;
119
120 if (exists $res->{error}) {
121 $msg = "AyEvent::WebDriver: $res->{error}: $res->{message}";
122 $msg .= "\n$res->{stacktrace}" if length $res->{stacktrace};
123 } else {
124 $msg = "AnyEvent::WebDriver: http status $status (wrong endpoint?), caught";
125 }
126
127 Carp::croak $msg;
128 }
129
130 $res
131 };
132
133 goto &$name;
134 }
135
136 =head2 CREATING WEBDRIVER OBJECTS
137
138 =over
139
140 =item new AnyEvent::WebDriver key => value...
141
142 Create a new WebDriver object. Example for a remote webdriver connection
143 (the only type supported at the moment):
144
145 my $wd = new AnyEvent::WebDriver host => "localhost", port => 4444;
146
147 Supported keys are:
148
149 =over
150
151 =item endpoint => $string
152
153 For remote connections, the endpoint to connect to (defaults to C<http://localhost:4444>).
154
155 =item proxy => $proxyspec
156
157 The proxy to use (same as the C<proxy> argument used by
158 L<AnyEvent::HTTP>). The default is C<undef>, which disables proxies. To
159 use the system-provided proxy (e.g. C<http_proxy> environment variable),
160 specify a value of C<default>.
161
162 =item autodelete => $boolean
163
164 If true (the default), then automatically execute C<delete_session> when
165 the WebDriver object is destroyed with an active session. IF set to a
166 false value, then the session will continue to exist.
167
168 =item timeout => $seconds
169
170 The HTTP timeout, in (fractional) seconds (default: C<300>, but this will
171 likely drastically reduce). This timeout is reset on any activity, so it
172 is not an overall requiest timeout. Also, individual requests might extend
173 this timeout if they are known to take longer.
174
175 =back
176
177 =cut
178
179 sub new {
180 my ($class, %kv) = @_;
181
182 bless {
183 endpoint => "http://localhost:4444",
184 proxy => undef,
185 autodelete => 1,
186 timeout => 300,
187 %kv,
188 }, $class
189 }
190
191 sub DESTROY {
192 my ($wdf) = @_;
193
194 $wdf->delete_session
195 if exists $wdf->{sid};
196 }
197
198 =back
199
200 =head2 SIMPLIFIED API
201
202 This section documents the simplified API, which is really just a very
203 thin wrapper around the WebDriver protocol commands. They all block (using
204 L<AnyEvent> condvars) the caller until the result is available, so must
205 not be called from an event loop callback - see L<EVENT BASED API> for an
206 alternative.
207
208 The method names are preetty much taken directly from the W3C WebDriver
209 specification, e.g. the request documented in the "Get All Cookies"
210 section is implemented via the C<get_all_cookies> method.
211
212 The order is the same as in the WebDriver draft at the tiome of this
213 writing, and only minimal massaging is done to request parameters and
214 results.
215
216 =head3 SESSIONS
217
218 =over
219
220 =cut
221
222 =item $wd->new_session ({ key => value... })
223
224 Try to connect to a webdriver and initialize session with a "new
225 session" command, passing the given key-value pairs as value
226 (e.g. C<capabilities>).
227
228 No session-dependent methods must be called before this function returns
229 successfully.
230
231 On success, C<< $wd->{sid} >> is set to the session ID, and C<<
232 $wd->{capabilities} >> is set to the returned capabilities.
233
234 my $wd = new AnyEvent::Selenium endpoint => "http://localhost:4545";
235
236 $wd->new_session ({
237 capabilities => {
238 pageLoadStrategy => "normal",
239 }.
240 });
241
242 =cut
243
244 sub new_session_ {
245 my ($wdf, $kv, $cb) = @_;
246
247 local $wdf->{_ep} = "$wdf->{endpoint}/";
248 $wdf->post_ (session => $kv, sub {
249 my ($status, $res) = @_;
250
251 if ($status eq "200") {
252 $wdf->{sid} = $res->{sessionId};
253 $wdf->{capabilities} = $res->{capabilities};
254
255 $wdf->{_ep} = "$wdf->{endpoint}/session/$wdf->{sid}/";
256 }
257
258 $cb->($status, $res);
259 });
260 }
261
262 =item $wd->delete_session
263
264 Deletes the session - the WebDriver object must not be used after this
265 call.
266
267 =cut
268
269 sub delete_session_ {
270 my ($wdf, $cb) = @_;
271
272 local $wdf->{_ep} = "$wdf->{endpoint}/session/$wdf->{sid}";
273 $wdf->delete_ ("" => $cb);
274 }
275
276 =item $timeouts = $wd->get_timeouts
277
278 Get the current timeouts, e.g.:
279
280 my $timeouts = $wd->get_timeouts;
281 => { implicit => 0, pageLoad => 300000, script => 30000 }
282
283 =item $wd->set_timeouts ($timeouts)
284
285 Sets one or more timeouts, e.g.:
286
287 $wd->set_timeouts ({ script => 60000 });
288
289 =cut
290
291 sub get_timeouts_ {
292 $_[0]->get_ (timeouts => $_[1], $_[2]);
293 }
294
295 sub set_timeouts_ {
296 $_[0]->post_ (timeouts => $_[1], $_[2], $_[3]);
297 }
298
299 =back
300
301 =head3 NAVIGATION
302
303 =over
304
305 =cut
306
307 =item $wd->navigate_to ($url)
308
309 Navigates to the specified URL.
310
311 =item $url = $wd->get_current_url
312
313 Queries the current page URL as set by C<navigate_to>.
314
315 =cut
316
317 sub navigate_to_ {
318 $_[0]->post_ (url => { url => "$_[1]" }, $_[2]);
319 }
320
321 sub get_current_url_ {
322 $_[0]->get_ (url => $_[1])
323 }
324
325 =item $wd->back
326
327 The equivalent of pressing "back" in the browser.
328
329 =item $wd->forward
330
331 The equivalent of pressing "forward" in the browser.
332
333 =item $wd->refresh
334
335 The equivalent of pressing "refresh" in the browser.
336
337 =cut
338
339 sub back_ {
340 $_[0]->post_ (back => undef, $_[1]);
341 }
342
343 sub forward_ {
344 $_[0]->post_ (forward => undef, $_[1]);
345 }
346
347 sub refresh_ {
348 $_[0]->post_ (refresh => undef, $_[1]);
349 }
350
351 =item $title = $wd->get_title
352
353 Returns the current document title.
354
355 =cut
356
357 sub get_title_ {
358 $_[0]->get_ (title => $_[1]);
359 }
360
361 =back
362
363 =head3 COMMAND CONTEXTS
364
365 =over
366
367 =cut
368
369 =item $handle = $wd->get_window_handle
370
371 Returns the current window handle.
372
373 =item $wd->close_window
374
375 Closes the current browsing context.
376
377 =item $wd->switch_to_window ($handle)
378
379 Changes the current browsing context to the given window.
380
381 =cut
382
383 sub get_window_handle_ {
384 $_[0]->get_ (window => $_[1]);
385 }
386
387 sub close_window_ {
388 $_[0]->delete_ (window => $_[1]);
389 }
390
391 sub switch_to_window_ {
392 $_[0]->post_ (window => "$_[1]", $_[2]);
393 }
394
395 =item $handles = $wd->get_window_handles
396
397 Return the current window handles as an array-ref of handle IDs.
398
399 =cut
400
401 sub get_window_handles_ {
402 $_[0]->get_ ("window/handles" => $_[1]);
403 }
404
405 =item $handles = $wd->switch_to_frame ($frame)
406
407 Switch to the given frame identified by C<$frame>, which must be either
408 C<undef> to go back to the top-level browsing context, an integer to
409 select the nth subframe, or an element object (as e.g. returned by the
410 C<element_object> method.
411
412 =cut
413
414 sub switch_to_frame_ {
415 $_[0]->post_ (frame => { id => "$_[1]" }, $_[2]);
416 }
417
418 =item $handles = $wd->switch_to_parent_frame
419
420 Switch to the parent frame.
421
422 =cut
423
424 sub switch_to_parent_frame_ {
425 $_[0]->post_ ("frame/parent" => undef, $_[1]);
426 }
427
428 =item $rect = $wd->get_window_rect
429
430 Return the current window rect, e.g.:
431
432 $rect = $wd->get_window_rect
433 => { height => 1040, width => 540, x => 0, y => 0 }
434
435 =item $wd->set_window_rect ($rect)
436
437 Sets the window rect.
438
439 =cut
440
441 sub get_window_rect_ {
442 $_[0]->get_ ("window/rect" => $_[1]);
443 }
444
445 sub set_window_rect_ {
446 $_[0]->post_ ("window/rect" => $_[1], $_[2]);
447 }
448
449 =item $wd->maximize_window
450
451 =item $wd->minimize_window
452
453 =item $wd->fullscreen_window
454
455 Changes the window size by either maximising, minimising or making it
456 fullscreen. In my experience, this might timeout if no window manager is
457 running.
458
459 =cut
460
461 sub maximize_window_ {
462 $_[0]->post_ ("window/maximize" => undef, $_[1]);
463 }
464
465 sub minimize_window_ {
466 $_[0]->post_ ("window/minimize" => undef, $_[1]);
467 }
468
469 sub fullscreen_window_ {
470 $_[0]->post_ ("window/fullscreen" => undef, $_[1]);
471 }
472
473 =back
474
475 =head3 ELEMENT RETRIEVAL
476
477 =over
478
479 =cut
480
481 =item $element_id = $wd->find_element ($location_strategy, $selector)
482
483 Finds the first element specified by the given selector and returns its
484 web element ID (the strong, not the object from the protocol). Raises an
485 error when no element was found.
486
487 $element = $wd->find_element ("css selector" => "body a");
488 $element = $wd->find_element ("link text" => "Click Here For Porn");
489 $element = $wd->find_element ("partial link text" => "orn");
490 $element = $wd->find_element ("tag name" => "input");
491 $element = $wd->find_element ("xpath" => '//input[@type="text"]');
492 => e.g. "decddca8-5986-4e1d-8c93-efe952505a5f"
493
494 =item $element_ids = $wd->find_elements ($location_strategy, $selector)
495
496 As above, but returns an arrayref of all found element IDs.
497
498 =item $element_id = $wd->find_element_from_element ($element_id, $location_strategy, $selector)
499
500 Like C<find_element>, but looks only inside the specified C<$element>.
501
502 =item $element_ids = $wd->find_elements_from_element ($element_id, $location_strategy, $selector)
503
504 Like C<find_elements>, but looks only inside the specified C<$element>.
505
506 my $head = $wd->find_element ("tag name" => "head");
507 my $links = $wd->find_elements_from_element ($head, "tag name", "link");
508
509 =item $element_id = $wd->get_active_element
510
511 Returns the active element.
512
513 =cut
514
515 sub find_element_ {
516 my $cb = pop;
517 $_[0]->post_ (element => { using => "$_[1]", value => "$_[2]" }, sub {
518 $cb->($_[0], $_[0] ne "200" ? $_[1] : $_[1]{$WEB_ELEMENT_IDENTIFIER})
519 });
520 }
521
522 sub find_elements_ {
523 my $cb = pop;
524 $_[0]->post_ (elements => { using => "$_[1]", value => "$_[2]" }, sub {
525 $cb->($_[0], $_[0] ne "200" ? $_[1] : [ map $_->{$WEB_ELEMENT_IDENTIFIER}, @{$_[1]} ]);
526 });
527 }
528
529 sub find_element_from_element_ {
530 my $cb = pop;
531 $_[0]->post_ ("element/$_[1]/element" => { using => "$_[2]", value => "$_[3]" }, sub {
532 $cb->($_[0], $_[0] ne "200" ? $_[1] : $_[1]{$WEB_ELEMENT_IDENTIFIER})
533 });
534 }
535
536 sub find_elements_from_element_ {
537 my $cb = pop;
538 $_[0]->post_ ("element/$_[1]/elements" => { using => "$_[2]", value => "$_[3]" }, sub {
539 $cb->($_[0], $_[0] ne "200" ? $_[1] : [ map $_->{$WEB_ELEMENT_IDENTIFIER}, @{$_[1]} ]);
540 });
541 }
542
543 sub get_active_element_ {
544 my $cb = pop;
545 $_[0]->get_ ("element/active" => sub {
546 $cb->($_[0], $_[0] ne "200" ? $_[1] : $_[1]{$WEB_ELEMENT_IDENTIFIER})
547 });
548 }
549
550 =back
551
552 =head3 ELEMENT STATE
553
554 =over
555
556 =cut
557
558 =item $bool = $wd->is_element_selected
559
560 Returns whether the given input or option element is selected or not.
561
562 =item $string = $wd->get_element_attribute ($element_id, $name)
563
564 Returns the value of the given attribute.
565
566 =item $string = $wd->get_element_property ($element_id, $name)
567
568 Returns the value of the given property.
569
570 =item $string = $wd->get_element_css_value ($element_id, $name)
571
572 Returns the value of the given css value.
573
574 =item $string = $wd->get_element_text ($element_id)
575
576 Returns the (rendered) text content of the given element.
577
578 =item $string = $wd->get_element_tag_name ($element_id)
579
580 Returns the tag of the given element.
581
582 =item $rect = $wd->get_element_rect ($element_id)
583
584 Returns the element rect of the given element.
585
586 =item $bool = $wd->is_element_enabled
587
588 Returns whether the element is enabled or not.
589
590 =cut
591
592 sub is_element_selected_ {
593 $_[0]->get_ ("element/$_[1]/selected" => $_[2]);
594 }
595
596 sub get_element_attribute_ {
597 $_[0]->get_ ("element/$_[1]/attribute/$_[2]" => $_[3]);
598 }
599
600 sub get_element_property_ {
601 $_[0]->get_ ("element/$_[1]/property/$_[2]" => $_[3]);
602 }
603
604 sub get_element_css_value_ {
605 $_[0]->get_ ("element/$_[1]/css/$_[2]" => $_[3]);
606 }
607
608 sub get_element_text_ {
609 $_[0]->get_ ("element/$_[1]/text" => $_[2]);
610 }
611
612 sub get_element_tag_name_ {
613 $_[0]->get_ ("element/$_[1]/name" => $_[2]);
614 }
615
616 sub get_element_rect_ {
617 $_[0]->get_ ("element/$_[1]/rect" => $_[2]);
618 }
619
620 sub is_element_enabled_ {
621 $_[0]->get_ ("element/$_[1]/enabled" => $_[2]);
622 }
623
624 =back
625
626 =head3 ELEMENT INTERACTION
627
628 =over
629
630 =cut
631
632 =item $wd->element_click ($element_id)
633
634 Clicks the given element.
635
636 =item $wd->element_clear ($element_id)
637
638 Clear the contents of the given element.
639
640 =item $wd->element_send_keys ($element_id, $text)
641
642 Sends the given text as key events to the given element.
643
644 =cut
645
646 sub element_click_ {
647 $_[0]->post_ ("element/$_[1]/click" => undef, $_[2]);
648 }
649
650 sub element_clear_ {
651 $_[0]->post_ ("element/$_[1]/clear" => undef, $_[2]);
652 }
653
654 sub element_send_keys_ {
655 $_[0]->post_ ("element/$_[1]/value" => { text => "$_[2]" }, $_[3]);
656 }
657
658 =back
659
660 =head3 DOCUMENT HANDLING
661
662 =over
663
664 =cut
665
666 =item $source = $wd->get_page_source
667
668 Returns the (HTML/XML) page source of the current document.
669
670 =item $results = $wd->execute_script ($javascript, $args)
671
672 Synchronously execute the given script with given arguments and return its
673 results (C<$args> can be C<undef> if no arguments are wanted/needed).
674
675 $ten = $wd->execute_script ("return arguments[0]+arguments[1]", [3, 7]);
676
677 =item $results = $wd->execute_async_script ($javascript, $args)
678
679 Similar to C<execute_script>, but doesn't wait for script to return, but
680 instead waits for the script to call its last argument, which is added to
681 C<$args> automatically.
682
683 $twenty = $wd->execute_async_script ("arguments[0](20)", undef);
684
685 =cut
686
687 sub get_page_source_ {
688 $_[0]->get_ (source => $_[1]);
689 }
690
691 sub execute_script_ {
692 $_[0]->post_ ("execute/sync" => { script => "$_[1]", args => $_[2] || [] }, $_[3]);
693 }
694
695 sub execute_async_script_ {
696 $_[0]->post_ ("execute/async" => { script => "$_[1]", args => $_[2] || [] }, $_[3]);
697 }
698
699 =back
700
701 =head3 COOKIES
702
703 =over
704
705 =cut
706
707 =item $cookies = $wd->get_all_cookies
708
709 Returns all cookies, as an arrayref of hashrefs.
710
711 # google surely sets a lot of cookies without my consent
712 $wd->navigate_to ("http://google.com");
713 use Data::Dump;
714 ddx $wd->get_all_cookies;
715
716 =item $cookie = $wd->get_named_cookie ($name)
717
718 Returns a single cookie as a hashref.
719
720 =item $wd->add_cookie ($cookie)
721
722 Adds the given cookie hashref.
723
724 =item $wd->delete_cookie ($name)
725
726 Delete the named cookie.
727
728 =item $wd->delete_all_cookies
729
730 Delete all cookies.
731
732 =cut
733
734 sub get_all_cookies_ {
735 $_[0]->get_ (cookie => $_[1]);
736 }
737
738 sub get_named_cookie_ {
739 $_[0]->get_ ("cookie/$_[1]" => $_[2]);
740 }
741
742 sub add_cookie_ {
743 $_[0]->post_ (cookie => { cookie => $_[1] }, $_[2]);
744 }
745
746 sub delete_cookie_ {
747 $_[0]->delete_ ("cookie/$_[1]" => $_[2]);
748 }
749
750 sub delete_all_cookies_ {
751 $_[0]->delete_ (cookie => $_[2]);
752 }
753
754 =back
755
756 =head3 ACTIONS
757
758 =over
759
760 =cut
761
762 =item $wd->perform_actions ($actions)
763
764 Perform the given actions (an arrayref of action specifications simulating
765 user activity). For further details, read the spec.
766
767 An example to get you started:
768
769 $wd->navigate_to ("https://duckduckgo.com/html");
770 $wd->set_timeouts ({ implicit => 10000 });
771 my $input = $wd->find_element ("css selector", 'input[type="text"]');
772 $wd->perform_actions ([
773 {
774 id => "myfatfinger",
775 type => "pointer",
776 pointerType => "touch",
777 actions => [
778 { type => "pointerMove", duration => 100, origin => $wd->element_object ($input), x => 40, y => 5 },
779 { type => "pointerDown", button => 1 },
780 { type => "pause", duration => 40 },
781 { type => "pointerUp", button => 1 },
782 ],
783 },
784 {
785 id => "mykeyboard",
786 type => "key",
787 actions => [
788 { type => "pause" },
789 { type => "pause" },
790 { type => "pause" },
791 { type => "pause" },
792 { type => "keyDown", value => "a" },
793 { type => "pause", duration => 100 },
794 { type => "keyUp", value => "a" },
795 { type => "pause", duration => 100 },
796 { type => "keyDown", value => "b" },
797 { type => "pause", duration => 100 },
798 { type => "keyUp", value => "b" },
799 { type => "pause", duration => 2000 },
800 { type => "keyDown", value => "\x{E007}" }, # enter
801 { type => "pause", duration => 100 },
802 { type => "keyUp", value => "\x{E007}" }, # enter
803 { type => "pause", duration => 5000 },
804 ],
805 },
806 ]);
807
808 =item $wd->release_actions
809
810 Release all keys and pointer buttons currently depressed.
811
812 =cut
813
814 sub perform_actions_ {
815 $_[0]->post_ (actions => { actions => $_[1] }, $_[2]);
816 }
817
818 sub release_actions_ {
819 $_[0]->delete_ (actions => $_[1]);
820 }
821
822 =back
823
824 =head3 USER PROMPTS
825
826 =over
827
828 =cut
829
830 =item $wd->dismiss_alert
831
832 Dismiss a simple dialog, if present.
833
834 =item $wd->accept_alert
835
836 Accept a simple dialog, if present.
837
838 =item $text = $wd->get_alert_text
839
840 Returns the text of any simple dialog.
841
842 =item $text = $wd->send_alert_text
843
844 Fills in the user prompt with the given text.
845
846
847 =cut
848
849 sub dismiss_alert_ {
850 $_[0]->post_ ("alert/dismiss" => undef, $_[1]);
851 }
852
853 sub accept_alert_ {
854 $_[0]->post_ ("alert/accept" => undef, $_[1]);
855 }
856
857 sub get_alert_text_ {
858 $_[0]->get_ ("alert/text" => $_[1]);
859 }
860
861 sub send_alert_text_ {
862 $_[0]->post_ ("alert/text" => { text => "$_[1]" }, $_[2]);
863 }
864
865 =back
866
867 =head3 SCREEN CAPTURE
868
869 =over
870
871 =cut
872
873 =item $wd->take_screenshot
874
875 Create a screenshot, returning it as a PNG image in a data url.
876
877 =item $wd->take_element_screenshot ($element_id)
878
879 Accept a simple dialog, if present.
880
881 =cut
882
883 sub take_screenshot_ {
884 $_[0]->get_ (screenshot => $_[1]);
885 }
886
887 sub take_element_screenshot_ {
888 $_[0]->get_ ("element/$_[1]/screenshot" => $_[2]);
889 }
890
891 =back
892
893 =head2 HELPER METHODS
894
895 =over
896
897 =cut
898
899 =item $object = $wd->element_object ($element_id)
900
901 Encoding element ids in data structures is done by represetning them as an
902 object with a special key and the element ID as value. This helper method
903 does this for you.
904
905 =cut
906
907 sub element_object {
908 +{ $WEB_ELEMENT_IDENTIFIER => $_[1] }
909 }
910
911 =back
912
913 =head2 EVENT BASED API
914
915 This module wouldn't be a good AnyEvent citizen if it didn't have a true
916 event-based API.
917
918 In fact, the simplified API, as documented above, is emulated via the
919 event-based API and an C<AUTOLOAD> function that automatically provides
920 blocking wrappers around the callback-based API.
921
922 Every method documented in the L<SIMPLIFIED API> section has an equivalent
923 event-based method that is formed by appending a underscore (C<_>) to the
924 method name, and appending a callback to the argument list (mnemonic: the
925 underscore indicates the "the action is not yet finished" after the call
926 returns).
927
928 For example, instead of a blocking calls to C<new_session>, C<navigate_to>
929 and C<back>, you can make a callback-based ones:
930
931 my $cv = AE::cv;
932
933 $wd->new_session ({}, sub {
934 my ($status, $value) = @_,
935
936 die "error $value->{error}" if $status ne "200";
937
938 $wd->navigate_to_ ("http://www.nethype.de", sub {
939
940 $wd->back_ (sub {
941 print "all done\n";
942 $cv->send;
943 });
944
945 });
946 });
947
948 $cv->recv;
949
950 While the blocking methods C<croak> on errors, the callback-based ones all
951 pass two values to the callback, C<$status> and C<$res>, where C<$status>
952 is the HTTP status code (200 for successful requests, typically 4xx ot
953 5xx for errors), and C<$res> is the value of the C<value> key in the JSON
954 response object.
955
956 Other than that, the underscore variants and the blocking variants are
957 identical.
958
959 =head2 LOW LEVEL API
960
961 All the simplfiied API methods are very thin wrappers around WebDriver
962 commands of the same name. Theyx are all implemented in terms of the
963 low-level methods (C<req>, C<get>, C<post> and C<delete>), which exists
964 in blocking and callback-based variants (C<req_>, C<get_>, C<post_> and
965 C<delete_>).
966
967 Examples are after the function descriptions.
968
969 =over
970
971 =item $wd->req_ ($method, $uri, $body, $cb->($status, $value))
972
973 =item $value = $wd->req ($method, $uri, $body)
974
975 Appends the C<$uri> to the C<endpoint/session/{sessionid}/> URL and makes
976 a HTTP C<$method> request (C<GET>, C<POST> etc.). C<POST> requests can
977 provide a UTF-8-encoded JSON text as HTTP request body, or the empty
978 string to indicate no body is used.
979
980 For the callback version, the callback gets passed the HTTP status code
981 (200 for every successful request), and the value of the C<value> key in
982 the JSON response object as second argument.
983
984 =item $wd->get_ ($uri, $cb->($status, $value))
985
986 =item $value = $wd->get ($uri)
987
988 Simply a call to C<req_> with C<$method> set to C<GET> and an empty body.
989
990 =item $wd->post_ ($uri, $data, $cb->($status, $value))
991
992 =item $value = $wd->post ($uri, $data)
993
994 Simply a call to C<req_> with C<$method> set to C<POST> - if C<$body> is
995 C<undef>, then an empty object is send, otherwise, C<$data> must be a
996 valid request object, which gets encoded into JSON for you.
997
998 =item $wd->delete_ ($uri, $cb->($status, $value))
999
1000 =item $value = $wd->delete ($uri)
1001
1002 Simply a call to C<req_> with C<$method> set to C<DELETE> and an empty body.
1003
1004 =cut
1005
1006 =back
1007
1008 Example: implement C<get_all_cookies>, which is a simple C<GET> request
1009 without any parameters:
1010
1011 $cookies = $wd->get ("cookie");
1012
1013 Example: implement C<execute_script>, which needs some parameters:
1014
1015 $results = $wd->post ("execute/sync" => { script => "$javascript", args => [] });
1016
1017 Example: call C<find_elements> to find all C<IMG> elements, stripping the
1018 returned element objects to only return the element ID strings:
1019
1020 my $elems = $wd->post (elements => { using => "css selector", value => "img" });
1021
1022 # yes, the W3C found an interesting way around the typelessness of JSON
1023 $_ = $_->{"element-6066-11e4-a52e-4f735466cecf"}
1024 for @$elems;
1025
1026 =cut
1027
1028 =head1 HISTORY
1029
1030 This module was unintentionally created (it started inside some quickly
1031 hacked-together script) simply because I couldn't get the existing
1032 C<Selenium::Remote::Driver> module to work, ever, despite multiple
1033 attempts over the years and trying to report multiple bugs, which have
1034 been completely ignored. It's also not event-based, so, yeah...
1035
1036 =head1 AUTHOR
1037
1038 Marc Lehmann <schmorp@schmorp.de>
1039 http://anyevent.schmorp.de
1040
1041 =cut
1042
1043 1
1044