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