ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-WebDriver/README
Revision: 1.5
Committed: Wed Aug 29 05:55:23 2018 UTC (5 years, 9 months ago) by root
Branch: MAIN
CVS Tags: rel-0_5
Changes since 1.4: +108 -11 lines
Log Message:
*** empty log message ***

File Contents

# Content
1 NAME
2 AnyEvent::WebDriver - control browsers using the W3C WebDriver protocol
3
4 SYNOPSIS
5 # start geckodriver or any other w3c-compatible webdriver via the shell
6 $ geckdriver -b myfirefox/firefox --log trace --port 4444
7
8 # then use it
9 use AnyEvent::WebDriver;
10
11 # create a new webdriver object
12 my $wd = new AnyEvent::WebDriver;
13
14 # create a new session with default capabilities.
15 $wd->new_session ({});
16
17 $wd->navigate_to ("https://duckduckgo.com/html");
18 my $searchbox = $wd->find_element ("css selector" => 'input[type="text"]');
19
20 $wd->element_send_keys ($searchbox => "free software");
21 $wd->element_click ($wd->find_element ("css selector" => 'input[type="submit"]'));
22
23 sleep 10;
24
25 DESCRIPTION
26 WARNING: THE API IS NOT GUARANTEED TO BE STABLE UNTIL VERSION 1.0.
27
28 This module aims to implement the W3C WebDriver specification which is
29 the standardised equivalent to the Selenium WebDriver API., which in
30 turn aims at remotely controlling web browsers such as Firefox or
31 Chromium.
32
33 At the time of this writing, it was so brand new that I ciould only get
34 "geckodriver" (For Firefox) to work, but that is expected to be fioxed
35 very soon indeed.
36
37 To make most of this module, or, in fact, to make any reasonable use of
38 this module, you would need to refer to the W3C WebDriver
39 recommendation, which can be found here
40 <https://www.w3.org/TR/webdriver1/>:
41
42 https://www.w3.org/TR/webdriver1/
43
44 CONVENTIONS
45 Unless otherwise stated, all delays and time differences in this module
46 are represented as an integer number of milliseconds.
47
48 WEBDRIVER OBJECTS
49 new AnyEvent::WebDriver key => value...
50 Create a new WebDriver object. Example for a remote WebDriver
51 connection (the only type supported at the moment):
52
53 my $wd = new AnyEvent::WebDriver host => "localhost", port => 4444;
54
55 Supported keys are:
56
57 endpoint => $string
58 For remote connections, the endpoint to connect to (defaults to
59 "http://localhost:4444").
60
61 proxy => $proxyspec
62 The proxy to use (same as the "proxy" argument used by
63 AnyEvent::HTTP). The default is "undef", which disables proxies.
64 To use the system-provided proxy (e.g. "http_proxy" environment
65 variable), specify a value of "default".
66
67 autodelete => $boolean
68 If true (the default), then automatically execute
69 "delete_session" when the WebDriver object is destroyed with an
70 active session. IF set to a false value, then the session will
71 continue to exist.
72
73 timeout => $seconds
74 The HTTP timeout, in (fractional) seconds (default: 300, but
75 this will likely drastically reduce). This timeout is reset on
76 any activity, so it is not an overall request timeout. Also,
77 individual requests might extend this timeout if they are known
78 to take longer.
79
80 $al = $wd->actions
81 Creates an action list associated with this WebDriver. See ACTION
82 LISTS, below, for full details.
83
84 $sessionstring = $wd->save_session
85 Save the current session in a string so it can be restored load with
86 "load_session". Note that only the session data itself is stored
87 (currently the session id and capabilities), not the endpoint
88 information itself.
89
90 The main use of this function is in conjunction with disabled
91 "autodelete", to save a session to e.g., and restore it later. It
92 could presumably used for other applications, suhc as using the same
93 sssion from multiple processes and so on.
94
95 $wd->load_session ($sessionstring)
96 $wd->set_session ($sessionid, $capabilities)
97 Starts using the given session, as identified by $sessionid.
98 $capabilities should be the original session capabilities, although
99 the current version of this module does not make any use of it.
100
101 The $sessionid is stored in "$wd->{sid}" (and could be fetched form
102 there for later use), while the capabilities are stored in
103 "$wd->{capabilities}".
104
105 SIMPLIFIED API
106 This section documents the simplified API, which is really just a very
107 thin wrapper around the WebDriver protocol commands. They all block
108 (using AnyEvent condvars) the caller until the result is available, so
109 must not be called from an event loop callback - see "EVENT BASED API"
110 for an alternative.
111
112 The method names are pretty much taken directly from the W3C WebDriver
113 specification, e.g. the request documented in the "Get All Cookies"
114 section is implemented via the "get_all_cookies" method.
115
116 The order is the same as in the WebDriver draft at the time of this
117 writing, and only minimal massaging is done to request parameters and
118 results.
119
120 SESSIONS
121 $wd->new_session ({ key => value... })
122 Try to connect to the WebDriver and initialize a new session with a
123 "new session" command, passing the given key-value pairs as value
124 (e.g. "capabilities").
125
126 No session-dependent methods must be called before this function
127 returns successfully, and only one session can be created per
128 WebDriver object.
129
130 On success, "$wd->{sid}" is set to the session ID, and
131 "$wd->{capabilities}" is set to the returned capabilities.
132
133 Simple example of creatring a WebDriver object and a new session:
134
135 my $wd = new AnyEvent::Selenium endpoint => "http://localhost:4545";
136 $wd->new_session ({});
137
138 Real-world example with capability negotiation:
139
140 $wd->new_session ({
141 capabilities => {
142 alwaysMatch => {
143 pageLoadStrategy => "eager",
144 unhandledPromptBehavior => "dismiss",
145 # proxy => { proxyType => "manual", httpProxy => "1.2.3.4:56", sslProxy => "1.2.3.4:56" },
146 },
147 firstMatch => [
148 {
149 browserName => "firefox",
150 "moz:firefoxOptions" => {
151 binary => "firefox/firefox",
152 args => ["-devtools"],
153 prefs => {
154 "dom.webnotifications.enabled" => \0,
155 "dom.disable_beforeunload" => \1,
156 "browser.link.open_newwindow" => 3,
157 "browser.link.open_newwindow.restrictions" => 0,
158 "dom.popup_allowed_events" => "",
159 "dom.disable_open_during_load" => \1,
160 },
161 },
162 },
163 {
164 # generic fallback
165 },
166 ],
167
168 },
169 });
170
171 Firefox-specific capability documentation can be found on MDN
172 <https://developer.mozilla.org/en-US/docs/Web/WebDriver/Capabilities
173 >, Chrome-specific capability documentation might be found here
174 <http://chromedriver.chromium.org/capabilities>, but the latest
175 release at the time of this writing has effectively no WebDriver
176 support at all, and canary releases are not freely downloadable.
177
178 If you have URLs for Safari/IE/Edge etc. capabilities, feel free to
179 tell me about them.
180
181 $wd->delete_session
182 Deletes the session - the WebDriver object must not be used after
183 this call.
184
185 $timeouts = $wd->get_timeouts
186 Get the current timeouts, e.g.:
187
188 my $timeouts = $wd->get_timeouts;
189 => { implicit => 0, pageLoad => 300000, script => 30000 }
190
191 $wd->set_timeouts ($timeouts)
192 Sets one or more timeouts, e.g.:
193
194 $wd->set_timeouts ({ script => 60000 });
195
196 NAVIGATION
197 $wd->navigate_to ($url)
198 Navigates to the specified URL.
199
200 $url = $wd->get_current_url
201 Queries the current page URL as set by "navigate_to".
202
203 $wd->back
204 The equivalent of pressing "back" in the browser.
205
206 $wd->forward
207 The equivalent of pressing "forward" in the browser.
208
209 $wd->refresh
210 The equivalent of pressing "refresh" in the browser.
211
212 $title = $wd->get_title
213 Returns the current document title.
214
215 COMMAND CONTEXTS
216 $handle = $wd->get_window_handle
217 Returns the current window handle.
218
219 $wd->close_window
220 Closes the current browsing context.
221
222 $wd->switch_to_window ($handle)
223 Changes the current browsing context to the given window.
224
225 $handles = $wd->get_window_handles
226 Return the current window handles as an array-ref of handle IDs.
227
228 $handles = $wd->switch_to_frame ($frame)
229 Switch to the given frame identified by $frame, which must be either
230 "undef" to go back to the top-level browsing context, an integer to
231 select the nth subframe, or an element object.
232
233 $handles = $wd->switch_to_parent_frame
234 Switch to the parent frame.
235
236 $rect = $wd->get_window_rect
237 Return the current window rect, e.g.:
238
239 $rect = $wd->get_window_rect
240 => { height => 1040, width => 540, x => 0, y => 0 }
241
242 $wd->set_window_rect ($rect)
243 Sets the window rect.
244
245 $wd->maximize_window
246 $wd->minimize_window
247 $wd->fullscreen_window
248 Changes the window size by either maximising, minimising or making
249 it fullscreen. In my experience, this will timeout if no window
250 manager is running.
251
252 ELEMENT RETRIEVAL
253 To reduce typing and memory strain, the element finding functions accept
254 some shorter and hopefully easier to remember aliases for the standard
255 locator strategy values, as follows:
256
257 Alias Locator Strategy
258 css css selector
259 link link text
260 substr partial link text
261 tag tag name
262
263 $element = $wd->find_element ($locator_strategy, $selector)
264 Finds the first element specified by the given selector and returns
265 its element object. Raises an error when no element was found.
266
267 $element = $wd->find_element ("css selector" => "body a");
268 $element = $wd->find_element ("link text" => "Click Here For Porn");
269 $element = $wd->find_element ("partial link text" => "orn");
270 $element = $wd->find_element ("tag name" => "input");
271 $element = $wd->find_element ("xpath" => '//input[@type="text"]');
272 => e.g. { "element-6066-11e4-a52e-4f735466cecf" => "decddca8-5986-4e1d-8c93-efe952505a5f" }
273
274 $elements = $wd->find_elements ($locator_strategy, $selector)
275 As above, but returns an arrayref of all found element objects.
276
277 $element = $wd->find_element_from_element ($element, $locator_strategy,
278 $selector)
279 Like "find_element", but looks only inside the specified $element.
280
281 $elements = $wd->find_elements_from_element ($element,
282 $locator_strategy, $selector)
283 Like "find_elements", but looks only inside the specified $element.
284
285 my $head = $wd->find_element ("tag name" => "head");
286 my $links = $wd->find_elements_from_element ($head, "tag name", "link");
287
288 $element = $wd->get_active_element
289 Returns the active element.
290
291 ELEMENT STATE
292 $bool = $wd->is_element_selected
293 Returns whether the given input or option element is selected or
294 not.
295
296 $string = $wd->get_element_attribute ($element, $name)
297 Returns the value of the given attribute.
298
299 $string = $wd->get_element_property ($element, $name)
300 Returns the value of the given property.
301
302 $string = $wd->get_element_css_value ($element, $name)
303 Returns the value of the given CSS value.
304
305 $string = $wd->get_element_text ($element)
306 Returns the (rendered) text content of the given element.
307
308 $string = $wd->get_element_tag_name ($element)
309 Returns the tag of the given element.
310
311 $rect = $wd->get_element_rect ($element)
312 Returns the element rect(angle) of the given element.
313
314 $bool = $wd->is_element_enabled
315 Returns whether the element is enabled or not.
316
317 ELEMENT INTERACTION
318 $wd->element_click ($element)
319 Clicks the given element.
320
321 $wd->element_clear ($element)
322 Clear the contents of the given element.
323
324 $wd->element_send_keys ($element, $text)
325 Sends the given text as key events to the given element.
326
327 DOCUMENT HANDLING
328 $source = $wd->get_page_source
329 Returns the (HTML/XML) page source of the current document.
330
331 $results = $wd->execute_script ($javascript, $args)
332 Synchronously execute the given script with given arguments and
333 return its results ($args can be "undef" if no arguments are
334 wanted/needed).
335
336 $ten = $wd->execute_script ("return arguments[0]+arguments[1]", [3, 7]);
337
338 $results = $wd->execute_async_script ($javascript, $args)
339 Similar to "execute_script", but doesn't wait for script to return,
340 but instead waits for the script to call its last argument, which is
341 added to $args automatically.
342
343 $twenty = $wd->execute_async_script ("arguments[0](20)", undef);
344
345 COOKIES
346 $cookies = $wd->get_all_cookies
347 Returns all cookies, as an arrayref of hashrefs.
348
349 # google surely sets a lot of cookies without my consent
350 $wd->navigate_to ("http://google.com");
351 use Data::Dump;
352 ddx $wd->get_all_cookies;
353
354 $cookie = $wd->get_named_cookie ($name)
355 Returns a single cookie as a hashref.
356
357 $wd->add_cookie ($cookie)
358 Adds the given cookie hashref.
359
360 $wd->delete_cookie ($name)
361 Delete the named cookie.
362
363 $wd->delete_all_cookies
364 Delete all cookies.
365
366 ACTIONS
367 $wd->perform_actions ($actions)
368 Perform the given actions (an arrayref of action specifications
369 simulating user activity, or an "AnyEvent::WebDriver::Actions"
370 object). For further details, read the spec or the section "ACTION
371 LISTS", below.
372
373 An example to get you started (see the next example for a mostly
374 equivalent example using the "AnyEvent::WebDriver::Actions" helper
375 API):
376
377 $wd->navigate_to ("https://duckduckgo.com/html");
378 my $input = $wd->find_element ("css selector", 'input[type="text"]');
379 $wd->perform_actions ([
380 {
381 id => "myfatfinger",
382 type => "pointer",
383 pointerType => "touch",
384 actions => [
385 { type => "pointerMove", duration => 100, origin => $input, x => 40, y => 5 },
386 { type => "pointerDown", button => 1 },
387 { type => "pause", duration => 40 },
388 { type => "pointerUp", button => 1 },
389 ],
390 },
391 {
392 id => "mykeyboard",
393 type => "key",
394 actions => [
395 { type => "pause" },
396 { type => "pause" },
397 { type => "pause" },
398 { type => "pause" },
399 { type => "keyDown", value => "a" },
400 { type => "pause", duration => 100 },
401 { type => "keyUp", value => "a" },
402 { type => "pause", duration => 100 },
403 { type => "keyDown", value => "b" },
404 { type => "pause", duration => 100 },
405 { type => "keyUp", value => "b" },
406 { type => "pause", duration => 2000 },
407 { type => "keyDown", value => "\x{E007}" }, # enter
408 { type => "pause", duration => 100 },
409 { type => "keyUp", value => "\x{E007}" }, # enter
410 { type => "pause", duration => 5000 },
411 ],
412 },
413 ]);
414
415 And here is essentially the same (except for fewer pauses) example
416 as above, using the much simpler "AnyEvent::WebDriver::Actions" API.
417 Note that the pointer up and key down event happen concurrently in
418 this example:
419
420 $wd->navigate_to ("https://duckduckgo.com/html");
421 my $input = $wd->find_element ("css selector", 'input[type="text"]');
422 $wd->actions
423 ->move ($input, 40, 5, "touch1")
424 ->click;
425 ->key ("a");
426 ->key ("b");
427 ->pause (2000);
428 ->key ("\x{E007}")
429 ->pause (5000);
430 ->perform;
431
432 $wd->release_actions
433 Release all keys and pointer buttons currently depressed.
434
435 USER PROMPTS
436 $wd->dismiss_alert
437 Dismiss a simple dialog, if present.
438
439 $wd->accept_alert
440 Accept a simple dialog, if present.
441
442 $text = $wd->get_alert_text
443 Returns the text of any simple dialog.
444
445 $text = $wd->send_alert_text
446 Fills in the user prompt with the given text.
447
448 SCREEN CAPTURE
449 $wd->take_screenshot
450 Create a screenshot, returning it as a PNG image in a "data:" URL.
451
452 $wd->take_element_screenshot ($element)
453 Accept a simple dialog, if present.
454
455 ACTION LISTS
456 Action lists can be quite complicated. Or at least it took a while for
457 me to twist my head around them. Basically, an action list consists of a
458 number of sources representing devices (such as a finger, a mouse, a pen
459 or a keyboard) and a list of actions for each source.
460
461 An action can be a key press, a pointer move or a pause (time delay).
462 Actions from different sources can happen "at the same time", while
463 actions from a single source are executed in order.
464
465 While you can provide an action list manually, it is (hopefully) less
466 cumbersome to use the API described in this section to create them.
467
468 The basic process of creating and performing actions is to create a new
469 action list, adding action sources, followed by adding actions. Finally
470 you would "perform" those actions on the WebDriver.
471
472 Virtual time progresses as long as you add actions to the same event
473 source. Adding events to different sources are considered to happen
474 concurrently. If you want to force time to progress, you can do this
475 using a call to "->pause (0)".
476
477 Most methods here are designed to chain, i.e. they return the web
478 actions object, to simplify multiple calls.
479
480 For example, to simulate a mouse click to an input element, followed by
481 entering some text and pressing enter, you can use this:
482
483 $wd->actions
484 ->click (1, 100)
485 ->type ("some text")
486 ->key ("{Enter}")
487 ->perform;
488
489 By default, keyboard and mouse input sources are provided. You can
490 create your own sources and use them when adding events. The above
491 example could be more verbosely written like this:
492
493 $wd->actions
494 ->click (1, 100, "mouse")
495 ->type ("some text")
496 ->key ("{Enter}")
497 ->perform;
498
499 When you specify the event source expliticly it will switch the current
500 "focus" for this class of device (all keyboards are in one class, all
501 pointer-like devices such as mice/fingers/pens are in one class), so you
502 don't have to specify the source for subsequent actions.
503
504 When you use the sources "keyboard", "mouse", "touch1".."touch3", "pen"
505 without defining them, then a suitable default source will be created
506 for them.
507
508 $al = new AnyEvent::WebDriver::Actions
509 Create a new empty action list object. More often you would use the
510 "$wd->action_list" method to create one that is already associated
511 with a given web driver.
512
513 $al = $al->source ($id, $type, key => value...)
514 The first time you call this with a givne ID, this defines the event
515 source using the extra parameters. Subsequent calls merely switch
516 the current source for its event class.
517
518 It's not an error to define built-in sources (such as "keyboard" or
519 "touch1") differently then the defaults.
520
521 Example: define a new touch device called "fatfinger".
522
523 $al->source (fatfinger => "pointer", pointerType => "touch");
524
525 Example: switchdefine a new touch device called "fatfinger".
526
527 $al->source (fatfinger => "pointer", pointerType => "touch");
528
529 $al = $al->pause ($duration)
530 Creates a pause with the given duration. Makes sure that time
531 progresses in any case, even when $duration is 0.
532
533 $al = $al->pointer_down ($button, $source)
534 $al = $al->pointer_up ($button, $source)
535 Press or release the given button. $button defaults to 1.
536
537 $al = $al->click ($button, $source)
538 Convenience function that creates a button press and release action
539 without any delay between them. $button defaults to 1.
540
541 $al = $al->doubleclick ($button, $source)
542 Convenience function that creates two button press and release
543 action pairs in a row, with no unnecessary delay between them.
544 $button defaults to 1.
545
546 $al = $al->move ($button, $origin, $x, $y, $duration, $source)
547 Moves a pointer to the given position, relative to origin (either
548 "viewport", "pointer" or an element object.
549
550 $al = $al->keyDown ($key, $source)
551 $al = $al->keyUp ($key, $source)
552 Press or release the given key.
553
554 $al = $al->key ($key, $source)
555 Peess and release the given key, without unnecessary delay.
556
557 A special syntax, "{keyname}" can be used for special keys - all the
558 special key names from section 17.4.2
559 <https://www.w3.org/TR/webdriver1/#keyboard-actions> of the
560 WebDriver recommendation can be used.
561
562 Example: press and release "a".
563
564 $al->key ("a");
565
566 Example: press and release the "Enter" key:
567
568 $al->key ("\x{e007}");
569
570 Example: press and release the "enter" key using the special key
571 name syntax:
572
573 $al->key ("{Enter}");
574
575 $al = $al->type ($string, $source)
576 Convenience method to simulate a series of key press and release
577 events for the keys in $string. There is no syntax for special keys,
578 everything will be typed "as-is" if possible.
579
580 $al->perform ($wd)
581 Finaluses and compiles the list, if not done yet, and calls
582 "$wd->perform" with it.
583
584 If $wd is undef, and the action list was created using the
585 "$wd->actions" method, then perform it against that WebDriver
586 object.
587
588 There is no underscore variant - call the "perform_actions_" method
589 with the action object instead.
590
591 $al->perform_release ($wd)
592 Exactly like "perform", but additionally call "release_actions"
593 afterwards.
594
595 ($actions, $duration) = $al->compile
596 Finalises and compiles the list, if not done yet, and returns an
597 actions object suitable for calls to "$wd->perform_actions". When
598 called in list context, additionally returns the total duration of
599 the action list.
600
601 Since building large action lists can take nontrivial amounts of
602 time, it can make sense to build an action list only once and then
603 perform it multiple times.
604
605 Actions must not be added after compiling a list.
606
607 EVENT BASED API
608 This module wouldn't be a good AnyEvent citizen if it didn't have a true
609 event-based API.
610
611 In fact, the simplified API, as documented above, is emulated via the
612 event-based API and an "AUTOLOAD" function that automatically provides
613 blocking wrappers around the callback-based API.
614
615 Every method documented in the "SIMPLIFIED API" section has an
616 equivalent event-based method that is formed by appending a underscore
617 ("_") to the method name, and appending a callback to the argument list
618 (mnemonic: the underscore indicates the "the action is not yet finished"
619 after the call returns).
620
621 For example, instead of a blocking calls to "new_session", "navigate_to"
622 and "back", you can make a callback-based ones:
623
624 my $cv = AE::cv;
625
626 $wd->new_session ({}, sub {
627 my ($status, $value) = @_,
628
629 die "error $value->{error}" if $status ne "200";
630
631 $wd->navigate_to_ ("http://www.nethype.de", sub {
632
633 $wd->back_ (sub {
634 print "all done\n";
635 $cv->send;
636 });
637
638 });
639 });
640
641 $cv->recv;
642
643 While the blocking methods "croak" on errors, the callback-based ones
644 all pass two values to the callback, $status and $res, where $status is
645 the HTTP status code (200 for successful requests, typically 4xx or 5xx
646 for errors), and $res is the value of the "value" key in the JSON
647 response object.
648
649 Other than that, the underscore variants and the blocking variants are
650 identical.
651
652 LOW LEVEL API
653 All the simplified API methods are very thin wrappers around WebDriver
654 commands of the same name. They are all implemented in terms of the
655 low-level methods ("req", "get", "post" and "delete"), which exists in
656 blocking and callback-based variants ("req_", "get_", "post_" and
657 "delete_").
658
659 Examples are after the function descriptions.
660
661 $wd->req_ ($method, $uri, $body, $cb->($status, $value))
662 $value = $wd->req ($method, $uri, $body)
663 Appends the $uri to the "endpoint/session/{sessionid}/" URL and
664 makes a HTTP $method request ("GET", "POST" etc.). "POST" requests
665 can provide a UTF-8-encoded JSON text as HTTP request body, or the
666 empty string to indicate no body is used.
667
668 For the callback version, the callback gets passed the HTTP status
669 code (200 for every successful request), and the value of the
670 "value" key in the JSON response object as second argument.
671
672 $wd->get_ ($uri, $cb->($status, $value))
673 $value = $wd->get ($uri)
674 Simply a call to "req_" with $method set to "GET" and an empty body.
675
676 $wd->post_ ($uri, $data, $cb->($status, $value))
677 $value = $wd->post ($uri, $data)
678 Simply a call to "req_" with $method set to "POST" - if $body is
679 "undef", then an empty object is send, otherwise, $data must be a
680 valid request object, which gets encoded into JSON for you.
681
682 $wd->delete_ ($uri, $cb->($status, $value))
683 $value = $wd->delete ($uri)
684 Simply a call to "req_" with $method set to "DELETE" and an empty
685 body.
686
687 Example: implement "get_all_cookies", which is a simple "GET" request
688 without any parameters:
689
690 $cookies = $wd->get ("cookie");
691
692 Example: implement "execute_script", which needs some parameters:
693
694 $results = $wd->post ("execute/sync" => { script => "$javascript", args => [] });
695
696 Example: call "find_elements" to find all "IMG" elements:
697
698 $elems = $wd->post (elements => { using => "css selector", value => "img" });
699
700 HISTORY
701 This module was unintentionally created (it started inside some quickly
702 hacked-together script) simply because I couldn't get the existing
703 "Selenium::Remote::Driver" module to work, ever, despite multiple
704 attempts over the years and trying to report multiple bugs, which have
705 been completely ignored. It's also not event-based, so, yeah...
706
707 AUTHOR
708 Marc Lehmann <schmorp@schmorp.de>
709 http://anyevent.schmorp.de
710