ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-WebDriver/WebDriver.pm
Revision: 1.13
Committed: Wed Aug 29 02:47:02 2018 UTC (5 years, 10 months ago) by root
Branch: MAIN
Changes since 1.12: +152 -19 lines
Log Message:
*** empty log message ***

File Contents

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