ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-WebDriver/WebDriver.pm
Revision: 1.2
Committed: Tue Aug 28 22:59:53 2018 UTC (5 years, 9 months ago) by root
Branch: MAIN
CVS Tags: rel-0_0
Changes since 1.1: +2 -1 lines
Log Message:
0.0

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