Initial commit

FossilOrigin-Name: 370c4e8ed3567a4e0f0933ac3ba749973ba041efa3b7d61310a6e5d296e68e21
This commit is contained in:
Daniel Ziltener 2023-11-09 01:21:49 +01:00
parent 03adbf588f
commit 244029a0c8
Signed by: zilti
GPG key ID: B38976E82C9DAE42
9 changed files with 2243 additions and 0 deletions

25
LICENSE Normal file
View file

@ -0,0 +1,25 @@
Copyright (C) 2023 Daniel Ziltener
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

23
Makefile Normal file
View file

@ -0,0 +1,23 @@
# Tangling and weaving
webdriver.scm webdriver-impl.scm webdriver.html webdriver.egg webdriver.release-info tests/run.scm: webdriver.org
emacs --batch \
--load "vendor/htmlize.el" \
--eval "(setq enable-local-variables :all)" \
--eval "(setq org-confirm-babel-evaluate nil)" \
--eval "(require 'ob-tangle)" \
--eval "(require 'ob-lob)" \
--eval "(require 'htmlize)" \
--file "${.ALLSRC:[1]}" \
-f org-babel-tangle \
-f org-html-export-to-html
mv "${.ALLSRC:[1]:R}.html" "${.ALLSRC:[1]:R}.html.old"
echo "<div class='fossil-doc' data-title='`cat "${.ALLSRC:[1]:R}.html.old" | grep 'class="title"' | sed -e 's/^.*">//' | sed -e 's/<\/h1>//'`'>" > "${.ALLSRC:[1]:R}.html"
cat "${.ALLSRC:[1]:R}.html.old" | sed -e '1,13d' | head -n -2 >> "${.ALLSRC:[1]:R}.html"
echo "</div>" >> "${.ALLSRC:[1]:R}.html"
rm "${.ALLSRC:[1]:R}.html.old"
# Extension Rules
.scm.so:
csc5 -R r7rs -X r7rs -sJ ${.IMPSRC}

57
tests/run.scm Normal file
View file

@ -0,0 +1,57 @@
(import (chicken string))
(import r7rs
test
(chicken base)
(chicken string)
(chicken process)
(chicken gc)
srfi-34 ;;Exception Handling
srfi-35 ;;Exception Types
base64 ;;decoding screenshot data
http-client ;;API interaction
intarweb ;;Supporting HTTP functionality
uri-common ;;Supporting HTTP functionality
coops ;;Object system
alist-lib ;;Handling alists from JSON objects
medea ;;JSON handling
)
;; [[file:../webdriver.org::*Dependencies][Dependencies:5]]
(include-relative "../webdriver-impl.scm")
;; Dependencies:5 ends here
;; #+name: prep-geckodriver-test
;; [[file:../webdriver.org::prep-geckodriver-test][prep-geckodriver-test]]
;; prep-geckodriver-test ends here
;; #+name: wd-session-test
;; [[file:../webdriver.org::wd-session-test][wd-session-test]]
(test-group "session"
(let ((browser (new-WebDriver <Gecko>)))
(test "Initial state" #f (slot-value browser 'session-id))
(test-assert "Session id check" (string? (begin (initialize-session browser) (slot-value browser 'session-id))))
(test-assert "Session id after termination" (eq? #f (begin (terminate-session browser) (slot-value browser 'session-id))))
(terminate browser)))
;; wd-session-test ends here
;; #+name: wd-url-test
;; [[file:../webdriver.org::wd-url-test][wd-url-test]]
(test-group "url"
(let ((browser (new-WebDriver <Gecko>)))
(test "Initial state" #f (slot-value browser 'session-id))
(test "Navigating to the first website" "http://info.cern.ch/hypertext/WWW/TheProject.html"
(begin (initialize-session browser)
(set-url browser "http://info.cern.ch/hypertext/WWW/TheProject.html")
(url browser)))
(terminate browser)))
;; wd-url-test ends here

497
webdriver-impl.scm Normal file
View file

@ -0,0 +1,497 @@
;; [[file:webdriver.org::*Dependencies][Dependencies:4]]
(import r7rs
(chicken base)
(chicken string)
(chicken process)
(chicken gc)
srfi-34 ;;Exception Handling
srfi-35 ;;Exception Types
base64 ;;decoding screenshot data
http-client ;;API interaction
intarweb ;;Supporting HTTP functionality
uri-common ;;Supporting HTTP functionality
coops ;;Object system
alist-lib ;;Handling alists from JSON objects
medea ;;JSON handling
)
;; Dependencies:4 ends here
;; * Error Conditions
;; #+name: wd-exception
;; [[file:webdriver.org::wd-exception][wd-exception]]
(define-condition-type &wd-exception &error wd-exception?
(stacktrace wd-stacktrace)
(data wd-data))
;; wd-exception ends here
;; #+name: conditions
;; [[file:webdriver.org::conditions][conditions]]
(define-condition-type &detached-shadow-root &wd-exception detached-shadow-root?)
(define-condition-type &element-click-intercepted &wd-exception element-click-intercepted?)
(define-condition-type &element-not-interactable &wd-exception element-not-interactable?)
(define-condition-type &insecure-certificate &wd-exception insecure-certificate?)
(define-condition-type &invalid-argument &wd-exception invalid-argument?)
(define-condition-type &invalid-cookie-domain &wd-exception invalid-cookie-domain?)
(define-condition-type &invalid-element-state &wd-exception invalid-element-state?)
(define-condition-type &invalid-selector &wd-exception invalid-selector?)
(define-condition-type &invalid-session-id &wd-exception invalid-session-id?)
(define-condition-type &javascript-error &wd-exception javascript-error?)
(define-condition-type &move-target-out-of-bounds &wd-exception move-target-out-of-bounds?)
(define-condition-type &no-such-alert &wd-exception no-such-alert?)
(define-condition-type &no-such-cookie &wd-exception no-such-cookie?)
(define-condition-type &no-such-element &wd-exception no-such-element?)
(define-condition-type &no-such-frame &wd-exception no-such-frame?)
(define-condition-type &no-such-shadow-root &wd-exception no-such-shadow-root?)
(define-condition-type &no-such-window &wd-exception no-such-window?)
(define-condition-type &script-timeout &wd-exception script-timeout?)
(define-condition-type &session-not-created &wd-exception session-not-created?)
(define-condition-type &stale-element-reference &wd-exception stale-element-reference?)
(define-condition-type &timeout &wd-exception timeout?)
(define-condition-type &unable-to-capture-screen &wd-exception unable-to-capture-screen?)
(define-condition-type &unable-to-set-cookie &wd-exception unable-to-set-cookie?)
(define-condition-type &unexpected-alert-open &wd-exception unexpected-alert-open?)
(define-condition-type &unknown-command &wd-exception unknown-command?)
(define-condition-type &unknown-error &wd-exception unknown-error?)
(define-condition-type &unknown-method &wd-exception unknown-method?)
(define-condition-type &unsupported-operation &wd-exception unsupported-operation?)
(define (wd-throw data)
(case (alist-ref data 'error)
(("detached shadow root") (raise (make-condition &detached-shadow-root (alist-ref data 'stacktrace) data)))
(("element click intercepted") (raise (make-condition &element-click-intercepted (alist-ref data 'stacktrace) data)))
(("element not interactable") (raise (make-condition &element-not-interactable (alist-ref data 'stacktrace) data)))
(("insecure certificate") (raise (make-condition &insecure-certificate (alist-ref data 'stacktrace) data)))
(("invalid argument") (raise (make-condition &invalid-argument (alist-ref data 'stacktrace) data)))
(("invalid cookie domain") (raise (make-condition &invalid-cookie-domain (alist-ref data 'stacktrace) data)))
(("invalid element state") (raise (make-condition &invalid-element-state (alist-ref data 'stacktrace) data)))
(("invalid selector") (raise (make-condition &invalid-selector (alist-ref data 'stacktrace) data)))
(("invalid session id") (raise (make-condition &invalid-session-id (alist-ref data 'stacktrace) data)))
(("javascript error") (raise (make-condition &javascript-error (alist-ref data 'stacktrace) data)))
(("move target out of bounds") (raise (make-condition &move-target-out-of-bounds (alist-ref data 'stacktrace) data)))
(("no such alert") (raise (make-condition &no-such-alert (alist-ref data 'stacktrace) data)))
(("no such cookie") (raise (make-condition &no-such-cookie (alist-ref data 'stacktrace) data)))
(("no such element") (raise (make-condition &no-such-element (alist-ref data 'stacktrace) data)))
(("no such frame") (raise (make-condition &no-such-frame (alist-ref data 'stacktrace) data)))
(("no such shadow root") (raise (make-condition &no-such-shadow-root (alist-ref data 'stacktrace) data)))
(("no such window") (raise (make-condition &no-such-window (alist-ref data 'stacktrace) data)))
(("script timeout") (raise (make-condition &script-timeout (alist-ref data 'stacktrace) data)))
(("session not created") (raise (make-condition &session-not-created (alist-ref data 'stacktrace) data)))
(("stale element reference") (raise (make-condition &stale-element-reference (alist-ref data 'stacktrace) data)))
(("timeout") (raise (make-condition &timeout (alist-ref data 'stacktrace) data)))
(("unable to capture screen") (raise (make-condition &unable-to-capture-screen (alist-ref data 'stacktrace) data)))
(("unable to set cookie") (raise (make-condition &unable-to-set-cookie (alist-ref data 'stacktrace) data)))
(("unexpected alert open") (raise (make-condition &unexpected-alert-open (alist-ref data 'stacktrace) data)))
(("unknown command") (raise (make-condition &unknown-command (alist-ref data 'stacktrace) data)))
(("unknown error") (raise (make-condition &unknown-error (alist-ref data 'stacktrace) data)))
(("unknown method") (raise (make-condition &unknown-method (alist-ref data 'stacktrace) data)))
(("unsupported operation") (raise (make-condition &unsupported-operation (alist-ref data 'stacktrace) data)))
(else (raise (make-condition &wd-exception (alist-ref data 'stacktrace) data)))
)
)
;; conditions ends here
;; * WebDriver
;; The core element of the library is the ~<WebDriver>~ class and its subclasses. The class has the following fields:
;; #+name: webdriver-class
;; [[file:webdriver.org::webdriver-class][webdriver-class]]
(define-class <WebDriver> ()
((browser #f)
(active? #f)
(browser-pid #f)
(server #f)
(port #f)
(session-id #f)
(prefs #f)
(capabilities #f)))
;; webdriver-class ends here
;; The parent class provides a handful of methods, but does not implement all of them; some are the sole responsibility of the subclass. The ~launch~ method, on the other hand, bears shared responsibility. It sets a finalizer to ensure termination of the web driver process in case the class is disposed of with a still-open driver.
;; #+name: webdriver-basics
;; [[file:webdriver.org::webdriver-basics][webdriver-basics]]
(define-method (launch #:after (instance <WebDriver>) options)
(set-finalizer! instance (lambda (obj)
(when (slot-value instance 'active?)
(terminate instance)))))
(define-method (terminate (instance <WebDriver>))
(terminate-session instance)
(process-signal (slot-value instance 'browser-pid))
(set! (slot-value instance 'browser-pid) #f)
(set! (slot-value instance 'active?) #f))
(define-method (construct-capabilities (instance <WebDriver>) #!optional caps)
(raise 'subclass-responsibility))
(define-method (postprocess-result (instance <WebDriver>) result)
result)
;; webdriver-basics ends here
;; Main initialization is done by calling the ~new-WebDriver~ procedure with the respective class name and optionally an alist of options.
;; #+name: webdriver-init
;; [[file:webdriver.org::webdriver-init][webdriver-init]]
(define (new-WebDriver browser #!optional options)
(let ((instance (make browser)))
(launch instance options)
(sleep 1)
instance))
;; webdriver-init ends here
;; ** Geckodriver
;; The Geckodriver is used to control Firefox.
;; #+name: geckodriver-basic
;; [[file:webdriver.org::geckodriver-basic][geckodriver-basic]]
(define-class <Gecko> (<WebDriver>)
((browser #:firefox)
(server "127.0.0.1")
(port 4444)))
(define-method (launch (instance <Gecko>) options)
(let ((pid (process-run "geckodriver > /dev/null 2>&1")))
(set! (slot-value instance 'browser-pid) pid)
(set! (slot-value instance 'active?) #t)
(set! (slot-value instance 'capabilities) options)))
;; geckodriver-basic ends here
;; The capabilities object for Geckodriver is of the form ={"capabilities": {...}}=.
;; For more information on capabilities, see https://developer.mozilla.org/en-US/docs/Web/WebDriver/Capabilities.
;; #+name: geckodriver-capabilities
;; [[file:webdriver.org::geckodriver-capabilities][geckodriver-capabilities]]
(define-method (construct-capabilities (instance <Gecko>))
(let ((caps (or (slot-value instance 'capabilities) (list))))
`((capabilities . ,caps))))
;; geckodriver-capabilities ends here
;; Sometimes, Geckodriver returns the results of a command in a JSON object with the sole key ="value"=. We have to correct that before returning the data to the user.
;; #+name: geckodriver-postprocess
;; [[file:webdriver.org::geckodriver-postprocess][geckodriver-postprocess]]
(define-method (postprocess-result (instance <Gecko>) result)
(alist-ref/default result 'value result))
;; geckodriver-postprocess ends here
;; * WebDriver API
;; ** Communication
;; Data is sent to the API via a central class method. For convenience, there is a ~send-with-session~ variant that automatically adds the session id.
;; #+name: wd-send
;; [[file:webdriver.org::wd-send][wd-send]]
(define-method (send (instance <WebDriver>) data uri method)
(let* ((remote (string-append "http://" (slot-value instance 'server) ":" (->string (slot-value instance 'port)) "/"))
(result (postprocess-result instance
(with-input-from-request
(make-request #:method method
#:uri (uri-reference (string-append remote uri))
#:headers (headers `((content-type application/json))))
(if data (json->string data) "")
read-json))))
(if (and (list? result) (alist-ref/default result 'error #f))
(wd-throw result)
result)))
(define-method (send-with-session (instance <WebDriver>) data uri method)
(send instance data (string-append "session/" (slot-value instance 'session-id) "/" uri) method))
;; wd-send ends here
;; ** Session management
;; Session management is very simple. There is just one method to initialize a new session. Everything else is handled automatically.
;; #+name: wd-init-session
;; [[file:webdriver.org::wd-init-session][wd-init-session]]
(define-method (initialize-session (instance <WebDriver>))
(let ((result (send instance (construct-capabilities instance) "session" 'POST)))
(set! (slot-value instance 'session-id) (alist-ref result 'sessionId))))
;; wd-init-session ends here
;; #+name: wd-term-session
;; [[file:webdriver.org::wd-term-session][wd-term-session]]
(define-method (terminate-session (instance <WebDriver>))
(when (slot-value instance 'session-id)
(send instance #f (string-append "session/" (slot-value instance 'session-id)) 'DELETE))
(set! (slot-value instance 'session-id) #f))
;; wd-term-session ends here
;; #+RESULTS: wd-session-test
;; : -- testing session -----------------------------------------------------------
;; : Initial state ........................................................ [ PASS]
;; : Session id check ..................................................... [ PASS]
;; : Session id after termination ......................................... [ PASS]
;; : 3 tests completed in 3.788 seconds.
;; : 3 out of 3 (100%) tests passed.
;; : -- done testing session ------------------------------------------------------
;; ** API Access Methods
;; #+name: wd-url
;; [[file:webdriver.org::wd-url][wd-url]]
(define-method (set-url (instance <WebDriver>) url)
(send-with-session instance `((url . ,url)) "url" 'POST))
(define-method (url (instance <WebDriver>))
(send-with-session instance #f "url" 'GET))
;; wd-url ends here
;; #+RESULTS: wd-url-test
;; : -- testing url ---------------------------------------------------------------
;; : Initial state ........................................................ [ PASS]
;; : Navigating to the first website ...................................... [ PASS]
;; : 2 tests completed in 5.247 seconds.
;; : 2 out of 2 (100%) tests passed.
;; : -- done testing url ----------------------------------------------------------
;; [[file:webdriver.org::*API Access Methods][API Access Methods:3]]
(define-method (back (instance <WebDriver>))
(send-with-session instance #f "back" 'POST))
;; API Access Methods:3 ends here
;; [[file:webdriver.org::*API Access Methods][API Access Methods:4]]
(define-method (forward (instance <WebDriver>))
(send-with-session instance #f "forward" 'POST))
;; API Access Methods:4 ends here
;; [[file:webdriver.org::*API Access Methods][API Access Methods:5]]
(define-method (refresh (instance <WebDriver>))
(send-with-session instance #f "refresh" 'POST))
;; API Access Methods:5 ends here
;; [[file:webdriver.org::*API Access Methods][API Access Methods:6]]
(define-method (title (instance <WebDriver>))
(send-with-session instance #f "title" 'GET))
;; API Access Methods:6 ends here
;; [[file:webdriver.org::*API Access Methods][API Access Methods:7]]
(define-method (status (instance <WebDriver>))
(send-with-session instance #f "status" 'GET))
;; API Access Methods:7 ends here
;; [[file:webdriver.org::*API Access Methods][API Access Methods:8]]
(define-method (source (instance <WebDriver>))
(send-with-session instance #f "source" 'GET))
;; API Access Methods:8 ends here
;; [[file:webdriver.org::*API Access Methods][API Access Methods:9]]
(define-method (screenshot (instance <WebDriver>))
(base64-decode (send-with-session instance #f "screenshot" 'GET)))
;; API Access Methods:9 ends here
;; [[file:webdriver.org::*API Access Methods][API Access Methods:10]]
(define-method (print-page (instance <WebDriver>))
(send-with-session instance #f "print" 'POST))
;; API Access Methods:10 ends here
;; [[file:webdriver.org::*API Access Methods][API Access Methods:11]]
(define-method (execute-async (instance <WebDriver>) script args)
(send-with-session instance `((script . ,script) (args . ,args)) "execute/async" 'POST))
;; API Access Methods:11 ends here
;; [[file:webdriver.org::*API Access Methods][API Access Methods:12]]
(define-method (execute-sync (instance <WebDriver>) script args)
(send-with-session instance `((script . ,script) (args . ,args)) "execute/sync" 'POST))
;; API Access Methods:12 ends here
;; The following timeouts are defined:
;; - =script=: defaults to 30'000, specifies when to interrupt a script that is being evaluated. A nil value implies that scripts should never be interrupted, but instead run indefinitely.
;; - =pageLoad=: defaults to 300'000, provides the timeout limit used to interrupt an explicit navigation attempt.
;; - =implicit=: defaults to 0, specifies a time to wait for the element location strategy to complete when locating an element.
;; [[file:webdriver.org::*Timeouts][Timeouts:1]]
(define-class <WDTimeouts> ()
((script 30000)
(pageLoad 300000)
(implicit 0)))
;; Timeouts:1 ends here
;; [[file:webdriver.org::*Timeouts][Timeouts:2]]
(define-method (extract (instance <WDTimeouts>))
`((script . ,(slot-value instance 'script))
(pageLoad . ,(slot-value instance 'pageLoad))
(implicit . ,(slot-value instance 'implicit))))
;; Timeouts:2 ends here
;; [[file:webdriver.org::*Setting and getting timeouts][Setting and getting timeouts:1]]
(define-method (set-timeouts (instance <WebDriver>) (timeouts <WDTimeouts>))
(send-with-session instance (extract timeouts) "timeouts" 'POST))
(define-method (timeouts (instance <WebDriver>))
(let ((result (send-with-session instance #f "timeouts" 'GET)))
(make <WDTimeouts>
'script (alist-ref result 'script)
'pageLoad (alist-ref result 'pageLoad)
'implicit (alist-ref result 'implicit))))
;; Setting and getting timeouts:1 ends here
;; [[file:webdriver.org::*Element Class][Element Class:1]]
(define-class <WDElement> ()
((driver #f)
(element #f)))
;; Element Class:1 ends here
;; [[file:webdriver.org::*Element Class][Element Class:2]]
(define-method (send-with-session (instance <WDElement>) data uri method)
(send-with-session (slot-value instance 'driver) data
(string-append "element/" (slot-value instance 'element) "/" uri)
method))
;; Element Class:2 ends here
;; [[file:webdriver.org::*Location Strategies][Location Strategies:1]]
(define css-selector "css selector")
(define link-text "link text")
(define partial-link-text "partial link text")
(define tag-name "tag name")
(define xpath "xpath")
;; Location Strategies:1 ends here
;; [[file:webdriver.org::*Accessor Methods][Accessor Methods:1]]
(define-method (find-element (instance <WebDriver>) strategy selector)
(let ((result (send-with-session instance `((using . ,strategy) (value . ,selector)) "element" 'POST)))
(make <WDElement> 'driver instance 'element (car (alist-values result)))
element))
;; Accessor Methods:1 ends here
;; [[file:webdriver.org::*Accessor Methods][Accessor Methods:2]]
(define-method (find-elements (instance <WebDriver>) strategy selector)
(let ((result (send-with-session instance `((using . ,strategy) (value . ,selector)) "elements" 'POST)))
(map
(lambda (elem)
(make <WDElement> 'driver instance 'element (car (alist-values elem))))
result)))
;; Accessor Methods:2 ends here
;; [[file:webdriver.org::*Accessor Methods][Accessor Methods:3]]
(define-method (find-element (instance <WDElement>) strategy selector)
(let ((result (send-with-session instance `((using . ,strategy) (value . ,selector)) "element" 'POST)))
(make <WDElement> 'driver (slot-value instance 'driver) 'element (car (alist-values result)))
element))
;; Accessor Methods:3 ends here
;; [[file:webdriver.org::*Accessor Methods][Accessor Methods:4]]
(define-method (find-elements (instance <WDElement>) strategy selector)
(let ((result (send-with-session instance `((using . ,strategy) (value . ,selector)) "elements" 'POST)))
(map
(lambda (elem)
(make <WDElement> 'driver (slot-value instance 'driver) 'element (car (alist-values elem))))
result)))
;; Accessor Methods:4 ends here
;; [[file:webdriver.org::*Working with Elements][Working with Elements:1]]
(define-method (attribute (instance <WDElement>) attribute)
(let ((result (send-with-session instance #f
(string-append "attribute/" attribute)
'GET)))
(if (equal? "true" result)
#t
result)))
;; Working with Elements:1 ends here
;; [[file:webdriver.org::*Working with Elements][Working with Elements:2]]
(define-method (property (instance <WDElement>) property)
(send-with-session instance #f (string-append "property/" property) 'GET))
;; Working with Elements:2 ends here
;; [[file:webdriver.org::*Working with Elements][Working with Elements:3]]
(define-method (clear (instance <WDElement>))
(send-with-session instance #f "clear" 'POST))
;; Working with Elements:3 ends here
;; [[file:webdriver.org::*Working with Elements][Working with Elements:4]]
(define-method (click (instance <WDElement>))
(send-with-session instance #f "click" 'POST))
;; Working with Elements:4 ends here
;; [[file:webdriver.org::*Working with Elements][Working with Elements:5]]
(define-method (computed-label (instance <WDElement>))
(send-with-session instance #f "computedlabel" 'GET))
;; Working with Elements:5 ends here
;; [[file:webdriver.org::*Working with Elements][Working with Elements:6]]
(define-method (computed-role (instance <WDElement>))
(send-with-session instance #f "computedrole" 'GET))
;; Working with Elements:6 ends here
;; [[file:webdriver.org::*Working with Elements][Working with Elements:7]]
(define-method (enabled? (instance <WDElement>))
(send-with-session instance #f "enabled" 'GET))
;; Working with Elements:7 ends here
;; [[file:webdriver.org::*Working with Elements][Working with Elements:8]]
(define-method (selected? (instance <WDElement>))
(send-with-session instance #f "selected" 'GET))
;; Working with Elements:8 ends here
;; [[file:webdriver.org::*Working with Elements][Working with Elements:9]]
(define-method (name (instance <WDElement>))
(send-with-session instance #f "name" 'GET))
;; Working with Elements:9 ends here
;; [[file:webdriver.org::*Working with Elements][Working with Elements:10]]
(define-method (rect (instance <WDElement>))
(send-with-session instance #f "rect" 'GET))
;; Working with Elements:10 ends here
;; [[file:webdriver.org::*Working with Elements][Working with Elements:11]]
(define-method (screenshot (instance <WDElement>))
(base64-decode (send-with-session instance #f "screenshot" 'GET)))
;; Working with Elements:11 ends here
;; [[file:webdriver.org::*Working with Elements][Working with Elements:12]]
(define-method (text (instance <WDElement>))
(send-with-session instance #f "text" 'GET))
;; Working with Elements:12 ends here
;; [[file:webdriver.org::*Working with Elements][Working with Elements:13]]
(define-method (set-value (instance <WDElement>) value)
(send-with-session instance `((text . ,value)) "value" 'POST))
;; Working with Elements:13 ends here

14
webdriver.egg Normal file
View file

@ -0,0 +1,14 @@
;; [[file:webdriver.org::*About This Egg][About This Egg:1]]
;; -*- scheme -*-
((author "Daniel Ziltener")
(synopsis "A WebDriver API implementation for Chicken")
(category web)
(license "BSD")
(version "0.5")
(dependencies r7rs srfi-34 srfi-35 base64 http-client intarweb uri-common coops alist-lib medea)
(test-dependencies test)
(components
(extension webdriver
(csc-options "-X" "r7rs" "-R" "r7rs" "-sJ"))))
;; About This Egg:1 ends here

957
webdriver.html Normal file
View file

@ -0,0 +1,957 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
<!-- 2023-04-13 Do 16:13 -->
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Webdriver implementation in Chicken Scheme</title>
<meta name="author" content="Daniel Ziltener" />
<meta name="generator" content="Org Mode" />
<style>
#content { max-width: 60em; margin: auto; }
.title { text-align: center;
margin-bottom: .2em; }
.subtitle { text-align: center;
font-size: medium;
font-weight: bold;
margin-top:0; }
.todo { font-family: monospace; color: red; }
.done { font-family: monospace; color: green; }
.priority { font-family: monospace; color: orange; }
.tag { background-color: #eee; font-family: monospace;
padding: 2px; font-size: 80%; font-weight: normal; }
.timestamp { color: #bebebe; }
.timestamp-kwd { color: #5f9ea0; }
.org-right { margin-left: auto; margin-right: 0px; text-align: right; }
.org-left { margin-left: 0px; margin-right: auto; text-align: left; }
.org-center { margin-left: auto; margin-right: auto; text-align: center; }
.underline { text-decoration: underline; }
#postamble p, #preamble p { font-size: 90%; margin: .2em; }
p.verse { margin-left: 3%; }
pre {
border: 1px solid #e6e6e6;
border-radius: 3px;
background-color: #f2f2f2;
padding: 8pt;
font-family: monospace;
overflow: auto;
margin: 1.2em;
}
pre.src {
position: relative;
overflow: auto;
}
pre.src:before {
display: none;
position: absolute;
top: -8px;
right: 12px;
padding: 3px;
color: #555;
background-color: #f2f2f299;
}
pre.src:hover:before { display: inline; margin-top: 14px;}
/* Languages per Org manual */
pre.src-asymptote:before { content: 'Asymptote'; }
pre.src-awk:before { content: 'Awk'; }
pre.src-authinfo::before { content: 'Authinfo'; }
pre.src-C:before { content: 'C'; }
/* pre.src-C++ doesn't work in CSS */
pre.src-clojure:before { content: 'Clojure'; }
pre.src-css:before { content: 'CSS'; }
pre.src-D:before { content: 'D'; }
pre.src-ditaa:before { content: 'ditaa'; }
pre.src-dot:before { content: 'Graphviz'; }
pre.src-calc:before { content: 'Emacs Calc'; }
pre.src-emacs-lisp:before { content: 'Emacs Lisp'; }
pre.src-fortran:before { content: 'Fortran'; }
pre.src-gnuplot:before { content: 'gnuplot'; }
pre.src-haskell:before { content: 'Haskell'; }
pre.src-hledger:before { content: 'hledger'; }
pre.src-java:before { content: 'Java'; }
pre.src-js:before { content: 'Javascript'; }
pre.src-latex:before { content: 'LaTeX'; }
pre.src-ledger:before { content: 'Ledger'; }
pre.src-lisp:before { content: 'Lisp'; }
pre.src-lilypond:before { content: 'Lilypond'; }
pre.src-lua:before { content: 'Lua'; }
pre.src-matlab:before { content: 'MATLAB'; }
pre.src-mscgen:before { content: 'Mscgen'; }
pre.src-ocaml:before { content: 'Objective Caml'; }
pre.src-octave:before { content: 'Octave'; }
pre.src-org:before { content: 'Org mode'; }
pre.src-oz:before { content: 'OZ'; }
pre.src-plantuml:before { content: 'Plantuml'; }
pre.src-processing:before { content: 'Processing.js'; }
pre.src-python:before { content: 'Python'; }
pre.src-R:before { content: 'R'; }
pre.src-ruby:before { content: 'Ruby'; }
pre.src-sass:before { content: 'Sass'; }
pre.src-scheme:before { content: 'Scheme'; }
pre.src-screen:before { content: 'Gnu Screen'; }
pre.src-sed:before { content: 'Sed'; }
pre.src-sh:before { content: 'shell'; }
pre.src-sql:before { content: 'SQL'; }
pre.src-sqlite:before { content: 'SQLite'; }
/* additional languages in org.el's org-babel-load-languages alist */
pre.src-forth:before { content: 'Forth'; }
pre.src-io:before { content: 'IO'; }
pre.src-J:before { content: 'J'; }
pre.src-makefile:before { content: 'Makefile'; }
pre.src-maxima:before { content: 'Maxima'; }
pre.src-perl:before { content: 'Perl'; }
pre.src-picolisp:before { content: 'Pico Lisp'; }
pre.src-scala:before { content: 'Scala'; }
pre.src-shell:before { content: 'Shell Script'; }
pre.src-ebnf2ps:before { content: 'ebfn2ps'; }
/* additional language identifiers per "defun org-babel-execute"
in ob-*.el */
pre.src-cpp:before { content: 'C++'; }
pre.src-abc:before { content: 'ABC'; }
pre.src-coq:before { content: 'Coq'; }
pre.src-groovy:before { content: 'Groovy'; }
/* additional language identifiers from org-babel-shell-names in
ob-shell.el: ob-shell is the only babel language using a lambda to put
the execution function name together. */
pre.src-bash:before { content: 'bash'; }
pre.src-csh:before { content: 'csh'; }
pre.src-ash:before { content: 'ash'; }
pre.src-dash:before { content: 'dash'; }
pre.src-ksh:before { content: 'ksh'; }
pre.src-mksh:before { content: 'mksh'; }
pre.src-posh:before { content: 'posh'; }
/* Additional Emacs modes also supported by the LaTeX listings package */
pre.src-ada:before { content: 'Ada'; }
pre.src-asm:before { content: 'Assembler'; }
pre.src-caml:before { content: 'Caml'; }
pre.src-delphi:before { content: 'Delphi'; }
pre.src-html:before { content: 'HTML'; }
pre.src-idl:before { content: 'IDL'; }
pre.src-mercury:before { content: 'Mercury'; }
pre.src-metapost:before { content: 'MetaPost'; }
pre.src-modula-2:before { content: 'Modula-2'; }
pre.src-pascal:before { content: 'Pascal'; }
pre.src-ps:before { content: 'PostScript'; }
pre.src-prolog:before { content: 'Prolog'; }
pre.src-simula:before { content: 'Simula'; }
pre.src-tcl:before { content: 'tcl'; }
pre.src-tex:before { content: 'TeX'; }
pre.src-plain-tex:before { content: 'Plain TeX'; }
pre.src-verilog:before { content: 'Verilog'; }
pre.src-vhdl:before { content: 'VHDL'; }
pre.src-xml:before { content: 'XML'; }
pre.src-nxml:before { content: 'XML'; }
/* add a generic configuration mode; LaTeX export needs an additional
(add-to-list 'org-latex-listings-langs '(conf " ")) in .emacs */
pre.src-conf:before { content: 'Configuration File'; }
table { border-collapse:collapse; }
caption.t-above { caption-side: top; }
caption.t-bottom { caption-side: bottom; }
td, th { vertical-align:top; }
th.org-right { text-align: center; }
th.org-left { text-align: center; }
th.org-center { text-align: center; }
td.org-right { text-align: right; }
td.org-left { text-align: left; }
td.org-center { text-align: center; }
dt { font-weight: bold; }
.footpara { display: inline; }
.footdef { margin-bottom: 1em; }
.figure { padding: 1em; }
.figure p { text-align: center; }
.equation-container {
display: table;
text-align: center;
width: 100%;
}
.equation {
vertical-align: middle;
}
.equation-label {
display: table-cell;
text-align: right;
vertical-align: middle;
}
.inlinetask {
padding: 10px;
border: 2px solid gray;
margin: 10px;
background: #ffffcc;
}
#org-div-home-and-up
{ text-align: right; font-size: 70%; white-space: nowrap; }
textarea { overflow-x: auto; }
.linenr { font-size: smaller }
.code-highlighted { background-color: #ffff00; }
.org-info-js_info-navigation { border-style: none; }
#org-info-js_console-label
{ font-size: 10px; font-weight: bold; white-space: nowrap; }
.org-info-js_search-highlight
{ background-color: #ffff00; color: #000000; font-weight: bold; }
.org-svg { }
</style>
</head>
<body>
<div id="content" class="content">
<h1 class="title">Webdriver implementation in Chicken Scheme</h1>
<div id="table-of-contents" role="doc-toc">
<h2>Table of Contents</h2>
<div id="text-table-of-contents" role="doc-toc">
<ul>
<li><a href="#org4399fba">1. Dependencies</a></li>
<li><a href="#org686fad8">2. Error Conditions</a></li>
<li><a href="#orgb911463">3. WebDriver</a>
<ul>
<li><a href="#org3d13a0c">3.1. Geckodriver</a></li>
</ul>
</li>
<li><a href="#orgf472d01">4. WebDriver API</a>
<ul>
<li><a href="#org5685400">4.1. Communication</a></li>
<li><a href="#orgd3b1c20">4.2. Session management</a></li>
<li><a href="#orgca687e8">4.3. API Access Methods</a></li>
<li><a href="#org8175c38">4.4. Timeouts</a>
<ul>
<li><a href="#org2be29cc">4.4.1. Setting and getting timeouts</a></li>
</ul>
</li>
<li><a href="#org0b96887">4.5. Elements</a>
<ul>
<li><a href="#org3abeb2f">4.5.1. Element Class</a></li>
<li><a href="#orgf5ba942">4.5.2. Finding Elements</a></li>
<li><a href="#org25c6507">4.5.3. Working with Elements</a></li>
</ul>
</li>
</ul>
</li>
</ul>
</div>
</div>
<div id="outline-container-org4399fba" class="outline-2">
<h2 id="org4399fba"><span class="section-number-2">1.</span> Dependencies</h2>
<div class="outline-text-2" id="text-1">
<table id="orgb55ce99" border="2" cellspacing="0" cellpadding="6" rules="groups" frame="hsides">
<colgroup>
<col class="org-left" />
<col class="org-left" />
</colgroup>
<thead>
<tr>
<th scope="col" class="org-left">Dependency</th>
<th scope="col" class="org-left">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="org-left">srfi-34</td>
<td class="org-left">Exception Handling</td>
</tr>
<tr>
<td class="org-left">srfi-35</td>
<td class="org-left">Exception Types</td>
</tr>
<tr>
<td class="org-left">base64</td>
<td class="org-left">decoding screenshot data</td>
</tr>
<tr>
<td class="org-left">http-client</td>
<td class="org-left">API interaction</td>
</tr>
<tr>
<td class="org-left">intarweb</td>
<td class="org-left">Supporting HTTP functionality</td>
</tr>
<tr>
<td class="org-left">uri-common</td>
<td class="org-left">Supporting HTTP functionality</td>
</tr>
<tr>
<td class="org-left">coops</td>
<td class="org-left">Object system</td>
</tr>
<tr>
<td class="org-left">alist-lib</td>
<td class="org-left">Handling alists from JSON objects</td>
</tr>
<tr>
<td class="org-left">medea</td>
<td class="org-left">JSON handling</td>
</tr>
</tbody>
</table>
</div>
</div>
<div id="outline-container-org686fad8" class="outline-2">
<h2 id="org686fad8"><span class="section-number-2">2.</span> Error Conditions</h2>
<div class="outline-text-2" id="text-2">
<div class="org-src-container">
<pre class="src src-scheme" id="orgca58a9e"><span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define-condition-type</span> &amp;wd-exception &amp;error wd-exception?
<span style="color: #c49619;">(</span>stacktrace wd-stacktrace<span style="color: #c49619;">)</span>
<span style="color: #c49619;">(</span>data wd-data<span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span>
</pre>
</div>
<p>
Every API error code (key &ldquo;error&rdquo; in the returned JSON data) gets its own condition type, prefixed by <code>&amp;</code>. They all inherit from <code>&amp;wd-exception</code>.
</p>
<table id="org1b76a29" border="2" cellspacing="0" cellpadding="6" rules="groups" frame="hsides">
<colgroup>
<col class="org-left" />
<col class="org-left" />
</colgroup>
<thead>
<tr>
<th scope="col" class="org-left">Name</th>
<th scope="col" class="org-left">API Error Code</th>
</tr>
</thead>
<tbody>
<tr>
<td class="org-left">detached-shadow-root</td>
<td class="org-left">detached shadow root</td>
</tr>
<tr>
<td class="org-left">element-click-intercepted</td>
<td class="org-left">element click intercepted</td>
</tr>
<tr>
<td class="org-left">element-not-interactable</td>
<td class="org-left">element not interactable</td>
</tr>
<tr>
<td class="org-left">insecure-certificate</td>
<td class="org-left">insecure certificate</td>
</tr>
<tr>
<td class="org-left">invalid-argument</td>
<td class="org-left">invalid argument</td>
</tr>
<tr>
<td class="org-left">invalid-cookie-domain</td>
<td class="org-left">invalid cookie domain</td>
</tr>
<tr>
<td class="org-left">invalid-element-state</td>
<td class="org-left">invalid element state</td>
</tr>
<tr>
<td class="org-left">invalid-selector</td>
<td class="org-left">invalid selector</td>
</tr>
<tr>
<td class="org-left">invalid-session-id</td>
<td class="org-left">invalid session id</td>
</tr>
<tr>
<td class="org-left">javascript-error</td>
<td class="org-left">javascript error</td>
</tr>
<tr>
<td class="org-left">move-target-out-of-bounds</td>
<td class="org-left">move target out of bounds</td>
</tr>
<tr>
<td class="org-left">no-such-alert</td>
<td class="org-left">no such alert</td>
</tr>
<tr>
<td class="org-left">no-such-cookie</td>
<td class="org-left">no such cookie</td>
</tr>
<tr>
<td class="org-left">no-such-element</td>
<td class="org-left">no such element</td>
</tr>
<tr>
<td class="org-left">no-such-frame</td>
<td class="org-left">no such frame</td>
</tr>
<tr>
<td class="org-left">no-such-shadow-root</td>
<td class="org-left">no such shadow root</td>
</tr>
<tr>
<td class="org-left">no-such-window</td>
<td class="org-left">no such window</td>
</tr>
<tr>
<td class="org-left">script-timeout</td>
<td class="org-left">script timeout</td>
</tr>
<tr>
<td class="org-left">session-not-created</td>
<td class="org-left">session not created</td>
</tr>
<tr>
<td class="org-left">stale-element-reference</td>
<td class="org-left">stale element reference</td>
</tr>
<tr>
<td class="org-left">timeout</td>
<td class="org-left">timeout</td>
</tr>
<tr>
<td class="org-left">unable-to-capture-screen</td>
<td class="org-left">unable to capture screen</td>
</tr>
<tr>
<td class="org-left">unable-to-set-cookie</td>
<td class="org-left">unable to set cookie</td>
</tr>
<tr>
<td class="org-left">unexpected-alert-open</td>
<td class="org-left">unexpected alert open</td>
</tr>
<tr>
<td class="org-left">unknown-command</td>
<td class="org-left">unknown command</td>
</tr>
<tr>
<td class="org-left">unknown-error</td>
<td class="org-left">unknown error</td>
</tr>
<tr>
<td class="org-left">unknown-method</td>
<td class="org-left">unknown method</td>
</tr>
<tr>
<td class="org-left">unsupported-operation</td>
<td class="org-left">unsupported operation</td>
</tr>
</tbody>
</table>
</div>
</div>
<div id="outline-container-orgb911463" class="outline-2">
<h2 id="orgb911463"><span class="section-number-2">3.</span> WebDriver</h2>
<div class="outline-text-2" id="text-3">
<p>
The core element of the library is the <code>&lt;WebDriver&gt;</code> class and its subclasses. The class has the following fields:
</p>
<div class="org-src-container">
<pre class="src src-scheme" id="orgdcaa051"><span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define-class</span> <span style="color: #c49619; font-style: italic;">&lt;WebDriver&gt;</span> <span style="color: #c49619;">()</span>
<span style="color: #c49619;">(</span><span style="color: #db5823;">(</span>browser #f<span style="color: #db5823;">)</span>
<span style="color: #db5823;">(</span>active? #f<span style="color: #db5823;">)</span>
<span style="color: #db5823;">(</span>browser-pid #f<span style="color: #db5823;">)</span>
<span style="color: #db5823;">(</span>server #f<span style="color: #db5823;">)</span>
<span style="color: #db5823;">(</span>port #f<span style="color: #db5823;">)</span>
<span style="color: #db5823;">(</span>session-id #f<span style="color: #db5823;">)</span>
<span style="color: #db5823;">(</span>prefs #f<span style="color: #db5823;">)</span>
<span style="color: #db5823;">(</span>capabilities #f<span style="color: #db5823;">)</span><span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span>
</pre>
</div>
<p>
The parent class provides a handful of methods, but does not implement all of them; some are the sole responsibility of the subclass. The <code>launch</code> method, on the other hand, bears shared responsibility. It sets a finalizer to ensure termination of the web driver process in case the class is disposed of with a still-open driver.
</p>
<div class="org-src-container">
<pre class="src src-scheme" id="org47d44ea"><span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define-method</span> <span style="color: #c49619;">(</span><span style="color: #3c98e0;">launch</span> <span style="color: #3c98e0; font-style: italic;">#:after</span> <span style="color: #db5823;">(</span>instance <span style="color: #c49619; font-style: italic;">&lt;WebDriver&gt;</span><span style="color: #db5823;">)</span> options<span style="color: #c49619;">)</span>
<span style="color: #c49619;">(</span>set-finalizer! instance <span style="color: #db5823;">(</span><span style="color: #93a61a; font-weight: bold;">lambda</span> <span style="color: #93a61a;">(</span>obj<span style="color: #93a61a;">)</span>
<span style="color: #93a61a;">(</span><span style="color: #93a61a; font-weight: bold;">when</span> <span style="color: #3c98e0;">(</span>slot-value instance 'active?<span style="color: #3c98e0;">)</span>
<span style="color: #3c98e0;">(</span>terminate instance<span style="color: #3c98e0;">)</span><span style="color: #93a61a;">)</span><span style="color: #db5823;">)</span><span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span>
<span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define-method</span> <span style="color: #c49619;">(</span><span style="color: #3c98e0;">terminate</span> <span style="color: #db5823;">(</span>instance <span style="color: #c49619; font-style: italic;">&lt;WebDriver&gt;</span><span style="color: #db5823;">)</span><span style="color: #c49619;">)</span>
<span style="color: #c49619;">(</span>terminate-session instance<span style="color: #c49619;">)</span>
<span style="color: #c49619;">(</span>process-signal <span style="color: #db5823;">(</span>slot-value instance 'browser-pid<span style="color: #db5823;">)</span><span style="color: #c49619;">)</span>
<span style="color: #c49619;">(</span><span style="color: #93a61a; font-weight: bold;">set!</span> <span style="color: #db5823;">(</span>slot-value instance 'browser-pid<span style="color: #db5823;">)</span> #f<span style="color: #c49619;">)</span>
<span style="color: #c49619;">(</span><span style="color: #93a61a; font-weight: bold;">set!</span> <span style="color: #db5823;">(</span>slot-value instance 'active?<span style="color: #db5823;">)</span> #f<span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span>
<span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define-method</span> <span style="color: #c49619;">(</span><span style="color: #3c98e0;">construct-capabilities</span> <span style="color: #db5823;">(</span>instance <span style="color: #c49619; font-style: italic;">&lt;WebDriver&gt;</span><span style="color: #db5823;">)</span> #!optional caps<span style="color: #c49619;">)</span>
<span style="color: #c49619;">(</span>raise 'subclass-responsibility<span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span>
<span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define-method</span> <span style="color: #c49619;">(</span><span style="color: #3c98e0;">postprocess-result</span> <span style="color: #db5823;">(</span>instance <span style="color: #c49619; font-style: italic;">&lt;WebDriver&gt;</span><span style="color: #db5823;">)</span> result<span style="color: #c49619;">)</span>
result<span style="color: #3c98e0;">)</span>
</pre>
</div>
<p>
Main initialization is done by calling the <code>new-WebDriver</code> procedure with the respective class name and optionally an alist of options.
</p>
<div class="org-src-container">
<pre class="src src-scheme" id="org5798eb1"><span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define</span> <span style="color: #c49619;">(</span><span style="color: #3c98e0;">new-WebDriver</span> browser #!optional options<span style="color: #c49619;">)</span>
<span style="color: #c49619;">(</span><span style="color: #93a61a; font-weight: bold;">let</span> <span style="color: #db5823;">(</span><span style="color: #93a61a;">(</span>instance <span style="color: #3c98e0;">(</span>make browser<span style="color: #3c98e0;">)</span><span style="color: #93a61a;">)</span><span style="color: #db5823;">)</span>
<span style="color: #db5823;">(</span>launch instance options<span style="color: #db5823;">)</span>
<span style="color: #db5823;">(</span>sleep <span style="color: #7a7ed2; font-weight: bold;">1</span><span style="color: #db5823;">)</span>
instance<span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span>
</pre>
</div>
</div>
<div id="outline-container-org3d13a0c" class="outline-3">
<h3 id="org3d13a0c"><span class="section-number-3">3.1.</span> Geckodriver</h3>
<div class="outline-text-3" id="text-3-1">
<p>
The Geckodriver is used to control Firefox.
</p>
<div class="org-src-container">
<pre class="src src-scheme" id="orge7a717b"><span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define-class</span> <span style="color: #c49619; font-style: italic;">&lt;Gecko&gt;</span> <span style="color: #c49619;">(</span><span style="color: #c49619; font-style: italic;">&lt;WebDriver&gt;</span><span style="color: #c49619;">)</span>
<span style="color: #c49619;">(</span><span style="color: #db5823;">(</span>browser <span style="color: #3c98e0; font-style: italic;">#:firefox</span><span style="color: #db5823;">)</span>
<span style="color: #db5823;">(</span>server <span style="color: #93a61a;">"127.0.0.1"</span><span style="color: #db5823;">)</span>
<span style="color: #db5823;">(</span>port <span style="color: #7a7ed2; font-weight: bold;">4444</span><span style="color: #db5823;">)</span><span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span>
<span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define-method</span> <span style="color: #c49619;">(</span><span style="color: #3c98e0;">launch</span> <span style="color: #db5823;">(</span>instance <span style="color: #c49619; font-style: italic;">&lt;Gecko&gt;</span><span style="color: #db5823;">)</span> options<span style="color: #c49619;">)</span>
<span style="color: #c49619;">(</span><span style="color: #93a61a; font-weight: bold;">let</span> <span style="color: #db5823;">(</span><span style="color: #93a61a;">(</span>pid <span style="color: #3c98e0;">(</span>process-run <span style="color: #93a61a;">"geckodriver &gt; /dev/null 2&gt;&amp;1"</span><span style="color: #3c98e0;">)</span><span style="color: #93a61a;">)</span><span style="color: #db5823;">)</span>
<span style="color: #db5823;">(</span><span style="color: #93a61a; font-weight: bold;">set!</span> <span style="color: #93a61a;">(</span>slot-value instance 'browser-pid<span style="color: #93a61a;">)</span> pid<span style="color: #db5823;">)</span>
<span style="color: #db5823;">(</span><span style="color: #93a61a; font-weight: bold;">set!</span> <span style="color: #93a61a;">(</span>slot-value instance 'active?<span style="color: #93a61a;">)</span> #t<span style="color: #db5823;">)</span>
<span style="color: #db5823;">(</span><span style="color: #93a61a; font-weight: bold;">set!</span> <span style="color: #93a61a;">(</span>slot-value instance 'capabilities<span style="color: #93a61a;">)</span> options<span style="color: #db5823;">)</span><span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span>
</pre>
</div>
<p>
The capabilities object for Geckodriver is of the form <code>{"capabilities": {...}}</code>.
For more information on capabilities, see <a href="https://developer.mozilla.org/en-US/docs/Web/WebDriver/Capabilities">https://developer.mozilla.org/en-US/docs/Web/WebDriver/Capabilities</a>.
</p>
<div class="org-src-container">
<pre class="src src-scheme" id="org3337880"><span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define-method</span> <span style="color: #c49619;">(</span><span style="color: #3c98e0;">construct-capabilities</span> <span style="color: #db5823;">(</span>instance <span style="color: #c49619; font-style: italic;">&lt;Gecko&gt;</span><span style="color: #db5823;">)</span><span style="color: #c49619;">)</span>
<span style="color: #c49619;">(</span><span style="color: #93a61a; font-weight: bold;">let</span> <span style="color: #db5823;">(</span><span style="color: #93a61a;">(</span>caps <span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">or</span> <span style="color: #c49619;">(</span>slot-value instance 'capabilities<span style="color: #c49619;">)</span> <span style="color: #c49619;">(</span>list<span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span><span style="color: #93a61a;">)</span><span style="color: #db5823;">)</span>
`<span style="color: #db5823;">(</span><span style="color: #93a61a;">(</span>capabilities . ,caps<span style="color: #93a61a;">)</span><span style="color: #db5823;">)</span><span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span>
</pre>
</div>
<p>
Sometimes, Geckodriver returns the results of a command in a JSON object with the sole key <code>"value"</code>. We have to correct that before returning the data to the user.
</p>
<div class="org-src-container">
<pre class="src src-scheme" id="org75d89a5"><span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define-method</span> <span style="color: #c49619;">(</span><span style="color: #3c98e0;">postprocess-result</span> <span style="color: #db5823;">(</span>instance <span style="color: #c49619; font-style: italic;">&lt;Gecko&gt;</span><span style="color: #db5823;">)</span> result<span style="color: #c49619;">)</span>
<span style="color: #c49619;">(</span>alist-ref/default result 'value result<span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span>
</pre>
</div>
</div>
</div>
</div>
<div id="outline-container-orgf472d01" class="outline-2">
<h2 id="orgf472d01"><span class="section-number-2">4.</span> WebDriver API</h2>
<div class="outline-text-2" id="text-4">
</div>
<div id="outline-container-org5685400" class="outline-3">
<h3 id="org5685400"><span class="section-number-3">4.1.</span> Communication</h3>
<div class="outline-text-3" id="text-4-1">
<p>
Data is sent to the API via a central class method. For convenience, there is a <code>send-with-session</code> variant that automatically adds the session id.
</p>
<div class="org-src-container">
<pre class="src src-scheme" id="orgaf961e8"><span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define-method</span> <span style="color: #c49619;">(</span><span style="color: #3c98e0;">send</span> <span style="color: #db5823;">(</span>instance <span style="color: #c49619; font-style: italic;">&lt;WebDriver&gt;</span><span style="color: #db5823;">)</span> data uri method<span style="color: #c49619;">)</span>
<span style="color: #c49619;">(</span><span style="color: #93a61a; font-weight: bold;">let*</span> <span style="color: #db5823;">(</span><span style="color: #93a61a;">(</span>remote <span style="color: #3c98e0;">(</span>string-append <span style="color: #93a61a;">"http://"</span> <span style="color: #c49619;">(</span>slot-value instance 'server<span style="color: #c49619;">)</span> <span style="color: #93a61a;">":"</span> <span style="color: #c49619;">(</span>-&gt;string <span style="color: #db5823;">(</span>slot-value instance 'port<span style="color: #db5823;">)</span><span style="color: #c49619;">)</span> <span style="color: #93a61a;">"/"</span><span style="color: #3c98e0;">)</span><span style="color: #93a61a;">)</span>
<span style="color: #93a61a;">(</span>result <span style="color: #3c98e0;">(</span>postprocess-result instance
<span style="color: #c49619;">(</span>with-input-from-request
<span style="color: #db5823;">(</span>make-request <span style="color: #3c98e0; font-style: italic;">#:method</span> method
<span style="color: #3c98e0; font-style: italic;">#:uri</span> <span style="color: #93a61a;">(</span>uri-reference <span style="color: #3c98e0;">(</span>string-append remote uri<span style="color: #3c98e0;">)</span><span style="color: #93a61a;">)</span>
<span style="color: #3c98e0; font-style: italic;">#:headers</span> <span style="color: #93a61a;">(</span>headers `<span style="color: #3c98e0;">(</span><span style="color: #c49619;">(</span>content-type application/json<span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span><span style="color: #93a61a;">)</span><span style="color: #db5823;">)</span>
<span style="color: #db5823;">(</span><span style="color: #93a61a; font-weight: bold;">if</span> data <span style="color: #93a61a;">(</span>json-&gt;string data<span style="color: #93a61a;">)</span> <span style="color: #93a61a;">""</span><span style="color: #db5823;">)</span>
read-json<span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span><span style="color: #93a61a;">)</span><span style="color: #db5823;">)</span>
<span style="color: #db5823;">(</span><span style="color: #93a61a; font-weight: bold;">if</span> <span style="color: #93a61a;">(</span><span style="color: #93a61a; font-weight: bold;">and</span> <span style="color: #3c98e0;">(</span>list? result<span style="color: #3c98e0;">)</span> <span style="color: #3c98e0;">(</span>alist-ref/default result 'error #f<span style="color: #3c98e0;">)</span><span style="color: #93a61a;">)</span>
<span style="color: #93a61a;">(</span>wd-throw result<span style="color: #93a61a;">)</span>
result<span style="color: #db5823;">)</span><span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span>
<span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define-method</span> <span style="color: #c49619;">(</span><span style="color: #3c98e0;">send-with-session</span> <span style="color: #db5823;">(</span>instance <span style="color: #c49619; font-style: italic;">&lt;WebDriver&gt;</span><span style="color: #db5823;">)</span> data uri method<span style="color: #c49619;">)</span>
<span style="color: #c49619;">(</span>send instance data <span style="color: #db5823;">(</span>string-append <span style="color: #93a61a;">"session/"</span> <span style="color: #93a61a;">(</span>slot-value instance 'session-id<span style="color: #93a61a;">)</span> <span style="color: #93a61a;">"/"</span> uri<span style="color: #db5823;">)</span> method<span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span>
</pre>
</div>
</div>
</div>
<div id="outline-container-orgd3b1c20" class="outline-3">
<h3 id="orgd3b1c20"><span class="section-number-3">4.2.</span> Session management</h3>
<div class="outline-text-3" id="text-4-2">
<p>
Session management is very simple. There is just one method to initialize a new session. Everything else is handled automatically.
</p>
<div class="org-src-container">
<pre class="src src-scheme" id="org9cfce71"><span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define-method</span> <span style="color: #c49619;">(</span><span style="color: #3c98e0;">initialize-session</span> <span style="color: #db5823;">(</span>instance <span style="color: #c49619; font-style: italic;">&lt;WebDriver&gt;</span><span style="color: #db5823;">)</span><span style="color: #c49619;">)</span>
<span style="color: #c49619;">(</span><span style="color: #93a61a; font-weight: bold;">let</span> <span style="color: #db5823;">(</span><span style="color: #93a61a;">(</span>result <span style="color: #3c98e0;">(</span>send instance <span style="color: #c49619;">(</span>construct-capabilities instance<span style="color: #c49619;">)</span> <span style="color: #93a61a;">"session"</span> 'POST<span style="color: #3c98e0;">)</span><span style="color: #93a61a;">)</span><span style="color: #db5823;">)</span>
<span style="color: #db5823;">(</span><span style="color: #93a61a; font-weight: bold;">set!</span> <span style="color: #93a61a;">(</span>slot-value instance 'session-id<span style="color: #93a61a;">)</span> <span style="color: #93a61a;">(</span>alist-ref result 'sessionId<span style="color: #93a61a;">)</span><span style="color: #db5823;">)</span><span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span>
</pre>
</div>
<div class="org-src-container">
<pre class="src src-scheme" id="org73e6879"><span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define-method</span> <span style="color: #c49619;">(</span><span style="color: #3c98e0;">terminate-session</span> <span style="color: #db5823;">(</span>instance <span style="color: #c49619; font-style: italic;">&lt;WebDriver&gt;</span><span style="color: #db5823;">)</span><span style="color: #c49619;">)</span>
<span style="color: #c49619;">(</span><span style="color: #93a61a; font-weight: bold;">when</span> <span style="color: #db5823;">(</span>slot-value instance 'session-id<span style="color: #db5823;">)</span>
<span style="color: #db5823;">(</span>send instance #f <span style="color: #93a61a;">(</span>string-append <span style="color: #93a61a;">"session/"</span> <span style="color: #3c98e0;">(</span>slot-value instance 'session-id<span style="color: #3c98e0;">)</span><span style="color: #93a61a;">)</span> 'DELETE<span style="color: #db5823;">)</span><span style="color: #c49619;">)</span>
<span style="color: #c49619;">(</span><span style="color: #93a61a; font-weight: bold;">set!</span> <span style="color: #db5823;">(</span>slot-value instance 'session-id<span style="color: #db5823;">)</span> #f<span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span>
</pre>
</div>
<pre class="example">
-- testing session -----------------------------------------------------------
Initial state ........................................................ [ PASS]
Session id check ..................................................... [ PASS]
Session id after termination ......................................... [ PASS]
3 tests completed in 4.952 seconds.
3 out of 3 (100%) tests passed.
-- done testing session ------------------------------------------------------
</pre>
</div>
</div>
<div id="outline-container-orgca687e8" class="outline-3">
<h3 id="orgca687e8"><span class="section-number-3">4.3.</span> API Access Methods</h3>
<div class="outline-text-3" id="text-4-3">
<div class="org-src-container">
<pre class="src src-scheme" id="org9ede152"><span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define-method</span> <span style="color: #c49619;">(</span><span style="color: #3c98e0;">set-url</span> <span style="color: #db5823;">(</span>instance <span style="color: #c49619; font-style: italic;">&lt;WebDriver&gt;</span><span style="color: #db5823;">)</span> url<span style="color: #c49619;">)</span>
<span style="color: #c49619;">(</span>send-with-session instance `<span style="color: #db5823;">(</span><span style="color: #93a61a;">(</span>url . ,url<span style="color: #93a61a;">)</span><span style="color: #db5823;">)</span> <span style="color: #93a61a;">"url"</span> 'POST<span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span>
<span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define-method</span> <span style="color: #c49619;">(</span><span style="color: #3c98e0;">url</span> <span style="color: #db5823;">(</span>instance <span style="color: #c49619; font-style: italic;">&lt;WebDriver&gt;</span><span style="color: #db5823;">)</span><span style="color: #c49619;">)</span>
<span style="color: #c49619;">(</span>send-with-session instance #f <span style="color: #93a61a;">"url"</span> 'GET<span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span>
</pre>
</div>
<pre class="example">
-- testing url ---------------------------------------------------------------
Initial state ........................................................ [ PASS]
Navigating to the first website ...................................... [ PASS]
2 tests completed in 5.471 seconds.
2 out of 2 (100%) tests passed.
-- done testing url ----------------------------------------------------------
</pre>
<div class="org-src-container">
<pre class="src src-scheme"><span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define-method</span> <span style="color: #c49619;">(</span><span style="color: #3c98e0;">back</span> <span style="color: #db5823;">(</span>instance <span style="color: #c49619; font-style: italic;">&lt;WebDriver&gt;</span><span style="color: #db5823;">)</span><span style="color: #c49619;">)</span>
<span style="color: #c49619;">(</span>send-with-session instance #f <span style="color: #93a61a;">"back"</span> 'POST<span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span>
</pre>
</div>
<div class="org-src-container">
<pre class="src src-scheme"><span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define-method</span> <span style="color: #c49619;">(</span><span style="color: #3c98e0;">forward</span> <span style="color: #db5823;">(</span>instance <span style="color: #c49619; font-style: italic;">&lt;WebDriver&gt;</span><span style="color: #db5823;">)</span><span style="color: #c49619;">)</span>
<span style="color: #c49619;">(</span>send-with-session instance #f <span style="color: #93a61a;">"forward"</span> 'POST<span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span>
</pre>
</div>
<div class="org-src-container">
<pre class="src src-scheme"><span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define-method</span> <span style="color: #c49619;">(</span><span style="color: #3c98e0;">forward</span> <span style="color: #db5823;">(</span>instance <span style="color: #c49619; font-style: italic;">&lt;WebDriver&gt;</span><span style="color: #db5823;">)</span><span style="color: #c49619;">)</span>
<span style="color: #c49619;">(</span>send-with-session instance #f <span style="color: #93a61a;">"refresh"</span> 'POST<span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span>
</pre>
</div>
<div class="org-src-container">
<pre class="src src-scheme"><span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define-method</span> <span style="color: #c49619;">(</span><span style="color: #3c98e0;">title</span> <span style="color: #db5823;">(</span>instance <span style="color: #c49619; font-style: italic;">&lt;WebDriver&gt;</span><span style="color: #db5823;">)</span><span style="color: #c49619;">)</span>
<span style="color: #c49619;">(</span>send-with-session instance #f <span style="color: #93a61a;">"title"</span> 'GET<span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span>
</pre>
</div>
<div class="org-src-container">
<pre class="src src-scheme"><span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define-method</span> <span style="color: #c49619;">(</span><span style="color: #3c98e0;">status</span> <span style="color: #db5823;">(</span>instance <span style="color: #c49619; font-style: italic;">&lt;WebDriver&gt;</span><span style="color: #db5823;">)</span><span style="color: #c49619;">)</span>
<span style="color: #c49619;">(</span>send-with-session instance #f <span style="color: #93a61a;">"status"</span> 'GET<span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span>
</pre>
</div>
<div class="org-src-container">
<pre class="src src-scheme"><span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define-method</span> <span style="color: #c49619;">(</span><span style="color: #3c98e0;">source</span> <span style="color: #db5823;">(</span>instance <span style="color: #c49619; font-style: italic;">&lt;WebDriver&gt;</span><span style="color: #db5823;">)</span><span style="color: #c49619;">)</span>
<span style="color: #c49619;">(</span>send-with-session instance #f <span style="color: #93a61a;">"source"</span> 'GET<span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span>
</pre>
</div>
<div class="org-src-container">
<pre class="src src-scheme"><span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define-method</span> <span style="color: #c49619;">(</span><span style="color: #3c98e0;">screenshot</span> <span style="color: #db5823;">(</span>instance <span style="color: #c49619; font-style: italic;">&lt;WebDriver&gt;</span><span style="color: #db5823;">)</span><span style="color: #c49619;">)</span>
<span style="color: #c49619;">(</span>base64-decode <span style="color: #db5823;">(</span>send-with-session instance #f <span style="color: #93a61a;">"screenshot"</span> 'GET<span style="color: #db5823;">)</span><span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span>
</pre>
</div>
<div class="org-src-container">
<pre class="src src-scheme"><span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define-method</span> <span style="color: #c49619;">(</span><span style="color: #3c98e0;">print-page</span> <span style="color: #db5823;">(</span>instance <span style="color: #c49619; font-style: italic;">&lt;WebDriver&gt;</span><span style="color: #db5823;">)</span><span style="color: #c49619;">)</span>
<span style="color: #c49619;">(</span>send-with-session instance #f <span style="color: #93a61a;">"print"</span> 'POST<span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span>
</pre>
</div>
<div class="org-src-container">
<pre class="src src-scheme"><span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define-method</span> <span style="color: #c49619;">(</span><span style="color: #3c98e0;">execute-async</span> <span style="color: #db5823;">(</span>instance <span style="color: #c49619; font-style: italic;">&lt;WebDriver&gt;</span><span style="color: #db5823;">)</span> script args<span style="color: #c49619;">)</span>
<span style="color: #c49619;">(</span>send-with-session instance `<span style="color: #db5823;">(</span><span style="color: #93a61a;">(</span>script . ,script<span style="color: #93a61a;">)</span> <span style="color: #93a61a;">(</span>args . ,args<span style="color: #93a61a;">)</span><span style="color: #db5823;">)</span> <span style="color: #93a61a;">"execute/async"</span> 'POST<span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span>
</pre>
</div>
<div class="org-src-container">
<pre class="src src-scheme"><span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define-method</span> <span style="color: #c49619;">(</span><span style="color: #3c98e0;">execute-sync</span> <span style="color: #db5823;">(</span>instance <span style="color: #c49619; font-style: italic;">&lt;WebDriver&gt;</span><span style="color: #db5823;">)</span> script args<span style="color: #c49619;">)</span>
<span style="color: #c49619;">(</span>send-with-session instance `<span style="color: #db5823;">(</span><span style="color: #93a61a;">(</span>script . ,script<span style="color: #93a61a;">)</span> <span style="color: #93a61a;">(</span>args . ,args<span style="color: #93a61a;">)</span><span style="color: #db5823;">)</span> <span style="color: #93a61a;">"execute/sync"</span> 'POST<span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span>
</pre>
</div>
</div>
</div>
<div id="outline-container-org8175c38" class="outline-3">
<h3 id="org8175c38"><span class="section-number-3">4.4.</span> Timeouts</h3>
<div class="outline-text-3" id="text-4-4">
<p>
The following timeouts are defined:
</p>
<ul class="org-ul">
<li><code>script</code>: defaults to 30&rsquo;000, specifies when to interrupt a script that is being evaluated. A nil value implies that scripts should never be interrupted, but instead run indefinitely.</li>
<li><code>pageLoad</code>: defaults to 300&rsquo;000, provides the timeout limit used to interrupt an explicit navigation attempt.</li>
<li><code>implicit</code>: defaults to 0, specifies a time to wait for the element location strategy to complete when locating an element.</li>
</ul>
<div class="org-src-container">
<pre class="src src-scheme"><span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define-class</span> <span style="color: #c49619; font-style: italic;">&lt;WDTimeouts&gt;</span> <span style="color: #c49619;">()</span>
<span style="color: #c49619;">(</span><span style="color: #db5823;">(</span>script <span style="color: #7a7ed2; font-weight: bold;">30000</span><span style="color: #db5823;">)</span>
<span style="color: #db5823;">(</span>pageLoad <span style="color: #7a7ed2; font-weight: bold;">300000</span><span style="color: #db5823;">)</span>
<span style="color: #db5823;">(</span>implicit <span style="color: #7a7ed2; font-weight: bold;">0</span><span style="color: #db5823;">)</span><span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span>
</pre>
</div>
<div class="org-src-container">
<pre class="src src-scheme"><span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define-method</span> <span style="color: #c49619;">(</span><span style="color: #3c98e0;">extract</span> <span style="color: #db5823;">(</span>instance <span style="color: #c49619; font-style: italic;">&lt;WDTimeouts&gt;</span><span style="color: #db5823;">)</span><span style="color: #c49619;">)</span>
`<span style="color: #c49619;">(</span><span style="color: #db5823;">(</span>script . ,<span style="color: #93a61a;">(</span>slot-value instance 'script<span style="color: #93a61a;">)</span><span style="color: #db5823;">)</span>
<span style="color: #db5823;">(</span>pageLoad . ,<span style="color: #93a61a;">(</span>slot-value instance 'pageLoad<span style="color: #93a61a;">)</span><span style="color: #db5823;">)</span>
<span style="color: #db5823;">(</span>implicit . ,<span style="color: #93a61a;">(</span>slot-value instance 'implicit<span style="color: #93a61a;">)</span><span style="color: #db5823;">)</span><span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span>
</pre>
</div>
</div>
<div id="outline-container-org2be29cc" class="outline-4">
<h4 id="org2be29cc"><span class="section-number-4">4.4.1.</span> Setting and getting timeouts</h4>
<div class="outline-text-4" id="text-4-4-1">
<div class="org-src-container">
<pre class="src src-scheme"><span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define-method</span> <span style="color: #c49619;">(</span><span style="color: #3c98e0;">set-timeouts</span> <span style="color: #db5823;">(</span>instance <span style="color: #c49619; font-style: italic;">&lt;WebDriver&gt;</span><span style="color: #db5823;">)</span> <span style="color: #db5823;">(</span>timeouts <span style="color: #c49619; font-style: italic;">&lt;WDTimeouts&gt;</span><span style="color: #db5823;">)</span><span style="color: #c49619;">)</span>
<span style="color: #c49619;">(</span>send-with-session instance <span style="color: #db5823;">(</span>extract timeouts<span style="color: #db5823;">)</span> <span style="color: #93a61a;">"timeouts"</span> 'POST<span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span>
<span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define-method</span> <span style="color: #c49619;">(</span><span style="color: #3c98e0;">timeouts</span> <span style="color: #db5823;">(</span>instance <span style="color: #c49619; font-style: italic;">&lt;WebDriver&gt;</span><span style="color: #db5823;">)</span><span style="color: #c49619;">)</span>
<span style="color: #c49619;">(</span><span style="color: #93a61a; font-weight: bold;">let</span> <span style="color: #db5823;">(</span><span style="color: #93a61a;">(</span>result <span style="color: #3c98e0;">(</span>send-with-session instance #f <span style="color: #93a61a;">"timeouts"</span> 'GET<span style="color: #3c98e0;">)</span><span style="color: #93a61a;">)</span><span style="color: #db5823;">)</span>
<span style="color: #db5823;">(</span>make <span style="color: #c49619; font-style: italic;">&lt;WDTimeouts&gt;</span>
'script <span style="color: #93a61a;">(</span>alist-ref result 'script<span style="color: #93a61a;">)</span>
'pageLoad <span style="color: #93a61a;">(</span>alist-ref result 'pageLoad<span style="color: #93a61a;">)</span>
'implicit <span style="color: #93a61a;">(</span>alist-ref result 'implicit<span style="color: #93a61a;">)</span><span style="color: #db5823;">)</span><span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span>
</pre>
</div>
</div>
</div>
</div>
<div id="outline-container-org0b96887" class="outline-3">
<h3 id="org0b96887"><span class="section-number-3">4.5.</span> Elements</h3>
<div class="outline-text-3" id="text-4-5">
</div>
<div id="outline-container-org3abeb2f" class="outline-4">
<h4 id="org3abeb2f"><span class="section-number-4">4.5.1.</span> Element Class</h4>
<div class="outline-text-4" id="text-4-5-1">
<div class="org-src-container">
<pre class="src src-scheme"><span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define-class</span> <span style="color: #c49619; font-style: italic;">&lt;WDElement&gt;</span> <span style="color: #c49619;">()</span>
<span style="color: #c49619;">(</span><span style="color: #db5823;">(</span>driver #f<span style="color: #db5823;">)</span>
<span style="color: #db5823;">(</span>element #f<span style="color: #db5823;">)</span><span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span>
</pre>
</div>
<div class="org-src-container">
<pre class="src src-scheme"><span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define-method</span> <span style="color: #c49619;">(</span><span style="color: #3c98e0;">send-with-session</span> <span style="color: #db5823;">(</span>instance <span style="color: #c49619; font-style: italic;">&lt;WDElement&gt;</span><span style="color: #db5823;">)</span> data uri method<span style="color: #c49619;">)</span>
<span style="color: #c49619;">(</span>send-with-session <span style="color: #db5823;">(</span>slot-value instance 'driver<span style="color: #db5823;">)</span> data
<span style="color: #db5823;">(</span>string-append <span style="color: #93a61a;">"element/"</span> <span style="color: #93a61a;">(</span>slot-value instance 'element<span style="color: #93a61a;">)</span> <span style="color: #93a61a;">"/"</span> uri<span style="color: #db5823;">)</span>
method<span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span>
</pre>
</div>
</div>
</div>
<div id="outline-container-orgf5ba942" class="outline-4">
<h4 id="orgf5ba942"><span class="section-number-4">4.5.2.</span> Finding Elements</h4>
<div class="outline-text-4" id="text-4-5-2">
</div>
<ol class="org-ol">
<li><a id="org4cf0712"></a>Location Strategies<br />
<div class="outline-text-5" id="text-4-5-2-1">
<div class="org-src-container">
<pre class="src src-scheme"><span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define</span> <span style="color: #3c98e0;">css-selector</span> <span style="color: #93a61a;">"css selector"</span><span style="color: #3c98e0;">)</span>
<span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define</span> <span style="color: #3c98e0;">link-text</span> <span style="color: #93a61a;">"link text"</span><span style="color: #3c98e0;">)</span>
<span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define</span> <span style="color: #3c98e0;">partial-link-text</span> <span style="color: #93a61a;">"partial link text"</span><span style="color: #3c98e0;">)</span>
<span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define</span> <span style="color: #3c98e0;">tag-name</span> <span style="color: #93a61a;">"tag name"</span><span style="color: #3c98e0;">)</span>
<span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define</span> <span style="color: #3c98e0;">xpath</span> <span style="color: #93a61a;">"xpath"</span><span style="color: #3c98e0;">)</span>
</pre>
</div>
</div>
</li>
<li><a id="orga7a21a2"></a>Accessor Methods<br />
<div class="outline-text-5" id="text-4-5-2-2">
<div class="org-src-container">
<pre class="src src-scheme"><span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define-method</span> <span style="color: #c49619;">(</span><span style="color: #3c98e0;">find-element</span> <span style="color: #db5823;">(</span>instance <span style="color: #c49619; font-style: italic;">&lt;WebDriver&gt;</span><span style="color: #db5823;">)</span> strategy selector<span style="color: #c49619;">)</span>
<span style="color: #c49619;">(</span><span style="color: #93a61a; font-weight: bold;">let</span> <span style="color: #db5823;">(</span><span style="color: #93a61a;">(</span>result <span style="color: #3c98e0;">(</span>send-with-session instance `<span style="color: #c49619;">(</span><span style="color: #db5823;">(</span>using . ,strategy<span style="color: #db5823;">)</span> <span style="color: #db5823;">(</span>value . ,selector<span style="color: #db5823;">)</span><span style="color: #c49619;">)</span> <span style="color: #93a61a;">"element"</span> 'POST<span style="color: #3c98e0;">)</span><span style="color: #93a61a;">)</span><span style="color: #db5823;">)</span>
<span style="color: #db5823;">(</span>make <span style="color: #c49619; font-style: italic;">&lt;WDElement&gt;</span> 'driver instance 'element <span style="color: #93a61a;">(</span>car <span style="color: #3c98e0;">(</span>alist-values result<span style="color: #3c98e0;">)</span><span style="color: #93a61a;">)</span><span style="color: #db5823;">)</span>
element<span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span>
</pre>
</div>
<div class="org-src-container">
<pre class="src src-scheme"><span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define-method</span> <span style="color: #c49619;">(</span><span style="color: #3c98e0;">find-elements</span> <span style="color: #db5823;">(</span>instance <span style="color: #c49619; font-style: italic;">&lt;WebDriver&gt;</span><span style="color: #db5823;">)</span> strategy selector<span style="color: #c49619;">)</span>
<span style="color: #c49619;">(</span><span style="color: #93a61a; font-weight: bold;">let</span> <span style="color: #db5823;">(</span><span style="color: #93a61a;">(</span>result <span style="color: #3c98e0;">(</span>send-with-session instance `<span style="color: #c49619;">(</span><span style="color: #db5823;">(</span>using . ,strategy<span style="color: #db5823;">)</span> <span style="color: #db5823;">(</span>value . ,selector<span style="color: #db5823;">)</span><span style="color: #c49619;">)</span> <span style="color: #93a61a;">"elements"</span> 'POST<span style="color: #3c98e0;">)</span><span style="color: #93a61a;">)</span><span style="color: #db5823;">)</span>
<span style="color: #db5823;">(</span><span style="color: #93a61a; font-weight: bold;">map</span>
<span style="color: #93a61a;">(</span><span style="color: #93a61a; font-weight: bold;">lambda</span> <span style="color: #3c98e0;">(</span>elem<span style="color: #3c98e0;">)</span>
<span style="color: #3c98e0;">(</span>make <span style="color: #c49619; font-style: italic;">&lt;WDElement&gt;</span> 'driver instance 'element <span style="color: #c49619;">(</span>car <span style="color: #db5823;">(</span>alist-values elem<span style="color: #db5823;">)</span><span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span><span style="color: #93a61a;">)</span>
result<span style="color: #db5823;">)</span><span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span>
</pre>
</div>
<div class="org-src-container">
<pre class="src src-scheme"><span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define-method</span> <span style="color: #c49619;">(</span><span style="color: #3c98e0;">find-element</span> <span style="color: #db5823;">(</span>instance <span style="color: #c49619; font-style: italic;">&lt;WDElement&gt;</span><span style="color: #db5823;">)</span> strategy selector<span style="color: #c49619;">)</span>
<span style="color: #c49619;">(</span><span style="color: #93a61a; font-weight: bold;">let</span> <span style="color: #db5823;">(</span><span style="color: #93a61a;">(</span>result <span style="color: #3c98e0;">(</span>send-with-session instance `<span style="color: #c49619;">(</span><span style="color: #db5823;">(</span>using . ,strategy<span style="color: #db5823;">)</span> <span style="color: #db5823;">(</span>value . ,selector<span style="color: #db5823;">)</span><span style="color: #c49619;">)</span> <span style="color: #93a61a;">"element"</span> 'POST<span style="color: #3c98e0;">)</span><span style="color: #93a61a;">)</span><span style="color: #db5823;">)</span>
<span style="color: #db5823;">(</span>make <span style="color: #c49619; font-style: italic;">&lt;WDElement&gt;</span> 'driver <span style="color: #93a61a;">(</span>slot-value instance 'driver<span style="color: #93a61a;">)</span> 'element <span style="color: #93a61a;">(</span>car <span style="color: #3c98e0;">(</span>alist-values result<span style="color: #3c98e0;">)</span><span style="color: #93a61a;">)</span><span style="color: #db5823;">)</span>
element<span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span>
</pre>
</div>
<div class="org-src-container">
<pre class="src src-scheme"><span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define-method</span> <span style="color: #c49619;">(</span><span style="color: #3c98e0;">find-elements</span> <span style="color: #db5823;">(</span>instance <span style="color: #c49619; font-style: italic;">&lt;WDElement&gt;</span><span style="color: #db5823;">)</span> strategy selector<span style="color: #c49619;">)</span>
<span style="color: #c49619;">(</span><span style="color: #93a61a; font-weight: bold;">let</span> <span style="color: #db5823;">(</span><span style="color: #93a61a;">(</span>result <span style="color: #3c98e0;">(</span>send-with-session instance `<span style="color: #c49619;">(</span><span style="color: #db5823;">(</span>using . ,strategy<span style="color: #db5823;">)</span> <span style="color: #db5823;">(</span>value . ,selector<span style="color: #db5823;">)</span><span style="color: #c49619;">)</span> <span style="color: #93a61a;">"elements"</span> 'POST<span style="color: #3c98e0;">)</span><span style="color: #93a61a;">)</span><span style="color: #db5823;">)</span>
<span style="color: #db5823;">(</span><span style="color: #93a61a; font-weight: bold;">map</span>
<span style="color: #93a61a;">(</span><span style="color: #93a61a; font-weight: bold;">lambda</span> <span style="color: #3c98e0;">(</span>elem<span style="color: #3c98e0;">)</span>
<span style="color: #3c98e0;">(</span>make <span style="color: #c49619; font-style: italic;">&lt;WDElement&gt;</span> 'driver <span style="color: #c49619;">(</span>slot-value instance 'driver<span style="color: #c49619;">)</span> 'element <span style="color: #c49619;">(</span>car <span style="color: #db5823;">(</span>alist-values elem<span style="color: #db5823;">)</span><span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span><span style="color: #93a61a;">)</span>
result<span style="color: #db5823;">)</span><span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span>
</pre>
</div>
</div>
</li>
</ol>
</div>
<div id="outline-container-org25c6507" class="outline-4">
<h4 id="org25c6507"><span class="section-number-4">4.5.3.</span> Working with Elements</h4>
<div class="outline-text-4" id="text-4-5-3">
<div class="org-src-container">
<pre class="src src-scheme"><span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define-method</span> <span style="color: #c49619;">(</span><span style="color: #3c98e0;">attribute</span> <span style="color: #db5823;">(</span>instance <span style="color: #c49619; font-style: italic;">&lt;WDElement&gt;</span><span style="color: #db5823;">)</span> attribute<span style="color: #c49619;">)</span>
<span style="color: #c49619;">(</span><span style="color: #93a61a; font-weight: bold;">let</span> <span style="color: #db5823;">(</span><span style="color: #93a61a;">(</span>result <span style="color: #3c98e0;">(</span>send-with-session instance #f
<span style="color: #c49619;">(</span>string-append <span style="color: #93a61a;">"attribute/"</span> attribute<span style="color: #c49619;">)</span>
'GET<span style="color: #3c98e0;">)</span><span style="color: #93a61a;">)</span><span style="color: #db5823;">)</span>
<span style="color: #db5823;">(</span><span style="color: #93a61a; font-weight: bold;">if</span> <span style="color: #93a61a;">(</span>equal? <span style="color: #93a61a;">"true"</span> result<span style="color: #93a61a;">)</span>
#t
result<span style="color: #db5823;">)</span><span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span>
</pre>
</div>
<div class="org-src-container">
<pre class="src src-scheme"><span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define-method</span> <span style="color: #c49619;">(</span><span style="color: #3c98e0;">property</span> <span style="color: #db5823;">(</span>instance <span style="color: #c49619; font-style: italic;">&lt;WDElement&gt;</span><span style="color: #db5823;">)</span> property<span style="color: #c49619;">)</span>
<span style="color: #c49619;">(</span>send-with-session instance #f <span style="color: #db5823;">(</span>string-append <span style="color: #93a61a;">"property/"</span> property<span style="color: #db5823;">)</span> 'GET<span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span>
</pre>
</div>
<div class="org-src-container">
<pre class="src src-scheme"><span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define-method</span> <span style="color: #c49619;">(</span><span style="color: #3c98e0;">clear</span> <span style="color: #db5823;">(</span>instance <span style="color: #c49619; font-style: italic;">&lt;WDElement&gt;</span><span style="color: #db5823;">)</span><span style="color: #c49619;">)</span>
<span style="color: #c49619;">(</span>send-with-session instance #f <span style="color: #93a61a;">"clear"</span> 'POST<span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span>
</pre>
</div>
<div class="org-src-container">
<pre class="src src-scheme"><span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define-method</span> <span style="color: #c49619;">(</span><span style="color: #3c98e0;">click</span> <span style="color: #db5823;">(</span>instance <span style="color: #c49619; font-style: italic;">&lt;WDElement&gt;</span><span style="color: #db5823;">)</span><span style="color: #c49619;">)</span>
<span style="color: #c49619;">(</span>send-with-session instance #f <span style="color: #93a61a;">"click"</span> 'POST<span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span>
</pre>
</div>
<div class="org-src-container">
<pre class="src src-scheme"><span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define-method</span> <span style="color: #c49619;">(</span><span style="color: #3c98e0;">computed-label</span> <span style="color: #db5823;">(</span>instance <span style="color: #c49619; font-style: italic;">&lt;WDElement&gt;</span><span style="color: #db5823;">)</span><span style="color: #c49619;">)</span>
<span style="color: #c49619;">(</span>send-with-session instance #f <span style="color: #93a61a;">"computedlabel"</span> 'GET<span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span>
</pre>
</div>
<div class="org-src-container">
<pre class="src src-scheme"><span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define-method</span> <span style="color: #c49619;">(</span><span style="color: #3c98e0;">computed-role</span> <span style="color: #db5823;">(</span>instance <span style="color: #c49619; font-style: italic;">&lt;WDElement&gt;</span><span style="color: #db5823;">)</span><span style="color: #c49619;">)</span>
<span style="color: #c49619;">(</span>send-with-session instance #f <span style="color: #93a61a;">"computedrole"</span> 'GET<span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span>
</pre>
</div>
<div class="org-src-container">
<pre class="src src-scheme"><span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define-method</span> <span style="color: #c49619;">(</span><span style="color: #3c98e0;">enabled?</span> <span style="color: #db5823;">(</span>instance <span style="color: #c49619; font-style: italic;">&lt;WDElement&gt;</span><span style="color: #db5823;">)</span><span style="color: #c49619;">)</span>
<span style="color: #c49619;">(</span>send-with-session instance #f <span style="color: #93a61a;">"enabled"</span> 'GET<span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span>
</pre>
</div>
<div class="org-src-container">
<pre class="src src-scheme"><span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define-method</span> <span style="color: #c49619;">(</span><span style="color: #3c98e0;">selected?</span> <span style="color: #db5823;">(</span>instance <span style="color: #c49619; font-style: italic;">&lt;WDElement&gt;</span><span style="color: #db5823;">)</span><span style="color: #c49619;">)</span>
<span style="color: #c49619;">(</span>send-with-session instance #f <span style="color: #93a61a;">"selected"</span> 'GET<span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span>
</pre>
</div>
<div class="org-src-container">
<pre class="src src-scheme"><span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define-method</span> <span style="color: #c49619;">(</span><span style="color: #3c98e0;">name</span> <span style="color: #db5823;">(</span>instance <span style="color: #c49619; font-style: italic;">&lt;WDElement&gt;</span><span style="color: #db5823;">)</span><span style="color: #c49619;">)</span>
<span style="color: #c49619;">(</span>send-with-session instance #f <span style="color: #93a61a;">"name"</span> 'GET<span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span>
</pre>
</div>
<div class="org-src-container">
<pre class="src src-scheme"><span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define-method</span> <span style="color: #c49619;">(</span><span style="color: #3c98e0;">rect</span> <span style="color: #db5823;">(</span>instance <span style="color: #c49619; font-style: italic;">&lt;WDElement&gt;</span><span style="color: #db5823;">)</span><span style="color: #c49619;">)</span>
<span style="color: #c49619;">(</span>send-with-session instance #f <span style="color: #93a61a;">"rect"</span> 'GET<span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span>
</pre>
</div>
<div class="org-src-container">
<pre class="src src-scheme"><span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define-method</span> <span style="color: #c49619;">(</span><span style="color: #3c98e0;">screenshot</span> <span style="color: #db5823;">(</span>instance <span style="color: #c49619; font-style: italic;">&lt;WDElement&gt;</span><span style="color: #db5823;">)</span><span style="color: #c49619;">)</span>
<span style="color: #c49619;">(</span>base64-decode <span style="color: #db5823;">(</span>send-with-session instance #f <span style="color: #93a61a;">"screenshot"</span> 'GET<span style="color: #db5823;">)</span><span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span>
</pre>
</div>
<div class="org-src-container">
<pre class="src src-scheme"><span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define-method</span> <span style="color: #c49619;">(</span><span style="color: #3c98e0;">text</span> <span style="color: #db5823;">(</span>instance <span style="color: #c49619; font-style: italic;">&lt;WDElement&gt;</span><span style="color: #db5823;">)</span><span style="color: #c49619;">)</span>
<span style="color: #c49619;">(</span>send-with-session instance #f <span style="color: #93a61a;">"text"</span> 'GET<span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span>
</pre>
</div>
<div class="org-src-container">
<pre class="src src-scheme"><span style="color: #3c98e0;">(</span><span style="color: #93a61a; font-weight: bold;">define-method</span> <span style="color: #c49619;">(</span><span style="color: #3c98e0;">set-value</span> <span style="color: #db5823;">(</span>instance <span style="color: #c49619; font-style: italic;">&lt;WDElement&gt;</span><span style="color: #db5823;">)</span> value<span style="color: #c49619;">)</span>
<span style="color: #c49619;">(</span>send-with-session instance `<span style="color: #db5823;">(</span><span style="color: #93a61a;">(</span>text . ,value<span style="color: #93a61a;">)</span><span style="color: #db5823;">)</span> <span style="color: #93a61a;">"value"</span> 'POST<span style="color: #c49619;">)</span><span style="color: #3c98e0;">)</span>
</pre>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="postamble" class="status">
<p class="author">Author: Daniel Ziltener</p>
<p class="date">Created: 2023-04-13 Do 16:13</p>
</div>
</body>
</html>

657
webdriver.org Normal file
View file

@ -0,0 +1,657 @@
#+title: Webdriver implementation in Chicken Scheme
#+author: Daniel Ziltener
#+property: header-args:scheme :session *chicken* :comments both
* Helpers :noexport:
:PROPERTIES:
:header-args:scheme: :prologue "(import (chicken string))"
:END:
** Strip garbage from test results
#+name: test-post
#+begin_src scheme :var input='() :results output
(for-each (lambda (str)
(or (substring=? str ";")
(substring=? str "Note")
(print str)))
(string-split input "\n"))
#+end_src
** Prepare in-line testing
#+name: prep-test
#+begin_src scheme :noweb yes :tangle tests/run.scm :results silent
(import r7rs
test
(chicken base)
(chicken string)
(chicken process)
(chicken gc)
<<dependencies-for-imports()>>
)
#+end_src
* Dependencies
#+name: dependencies
| Dependency | Description |
|-------------+-----------------------------------|
| srfi-34 | Exception Handling |
| srfi-35 | Exception Types |
| base64 | decoding screenshot data |
| http-client | API interaction |
| intarweb | Supporting HTTP functionality |
| uri-common | Supporting HTTP functionality |
| coops | Object system |
| alist-lib | Handling alists from JSON objects |
| medea | JSON handling |
#+name: dependencies-for-egg
#+begin_src emacs-lisp :var tbl=dependencies :colnames yes :results raw :exports none
(mapconcat (lambda (row) (car row)) tbl " ")
#+end_src
#+name: dependencies-for-imports
#+begin_src emacs-lisp :var tbl=dependencies :colnames yes :results raw :exports none
(mapconcat (lambda (row) (concat (car row) "\t\t;;" (cadr row)))
tbl "\n")
#+end_src
#+begin_src scheme :noweb yes :tangle webdriver.scm :exports none
(import r7rs)
(define-library (webdriver)
(import (scheme base))
(export <WDElement>)
(include "webdriver-impl.scm"))
#+end_src
#+begin_src scheme :noweb yes :tangle webdriver-impl.scm :exports none
(import r7rs
(chicken base)
(chicken string)
(chicken process)
(chicken gc)
<<dependencies-for-imports()>>
)
#+end_src
#+begin_src scheme :tangle tests/run.scm :exports none :mkdirp yes
(include-relative "../webdriver-impl.scm")
#+end_src
* Error Conditions
#+name: wd-exception
#+begin_src scheme :tangle webdriver-impl.scm
(define-condition-type &wd-exception &error wd-exception?
(stacktrace wd-stacktrace)
(data wd-data))
#+end_src
Every API error code (key "error" in the returned JSON data) gets its own condition type, prefixed by ~&~. They all inherit from ~&wd-exception~.
#+name: error-code-table
| Name | API Error Code |
|---------------------------+---------------------------|
| detached-shadow-root | detached shadow root |
| element-click-intercepted | element click intercepted |
| element-not-interactable | element not interactable |
| insecure-certificate | insecure certificate |
| invalid-argument | invalid argument |
| invalid-cookie-domain | invalid cookie domain |
| invalid-element-state | invalid element state |
| invalid-selector | invalid selector |
| invalid-session-id | invalid session id |
| javascript-error | javascript error |
| move-target-out-of-bounds | move target out of bounds |
| no-such-alert | no such alert |
| no-such-cookie | no such cookie |
| no-such-element | no such element |
| no-such-frame | no such frame |
| no-such-shadow-root | no such shadow root |
| no-such-window | no such window |
| script-timeout | script timeout |
| session-not-created | session not created |
| stale-element-reference | stale element reference |
| timeout | timeout |
| unable-to-capture-screen | unable to capture screen |
| unable-to-set-cookie | unable to set cookie |
| unexpected-alert-open | unexpected alert open |
| unknown-command | unknown command |
| unknown-error | unknown error |
| unknown-method | unknown method |
| unsupported-operation | unsupported operation |
#+name: define-conditions
#+begin_src emacs-lisp :var src=error-code-table :exports none :results raw
(mapconcat
(lambda (row)
(let ((replace `((?n . ,(cl-first row))
(?c . ,(cl-second row)))))
(format-spec "(define-condition-type &%n &wd-exception %n?)" replace)))
src "\n")
#+end_src
#+name: define-condition-thrower
#+begin_src emacs-lisp :var src=error-code-table :exports none :results raw
(concat "(define (wd-throw data)\n"
" (case (alist-ref data 'error)\n"
(mapconcat
(lambda (row)
(let ((replace `((?n . ,(cl-first row))
(?c . ,(cl-second row)))))
(format-spec " ((\"%c\") (raise (make-condition &%n (alist-ref data 'stacktrace) data)))" replace)))
src "\n")
"\n"
" (else (raise (make-condition &wd-exception (alist-ref data 'stacktrace) data)))\n"
" )\n"
")")
#+end_src
#+name: conditions
#+begin_src scheme :tangle webdriver-impl.scm :noweb yes :exports none
<<define-conditions()>>
<<define-condition-thrower()>>
#+end_src
* WebDriver
The core element of the library is the ~<WebDriver>~ class and its subclasses. The class has the following fields:
#+name: webdriver-class
#+begin_src scheme :tangle webdriver-impl.scm
(define-class <WebDriver> ()
((browser #f)
(active? #f)
(browser-pid #f)
(server #f)
(port #f)
(session-id #f)
(prefs #f)
(capabilities #f)))
#+end_src
The parent class provides a handful of methods, but does not implement all of them; some are the sole responsibility of the subclass. The ~launch~ method, on the other hand, bears shared responsibility. It sets a finalizer to ensure termination of the web driver process in case the class is disposed of with a still-open driver.
#+name: webdriver-basics
#+begin_src scheme :tangle webdriver-impl.scm
(define-method (launch #:after (instance <WebDriver>) options)
(set-finalizer! instance (lambda (obj)
(when (slot-value instance 'active?)
(terminate instance)))))
(define-method (terminate (instance <WebDriver>))
(terminate-session instance)
(process-signal (slot-value instance 'browser-pid))
(set! (slot-value instance 'browser-pid) #f)
(set! (slot-value instance 'active?) #f))
(define-method (construct-capabilities (instance <WebDriver>) #!optional caps)
(raise 'subclass-responsibility))
(define-method (postprocess-result (instance <WebDriver>) result)
result)
#+end_src
Main initialization is done by calling the ~new-WebDriver~ procedure with the respective class name and optionally an alist of options.
#+name: webdriver-init
#+begin_src scheme :tangle webdriver-impl.scm
(define (new-WebDriver browser #!optional options)
(let ((instance (make browser)))
(launch instance options)
(sleep 1)
instance))
#+end_src
** Geckodriver
The Geckodriver is used to control Firefox.
#+name: geckodriver-basic
#+begin_src scheme :tangle webdriver-impl.scm
(define-class <Gecko> (<WebDriver>)
((browser #:firefox)
(server "127.0.0.1")
(port 4444)))
(define-method (launch (instance <Gecko>) options)
(let ((pid (process-run "geckodriver > /dev/null 2>&1")))
(set! (slot-value instance 'browser-pid) pid)
(set! (slot-value instance 'active?) #t)
(set! (slot-value instance 'capabilities) options)))
#+end_src
The capabilities object for Geckodriver is of the form ={"capabilities": {...}}=.
For more information on capabilities, see https://developer.mozilla.org/en-US/docs/Web/WebDriver/Capabilities.
#+name: geckodriver-capabilities
#+begin_src scheme :tangle webdriver-impl.scm
(define-method (construct-capabilities (instance <Gecko>))
(let ((caps (or (slot-value instance 'capabilities) (list))))
`((capabilities . ,caps))))
#+end_src
Sometimes, Geckodriver returns the results of a command in a JSON object with the sole key ="value"=. We have to correct that before returning the data to the user.
#+name: geckodriver-postprocess
#+begin_src scheme :tangle webdriver-impl.scm
(define-method (postprocess-result (instance <Gecko>) result)
(alist-ref/default result 'value result))
#+end_src
#+name: prep-geckodriver-test
#+begin_src scheme :tangle tests/run.scm :noweb strip-tangle :exports none :post test-post(input=*this*) :results output
<<prep-test>>
<<wd-exception>>
<<conditions>>
<<webdriver-class>>
<<webdriver-basics>>
<<webdriver-init>>
<<geckodriver-basic>>
<<geckodriver-capabilities>>
<<geckodriver-postprocess>>
<<wd-send>>
<<wd-init-session>>
<<wd-term-session>>
#+end_src
* WebDriver API
** Communication
Data is sent to the API via a central class method. For convenience, there is a ~send-with-session~ variant that automatically adds the session id.
#+name: wd-send
#+begin_src scheme :tangle webdriver-impl.scm
(define-method (send (instance <WebDriver>) data uri method)
(let* ((remote (string-append "http://" (slot-value instance 'server) ":" (->string (slot-value instance 'port)) "/"))
(result (postprocess-result instance
(with-input-from-request
(make-request #:method method
#:uri (uri-reference (string-append remote uri))
#:headers (headers `((content-type application/json))))
(if data (json->string data) "")
read-json))))
(if (and (list? result) (alist-ref/default result 'error #f))
(wd-throw result)
result)))
(define-method (send-with-session (instance <WebDriver>) data uri method)
(send instance data (string-append "session/" (slot-value instance 'session-id) "/" uri) method))
#+end_src
** Session management
Session management is very simple. There is just one method to initialize a new session. Everything else is handled automatically.
#+name: wd-init-session
#+begin_src scheme :tangle webdriver-impl.scm
(define-method (initialize-session (instance <WebDriver>))
(let ((result (send instance (construct-capabilities instance) "session" 'POST)))
(set! (slot-value instance 'session-id) (alist-ref result 'sessionId))))
#+end_src
#+name: wd-term-session
#+begin_src scheme :tangle webdriver-impl.scm
(define-method (terminate-session (instance <WebDriver>))
(when (slot-value instance 'session-id)
(send instance #f (string-append "session/" (slot-value instance 'session-id)) 'DELETE))
(set! (slot-value instance 'session-id) #f))
#+end_src
#+name: wd-session-test
#+begin_src scheme :tangle tests/run.scm :noweb strip-tangle :exports results :post test-post(input=*this*) :results output
<<prep-geckodriver-test>>
(test-group "session"
(let ((browser (new-WebDriver <Gecko>)))
(test "Initial state" #f (slot-value browser 'session-id))
(test-assert "Session id check" (string? (begin (initialize-session browser) (slot-value browser 'session-id))))
(test-assert "Session id after termination" (eq? #f (begin (terminate-session browser) (slot-value browser 'session-id))))
(terminate browser)))
#+end_src
#+RESULTS: wd-session-test
: -- testing session -----------------------------------------------------------
: Initial state ........................................................ [ PASS]
: Session id check ..................................................... [ PASS]
: Session id after termination ......................................... [ PASS]
: 3 tests completed in 3.788 seconds.
: 3 out of 3 (100%) tests passed.
: -- done testing session ------------------------------------------------------
** API Access Methods
#+name: wd-url
#+begin_src scheme :tangle webdriver-impl.scm
(define-method (set-url (instance <WebDriver>) url)
(send-with-session instance `((url . ,url)) "url" 'POST))
(define-method (url (instance <WebDriver>))
(send-with-session instance #f "url" 'GET))
#+end_src
#+name: wd-url-test
#+begin_src scheme :tangle tests/run.scm :noweb strip-tangle :exports results :post test-post(input=*this*) :results output
<<prep-geckodriver-test>>
<<wd-url>>
(test-group "url"
(let ((browser (new-WebDriver <Gecko>)))
(test "Initial state" #f (slot-value browser 'session-id))
(test "Navigating to the first website" "http://info.cern.ch/hypertext/WWW/TheProject.html"
(begin (initialize-session browser)
(set-url browser "http://info.cern.ch/hypertext/WWW/TheProject.html")
(url browser)))
(terminate browser)))
#+end_src
#+RESULTS: wd-url-test
: -- testing url ---------------------------------------------------------------
: Initial state ........................................................ [ PASS]
: Navigating to the first website ...................................... [ PASS]
: 2 tests completed in 5.247 seconds.
: 2 out of 2 (100%) tests passed.
: -- done testing url ----------------------------------------------------------
#+begin_src scheme :tangle webdriver-impl.scm
(define-method (back (instance <WebDriver>))
(send-with-session instance #f "back" 'POST))
#+end_src
#+begin_src scheme :tangle webdriver-impl.scm
(define-method (forward (instance <WebDriver>))
(send-with-session instance #f "forward" 'POST))
#+end_src
#+begin_src scheme :tangle webdriver-impl.scm
(define-method (refresh (instance <WebDriver>))
(send-with-session instance #f "refresh" 'POST))
#+end_src
#+begin_src scheme :tangle webdriver-impl.scm
(define-method (title (instance <WebDriver>))
(send-with-session instance #f "title" 'GET))
#+end_src
#+begin_src scheme :tangle webdriver-impl.scm
(define-method (status (instance <WebDriver>))
(send-with-session instance #f "status" 'GET))
#+end_src
#+begin_src scheme :tangle webdriver-impl.scm
(define-method (source (instance <WebDriver>))
(send-with-session instance #f "source" 'GET))
#+end_src
#+begin_src scheme :tangle webdriver-impl.scm
(define-method (screenshot (instance <WebDriver>))
(base64-decode (send-with-session instance #f "screenshot" 'GET)))
#+end_src
#+begin_src scheme :tangle webdriver-impl.scm
(define-method (print-page (instance <WebDriver>))
(send-with-session instance #f "print" 'POST))
#+end_src
#+begin_src scheme :tangle webdriver-impl.scm
(define-method (execute-async (instance <WebDriver>) script args)
(send-with-session instance `((script . ,script) (args . ,args)) "execute/async" 'POST))
#+end_src
#+begin_src scheme :tangle webdriver-impl.scm
(define-method (execute-sync (instance <WebDriver>) script args)
(send-with-session instance `((script . ,script) (args . ,args)) "execute/sync" 'POST))
#+end_src
** Timeouts
The following timeouts are defined:
- =script=: defaults to 30'000, specifies when to interrupt a script that is being evaluated. A nil value implies that scripts should never be interrupted, but instead run indefinitely.
- =pageLoad=: defaults to 300'000, provides the timeout limit used to interrupt an explicit navigation attempt.
- =implicit=: defaults to 0, specifies a time to wait for the element location strategy to complete when locating an element.
#+begin_src scheme :tangle webdriver-impl.scm
(define-class <WDTimeouts> ()
((script 30000)
(pageLoad 300000)
(implicit 0)))
#+end_src
#+begin_src scheme :tangle webdriver-impl.scm
(define-method (extract (instance <WDTimeouts>))
`((script . ,(slot-value instance 'script))
(pageLoad . ,(slot-value instance 'pageLoad))
(implicit . ,(slot-value instance 'implicit))))
#+end_src
*** Setting and getting timeouts
#+begin_src scheme :tangle webdriver-impl.scm
(define-method (set-timeouts (instance <WebDriver>) (timeouts <WDTimeouts>))
(send-with-session instance (extract timeouts) "timeouts" 'POST))
(define-method (timeouts (instance <WebDriver>))
(let ((result (send-with-session instance #f "timeouts" 'GET)))
(make <WDTimeouts>
'script (alist-ref result 'script)
'pageLoad (alist-ref result 'pageLoad)
'implicit (alist-ref result 'implicit))))
#+end_src
** Elements
*** Element Class
#+begin_src scheme :tangle webdriver-impl.scm
(define-class <WDElement> ()
((driver #f)
(element #f)))
#+end_src
#+begin_src scheme :tangle webdriver-impl.scm
(define-method (send-with-session (instance <WDElement>) data uri method)
(send-with-session (slot-value instance 'driver) data
(string-append "element/" (slot-value instance 'element) "/" uri)
method))
#+end_src
*** Finding Elements
**** Location Strategies
#+begin_src scheme :tangle webdriver-impl.scm
(define css-selector "css selector")
(define link-text "link text")
(define partial-link-text "partial link text")
(define tag-name "tag name")
(define xpath "xpath")
#+end_src
**** Accessor Methods
#+begin_src scheme :tangle webdriver-impl.scm
(define-method (find-element (instance <WebDriver>) strategy selector)
(let ((result (send-with-session instance `((using . ,strategy) (value . ,selector)) "element" 'POST)))
(make <WDElement> 'driver instance 'element (car (alist-values result)))
element))
#+end_src
#+begin_src scheme :tangle webdriver-impl.scm
(define-method (find-elements (instance <WebDriver>) strategy selector)
(let ((result (send-with-session instance `((using . ,strategy) (value . ,selector)) "elements" 'POST)))
(map
(lambda (elem)
(make <WDElement> 'driver instance 'element (car (alist-values elem))))
result)))
#+end_src
#+begin_src scheme :tangle webdriver-impl.scm
(define-method (find-element (instance <WDElement>) strategy selector)
(let ((result (send-with-session instance `((using . ,strategy) (value . ,selector)) "element" 'POST)))
(make <WDElement> 'driver (slot-value instance 'driver) 'element (car (alist-values result)))
element))
#+end_src
#+begin_src scheme :tangle webdriver-impl.scm
(define-method (find-elements (instance <WDElement>) strategy selector)
(let ((result (send-with-session instance `((using . ,strategy) (value . ,selector)) "elements" 'POST)))
(map
(lambda (elem)
(make <WDElement> 'driver (slot-value instance 'driver) 'element (car (alist-values elem))))
result)))
#+end_src
*** Working with Elements
#+begin_src scheme :tangle webdriver-impl.scm
(define-method (attribute (instance <WDElement>) attribute)
(let ((result (send-with-session instance #f
(string-append "attribute/" attribute)
'GET)))
(if (equal? "true" result)
#t
result)))
#+end_src
#+begin_src scheme :tangle webdriver-impl.scm
(define-method (property (instance <WDElement>) property)
(send-with-session instance #f (string-append "property/" property) 'GET))
#+end_src
#+begin_src scheme :tangle webdriver-impl.scm
(define-method (clear (instance <WDElement>))
(send-with-session instance #f "clear" 'POST))
#+end_src
#+begin_src scheme :tangle webdriver-impl.scm
(define-method (click (instance <WDElement>))
(send-with-session instance #f "click" 'POST))
#+end_src
#+begin_src scheme :tangle webdriver-impl.scm
(define-method (computed-label (instance <WDElement>))
(send-with-session instance #f "computedlabel" 'GET))
#+end_src
#+begin_src scheme :tangle webdriver-impl.scm
(define-method (computed-role (instance <WDElement>))
(send-with-session instance #f "computedrole" 'GET))
#+end_src
#+begin_src scheme :tangle webdriver-impl.scm
(define-method (enabled? (instance <WDElement>))
(send-with-session instance #f "enabled" 'GET))
#+end_src
#+begin_src scheme :tangle webdriver-impl.scm
(define-method (selected? (instance <WDElement>))
(send-with-session instance #f "selected" 'GET))
#+end_src
#+begin_src scheme :tangle webdriver-impl.scm
(define-method (name (instance <WDElement>))
(send-with-session instance #f "name" 'GET))
#+end_src
#+begin_src scheme :tangle webdriver-impl.scm
(define-method (rect (instance <WDElement>))
(send-with-session instance #f "rect" 'GET))
#+end_src
#+begin_src scheme :tangle webdriver-impl.scm
(define-method (screenshot (instance <WDElement>))
(base64-decode (send-with-session instance #f "screenshot" 'GET)))
#+end_src
#+begin_src scheme :tangle webdriver-impl.scm
(define-method (text (instance <WDElement>))
(send-with-session instance #f "text" 'GET))
#+end_src
#+begin_src scheme :tangle webdriver-impl.scm
(define-method (set-value (instance <WDElement>) value)
(send-with-session instance `((text . ,value)) "value" 'POST))
#+end_src
* About This Egg
#+begin_src scheme :noweb yes :tangle webdriver.egg :exports none
;; -*- scheme -*-
((author "Daniel Ziltener")
(synopsis "A WebDriver API implementation for Chicken")
(category web)
(license "BSD")
(version <<latest-release()>>)
(dependencies r7rs <<dependencies-for-egg()>>)
(test-dependencies test)
(components
(extension webdriver
(csc-options "-X" "r7rs" "-R" "r7rs" "-sJ"))))
#+end_src
#+begin_src scheme :tangle tests/run.scm :exports none
(test-exit)
#+end_src
** Source
The source is available at [[https://fossil.lyrion.ch/chicken-webdriver]].
** Author
Daniel Ziltener
** Version History
#+name: version-history
| 0.5 | Initial Release |
#+name: gen-releases
#+begin_src emacs-lisp :var vers=version-history :results raw :exports none
(mapconcat (lambda (row) (concat "(release \"" (number-to-string (car row)) "\") ;; " (cadr row)))
vers "\n")
#+end_src
#+name: latest-release
#+begin_src emacs-lisp :var vers=version-history :exports none :results code
(number-to-string (caar vers))
#+end_src
#+begin_src scheme :noweb yes :tangle webdriver.release-info :exports none
;; -*- scheme -*-
(repo fossil "https://fossil.lyrion.ch/chicken-webdriver")
(uri targz "https://fossil.lyrion.ch/chicken-webdriver")
<<gen-releases()>>
#+end_src
** License
#+begin_src fundamental :tangle LICENSE
Copyright (C) 2023 Daniel Ziltener
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#+end_src

6
webdriver.release-info Normal file
View file

@ -0,0 +1,6 @@
;; [[file:webdriver.org::*Version History / Repository][Version History / Repository:3]]
;; -*- scheme -*-
(repo fossil "https://fossil.lyrion.ch/chicken-webdriver")
(uri targz "https://fossil.lyrion.ch/chicken-webdriver")
(release "0.5") ;; Initial Release
;; Version History / Repository:3 ends here

7
webdriver.scm Normal file
View file

@ -0,0 +1,7 @@
;; [[file:webdriver.org::*Dependencies][Dependencies:3]]
(import r7rs)
(define-library (webdriver)
(import (scheme base))
(export <WDElement>)
(include "webdriver-impl.scm"))
;; Dependencies:3 ends here