ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-WebDriver/WebDriver.pm
Revision: 1.19
Committed: Wed Aug 29 07:48:19 2018 UTC (5 years, 9 months ago) by root
Branch: MAIN
Changes since 1.18: +2 -0 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 expliticly 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 givne 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: switchdefine a new touch device called C<fatfinger>.
1135
1136 $al->source (fatfinger => "pointer", pointerType => "touch");
1137
1138 =cut
1139
1140 sub _default_source($) {
1141 my ($source) = @_;
1142
1143 $source eq "keyboard" ? { actions => [], id => $source, type => "key" }
1144 : $source eq "mouse" ? { actions => [], id => $source, type => "pointer", pointerType => "mouse" }
1145 : $source eq "touch" ? { actions => [], id => $source, type => "pointer", pointerType => "touch" }
1146 : $source eq "pen" ? { actions => [], id => $source, type => "pointer", pointerType => "pen" }
1147 : Carp::croak "AnyEvent::WebDriver::Actions: event source '$source' not defined"
1148 }
1149
1150 my %source_class = (
1151 key => "kbd",
1152 pointer => "ptr",
1153 );
1154
1155 sub source {
1156 my ($self, $id, $type, %kv) = @_;
1157
1158 if (defined $type) {
1159 !exists $self->{source}{$id}
1160 or Carp::croak "AnyEvent::WebDriver::Actions: source '$id' already defined";
1161
1162 $kv{id} = $id;
1163 $kv{type} = $type;
1164 $kv{actions} = [];
1165
1166 $self->{source}{$id} = \%kv;
1167 }
1168
1169 my $source = $self->{source}{$id} ||= _default_source $id;
1170
1171 my $last = $source_class{$source->{type}} // "xxx";
1172
1173 $self->{"last_$last"} = $id;
1174
1175 $self
1176 }
1177
1178 sub _add {
1179 my ($self, $source, $sourcetype, $type, %kv) = @_;
1180
1181 my $last = \$self->{"last_$sourcetype"};
1182
1183 $source
1184 ? ($$last = $source)
1185 : ($source = $$last);
1186
1187 my $source = $self->{source}{$source} ||= _default_source $source;
1188
1189 my $al = $source->{actions};
1190
1191 push @$al, { type => "pause" }
1192 while @$al < $self->{tick} - 1;
1193
1194 $kv{type} = $type;
1195
1196 push @{ $source->{actions} }, \%kv;
1197
1198 $self->{tick_duration} = $kv{duration}
1199 if $kv{duration} > $self->{tick_duration};
1200
1201 if ($self->{tick} != @$al) {
1202 $self->{tick} = @$al;
1203 $self->{duration} += delete $self->{tick_duration};
1204 }
1205
1206 $self
1207 }
1208
1209 =item $al = $al->pause ($duration)
1210
1211 Creates a pause with the given duration. Makes sure that time progresses
1212 in any case, even when C<$duration> is C<0>.
1213
1214 =cut
1215
1216 sub pause {
1217 my ($self, $duration) = @_;
1218
1219 $self->{tick_duration} = $duration
1220 if $duration > $self->{tick_duration};
1221
1222 $self->{duration} += delete $self->{tick_duration};
1223
1224 # find the source with the longest list
1225
1226 for my $source (values %{ $self->{source} }) {
1227 if (@{ $source->{actions} } == $self->{tick}) {
1228 # this source is one of the longest
1229
1230 # create a pause event only if $duration is non-zero...
1231 push @{ $source->{actions} }, { type => "pause", duration => $duration*1 }
1232 if $duration;
1233
1234 # ... but advance time in any case
1235 ++$self->{tick};
1236
1237 return $self;
1238 }
1239 }
1240
1241 # no event sources are longest. so advance time in any case
1242 ++$self->{tick};
1243
1244 Carp::croak "AnyEvent::WebDriver::Actions: multiple pause calls in a row not (yet) supported"
1245 if $duration;
1246
1247 $self
1248 }
1249
1250 =item $al = $al->pointer_down ($button, $source)
1251
1252 =item $al = $al->pointer_up ($button, $source)
1253
1254 Press or release the given button. C<$button> defaults to C<1>.
1255
1256 =item $al = $al->click ($button, $source)
1257
1258 Convenience function that creates a button press and release action
1259 without any delay between them. C<$button> defaults to C<1>.
1260
1261 =item $al = $al->doubleclick ($button, $source)
1262
1263 Convenience function that creates two button press and release action
1264 pairs in a row, with no unnecessary delay between them. C<$button>
1265 defaults to C<1>.
1266
1267 =cut
1268
1269 sub pointer_down {
1270 my ($self, $button, $source) = @_;
1271
1272 $self->_add ($source, ptr => pointerDown => button => ($button // 1)*1)
1273 }
1274
1275 sub pointer_up {
1276 my ($self, $button, $source) = @_;
1277
1278 $self->_add ($source, ptr => pointerUp => button => ($button // 1)*1)
1279 }
1280
1281 sub click {
1282 my ($self, $button, $source) = @_;
1283
1284 $self
1285 ->pointer_down ($button, $source)
1286 ->pointer_up ($button)
1287 }
1288
1289 sub doubleclick {
1290 my ($self, $button, $source) = @_;
1291
1292 $self
1293 ->click ($button, $source)
1294 ->click ($button)
1295 }
1296
1297 =item $al = $al->move ($button, $origin, $x, $y, $duration, $source)
1298
1299 Moves a pointer to the given position, relative to origin (either
1300 "viewport", "pointer" or an element object.
1301
1302 =cut
1303
1304 sub move {
1305 my ($self, $origin, $x, $y, $duration, $source) = @_;
1306
1307 $self->_add ($source, ptr => pointerMove =>
1308 origin => $origin, x => $x*1, y => $y*1, duration => $duration*1)
1309 }
1310
1311 =item $al = $al->keyDown ($key, $source)
1312
1313 =item $al = $al->keyUp ($key, $source)
1314
1315 Press or release the given key.
1316
1317 =item $al = $al->key ($key, $source)
1318
1319 Peess and release the given key, without unnecessary delay.
1320
1321 A special syntax, C<{keyname}> can be used for special keys - all the special key names from
1322 L<section 17.4.2|https://www.w3.org/TR/webdriver1/#keyboard-actions> of the WebDriver recommendation
1323 can be used.
1324
1325 Example: press and release "a".
1326
1327 $al->key ("a");
1328
1329 Example: press and release the "Enter" key:
1330
1331 $al->key ("\x{e007}");
1332
1333 Example: press and release the "enter" key using the special key name syntax:
1334
1335 $al->key ("{Enter}");
1336
1337 =item $al = $al->type ($string, $source)
1338
1339 Convenience method to simulate a series of key press and release events
1340 for the keys in C<$string>. There is no syntax for special keys,
1341 everything will be typed "as-is" if possible.
1342
1343 =cut
1344
1345 our %SPECIAL_KEY = (
1346 "Unidentified" => 0xE000,
1347 "Cancel" => 0xE001,
1348 "Help" => 0xE002,
1349 "Backspace" => 0xE003,
1350 "Tab" => 0xE004,
1351 "Clear" => 0xE005,
1352 "Return" => 0xE006,
1353 "Enter" => 0xE007,
1354 "Shift" => 0xE008,
1355 "Control" => 0xE009,
1356 "Alt" => 0xE00A,
1357 "Pause" => 0xE00B,
1358 "Escape" => 0xE00C,
1359 " " => 0xE00D,
1360 "PageUp" => 0xE00E,
1361 "PageDown" => 0xE00F,
1362 "End" => 0xE010,
1363 "Home" => 0xE011,
1364 "ArrowLeft" => 0xE012,
1365 "ArrowUp" => 0xE013,
1366 "ArrowRight" => 0xE014,
1367 "ArrowDown" => 0xE015,
1368 "Insert" => 0xE016,
1369 "Delete" => 0xE017,
1370 ";" => 0xE018,
1371 "=" => 0xE019,
1372 "0" => 0xE01A,
1373 "1" => 0xE01B,
1374 "2" => 0xE01C,
1375 "3" => 0xE01D,
1376 "4" => 0xE01E,
1377 "5" => 0xE01F,
1378 "6" => 0xE020,
1379 "7" => 0xE021,
1380 "8" => 0xE022,
1381 "9" => 0xE023,
1382 "*" => 0xE024,
1383 "+" => 0xE025,
1384 "," => 0xE026,
1385 "-" => 0xE027,
1386 "." => 0xE028,
1387 "/" => 0xE029,
1388 "F1" => 0xE031,
1389 "F2" => 0xE032,
1390 "F3" => 0xE033,
1391 "F4" => 0xE034,
1392 "F5" => 0xE035,
1393 "F6" => 0xE036,
1394 "F7" => 0xE037,
1395 "F8" => 0xE038,
1396 "F9" => 0xE039,
1397 "F10" => 0xE03A,
1398 "F11" => 0xE03B,
1399 "F12" => 0xE03C,
1400 "Meta" => 0xE03D,
1401 "ZenkakuHankaku" => 0xE040,
1402 "Shift" => 0xE050,
1403 "Control" => 0xE051,
1404 "Alt" => 0xE052,
1405 "Meta" => 0xE053,
1406 "PageUp" => 0xE054,
1407 "PageDown" => 0xE055,
1408 "End" => 0xE056,
1409 "Home" => 0xE057,
1410 "ArrowLeft" => 0xE058,
1411 "ArrowUp" => 0xE059,
1412 "ArrowRight" => 0xE05A,
1413 "ArrowDown" => 0xE05B,
1414 "Insert" => 0xE05C,
1415 "Delete" => 0xE05D,
1416 );
1417
1418 sub _kv($) {
1419 $_[0] =~ /^\{(.*)\}$/s
1420 ? (exists $SPECIAL_KEY{$1}
1421 ? chr $SPECIAL_KEY{$1}
1422 : Carp::croak "AnyEvent::WebDriver::Actions: special key '$1' not known")
1423 : $_[0]
1424 }
1425
1426 sub key_down {
1427 my ($self, $key, $source) = @_;
1428
1429 $self->_add ($source, kbd => keyDown => value => _kv $key)
1430 }
1431
1432 sub key_up {
1433 my ($self, $key, $source) = @_;
1434
1435 $self->_add ($source, kbd => keyUp => value => _kv $key)
1436 }
1437
1438 sub key {
1439 my ($self, $key, $source) = @_;
1440
1441 $self
1442 ->key_down ($key, $source)
1443 ->key_up ($key)
1444 }
1445
1446 sub type {
1447 my ($self, $string, $source) = @_;
1448
1449 $self->key ($_, $source)
1450 for $string =~ /(\X)/g;
1451
1452 $self
1453 }
1454
1455 =item $al->perform ($wd)
1456
1457 Finaluses and compiles the list, if not done yet, and calls C<<
1458 $wd->perform >> with it.
1459
1460 If C<$wd> is undef, and the action list was created using the C<<
1461 $wd->actions >> method, then perform it against that WebDriver object.
1462
1463 There is no underscore variant - call the C<perform_actions_> method with
1464 the action object instead.
1465
1466 =item $al->perform_release ($wd)
1467
1468 Exactly like C<perform>, but additionally call C<release_actions>
1469 afterwards.
1470
1471 =cut
1472
1473 sub perform {
1474 my ($self, $wd) = @_;
1475
1476 ($wd //= $self->{wd})->perform_actions ($self)
1477 }
1478
1479 sub perform_release {
1480 my ($self, $wd) = @_;
1481
1482 ($wd //= $self->{wd})->perform_actions ($self);
1483 $wd->release_actions;
1484 }
1485
1486 =item ($actions, $duration) = $al->compile
1487
1488 Finalises and compiles the list, if not done yet, and returns an actions
1489 object suitable for calls to C<< $wd->perform_actions >>. When called in
1490 list context, additionally returns the total duration of the action list.
1491
1492 Since building large action lists can take nontrivial amounts of time,
1493 it can make sense to build an action list only once and then perform it
1494 multiple times.
1495
1496 Actions must not be added after compiling a list.
1497
1498 =cut
1499
1500 sub compile {
1501 my ($self) = @_;
1502
1503 $self->{duration} += delete $self->{tick_duration};
1504
1505 delete $self->{tick};
1506 delete $self->{last_kbd};
1507 delete $self->{last_ptr};
1508
1509 $self->{actions} ||= [values %{ delete $self->{source} }];
1510
1511 wantarray
1512 ? ($self->{actions}, $self->{duration})
1513 : $self->{actions}
1514 }
1515
1516 =back
1517
1518 =head2 EVENT BASED API
1519
1520 This module wouldn't be a good AnyEvent citizen if it didn't have a true
1521 event-based API.
1522
1523 In fact, the simplified API, as documented above, is emulated via the
1524 event-based API and an C<AUTOLOAD> function that automatically provides
1525 blocking wrappers around the callback-based API.
1526
1527 Every method documented in the L<SIMPLIFIED API> section has an equivalent
1528 event-based method that is formed by appending a underscore (C<_>) to the
1529 method name, and appending a callback to the argument list (mnemonic: the
1530 underscore indicates the "the action is not yet finished" after the call
1531 returns).
1532
1533 For example, instead of a blocking calls to C<new_session>, C<navigate_to>
1534 and C<back>, you can make a callback-based ones:
1535
1536 my $cv = AE::cv;
1537
1538 $wd->new_session ({}, sub {
1539 my ($status, $value) = @_,
1540
1541 die "error $value->{error}" if $status ne "200";
1542
1543 $wd->navigate_to_ ("http://www.nethype.de", sub {
1544
1545 $wd->back_ (sub {
1546 print "all done\n";
1547 $cv->send;
1548 });
1549
1550 });
1551 });
1552
1553 $cv->recv;
1554
1555 While the blocking methods C<croak> on errors, the callback-based ones all
1556 pass two values to the callback, C<$status> and C<$res>, where C<$status>
1557 is the HTTP status code (200 for successful requests, typically 4xx or
1558 5xx for errors), and C<$res> is the value of the C<value> key in the JSON
1559 response object.
1560
1561 Other than that, the underscore variants and the blocking variants are
1562 identical.
1563
1564 =head2 LOW LEVEL API
1565
1566 All the simplified API methods are very thin wrappers around WebDriver
1567 commands of the same name. They are all implemented in terms of the
1568 low-level methods (C<req>, C<get>, C<post> and C<delete>), which exists
1569 in blocking and callback-based variants (C<req_>, C<get_>, C<post_> and
1570 C<delete_>).
1571
1572 Examples are after the function descriptions.
1573
1574 =over
1575
1576 =item $wd->req_ ($method, $uri, $body, $cb->($status, $value))
1577
1578 =item $value = $wd->req ($method, $uri, $body)
1579
1580 Appends the C<$uri> to the C<endpoint/session/{sessionid}/> URL and makes
1581 a HTTP C<$method> request (C<GET>, C<POST> etc.). C<POST> requests can
1582 provide a UTF-8-encoded JSON text as HTTP request body, or the empty
1583 string to indicate no body is used.
1584
1585 For the callback version, the callback gets passed the HTTP status code
1586 (200 for every successful request), and the value of the C<value> key in
1587 the JSON response object as second argument.
1588
1589 =item $wd->get_ ($uri, $cb->($status, $value))
1590
1591 =item $value = $wd->get ($uri)
1592
1593 Simply a call to C<req_> with C<$method> set to C<GET> and an empty body.
1594
1595 =item $wd->post_ ($uri, $data, $cb->($status, $value))
1596
1597 =item $value = $wd->post ($uri, $data)
1598
1599 Simply a call to C<req_> with C<$method> set to C<POST> - if C<$body> is
1600 C<undef>, then an empty object is send, otherwise, C<$data> must be a
1601 valid request object, which gets encoded into JSON for you.
1602
1603 =item $wd->delete_ ($uri, $cb->($status, $value))
1604
1605 =item $value = $wd->delete ($uri)
1606
1607 Simply a call to C<req_> with C<$method> set to C<DELETE> and an empty body.
1608
1609 =cut
1610
1611 =back
1612
1613 Example: implement C<get_all_cookies>, which is a simple C<GET> request
1614 without any parameters:
1615
1616 $cookies = $wd->get ("cookie");
1617
1618 Example: implement C<execute_script>, which needs some parameters:
1619
1620 $results = $wd->post ("execute/sync" => { script => "$javascript", args => [] });
1621
1622 Example: call C<find_elements> to find all C<IMG> elements:
1623
1624 $elems = $wd->post (elements => { using => "css selector", value => "img" });
1625
1626 =cut
1627
1628 =head1 HISTORY
1629
1630 This module was unintentionally created (it started inside some quickly
1631 hacked-together script) simply because I couldn't get the existing
1632 C<Selenium::Remote::Driver> module to work, ever, despite multiple
1633 attempts over the years and trying to report multiple bugs, which have
1634 been completely ignored. It's also not event-based, so, yeah...
1635
1636 =head1 AUTHOR
1637
1638 Marc Lehmann <schmorp@schmorp.de>
1639 http://anyevent.schmorp.de
1640
1641 =cut
1642
1643 1
1644