ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-WebDriver/WebDriver.pm
Revision: 1.21
Committed: Wed Aug 29 08:14:54 2018 UTC (5 years, 9 months ago) by root
Branch: MAIN
Changes since 1.20: +7 -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 WARNING: THE API IS NOT GUARANTEED TO BE STABLE UNTIL VERSION 1.0.
30
31 This module aims to implement the W3C WebDriver specification which is the
32 standardised equivalent to the Selenium WebDriver API., which in turn aims
33 at remotely controlling web browsers such as Firefox or Chromium.
34
35 At the time of this writing, it was so brand new that I ciould only get
36 C<geckodriver> (For Firefox) to work, but that is expected to be fioxed
37 very soon indeed.
38
39 To make most of this module, or, in fact, to make any reasonable use of
40 this module, you would need to refer to the W3C WebDriver recommendation,
41 which can be found L<here|https://www.w3.org/TR/webdriver1/>:
42
43 https://www.w3.org/TR/webdriver1/
44
45 =head2 CONVENTIONS
46
47 Unless otherwise stated, all delays and time differences in this module
48 are represented as an integer number of milliseconds.
49
50 =cut
51
52 package AnyEvent::WebDriver;
53
54 use common::sense;
55
56 use Carp ();
57 use AnyEvent ();
58 use AnyEvent::HTTP ();
59
60 our $VERSION = 0.5;
61
62 our $WEB_ELEMENT_IDENTIFIER = "element-6066-11e4-a52e-4f735466cecf";
63 our $WEB_WINDOW_IDENTIFIER = "window-fcc6-11e5-b4f8-330a88ab9d7f";
64 our $WEB_FRAME_IDENTIFIER = "frame-075b-4da1-b6ba-e579c2d3230a";
65
66 my $json = eval { require JSON::XS; JSON::XS:: } || do { require JSON::PP; JSON::PP:: };
67 $json = $json->new->utf8;
68
69 $json->boolean_values (0, 1)
70 if $json->can ("boolean_values");
71
72 sub req_ {
73 my ($self, $method, $ep, $body, $cb) = @_;
74
75 AnyEvent::HTTP::http_request $method => "$self->{_ep}$ep",
76 body => $body,
77 timeout => $self->{timeout},
78 headers => { "content-type" => "application/json; charset=utf-8", "cache-control" => "no-cache" },
79 ($self->{proxy} eq "default" ? () : (proxy => $self->{proxy})),
80 sub {
81 my ($res, $hdr) = @_;
82
83 $res = eval { $json->decode ($res) };
84 $hdr->{Status} = 500 unless exists $res->{value};
85
86 $cb->($hdr->{Status}, $res->{value});
87 }
88 ;
89 }
90
91 sub get_ {
92 my ($self, $ep, $cb) = @_;
93
94 $self->req_ (GET => $ep, undef, $cb)
95 }
96
97 sub post_ {
98 my ($self, $ep, $data, $cb) = @_;
99
100 $self->req_ (POST => $ep, $json->encode ($data || {}), $cb)
101 }
102
103 sub delete_ {
104 my ($self, $ep, $cb) = @_;
105
106 $self->req_ (DELETE => $ep, "", $cb)
107 }
108
109 sub AUTOLOAD {
110 our $AUTOLOAD;
111
112 $_[0]->isa (__PACKAGE__)
113 or Carp::croak "$AUTOLOAD: no such function";
114
115 (my $name = $AUTOLOAD) =~ s/^.*://;
116
117 my $name_ = "$name\_";
118
119 defined &$name_
120 or Carp::croak "$AUTOLOAD: no such method";
121
122 my $func_ = \&$name_;
123
124 *$name = sub {
125 $func_->(@_, my $cv = AE::cv);
126 my ($status, $res) = $cv->recv;
127
128 if ($status ne "200") {
129 my $msg;
130
131 if (exists $res->{error}) {
132 $msg = "AyEvent::WebDriver: $res->{error}: $res->{message}";
133 $msg .= "\n$res->{stacktrace}" if length $res->{stacktrace};
134 } else {
135 $msg = "AnyEvent::WebDriver: http status $status (wrong endpoint?), caught";
136 }
137
138 Carp::croak $msg;
139 }
140
141 $res
142 };
143
144 goto &$name;
145 }
146
147 =head2 WEBDRIVER OBJECTS
148
149 =over
150
151 =item new AnyEvent::WebDriver key => value...
152
153 Create a new WebDriver object. Example for a remote WebDriver connection
154 (the only type supported at the moment):
155
156 my $wd = new AnyEvent::WebDriver host => "localhost", port => 4444;
157
158 Supported keys are:
159
160 =over
161
162 =item endpoint => $string
163
164 For remote connections, the endpoint to connect to (defaults to C<http://localhost:4444>).
165
166 =item proxy => $proxyspec
167
168 The proxy to use (same as the C<proxy> argument used by
169 L<AnyEvent::HTTP>). The default is C<undef>, which disables proxies. To
170 use the system-provided proxy (e.g. C<http_proxy> environment variable),
171 specify a value of C<default>.
172
173 =item autodelete => $boolean
174
175 If true (the default), then automatically execute C<delete_session> when
176 the WebDriver object is destroyed with an active session. IF set to a
177 false value, then the session will continue to exist.
178
179 =item timeout => $seconds
180
181 The HTTP timeout, in (fractional) seconds (default: C<300>, but this will
182 likely drastically reduce). This timeout is reset on any activity, so it
183 is not an overall request timeout. Also, individual requests might extend
184 this timeout if they are known to take longer.
185
186 =back
187
188 =cut
189
190 sub new {
191 my ($class, %kv) = @_;
192
193 bless {
194 endpoint => "http://localhost:4444",
195 proxy => undef,
196 autodelete => 1,
197 timeout => 300,
198 %kv,
199 }, $class
200 }
201
202 sub DESTROY {
203 my ($self) = @_;
204
205 $self->delete_session
206 if exists $self->{sid};
207 }
208
209 =item $al = $wd->actions
210
211 Creates an action list associated with this WebDriver. See L<ACTION
212 LISTS>, below, for full details.
213
214 =cut
215
216 sub actions {
217 AnyEvent::WebDriver::Actions->new (wd => $_[0])
218 }
219
220 =item $sessionstring = $wd->save_session
221
222 Save the current session in a string so it can be restored load with
223 C<load_session>. Note that only the session data itself is stored
224 (currently the session id and capabilities), not the endpoint information
225 itself.
226
227 The main use of this function is in conjunction with disabled
228 C<autodelete>, to save a session to e.g., and restore it later. It could
229 presumably used for other applications, suhc as using the same sssion from
230 multiple processes and so on.
231
232 =item $wd->load_session ($sessionstring)
233
234 =item $wd->set_session ($sessionid, $capabilities)
235
236 Starts using the given session, as identified by
237 C<$sessionid>. C<$capabilities> should be the original session
238 capabilities, although the current version of this module does not make
239 any use of it.
240
241 The C<$sessionid> is stored in C<< $wd->{sid} >> (and could be fetched
242 form there for later use), while the capabilities are stored in C<<
243 $wd->{capabilities} >>.
244
245 =cut
246
247 sub save_session {
248 my ($self) = @_;
249
250 $json->encode ([1, $self->{sid}, $self->{capabilities}]);
251 }
252
253 sub load_session {
254 my ($self, $session) = @_;
255
256 $session = $json->decode ($session);
257
258 $session->[0] == 1
259 or Carp::croak "AnyEvent::WebDriver::load_session: session corrupted or from different version";
260
261 $self->set_session ($session->[1], $session->[2]);
262 }
263
264 sub set_session {
265 my ($self, $sid, $caps) = @_;
266
267 $self->{sid} = $sid;
268 $self->{capabilities} = $caps;
269
270 $self->{_ep} = "$self->{endpoint}/session/$self->{sid}/";
271 }
272
273 =back
274
275 =head2 SIMPLIFIED API
276
277 This section documents the simplified API, which is really just a very
278 thin wrapper around the WebDriver protocol commands. They all block (using
279 L<AnyEvent> condvars) the caller until the result is available, so must
280 not be called from an event loop callback - see L<EVENT BASED API> for an
281 alternative.
282
283 The method names are pretty much taken directly from the W3C WebDriver
284 specification, e.g. the request documented in the "Get All Cookies"
285 section is implemented via the C<get_all_cookies> method.
286
287 The order is the same as in the WebDriver draft at the time of this
288 writing, and only minimal massaging is done to request parameters and
289 results.
290
291 =head3 SESSIONS
292
293 =over
294
295 =cut
296
297 =item $wd->new_session ({ key => value... })
298
299 Try to connect to the WebDriver and initialize a new session with a
300 "new session" command, passing the given key-value pairs as value
301 (e.g. C<capabilities>).
302
303 No session-dependent methods must be called before this function returns
304 successfully, and only one session can be created per WebDriver object.
305
306 On success, C<< $wd->{sid} >> is set to the session ID, and C<<
307 $wd->{capabilities} >> is set to the returned capabilities.
308
309 Simple example of creatring a WebDriver object and a new session:
310
311 my $wd = new AnyEvent::Selenium endpoint => "http://localhost:4545";
312 $wd->new_session ({});
313
314 Real-world example with capability negotiation:
315
316 $wd->new_session ({
317 capabilities => {
318 alwaysMatch => {
319 pageLoadStrategy => "eager",
320 unhandledPromptBehavior => "dismiss",
321 # proxy => { proxyType => "manual", httpProxy => "1.2.3.4:56", sslProxy => "1.2.3.4:56" },
322 },
323 firstMatch => [
324 {
325 browserName => "firefox",
326 "moz:firefoxOptions" => {
327 binary => "firefox/firefox",
328 args => ["-devtools"],
329 prefs => {
330 "dom.webnotifications.enabled" => \0,
331 "dom.disable_beforeunload" => \1,
332 "browser.link.open_newwindow" => 3,
333 "browser.link.open_newwindow.restrictions" => 0,
334 "dom.popup_allowed_events" => "",
335 "dom.disable_open_during_load" => \1,
336 },
337 },
338 },
339 {
340 # generic fallback
341 },
342 ],
343
344 },
345 });
346
347 Firefox-specific capability documentation can be found L<on
348 MDN|https://developer.mozilla.org/en-US/docs/Web/WebDriver/Capabilities>,
349 Chrome-specific capability documentation might be found
350 L<here|http://chromedriver.chromium.org/capabilities>, but the latest
351 release at the time of this writing has effectively no WebDriver support
352 at all, and canary releases are not freely downloadable.
353
354 If you have URLs for Safari/IE/Edge etc. capabilities, feel free to tell
355 me about them.
356
357 =cut
358
359 sub new_session_ {
360 my ($self, $kv, $cb) = @_;
361
362 local $self->{_ep} = "$self->{endpoint}/";
363 $self->post_ (session => $kv, sub {
364 my ($status, $res) = @_;
365
366 exists $res->{capabilities}
367 or $status = "500"; # blasted chromedriver
368
369 $self->set_session ($res->{sessionId}, $res->{capabilities})
370 if $status eq "200";
371
372 $cb->($status, $res);
373 });
374 }
375
376 =item $wd->delete_session
377
378 Deletes the session - the WebDriver object must not be used after this
379 call.
380
381 =cut
382
383 sub delete_session_ {
384 my ($self, $cb) = @_;
385
386 local $self->{_ep} = "$self->{endpoint}/session/$self->{sid}";
387 $self->delete_ ("" => $cb);
388 }
389
390 =item $timeouts = $wd->get_timeouts
391
392 Get the current timeouts, e.g.:
393
394 my $timeouts = $wd->get_timeouts;
395 => { implicit => 0, pageLoad => 300000, script => 30000 }
396
397 =item $wd->set_timeouts ($timeouts)
398
399 Sets one or more timeouts, e.g.:
400
401 $wd->set_timeouts ({ script => 60000 });
402
403 =cut
404
405 sub get_timeouts_ {
406 $_[0]->get_ (timeouts => $_[1], $_[2]);
407 }
408
409 sub set_timeouts_ {
410 $_[0]->post_ (timeouts => $_[1], $_[2], $_[3]);
411 }
412
413 =back
414
415 =head3 NAVIGATION
416
417 =over
418
419 =cut
420
421 =item $wd->navigate_to ($url)
422
423 Navigates to the specified URL.
424
425 =item $url = $wd->get_current_url
426
427 Queries the current page URL as set by C<navigate_to>.
428
429 =cut
430
431 sub navigate_to_ {
432 $_[0]->post_ (url => { url => "$_[1]" }, $_[2]);
433 }
434
435 sub get_current_url_ {
436 $_[0]->get_ (url => $_[1])
437 }
438
439 =item $wd->back
440
441 The equivalent of pressing "back" in the browser.
442
443 =item $wd->forward
444
445 The equivalent of pressing "forward" in the browser.
446
447 =item $wd->refresh
448
449 The equivalent of pressing "refresh" in the browser.
450
451 =cut
452
453 sub back_ {
454 $_[0]->post_ (back => undef, $_[1]);
455 }
456
457 sub forward_ {
458 $_[0]->post_ (forward => undef, $_[1]);
459 }
460
461 sub refresh_ {
462 $_[0]->post_ (refresh => undef, $_[1]);
463 }
464
465 =item $title = $wd->get_title
466
467 Returns the current document title.
468
469 =cut
470
471 sub get_title_ {
472 $_[0]->get_ (title => $_[1]);
473 }
474
475 =back
476
477 =head3 COMMAND CONTEXTS
478
479 =over
480
481 =cut
482
483 =item $handle = $wd->get_window_handle
484
485 Returns the current window handle.
486
487 =item $wd->close_window
488
489 Closes the current browsing context.
490
491 =item $wd->switch_to_window ($handle)
492
493 Changes the current browsing context to the given window.
494
495 =cut
496
497 sub get_window_handle_ {
498 $_[0]->get_ (window => $_[1]);
499 }
500
501 sub close_window_ {
502 $_[0]->delete_ (window => $_[1]);
503 }
504
505 sub switch_to_window_ {
506 $_[0]->post_ (window => { handle => "$_[1]" }, $_[2]);
507 }
508
509 =item $handles = $wd->get_window_handles
510
511 Return the current window handles as an array-ref of handle IDs.
512
513 =cut
514
515 sub get_window_handles_ {
516 $_[0]->get_ ("window/handles" => $_[1]);
517 }
518
519 =item $handles = $wd->switch_to_frame ($frame)
520
521 Switch to the given frame identified by C<$frame>, which must be either
522 C<undef> to go back to the top-level browsing context, an integer to
523 select the nth subframe, or an element object.
524
525 =cut
526
527 sub switch_to_frame_ {
528 $_[0]->post_ (frame => { id => "$_[1]" }, $_[2]);
529 }
530
531 =item $handles = $wd->switch_to_parent_frame
532
533 Switch to the parent frame.
534
535 =cut
536
537 sub switch_to_parent_frame_ {
538 $_[0]->post_ ("frame/parent" => undef, $_[1]);
539 }
540
541 =item $rect = $wd->get_window_rect
542
543 Return the current window rect, e.g.:
544
545 $rect = $wd->get_window_rect
546 => { height => 1040, width => 540, x => 0, y => 0 }
547
548 =item $wd->set_window_rect ($rect)
549
550 Sets the window rect.
551
552 =cut
553
554 sub get_window_rect_ {
555 $_[0]->get_ ("window/rect" => $_[1]);
556 }
557
558 sub set_window_rect_ {
559 $_[0]->post_ ("window/rect" => $_[1], $_[2]);
560 }
561
562 =item $wd->maximize_window
563
564 =item $wd->minimize_window
565
566 =item $wd->fullscreen_window
567
568 Changes the window size by either maximising, minimising or making it
569 fullscreen. In my experience, this will timeout if no window manager is
570 running.
571
572 =cut
573
574 sub maximize_window_ {
575 $_[0]->post_ ("window/maximize" => undef, $_[1]);
576 }
577
578 sub minimize_window_ {
579 $_[0]->post_ ("window/minimize" => undef, $_[1]);
580 }
581
582 sub fullscreen_window_ {
583 $_[0]->post_ ("window/fullscreen" => undef, $_[1]);
584 }
585
586 =back
587
588 =head3 ELEMENT RETRIEVAL
589
590 To reduce typing and memory strain, the element finding functions accept
591 some shorter and hopefully easier to remember aliases for the standard
592 locator strategy values, as follows:
593
594 Alias Locator Strategy
595 css css selector
596 link link text
597 substr partial link text
598 tag tag name
599
600 =over
601
602 =cut
603
604 our %USING = (
605 css => "css selector",
606 link => "link text",
607 substr => "partial link text",
608 tag => "tag name",
609 );
610
611 sub _using($) {
612 using => $USING{$_[0]} // "$_[0]"
613 }
614
615 =item $element = $wd->find_element ($locator_strategy, $selector)
616
617 Finds the first element specified by the given selector and returns its
618 element object. Raises an error when no element was found.
619
620 Examples showing all standard locator strategies:
621
622 $element = $wd->find_element ("css selector" => "body a");
623 $element = $wd->find_element ("link text" => "Click Here For Porn");
624 $element = $wd->find_element ("partial link text" => "orn");
625 $element = $wd->find_element ("tag name" => "input");
626 $element = $wd->find_element ("xpath" => '//input[@type="text"]');
627 => e.g. { "element-6066-11e4-a52e-4f735466cecf" => "decddca8-5986-4e1d-8c93-efe952505a5f" }
628
629 Same examples using aliases provided by this module:
630
631 $element = $wd->find_element (css => "body a");
632 $element = $wd->find_element (link => "Click Here For Porn");
633 $element = $wd->find_element (substr => "orn");
634 $element = $wd->find_element (tag => "input");
635
636 =item $elements = $wd->find_elements ($locator_strategy, $selector)
637
638 As above, but returns an arrayref of all found element objects.
639
640 =item $element = $wd->find_element_from_element ($element, $locator_strategy, $selector)
641
642 Like C<find_element>, but looks only inside the specified C<$element>.
643
644 =item $elements = $wd->find_elements_from_element ($element, $locator_strategy, $selector)
645
646 Like C<find_elements>, but looks only inside the specified C<$element>.
647
648 my $head = $wd->find_element ("tag name" => "head");
649 my $links = $wd->find_elements_from_element ($head, "tag name", "link");
650
651 =item $element = $wd->get_active_element
652
653 Returns the active element.
654
655 =cut
656
657 sub find_element_ {
658 $_[0]->post_ (element => { _using $_[1], value => "$_[2]" }, $_[3]);
659 }
660
661 sub find_elements_ {
662 $_[0]->post_ (elements => { _using $_[1], value => "$_[2]" }, $_[3]);
663 }
664
665 sub find_element_from_element_ {
666 $_[0]->post_ ("element/$_[1]/element" => { _using $_[2], value => "$_[3]" }, $_[4]);
667 }
668
669 sub find_elements_from_element_ {
670 $_[0]->post_ ("element/$_[1]/elements" => { _using $_[2], value => "$_[3]" }, $_[4]);
671 }
672
673 sub get_active_element_ {
674 $_[0]->get_ ("element/active" => $_[1]);
675 }
676
677 =back
678
679 =head3 ELEMENT STATE
680
681 =over
682
683 =cut
684
685 =item $bool = $wd->is_element_selected
686
687 Returns whether the given input or option element is selected or not.
688
689 =item $string = $wd->get_element_attribute ($element, $name)
690
691 Returns the value of the given attribute.
692
693 =item $string = $wd->get_element_property ($element, $name)
694
695 Returns the value of the given property.
696
697 =item $string = $wd->get_element_css_value ($element, $name)
698
699 Returns the value of the given CSS value.
700
701 =item $string = $wd->get_element_text ($element)
702
703 Returns the (rendered) text content of the given element.
704
705 =item $string = $wd->get_element_tag_name ($element)
706
707 Returns the tag of the given element.
708
709 =item $rect = $wd->get_element_rect ($element)
710
711 Returns the element rect(angle) of the given element.
712
713 =item $bool = $wd->is_element_enabled
714
715 Returns whether the element is enabled or not.
716
717 =cut
718
719 sub is_element_selected_ {
720 $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/selected" => $_[2]);
721 }
722
723 sub get_element_attribute_ {
724 $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/attribute/$_[2]" => $_[3]);
725 }
726
727 sub get_element_property_ {
728 $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/property/$_[2]" => $_[3]);
729 }
730
731 sub get_element_css_value_ {
732 $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/css/$_[2]" => $_[3]);
733 }
734
735 sub get_element_text_ {
736 $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/text" => $_[2]);
737 }
738
739 sub get_element_tag_name_ {
740 $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/name" => $_[2]);
741 }
742
743 sub get_element_rect_ {
744 $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/rect" => $_[2]);
745 }
746
747 sub is_element_enabled_ {
748 $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/enabled" => $_[2]);
749 }
750
751 =back
752
753 =head3 ELEMENT INTERACTION
754
755 =over
756
757 =cut
758
759 =item $wd->element_click ($element)
760
761 Clicks the given element.
762
763 =item $wd->element_clear ($element)
764
765 Clear the contents of the given element.
766
767 =item $wd->element_send_keys ($element, $text)
768
769 Sends the given text as key events to the given element.
770
771 =cut
772
773 sub element_click_ {
774 $_[0]->post_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/click" => undef, $_[2]);
775 }
776
777 sub element_clear_ {
778 $_[0]->post_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/clear" => undef, $_[2]);
779 }
780
781 sub element_send_keys_ {
782 $_[0]->post_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/value" => { text => "$_[2]" }, $_[3]);
783 }
784
785 =back
786
787 =head3 DOCUMENT HANDLING
788
789 =over
790
791 =cut
792
793 =item $source = $wd->get_page_source
794
795 Returns the (HTML/XML) page source of the current document.
796
797 =item $results = $wd->execute_script ($javascript, $args)
798
799 Synchronously execute the given script with given arguments and return its
800 results (C<$args> can be C<undef> if no arguments are wanted/needed).
801
802 $ten = $wd->execute_script ("return arguments[0]+arguments[1]", [3, 7]);
803
804 =item $results = $wd->execute_async_script ($javascript, $args)
805
806 Similar to C<execute_script>, but doesn't wait for script to return, but
807 instead waits for the script to call its last argument, which is added to
808 C<$args> automatically.
809
810 $twenty = $wd->execute_async_script ("arguments[0](20)", undef);
811
812 =cut
813
814 sub get_page_source_ {
815 $_[0]->get_ (source => $_[1]);
816 }
817
818 sub execute_script_ {
819 $_[0]->post_ ("execute/sync" => { script => "$_[1]", args => $_[2] || [] }, $_[3]);
820 }
821
822 sub execute_async_script_ {
823 $_[0]->post_ ("execute/async" => { script => "$_[1]", args => $_[2] || [] }, $_[3]);
824 }
825
826 =back
827
828 =head3 COOKIES
829
830 =over
831
832 =cut
833
834 =item $cookies = $wd->get_all_cookies
835
836 Returns all cookies, as an arrayref of hashrefs.
837
838 # google surely sets a lot of cookies without my consent
839 $wd->navigate_to ("http://google.com");
840 use Data::Dump;
841 ddx $wd->get_all_cookies;
842
843 =item $cookie = $wd->get_named_cookie ($name)
844
845 Returns a single cookie as a hashref.
846
847 =item $wd->add_cookie ($cookie)
848
849 Adds the given cookie hashref.
850
851 =item $wd->delete_cookie ($name)
852
853 Delete the named cookie.
854
855 =item $wd->delete_all_cookies
856
857 Delete all cookies.
858
859 =cut
860
861 sub get_all_cookies_ {
862 $_[0]->get_ (cookie => $_[1]);
863 }
864
865 sub get_named_cookie_ {
866 $_[0]->get_ ("cookie/$_[1]" => $_[2]);
867 }
868
869 sub add_cookie_ {
870 $_[0]->post_ (cookie => { cookie => $_[1] }, $_[2]);
871 }
872
873 sub delete_cookie_ {
874 $_[0]->delete_ ("cookie/$_[1]" => $_[2]);
875 }
876
877 sub delete_all_cookies_ {
878 $_[0]->delete_ (cookie => $_[2]);
879 }
880
881 =back
882
883 =head3 ACTIONS
884
885 =over
886
887 =cut
888
889 =item $wd->perform_actions ($actions)
890
891 Perform the given actions (an arrayref of action specifications simulating
892 user activity, or an C<AnyEvent::WebDriver::Actions> object). For further
893 details, read the spec or the section L<ACTION LISTS>, below.
894
895 An example to get you started (see the next example for a mostly
896 equivalent example using the C<AnyEvent::WebDriver::Actions> helper API):
897
898 $wd->navigate_to ("https://duckduckgo.com/html");
899 my $input = $wd->find_element ("css selector", 'input[type="text"]');
900 $wd->perform_actions ([
901 {
902 id => "myfatfinger",
903 type => "pointer",
904 pointerType => "touch",
905 actions => [
906 { type => "pointerMove", duration => 100, origin => $input, x => 40, y => 5 },
907 { type => "pointerDown", button => 1 },
908 { type => "pause", duration => 40 },
909 { type => "pointerUp", button => 1 },
910 ],
911 },
912 {
913 id => "mykeyboard",
914 type => "key",
915 actions => [
916 { type => "pause" },
917 { type => "pause" },
918 { type => "pause" },
919 { type => "pause" },
920 { type => "keyDown", value => "a" },
921 { type => "pause", duration => 100 },
922 { type => "keyUp", value => "a" },
923 { type => "pause", duration => 100 },
924 { type => "keyDown", value => "b" },
925 { type => "pause", duration => 100 },
926 { type => "keyUp", value => "b" },
927 { type => "pause", duration => 2000 },
928 { type => "keyDown", value => "\x{E007}" }, # enter
929 { type => "pause", duration => 100 },
930 { type => "keyUp", value => "\x{E007}" }, # enter
931 { type => "pause", duration => 5000 },
932 ],
933 },
934 ]);
935
936 And here is essentially the same (except for fewer pauses) example as
937 above, using the much simpler C<AnyEvent::WebDriver::Actions> API. Note
938 that the pointer up and key down event happen concurrently in this
939 example:
940
941 $wd->navigate_to ("https://duckduckgo.com/html");
942 my $input = $wd->find_element ("css selector", 'input[type="text"]');
943 $wd->actions
944 ->move ($input, 40, 5, "touch1")
945 ->click
946 ->key ("a")
947 ->key ("b")
948 ->pause (2000)
949 ->key ("\x{E007}")
950 ->pause (5000)
951 ->perform;
952
953 =item $wd->release_actions
954
955 Release all keys and pointer buttons currently depressed.
956
957 =cut
958
959 sub perform_actions_ {
960 if (UNIVERSAL::isa $_[1], AnyEvent::WebDriver::Actions::) {
961 my ($actions, $duration) = $_[1]->compile;
962 local $_[0]{timeout} = $_[0]{timeout} + $duration * 1e-3;
963 $_[0]->post_ (actions => { actions => $actions }, $_[2]);
964 } else {
965 $_[0]->post_ (actions => { actions => $_[1] }, $_[2]);
966 }
967 }
968
969 sub release_actions_ {
970 $_[0]->delete_ (actions => $_[1]);
971 }
972
973 =back
974
975 =head3 USER PROMPTS
976
977 =over
978
979 =cut
980
981 =item $wd->dismiss_alert
982
983 Dismiss a simple dialog, if present.
984
985 =item $wd->accept_alert
986
987 Accept a simple dialog, if present.
988
989 =item $text = $wd->get_alert_text
990
991 Returns the text of any simple dialog.
992
993 =item $text = $wd->send_alert_text
994
995 Fills in the user prompt with the given text.
996
997
998 =cut
999
1000 sub dismiss_alert_ {
1001 $_[0]->post_ ("alert/dismiss" => undef, $_[1]);
1002 }
1003
1004 sub accept_alert_ {
1005 $_[0]->post_ ("alert/accept" => undef, $_[1]);
1006 }
1007
1008 sub get_alert_text_ {
1009 $_[0]->get_ ("alert/text" => $_[1]);
1010 }
1011
1012 sub send_alert_text_ {
1013 $_[0]->post_ ("alert/text" => { text => "$_[1]" }, $_[2]);
1014 }
1015
1016 =back
1017
1018 =head3 SCREEN CAPTURE
1019
1020 =over
1021
1022 =cut
1023
1024 =item $wd->take_screenshot
1025
1026 Create a screenshot, returning it as a PNG image in a C<data:> URL.
1027
1028 =item $wd->take_element_screenshot ($element)
1029
1030 Accept a simple dialog, if present.
1031
1032 =cut
1033
1034 sub take_screenshot_ {
1035 $_[0]->get_ (screenshot => $_[1]);
1036 }
1037
1038 sub take_element_screenshot_ {
1039 $_[0]->get_ ("element/$_[1]{$WEB_ELEMENT_IDENTIFIER}/screenshot" => $_[2]);
1040 }
1041
1042 =back
1043
1044 =head2 ACTION LISTS
1045
1046 Action lists can be quite complicated. Or at least it took a while for
1047 me to twist my head around them. Basically, an action list consists of a
1048 number of sources representing devices (such as a finger, a mouse, a pen
1049 or a keyboard) and a list of actions for each source.
1050
1051 An action can be a key press, a pointer move or a pause (time
1052 delay). Actions from different sources can happen "at the same time",
1053 while actions from a single source are executed in order.
1054
1055 While you can provide an action list manually, it is (hopefully) less
1056 cumbersome to use the API described in this section to create them.
1057
1058 The basic process of creating and performing actions is to create a new
1059 action list, adding action sources, followed by adding actions. Finally
1060 you would C<perform> those actions on the WebDriver.
1061
1062 Virtual time progresses as long as you add actions to the same event
1063 source. Adding events to different sources are considered to happen
1064 concurrently. If you want to force time to progress, you can do this using
1065 a call to C<< ->pause (0) >>.
1066
1067 Most methods here are designed to chain, i.e. they return the web actions
1068 object, to simplify multiple calls.
1069
1070 For example, to simulate a mouse click to an input element, followed by
1071 entering some text and pressing enter, you can use this:
1072
1073 $wd->actions
1074 ->click (1, 100)
1075 ->type ("some text")
1076 ->key ("{Enter}")
1077 ->perform;
1078
1079 By default, keyboard and mouse input sources are provided. You can create
1080 your own sources and use them when adding events. The above example could
1081 be more verbosely written like this:
1082
1083 $wd->actions
1084 ->click (1, 100, "mouse")
1085 ->type ("some text")
1086 ->key ("{Enter}")
1087 ->perform;
1088
1089 When you specify the event source explicitly it will switch the current
1090 "focus" for this class of device (all keyboards are in one class, all
1091 pointer-like devices such as mice/fingers/pens are in one class), so you
1092 don't have to specify the source for subsequent actions.
1093
1094 When you use the sources C<keyboard>, C<mouse>, C<touch1>..C<touch3>,
1095 C<pen> without defining them, then a suitable default source will be
1096 created for them.
1097
1098 =over 4
1099
1100 =cut
1101
1102 package AnyEvent::WebDriver::Actions;
1103
1104 =item $al = new AnyEvent::WebDriver::Actions
1105
1106 Create a new empty action list object. More often you would use the C<<
1107 $wd->action_list >> method to create one that is already associated with
1108 a given web driver.
1109
1110 =cut
1111
1112 sub new {
1113 my ($class, %kv) = @_;
1114
1115 $kv{last_kbd} = "keyboard";
1116 $kv{last_ptr} = "mouse";
1117
1118 bless \%kv, $class
1119 }
1120
1121 =item $al = $al->source ($id, $type, key => value...)
1122
1123 The first time you call this with a given ID, this defines the event
1124 source using the extra parameters. Subsequent calls merely switch the
1125 current source for its event class.
1126
1127 It's not an error to define built-in sources (such as C<keyboard> or
1128 C<touch1>) differently then the defaults.
1129
1130 Example: define a new touch device called C<fatfinger>.
1131
1132 $al->source (fatfinger => "pointer", pointerType => "touch");
1133
1134 Example: define a new touch device called C<fatfinger>.
1135
1136 $al->source (fatfinger => "pointer", pointerType => "touch");
1137
1138 Example: switch default keyboard source to C<kbd1>, assuming it is of C<key> class.
1139
1140 $al->source ("kbd1");
1141
1142 =cut
1143
1144 sub _default_source($) {
1145 my ($source) = @_;
1146
1147 $source eq "keyboard" ? { actions => [], id => $source, type => "key" }
1148 : $source eq "mouse" ? { actions => [], id => $source, type => "pointer", pointerType => "mouse" }
1149 : $source eq "touch" ? { actions => [], id => $source, type => "pointer", pointerType => "touch" }
1150 : $source eq "pen" ? { actions => [], id => $source, type => "pointer", pointerType => "pen" }
1151 : Carp::croak "AnyEvent::WebDriver::Actions: event source '$source' not defined"
1152 }
1153
1154 my %source_class = (
1155 key => "kbd",
1156 pointer => "ptr",
1157 );
1158
1159 sub source {
1160 my ($self, $id, $type, %kv) = @_;
1161
1162 if (defined $type) {
1163 !exists $self->{source}{$id}
1164 or Carp::croak "AnyEvent::WebDriver::Actions: source '$id' already defined";
1165
1166 $kv{id} = $id;
1167 $kv{type} = $type;
1168 $kv{actions} = [];
1169
1170 $self->{source}{$id} = \%kv;
1171 }
1172
1173 my $source = $self->{source}{$id} ||= _default_source $id;
1174
1175 my $last = $source_class{$source->{type}} // "xxx";
1176
1177 $self->{"last_$last"} = $id;
1178
1179 $self
1180 }
1181
1182 sub _add {
1183 my ($self, $source, $sourcetype, $type, %kv) = @_;
1184
1185 my $last = \$self->{"last_$sourcetype"};
1186
1187 $source
1188 ? ($$last = $source)
1189 : ($source = $$last);
1190
1191 my $source = $self->{source}{$source} ||= _default_source $source;
1192
1193 my $al = $source->{actions};
1194
1195 push @$al, { type => "pause" }
1196 while @$al < $self->{tick} - 1;
1197
1198 $kv{type} = $type;
1199
1200 push @{ $source->{actions} }, \%kv;
1201
1202 $self->{tick_duration} = $kv{duration}
1203 if $kv{duration} > $self->{tick_duration};
1204
1205 if ($self->{tick} != @$al) {
1206 $self->{tick} = @$al;
1207 $self->{duration} += delete $self->{tick_duration};
1208 }
1209
1210 $self
1211 }
1212
1213 =item $al = $al->pause ($duration)
1214
1215 Creates a pause with the given duration. Makes sure that time progresses
1216 in any case, even when C<$duration> is C<0>.
1217
1218 =cut
1219
1220 sub pause {
1221 my ($self, $duration) = @_;
1222
1223 $self->{tick_duration} = $duration
1224 if $duration > $self->{tick_duration};
1225
1226 $self->{duration} += delete $self->{tick_duration};
1227
1228 # find the source with the longest list
1229
1230 for my $source (values %{ $self->{source} }) {
1231 if (@{ $source->{actions} } == $self->{tick}) {
1232 # this source is one of the longest
1233
1234 # create a pause event only if $duration is non-zero...
1235 push @{ $source->{actions} }, { type => "pause", duration => $duration*1 }
1236 if $duration;
1237
1238 # ... but advance time in any case
1239 ++$self->{tick};
1240
1241 return $self;
1242 }
1243 }
1244
1245 # no event sources are longest. so advance time in any case
1246 ++$self->{tick};
1247
1248 Carp::croak "AnyEvent::WebDriver::Actions: multiple pause calls in a row not (yet) supported"
1249 if $duration;
1250
1251 $self
1252 }
1253
1254 =item $al = $al->pointer_down ($button, $source)
1255
1256 =item $al = $al->pointer_up ($button, $source)
1257
1258 Press or release the given button. C<$button> defaults to C<1>.
1259
1260 =item $al = $al->click ($button, $source)
1261
1262 Convenience function that creates a button press and release action
1263 without any delay between them. C<$button> defaults to C<1>.
1264
1265 =item $al = $al->doubleclick ($button, $source)
1266
1267 Convenience function that creates two button press and release action
1268 pairs in a row, with no unnecessary delay between them. C<$button>
1269 defaults to C<1>.
1270
1271 =cut
1272
1273 sub pointer_down {
1274 my ($self, $button, $source) = @_;
1275
1276 $self->_add ($source, ptr => pointerDown => button => ($button // 1)*1)
1277 }
1278
1279 sub pointer_up {
1280 my ($self, $button, $source) = @_;
1281
1282 $self->_add ($source, ptr => pointerUp => button => ($button // 1)*1)
1283 }
1284
1285 sub click {
1286 my ($self, $button, $source) = @_;
1287
1288 $self
1289 ->pointer_down ($button, $source)
1290 ->pointer_up ($button)
1291 }
1292
1293 sub doubleclick {
1294 my ($self, $button, $source) = @_;
1295
1296 $self
1297 ->click ($button, $source)
1298 ->click ($button)
1299 }
1300
1301 =item $al = $al->move ($button, $origin, $x, $y, $duration, $source)
1302
1303 Moves a pointer to the given position, relative to origin (either
1304 "viewport", "pointer" or an element object.
1305
1306 =cut
1307
1308 sub move {
1309 my ($self, $origin, $x, $y, $duration, $source) = @_;
1310
1311 $self->_add ($source, ptr => pointerMove =>
1312 origin => $origin, x => $x*1, y => $y*1, duration => $duration*1)
1313 }
1314
1315 =item $al = $al->keyDown ($key, $source)
1316
1317 =item $al = $al->keyUp ($key, $source)
1318
1319 Press or release the given key.
1320
1321 =item $al = $al->key ($key, $source)
1322
1323 Peess and release the given key, without unnecessary delay.
1324
1325 A special syntax, C<{keyname}> can be used for special keys - all the special key names from
1326 L<section 17.4.2|https://www.w3.org/TR/webdriver1/#keyboard-actions> of the WebDriver recommendation
1327 can be used.
1328
1329 Example: press and release "a".
1330
1331 $al->key ("a");
1332
1333 Example: press and release the "Enter" key:
1334
1335 $al->key ("\x{e007}");
1336
1337 Example: press and release the "enter" key using the special key name syntax:
1338
1339 $al->key ("{Enter}");
1340
1341 =item $al = $al->type ($string, $source)
1342
1343 Convenience method to simulate a series of key press and release events
1344 for the keys in C<$string>. There is no syntax for special keys,
1345 everything will be typed "as-is" if possible.
1346
1347 =cut
1348
1349 our %SPECIAL_KEY = (
1350 "Unidentified" => 0xE000,
1351 "Cancel" => 0xE001,
1352 "Help" => 0xE002,
1353 "Backspace" => 0xE003,
1354 "Tab" => 0xE004,
1355 "Clear" => 0xE005,
1356 "Return" => 0xE006,
1357 "Enter" => 0xE007,
1358 "Shift" => 0xE008,
1359 "Control" => 0xE009,
1360 "Alt" => 0xE00A,
1361 "Pause" => 0xE00B,
1362 "Escape" => 0xE00C,
1363 " " => 0xE00D,
1364 "PageUp" => 0xE00E,
1365 "PageDown" => 0xE00F,
1366 "End" => 0xE010,
1367 "Home" => 0xE011,
1368 "ArrowLeft" => 0xE012,
1369 "ArrowUp" => 0xE013,
1370 "ArrowRight" => 0xE014,
1371 "ArrowDown" => 0xE015,
1372 "Insert" => 0xE016,
1373 "Delete" => 0xE017,
1374 ";" => 0xE018,
1375 "=" => 0xE019,
1376 "0" => 0xE01A,
1377 "1" => 0xE01B,
1378 "2" => 0xE01C,
1379 "3" => 0xE01D,
1380 "4" => 0xE01E,
1381 "5" => 0xE01F,
1382 "6" => 0xE020,
1383 "7" => 0xE021,
1384 "8" => 0xE022,
1385 "9" => 0xE023,
1386 "*" => 0xE024,
1387 "+" => 0xE025,
1388 "," => 0xE026,
1389 "-" => 0xE027,
1390 "." => 0xE028,
1391 "/" => 0xE029,
1392 "F1" => 0xE031,
1393 "F2" => 0xE032,
1394 "F3" => 0xE033,
1395 "F4" => 0xE034,
1396 "F5" => 0xE035,
1397 "F6" => 0xE036,
1398 "F7" => 0xE037,
1399 "F8" => 0xE038,
1400 "F9" => 0xE039,
1401 "F10" => 0xE03A,
1402 "F11" => 0xE03B,
1403 "F12" => 0xE03C,
1404 "Meta" => 0xE03D,
1405 "ZenkakuHankaku" => 0xE040,
1406 "Shift" => 0xE050,
1407 "Control" => 0xE051,
1408 "Alt" => 0xE052,
1409 "Meta" => 0xE053,
1410 "PageUp" => 0xE054,
1411 "PageDown" => 0xE055,
1412 "End" => 0xE056,
1413 "Home" => 0xE057,
1414 "ArrowLeft" => 0xE058,
1415 "ArrowUp" => 0xE059,
1416 "ArrowRight" => 0xE05A,
1417 "ArrowDown" => 0xE05B,
1418 "Insert" => 0xE05C,
1419 "Delete" => 0xE05D,
1420 );
1421
1422 sub _kv($) {
1423 $_[0] =~ /^\{(.*)\}$/s
1424 ? (exists $SPECIAL_KEY{$1}
1425 ? chr $SPECIAL_KEY{$1}
1426 : Carp::croak "AnyEvent::WebDriver::Actions: special key '$1' not known")
1427 : $_[0]
1428 }
1429
1430 sub key_down {
1431 my ($self, $key, $source) = @_;
1432
1433 $self->_add ($source, kbd => keyDown => value => _kv $key)
1434 }
1435
1436 sub key_up {
1437 my ($self, $key, $source) = @_;
1438
1439 $self->_add ($source, kbd => keyUp => value => _kv $key)
1440 }
1441
1442 sub key {
1443 my ($self, $key, $source) = @_;
1444
1445 $self
1446 ->key_down ($key, $source)
1447 ->key_up ($key)
1448 }
1449
1450 sub type {
1451 my ($self, $string, $source) = @_;
1452
1453 $self->key ($_, $source)
1454 for $string =~ /(\X)/g;
1455
1456 $self
1457 }
1458
1459 =item $al->perform ($wd)
1460
1461 Finaluses and compiles the list, if not done yet, and calls C<<
1462 $wd->perform >> with it.
1463
1464 If C<$wd> is undef, and the action list was created using the C<<
1465 $wd->actions >> method, then perform it against that WebDriver object.
1466
1467 There is no underscore variant - call the C<perform_actions_> method with
1468 the action object instead.
1469
1470 =item $al->perform_release ($wd)
1471
1472 Exactly like C<perform>, but additionally call C<release_actions>
1473 afterwards.
1474
1475 =cut
1476
1477 sub perform {
1478 my ($self, $wd) = @_;
1479
1480 ($wd //= $self->{wd})->perform_actions ($self)
1481 }
1482
1483 sub perform_release {
1484 my ($self, $wd) = @_;
1485
1486 ($wd //= $self->{wd})->perform_actions ($self);
1487 $wd->release_actions;
1488 }
1489
1490 =item ($actions, $duration) = $al->compile
1491
1492 Finalises and compiles the list, if not done yet, and returns an actions
1493 object suitable for calls to C<< $wd->perform_actions >>. When called in
1494 list context, additionally returns the total duration of the action list.
1495
1496 Since building large action lists can take nontrivial amounts of time,
1497 it can make sense to build an action list only once and then perform it
1498 multiple times.
1499
1500 Actions must not be added after compiling a list.
1501
1502 =cut
1503
1504 sub compile {
1505 my ($self) = @_;
1506
1507 $self->{duration} += delete $self->{tick_duration};
1508
1509 delete $self->{tick};
1510 delete $self->{last_kbd};
1511 delete $self->{last_ptr};
1512
1513 $self->{actions} ||= [values %{ delete $self->{source} }];
1514
1515 wantarray
1516 ? ($self->{actions}, $self->{duration})
1517 : $self->{actions}
1518 }
1519
1520 =back
1521
1522 =head2 EVENT BASED API
1523
1524 This module wouldn't be a good AnyEvent citizen if it didn't have a true
1525 event-based API.
1526
1527 In fact, the simplified API, as documented above, is emulated via the
1528 event-based API and an C<AUTOLOAD> function that automatically provides
1529 blocking wrappers around the callback-based API.
1530
1531 Every method documented in the L<SIMPLIFIED API> section has an equivalent
1532 event-based method that is formed by appending a underscore (C<_>) to the
1533 method name, and appending a callback to the argument list (mnemonic: the
1534 underscore indicates the "the action is not yet finished" after the call
1535 returns).
1536
1537 For example, instead of a blocking calls to C<new_session>, C<navigate_to>
1538 and C<back>, you can make a callback-based ones:
1539
1540 my $cv = AE::cv;
1541
1542 $wd->new_session ({}, sub {
1543 my ($status, $value) = @_,
1544
1545 die "error $value->{error}" if $status ne "200";
1546
1547 $wd->navigate_to_ ("http://www.nethype.de", sub {
1548
1549 $wd->back_ (sub {
1550 print "all done\n";
1551 $cv->send;
1552 });
1553
1554 });
1555 });
1556
1557 $cv->recv;
1558
1559 While the blocking methods C<croak> on errors, the callback-based ones all
1560 pass two values to the callback, C<$status> and C<$res>, where C<$status>
1561 is the HTTP status code (200 for successful requests, typically 4xx or
1562 5xx for errors), and C<$res> is the value of the C<value> key in the JSON
1563 response object.
1564
1565 Other than that, the underscore variants and the blocking variants are
1566 identical.
1567
1568 =head2 LOW LEVEL API
1569
1570 All the simplified API methods are very thin wrappers around WebDriver
1571 commands of the same name. They are all implemented in terms of the
1572 low-level methods (C<req>, C<get>, C<post> and C<delete>), which exists
1573 in blocking and callback-based variants (C<req_>, C<get_>, C<post_> and
1574 C<delete_>).
1575
1576 Examples are after the function descriptions.
1577
1578 =over
1579
1580 =item $wd->req_ ($method, $uri, $body, $cb->($status, $value))
1581
1582 =item $value = $wd->req ($method, $uri, $body)
1583
1584 Appends the C<$uri> to the C<endpoint/session/{sessionid}/> URL and makes
1585 a HTTP C<$method> request (C<GET>, C<POST> etc.). C<POST> requests can
1586 provide a UTF-8-encoded JSON text as HTTP request body, or the empty
1587 string to indicate no body is used.
1588
1589 For the callback version, the callback gets passed the HTTP status code
1590 (200 for every successful request), and the value of the C<value> key in
1591 the JSON response object as second argument.
1592
1593 =item $wd->get_ ($uri, $cb->($status, $value))
1594
1595 =item $value = $wd->get ($uri)
1596
1597 Simply a call to C<req_> with C<$method> set to C<GET> and an empty body.
1598
1599 =item $wd->post_ ($uri, $data, $cb->($status, $value))
1600
1601 =item $value = $wd->post ($uri, $data)
1602
1603 Simply a call to C<req_> with C<$method> set to C<POST> - if C<$body> is
1604 C<undef>, then an empty object is send, otherwise, C<$data> must be a
1605 valid request object, which gets encoded into JSON for you.
1606
1607 =item $wd->delete_ ($uri, $cb->($status, $value))
1608
1609 =item $value = $wd->delete ($uri)
1610
1611 Simply a call to C<req_> with C<$method> set to C<DELETE> and an empty body.
1612
1613 =cut
1614
1615 =back
1616
1617 Example: implement C<get_all_cookies>, which is a simple C<GET> request
1618 without any parameters:
1619
1620 $cookies = $wd->get ("cookie");
1621
1622 Example: implement C<execute_script>, which needs some parameters:
1623
1624 $results = $wd->post ("execute/sync" => { script => "$javascript", args => [] });
1625
1626 Example: call C<find_elements> to find all C<IMG> elements:
1627
1628 $elems = $wd->post (elements => { using => "css selector", value => "img" });
1629
1630 =cut
1631
1632 =head1 HISTORY
1633
1634 This module was unintentionally created (it started inside some quickly
1635 hacked-together script) simply because I couldn't get the existing
1636 C<Selenium::Remote::Driver> module to work, ever, despite multiple
1637 attempts over the years and trying to report multiple bugs, which have
1638 been completely ignored. It's also not event-based, so, yeah...
1639
1640 =head1 AUTHOR
1641
1642 Marc Lehmann <schmorp@schmorp.de>
1643 http://anyevent.schmorp.de
1644
1645 =cut
1646
1647 1
1648