ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-WebDriver/WebDriver.pm
Revision: 1.10
Committed: Tue Aug 28 23:33:10 2018 UTC (5 years, 9 months ago) by root
Branch: MAIN
CVS Tags: rel-0_1
Changes since 1.9: +2 -1 lines
Log Message:
0.1

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