ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-WebDriver/WebDriver.pm
Revision: 1.5
Committed: Tue Aug 28 23:10:30 2018 UTC (5 years, 9 months ago) by root
Branch: MAIN
Changes since 1.4: +2 -3 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 czurrent 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.
408
409 =cut
410
411 sub switch_to_frame_ {
412 $_[0]->post_ (frame => { id => "$_[1]" }, $_[2]);
413 }
414
415 =item $handles = $wd->switch_to_parent_frame
416
417 Switch to the parent frame.
418
419 =cut
420
421 sub switch_to_parent_frame_ {
422 $_[0]->post_ ("frame/parent" => undef, $_[1]);
423 }
424
425 =item $rect = $wd->get_window_rect
426
427 Return the current window rect, e.g.:
428
429 $rect = $wd->get_window_rect
430 => { height => 1040, width => 540, x => 0, y => 0 }
431
432 =item $wd->set_window_rect ($rect)
433
434 Sets the window rect.
435
436 =cut
437
438 sub get_window_rect_ {
439 $_[0]->get_ ("window/rect" => $_[1]);
440 }
441
442 sub set_window_rect_ {
443 $_[0]->post_ ("window/rect" => $_[1], $_[2]);
444 }
445
446 =item $wd->maximize_window
447
448 =item $wd->minimize_window
449
450 =item $wd->fullscreen_window
451
452 Changes the window size by either maximising, minimising or making it
453 fullscreen. In my experience, this might timeout if no window manager is
454 running.
455
456 =cut
457
458 sub maximize_window_ {
459 $_[0]->post_ ("window/maximize" => undef, $_[1]);
460 }
461
462 sub minimize_window_ {
463 $_[0]->post_ ("window/minimize" => undef, $_[1]);
464 }
465
466 sub fullscreen_window_ {
467 $_[0]->post_ ("window/fullscreen" => undef, $_[1]);
468 }
469
470 =back
471
472 =head3 ELEMENT RETRIEVAL
473
474 =over
475
476 =cut
477
478 =item $element_id = $wd->find_element ($location_strategy, $selector)
479
480 Finds the first element specified by the given selector and returns its
481 web element id (the strong, not the object from the protocol). Raises an
482 error when no element was found.
483
484 $element = $wd->find_element ("css selector" => "body a");
485 $element = $wd->find_element ("link text" => "Click Here For Porn");
486 $element = $wd->find_element ("partial link text" => "orn");
487 $element = $wd->find_element ("tag name" => "input");
488 $element = $wd->find_element ("xpath" => '//input[@type="text"]');
489 # "decddca8-5986-4e1d-8c93-efe952505a5f"
490
491 =item $element_ids = $wd->find_elements ($location_strategy, $selector)
492
493 As above, but returns an arrayref of all found element IDs.
494
495 =item $element_id = $wd->find_element_from_element ($element_id, $location_strategy, $selector)
496
497 Like C<find_element>, but looks only inside the specified C<$element>.
498
499 =item $element_ids = $wd->find_elements_from_element ($element_id, $location_strategy, $selector)
500
501 Like C<find_elements>, but looks only inside the specified C<$element>.
502
503 my $head = $wd->find_element ("tag name" => "head");
504 my $links = $wd->find_elements_from_element ($head, "tag name", "link");
505
506 =item $element_id = $wd->get_active_element
507
508 Returns the active element.
509
510 =cut
511
512 sub find_element_ {
513 my $cb = pop;
514 $_[0]->post_ (element => { using => "$_[1]", value => "$_[2]" }, sub {
515 $cb->($_[0], $_[0] ne "200" ? $_[1] : $_[1]{$WEB_ELEMENT_IDENTIFIER})
516 });
517 }
518
519 sub find_elements_ {
520 my $cb = pop;
521 $_[0]->post_ (elements => { using => "$_[1]", value => "$_[2]" }, sub {
522 $cb->($_[0], $_[0] ne "200" ? $_[1] : [ map $_->{$WEB_ELEMENT_IDENTIFIER}, @{$_[1]} ]);
523 });
524 }
525
526 sub find_element_from_element_ {
527 my $cb = pop;
528 $_[0]->post_ ("element/$_[1]/element" => { using => "$_[2]", value => "$_[3]" }, sub {
529 $cb->($_[0], $_[0] ne "200" ? $_[1] : $_[1]{$WEB_ELEMENT_IDENTIFIER})
530 });
531 }
532
533 sub find_elements_from_element_ {
534 my $cb = pop;
535 $_[0]->post_ ("element/$_[1]/elements" => { using => "$_[2]", value => "$_[3]" }, sub {
536 $cb->($_[0], $_[0] ne "200" ? $_[1] : [ map $_->{$WEB_ELEMENT_IDENTIFIER}, @{$_[1]} ]);
537 });
538 }
539
540 sub get_active_element_ {
541 my $cb = pop;
542 $_[0]->get_ ("element/active" => sub {
543 $cb->($_[0], $_[0] ne "200" ? $_[1] : $_[1]{$WEB_ELEMENT_IDENTIFIER})
544 });
545 }
546
547 =back
548
549 =head3 ELEMENT STATE
550
551 =over
552
553 =cut
554
555 =item $bool = $wd->is_element_selected
556
557 Returns whether the given input or option element is selected or not.
558
559 =item $string = $wd->get_element_attribute ($element_id, $name)
560
561 Returns the value of the given attribute.
562
563 =item $string = $wd->get_element_property ($element_id, $name)
564
565 Returns the value of the given property.
566
567 =item $string = $wd->get_element_css_value ($element_id, $name)
568
569 Returns the value of the given css value.
570
571 =item $string = $wd->get_element_text ($element_id)
572
573 Returns the (rendered) text content of the given element.
574
575 =item $string = $wd->get_element_tag_name ($element_id)
576
577 Returns the tag of the given element.
578
579 =item $rect = $wd->get_element_rect ($element_id)
580
581 Returns the element rect of the given element.
582
583 =item $bool = $wd->is_element_enabled
584
585 Returns whether the element is enabled or not.
586
587 =cut
588
589 sub is_element_selected_ {
590 $_[0]->get_ ("element/$_[1]/selected" => $_[2]);
591 }
592
593 sub get_element_attribute_ {
594 $_[0]->get_ ("element/$_[1]/attribute/$_[2]" => $_[3]);
595 }
596
597 sub get_element_property_ {
598 $_[0]->get_ ("element/$_[1]/property/$_[2]" => $_[3]);
599 }
600
601 sub get_element_css_value_ {
602 $_[0]->get_ ("element/$_[1]/css/$_[2]" => $_[3]);
603 }
604
605 sub get_element_text_ {
606 $_[0]->get_ ("element/$_[1]/text" => $_[2]);
607 }
608
609 sub get_element_tag_name_ {
610 $_[0]->get_ ("element/$_[1]/name" => $_[2]);
611 }
612
613 sub get_element_rect_ {
614 $_[0]->get_ ("element/$_[1]/rect" => $_[2]);
615 }
616
617 sub is_element_enabled_ {
618 $_[0]->get_ ("element/$_[1]/enabled" => $_[2]);
619 }
620
621 =back
622
623 =head3 ELEMENT INTERACTION
624
625 =over
626
627 =cut
628
629 =item $wd->element_click ($element_id)
630
631 Clicks the given element.
632
633 =item $wd->element_clear ($element_id)
634
635 Clear the contents of the given element.
636
637 =item $wd->element_send_keys ($element_id, $text)
638
639 Sends the given text as key events to the given element.
640
641 =cut
642
643 sub element_click_ {
644 $_[0]->post_ ("element/$_[1]/click" => undef, $_[2]);
645 }
646
647 sub element_clear_ {
648 $_[0]->post_ ("element/$_[1]/clear" => undef, $_[2]);
649 }
650
651 sub element_send_keys_ {
652 $_[0]->post_ ("element/$_[1]/value" => { text => "$_[2]" }, $_[3]);
653 }
654
655 =back
656
657 =head3 DOCUMENT HANDLING
658
659 =over
660
661 =cut
662
663 =item $source = $wd->get_page_source
664
665 Returns the (HTML/XML) page source of the current document.
666
667 =item $results = $wd->execute_script ($javascript, $args)
668
669 Synchronously execute the given script with given arguments and return its
670 results (C<$args> can be C<undef> if no arguments are wanted/needed).
671
672 $ten = $wd->execute_script ("return arguments[0]+arguments[1]", [3, 7]);
673
674 =item $results = $wd->execute_async_script ($javascript, $args)
675
676 Similar to C<execute_script>, but doesn't wait for script to return, but
677 instead waits for the script to call its last argument, which is added to
678 C<$args> automatically.
679
680 $twenty = $wd->execute_async_script ("arguments[0](20)", undef);
681
682 =cut
683
684 sub get_page_source_ {
685 $_[0]->get_ (source => $_[1]);
686 }
687
688 sub execute_script_ {
689 $_[0]->post_ ("execute/sync" => { script => "$_[1]", args => $_[2] || [] }, $_[3]);
690 }
691
692 sub execute_async_script_ {
693 $_[0]->post_ ("execute/async" => { script => "$_[1]", args => $_[2] || [] }, $_[3]);
694 }
695
696 =back
697
698 =head3 COOKIES
699
700 =over
701
702 =cut
703
704 =item $cookies = $wd->get_all_cookies
705
706 Returns all cookies, as an arrayref of hashrefs.
707
708 # google surely sets a lot of cookies without my consent
709 $wd->navigate_to ("http://google.com");
710 use Data::Dump;
711 ddx $wd->get_all_cookies;
712
713 =item $cookie = $wd->get_named_cookie ($name)
714
715 Returns a single cookie as a hashref.
716
717 =item $wd->add_cookie ($cookie)
718
719 Adds the given cookie hashref.
720
721 =item $wd->delete_cookie ($name)
722
723 Delete the named cookie.
724
725 =item $wd->delete_all_cookies
726
727 Delete all cookies.
728
729 =cut
730
731 sub get_all_cookies_ {
732 $_[0]->get_ (cookie => $_[1]);
733 }
734
735 sub get_named_cookie_ {
736 $_[0]->get_ ("cookie/$_[1]" => $_[2]);
737 }
738
739 sub add_cookie_ {
740 $_[0]->post_ (cookie => { cookie => $_[1] }, $_[2]);
741 }
742
743 sub delete_cookie_ {
744 $_[0]->delete_ ("cookie/$_[1]" => $_[2]);
745 }
746
747 sub delete_all_cookies_ {
748 $_[0]->delete_ (cookie => $_[2]);
749 }
750
751 =back
752
753 =head3 ACTIONS
754
755 =over
756
757 =cut
758
759 =item $wd->perform_actions ($actions)
760
761 Perform the given actions (an arrayref of action specifications simulating
762 user activity). For further details, read the spec.
763
764 An example to get you started:
765
766 $wd->navigate_to ("https://duckduckgo.com/html");
767 $wd->set_timeouts ({ implicit => 10000 });
768 my $input = $wd->find_element ("css selector", 'input[type="text"]');
769 $wd->perform_actions ([
770 {
771 id => "myfatfinger",
772 type => "pointer",
773 pointerType => "touch",
774 actions => [
775 { type => "pointerMove", duration => 100, origin => $wd->element_object ($input), x => 40, y => 5 },
776 { type => "pointerDown", button => 1 },
777 { type => "pause", duration => 40 },
778 { type => "pointerUp", button => 1 },
779 ],
780 },
781 {
782 id => "mykeyboard",
783 type => "key",
784 actions => [
785 { type => "pause" },
786 { type => "pause" },
787 { type => "pause" },
788 { type => "pause" },
789 { type => "keyDown", value => "a" },
790 { type => "pause", duration => 100 },
791 { type => "keyUp", value => "a" },
792 { type => "pause", duration => 100 },
793 { type => "keyDown", value => "b" },
794 { type => "pause", duration => 100 },
795 { type => "keyUp", value => "b" },
796 { type => "pause", duration => 2000 },
797 { type => "keyDown", value => "\x{E007}" }, # enter
798 { type => "pause", duration => 100 },
799 { type => "keyUp", value => "\x{E007}" }, # enter
800 { type => "pause", duration => 5000 },
801 ],
802 },
803 ]);
804
805 =item $wd->release_actions
806
807 Release all keys and pointer buttons currently depressed.
808
809 =cut
810
811 sub perform_actions_ {
812 $_[0]->post_ (actions => { actions => $_[1] }, $_[2]);
813 }
814
815 sub release_actions_ {
816 $_[0]->delete_ (actions => $_[1]);
817 }
818
819 =back
820
821 =head3 USER PROMPTS
822
823 =over
824
825 =cut
826
827 =item $wd->dismiss_alert
828
829 Dismiss a simple dialog, if present.
830
831 =item $wd->accept_alert
832
833 Accept a simple dialog, if present.
834
835 =item $text = $wd->get_alert_text
836
837 Returns the text of any simple dialog.
838
839 =item $text = $wd->send_alert_text
840
841 Fills in the user prompt with the given text.
842
843
844 =cut
845
846 sub dismiss_alert_ {
847 $_[0]->post_ ("alert/dismiss" => undef, $_[1]);
848 }
849
850 sub accept_alert_ {
851 $_[0]->post_ ("alert/accept" => undef, $_[1]);
852 }
853
854 sub get_alert_text_ {
855 $_[0]->get_ ("alert/text" => $_[1]);
856 }
857
858 sub send_alert_text_ {
859 $_[0]->post_ ("alert/text" => { text => "$_[1]" }, $_[2]);
860 }
861
862 =back
863
864 =head3 SCREEN CAPTURE
865
866 =over
867
868 =cut
869
870 =item $wd->take_screenshot
871
872 Create a screenshot, returning it as a PNG image in a data url.
873
874 =item $wd->take_element_screenshot ($element_id)
875
876 Accept a simple dialog, if present.
877
878 =cut
879
880 sub take_screenshot_ {
881 $_[0]->get_ (screenshot => $_[1]);
882 }
883
884 sub take_element_screenshot_ {
885 $_[0]->get_ ("element/$_[1]/screenshot" => $_[2]);
886 }
887
888 =back
889
890 =head2 HELPER METHODS
891
892 =over
893
894 =cut
895
896 =item $object = $wd->element_object ($element_id)
897
898 Encoding element ids in data structures is done by represetning them as an
899 object with a special key and the element id as value. This helper method
900 does this for you.
901
902 =cut
903
904 sub element_object {
905 +{ $WEB_ELEMENT_IDENTIFIER => $_[1] }
906 }
907
908 =back
909
910 =head2 EVENT BASED API
911
912 This module wouldn't be a good AnyEvent citizen if it didn't have a true
913 event-based API.
914
915 In fact, the simplified API, as documented above, is emulated via the
916 event-based API and an C<AUTOLOAD> function that automatically provides
917 blocking wrappers around the callback-based API.
918
919 Every method documented in the L<SIMPLIFIED API> section has an equivalent
920 event-based method that is formed by appending a underscore (C<_>) to the
921 method name, and appending a callback to the argument list (mnemonic: the
922 underscore indicates the "the action is not yet finished" after the call
923 returns).
924
925 For example, instead of a blocking calls to C<new_session>, C<navigate_to>
926 and C<back>, you can make a callback-based ones:
927
928 my $cv = AE::cv;
929
930 $wd->new_session ({}, sub {
931 my ($status, $value) = @_,
932
933 die "error $value->{error}" if $status ne "200";
934
935 $wd->navigate_to_ ("http://www.nethype.de", sub {
936
937 $wd->back_ (sub {
938 print "all done\n";
939 $cv->send;
940 });
941
942 });
943 });
944
945 $cv->recv;
946
947 While the blocking methods C<croak> on errors, the callback-based ones all
948 pass two values to the callback, C<$status> and C<$res>, where C<$status>
949 is the HTTP status code (200 for successful requests, typically 4xx ot
950 5xx for errors), and C<$res> is the value of the C<value> key in the JSON
951 response object.
952
953 Other than that, the underscore variants and the blocking variants are
954 identical.
955
956 =head2 LOW LEVEL API
957
958 All the simplfiied API methods are very thin wrappers around WebDriver
959 commands of the same name. Theyx are all implemented in terms of the
960 low-level methods (C<req>, C<get>, C<post> and C<delete>), which exists
961 in blocking and callback-based variants (C<req_>, C<get_>, C<post_> and
962 C<delete_>).
963
964 Examples are after the function descriptions.
965
966 =over
967
968 =item $wd->req_ ($method, $uri, $body, $cb->($status, $value))
969
970 =item $value = $wd->req ($method, $uri, $body)
971
972 Appends the C<$uri> to the C<endpoint/session/{sessionid}/> URL and makes
973 a HTTP C<$method> request (C<GET>, C<POST> etc.). C<POST> requests can
974 provide a UTF-8-encoded JSON text as HTTP request body, or the empty
975 string to indicate no body is used.
976
977 For the callback version, the callback gets passed the HTTP status code
978 (200 for every successful request), and the value of the C<value> key in
979 the JSON response object as second argument.
980
981 =item $wd->get_ ($uri, $cb->($status, $value))
982
983 =item $value = $wd->get ($uri)
984
985 Simply a call to C<req_> with C<$method> set to C<GET> and an empty body.
986
987 =item $wd->post_ ($uri, $data, $cb->($status, $value))
988
989 =item $value = $wd->post ($uri, $data)
990
991 Simply a call to C<req_> with C<$method> set to C<POST> - if C<$body> is
992 C<undef>, then an empty object is send, otherwise, C<$data> must be a
993 valid request object, which gets encoded into JSON for you.
994
995 =item $wd->delete_ ($uri, $cb->($status, $value))
996
997 =item $value = $wd->delete ($uri)
998
999 Simply a call to C<req_> with C<$method> set to C<DELETE> and an empty body.
1000
1001 =cut
1002
1003 =back
1004
1005 Example: implement C<get_all_cookies>, which is a simple C<GET> request
1006 without any parameters:
1007
1008 $cookies = $wd->get ("cookie");
1009
1010 Example: implement C<execute_script>, which needs some parameters:
1011
1012 $results = $wd->post ("execute/sync" => { script => "$javascript", args => [] });
1013
1014 Example: call C<find_elements> to find all C<IMG> elements, stripping the
1015 returned element objects to only return the element ID strings:
1016
1017 my $elems = $wd->post (elements => { using => "css selector", value => "img" });
1018
1019 # yes, the W3C found an interetsing way around the typelessness of JSON
1020 $_ = $_->{"element-6066-11e4-a52e-4f735466cecf"}
1021 for @$elems;
1022
1023 =cut
1024
1025 =head1 HISTORY
1026
1027 This module was unintentionally created (it started inside some quickly
1028 hacked-together script) simply because I couldn't get the existing
1029 C<Selenium::Remote::Driver> module to work, ever, despite multiple
1030 attempts over the years and trying to report multiple bugs, which have
1031 been completely ignored. It's also not event-based, so, yeah...
1032
1033 =head1 AUTHOR
1034
1035 Marc Lehmann <schmorp@schmorp.de>
1036 http://anyevent.schmorp.de
1037
1038 =cut
1039
1040 1
1041