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