Pure Clojure -Implementierung des Webdriver -Protokolls. Verwenden Sie diese Bibliothek, um einen Browser zu automatisieren, Ihr Frontend -Verhalten zu testen, menschliche Handlungen zu simulieren oder was auch immer Sie wollen.
Es ist nach Etaoin Shrdlu benannt - einer Schreibmaschine, die nach einer Mysteries -Note am Leben wurde.
Seit 0.4.0
ist die Fahrerinstanz eine Karte, aber kein Atom wie früher. Es war eine schwierige Lösung, sich für die Entscheidung zu entscheiden, aber wir haben Atom losgeworden, um Clojure Way in unserem Code zu folgen. Im Allgemeinen haben Sie nie einen Fahrer oder speichern etwas darin. Alle internen Funktionen, die die Instanz geändert haben, geben jetzt einfach eine neue Version einer Karte zurück. Wenn Sie swap!
Oder ähnlich ähnlich in Ihrem Code für den Treiber, bitte reiten Sie Ihren Code vor, bevor Sie aktualisieren.
Seit 0.4.0
unterstützt die Bibliothek Webdriver -Aktionen. Aktionen sind Befehle, die an den Treiber in Stapel gesendet werden. Siehe den detaillierten Abschnitt in TOC.
Seit 0.4.0
kann Etaoin Skriptdateien abspielen, die in der interaktiven Selenium -IDE erstellt wurden. Siehe den verwandten Abschnitt unten.
Ct t
wie gewohnt.Sie können Ihr Unternehmen gerne in diese Liste einreichen.
Es gibt zwei Schritte zur Installation:
etaoin
in Ihrem Clojure -Code Fügen Sie Folgendes hinzu :dependencies
in Ihrem project.clj
-Datei:
[etaoin "0.4.6"]
Arbeitet mit Clojure 1.9 und höher.
Diese Seite enthält Anweisungen zum Installieren von Treibern, die Sie zum Automatisieren Ihres Browsers benötigen.
Installieren Sie Chrome- und Firefox -Browser, die sie von den offiziellen Websites herunterladen. Auf allen Plattformen wird es kein Problem geben.
Installieren Sie bestimmte Treiber, die Sie benötigen:
Google Chrome -Treiber:
brew cask install chromedriver
für Mac -Benutzer2.28
Versionen installiert haben. 2.27
und unten hat einen Fehler zur Maximierung eines Fensters (siehe [[Fehlerbehebung]]).Geckodriver, ein Fahrer für Firefox:
brew install geckodriver
für Mac -BenutzerPhantom.js Browser:
brew install phantomjs
Safari -Treiber (nur für Mac):
Überprüfen Sie nun Ihre Installation und starten Sie diese Befehle. Für jeden Befehl sollte ein endloser Prozess mit einem lokalen HTTP -Server starten.
chromedriver
geckodriver
phantomjs --wd
safaridriver -p 0
Sie können Tests für diese Bibliotheksstart ausführen:
lein test
Sie sehen Browserfenster, die in Serien geöffnet und geschlossen sind. Die Tests verwenden eine lokale HTML -Datei mit einem speziellen Layout, um die meisten Fälle zu validieren.
Im Folgenden finden Sie den Abschnitt "Fehlerbehebung", wenn Sie Probleme haben
Die guten Nachrichten können Sie Ihren Browser direkt aus der Replung automatisieren:
( use 'etaoin.api)
( require '[etaoin.keys :as k])
( def driver ( firefox )) ; ; here, a Firefox window should appear
; ; let's perform a quick Wiki session
( go driver " https://en.wikipedia.org/ " )
( wait-visible driver [{ :id :simpleSearch } { :tag :input :name :search }])
; ; search for something
( fill driver { :tag :input :name :search } " Clojure programming language " )
( fill driver { :tag :input :name :search } k/enter)
( wait-visible driver { :class :mw-search-results })
; ; select an `option` in select-box by visible text
; ; <select id="country">
; ; <option value="rf">Russia</option>
; ; <option value="usa">United States</option>
; ; <option value="uk">United Kingdom</option>
; ; <option value="fr">France</option>
; ;</select>
( select driver :country " France " )
( get-element-value driver :country )
; ;=> "fr"
; ; I'm sure the first link is what I was looking for
( click driver [{ :class :mw-search-results } { :class :mw-search-result-heading } { :tag :a }])
( wait-visible driver { :id :firstHeading })
; ; let's ensure
( get-url driver) ; ; "https://en.wikipedia.org/wiki/Clojure"
( get-title driver) ; ; "Clojure - Wikipedia"
( has-text? driver " Clojure " ) ; ; true
; ; navigate on history
( back driver)
( forward driver)
( refresh driver)
( get-title driver) ; ; "Clojure - Wikipedia"
; ; stops Firefox and HTTP server
( quit driver)
Sie sehen, jede Funktion erfordert eine Treiberinstanz als erstes Argument. Sie können es also mit doto
-Makros vereinfachen:
( def driver ( firefox ))
( doto driver
( go " https://en.wikipedia.org/ " )
( wait-visible [{ :id :simpleSearch } { :tag :input :name :search }])
; ; ...
( fill { :tag :input :name :search } k/enter)
( wait-visible { :class :mw-search-results })
( click :some-button )
; ; ...
( wait-visible { :id :firstHeading })
; ; ...
( quit ))
In diesem Fall sieht Ihr Code wie ein DSL aus, das nur für solche Zwecke entworfen wurde.
Sie können fill-multi
verwenden, um den Code zu verkürzen wie:
( fill driver :login " login " )
( fill driver :password " pass " )
( fill driver :textarea " some text " )
hinein
( fill-multi driver { :login " login "
:password " pass "
:textarea " some text " })
Wenn eine Ausnahme während einer Browser -Sitzung auftritt, kann der externe Prozess für immer hängen, bis Sie ihn manuell töten. Um dies zu verhindern, verwenden Sie with-<browser>
Makros wie folgt:
( with-firefox {} ff ; ; additional options first, then bind name
( doto ff
( go " https://google.com " )
...))
Was auch immer während einer Sitzung passiert, der Prozess wird sowieso gestoppt.
Für die meisten Funktionen wie click
, fill
usw. erfordern ein Abfragestand, um ein Element auf einer Seite zu entdecken. Zum Beispiel:
( click driver { :tag :button })
( fill driver { :id " searchInput " } " Clojure " )
Die Bibliothek unterstützt die folgenden Abfragetypen und -werte.
:active
steht für das aktuelle aktive Element. Wenn Sie beispielsweise die Google -Seite öffnen, konzentriert sich der Cursor auf die Hauptsucheingabe. Es müssen also nicht manuell klicken. Beispiel:
( fill driver :active " Let's search for something " keys/enter)
Jedes andere Schlüsselwort, das die ID eines Elements angibt. Für die Google-Seite ist es :lst-ib
oder "lst-ib"
(auch Strings werden unterstützt). Das Register ist wichtig. Beispiel:
( fill driver :lst-ib " What is the Matrix? " keys/enter)
eine Zeichenfolge mit einem XPath -Ausdruck. Seien Sie vorsichtig, wenn Sie sie manuell schreiben. Überprüfen Sie den Abschnitt Troubleshooting
unten. Beispiel:
( fill driver " .//input[@id='lst-ib'][@name='q'] " " XPath in action! " keys/enter)
Eine Karte mit entweder :xpath
oder :css
-Schlüssel mit einem String -Expression der entsprechenden Syntax. Beispiel:
( fill driver { :xpath " .//input[@id='lst-ib'] " } " XPath selector " keys/enter)
( fill driver { :css " input#lst-ib[name='q'] " } " CSS selector " keys/enter)
Weitere Informationen finden Sie im CSS -Selektorhandbuch.
Eine Abfrage kann jede andere Karte sein, die einen XPath -Ausdruck als Daten darstellt. Die Regeln sind:
:tag
Tagschlüssel repräsentiert den Namen eines Tags. Es wird *
, wenn nicht bestanden.:index
Indexschlüssel erweitert sich in die nachfolgende [x]
Klausel. Nützlich, wenn Sie beispielsweise eine dritte Zeile aus einer Tabelle auswählen müssen.:fn/
Namespace und erweitert sich zu etwas Spezifisch.Beispiele:
Finden Sie das erste div
-Tag
( query driver { :tag :div })
; ; expands into .//div
Finden Sie das n-te div
Tag
( query driver { :tag :div :index 1 })
; ; expands into .//div[1]
Finden Sie das Tag a
mit dem Klassenattribut gleichzeitig active
( query driver { :tag :a :class " active " })
; ; ".//a[@class="active"]"
Finden Sie eine Form nach seinen Attributen:
( query driver { :tag :form :method :GET :class :message })
; ; expands into .//form[@method="GET"][@class="message"]
Suchen Sie eine Schaltfläche nach Text (genau übereinstimmen):
( query driver { :tag :button :fn/text " Press Me " })
; ; .//button[text()="Press Me"]
Finden Sie ein n -ter Element ( p
, a
, was auch immer) mit "Download" -Text:
( query driver { :fn/has-text " download " :index 3 })
; ; .//*[contains(text(), "download")][3]
Finden Sie ein Element, das die folgende Klasse hat:
( query driver { :tag :div :fn/has-class " overlay " })
; ; .//div[contains(@class, "overlay")]
Finden Sie ein Element, das die folgende Domain in einem HREF hat:
( query driver { :tag :a :fn/link " google.com " })
; ; .//a[contains(@href, "google.com")]
Finden Sie ein Element mit den folgenden Klassen gleichzeitig:
( query driver { :fn/has-classes [ :active :sticky :marked ]})
; ; .//*[contains(@class, "active")][contains(@class, "sticky")][contains(@class, "marked")]
Suchen Sie die Widgets für aktiviertes/deaktiviertes Eingang:
; ; first input
( query driver { :tag :input :fn/disabled true })
; ; .//input[@disabled=true()]
( query driver { :tag :input :fn/enabled true })
; ; .//input[@enabled=true()]
; ; all inputs
( query-all driver { :tag :input :fn/disabled true })
; ; .//input[@disabled=true()]
Eine Abfrage könnte ein Vektor sein, der aus allen oben genannten Ausdrücken besteht. In einer solchen Abfrage sucht jede nächste Semester von einem früheren rekursiv.
Ein einfaches Beispiel:
( click driver [{ :tag :html } { :tag :body } { :tag :a }])
Sie können sowohl XPath- als auch CSS -Ausdrücke auch kombinieren (achten Sie bei einem führenden Punkt im XPath -Ausdruck:
( click driver [{ :tag :html } { :css " div.class " } " .//a[@class='download'] " ])
Manchmal müssen Sie möglicherweise mit dem n -ten Element einer Abfrage interagieren, zum Beispiel, wenn Sie in diesem Beispiel auf den zweiten Link klicken möchten:
< ul >
< li class =" search-result " >
< a href =" a " > a </ a >
</ li >
< li class =" search-result " >
< a href =" b " > b </ a >
</ li >
< li class =" search-result " >
< a href =" c " > c </ a >
</ li >
</ ul >
In diesem Fall können Sie entweder die :index
verwenden, die für XPath -Ausdrücke wie folgt unterstützt wird:
( click driver [{ :tag :li :class :search-result :index 2 } { :tag :a }])
Oder Sie können den n-ten-Kind-Trick mit dem CSS-Ausdruck wie diesem verwenden:
( click driver { :css " li.search-result:nth-child(2) a " })
Schließlich ist es auch möglich, das n-te Element direkt mit query-all
zu erhalten:
( click-el driver ( nth ( query-all driver { :css " li.search-result a " }) 2 ))
Beachten Sie hier die Verwendung von click-el
, da query-all
ein Element zurückgibt, nicht um einen Selektor, der an direkte click
übergeben werden kann.
query-tree
nimmt Selektoren und wirkt wie ein Baum. Jeder nächste Selektor fragt Elemente von den vorherigen ab. Der Faustauswahl ist auf Fundelemente angewiesen, und die Rest verwenden Find-Elemente-From
( query-tree driver { :tag :div } { :tag :a })
bedeutet
{:tag :div} -> [div1 div2 div3]
div1 -> [a1 a2 a3]
div2 -> [a4 a5 a6]
div3 -> [a7 a8 a9]
Das Ergebnis wird also [A1 ... A9] sein
Um mit Elementen zu interagieren, die über eine Abfrage gefunden wurden, müssen Sie das Abfrageergebnis entweder an click-el
oder fill-el
übergeben:
( click-el driver ( first ( query-all driver { :tag :a })))
Sie können also Elemente in einen Vektor sammeln und jederzeit willkürlich mit ihnen interagieren:
( def elements ( query-all driver { :tag :input :type :text })
( fill-el driver ( first elements) " This is a test " )
( fill-el driver ( rand-nth elements) " I like tests! " )
Um die menschliche Eingabe zu emulieren, können Sie die fill-human
verwenden. Die folgenden Optionen sind standardmäßig aktiviert:
{ :mistake-prob 0.1 ; ; a real number from 0.1 to 0.9, the higher the number, the more typos will be made
:pause-max 0.2 } ; ; max typing delay in seconds
Und Sie können sie neu definieren:
( fill-human driver q text { :mistake-prob 0.5
:pause-max 1 })
; ; or just use default opts by omitting them
( fill-human driver q text)
Verwenden Sie für mehrere Eingaben mit menschlicher Emulation fill-human-multi
( fill-human-multi driver { :login " login "
:pass " password "
:textarea " some text " }
{ :mistake-prob 0.5
:pause-max 1 })
Die click
löst die linke Maus aus. Klicken Sie auf ein Element, das von einem Abfragebegriff gefunden wurde:
( click driver { :tag :button })
Die click
verwendet nur das erste von der Abfrage gefundene Element, das manchmal zum Klicken auf die falschen Elemente führt. Um sicherzustellen, dass nur ein Element gefunden wird, verwenden Sie die Funktion click-single
. Es wirkt gleich, legt jedoch eine Ausnahme auf, wenn die Seite die Seite abfragt, die mehrere Elemente zurückgibt:
( click-single driver { :tag :button :name " search " })
Ein Doppelklick wird selten im Web verwendet, ist jedoch mit der double-click
-Funktion (Chrome, Phantom.js) möglich:
( double-click driver { :tag :dbl-click-btn })
Es gibt auch eine Reihe von "blinden" Klickfunktionen. Sie lösen Mausklicks auf die aktuelle Mausposition:
( left-click driver)
( middle-click driver)
( right-click driver)
Eine weitere Reihe von Funktionen erledigen das Gleiche, verschieben Sie jedoch den Mauszeiger in ein bestimmtes Element, bevor Sie darauf klicken:
( left-click-on driver { :tag :img })
( middle-click-on driver { :tag :img })
( right-click-on driver { :tag :img })
Eine mittlere Mausklick ist nützlich, wenn Sie einen Link auf einer neuen Registerkarte „Hintergrund“ öffnen. Mit dem Rechtsklick wird manchmal ein Kontextmenü in Webanwendungen imitiert.
Die Bibliothek unterstützt Webdriver -Aktionen. Im Allgemeinen sind Aktionen Befehle, die virtuelle Eingabegeräte beschreiben.
{ :actions [{ :type " key "
:id " some name "
:actions [{ :type " keyDown " :value cmd}
{ :type " keyDown " :value " a " }
{ :type " keyUp " :value " a " }
{ :type " keyUp " :value cmd}
{ :type " pause " :duration 100 }]}
{ :type " pointer "
:id " UUID or some name "
:parameters { :pointerType " mouse " }
:actions [{ :type " pointerMove " :origin " pointer " :x 396 :y 323 }
; ; double click
{ :type " pointerDown " :duration 0 :button 0 }
{ :type " pointerUp " :duration 0 :button 0 }
{ :type " pointerDown " :duration 0 :button 0 }
{ :type " pointerUp " :duration 0 :button 0 }]}]}
Sie können eine Karte manuell erstellen und an die perform-actions
-Methode senden:
( def keyboard-input { :type " key "
:id " some name "
:actions [{ :type " keyDown " :value cmd}
{ :type " keyDown " :value " a " }
{ :type " keyUp " :value " a " }
{ :type " keyUp " :value cmd}
{ :type " pause " :duration 100 }]})
( perform-actions driver keyboard-input)
oder Wrapper verwenden. Zuerst müssen Sie virtuelle Eingabegeräte erstellen, z. B.:
( def keyboard ( make-key-input ))
und füllen Sie es dann mit den notwendigen Aktionen aus:
( -> keyboard
( add-key-down keys/shift-left)
( add-key-down " a " )
( add-key-up " a " )
( add-key-up keys/shift-left))
erweitertes Beispiel:
( let [driver ( chrome )
_ ( go driver " https://google.com " )
search-box ( query driver { :name :q })
mouse ( -> ( make-mouse-input )
( add-pointer-click-el search-box))
keyboard ( -> ( make-key-input )
add-pause
( with-key-down keys/shift-left
( add-key-press " e " ))
( add-key-press " t " )
( add-key-press " a " )
( add-key-press " o " )
( add-key-press " i " )
( add-key-press " n " )
( add-key-press keys/enter))]
( perform-actions driver keyboard mouse)
( quit driver))
Um den Status virtueller Eingabegeräte zu löschen, alle gedrückten Schlüsseln usw. freizusetzen, verwenden Sie die release-actions
-Methode:
( release-actions driver)
Wenn Sie auf eine Schaltfläche "Dateieingabe" klicken, wird ein OS-spezifischer Dialogfeld geöffnet, mit dem Sie nicht mit dem Webdriver-Protokoll interagieren dürfen. Verwenden Sie die Funktion upload-file
, um eine lokale Datei an ein Dateieingangs-Widget anzuhängen. Die Funktion nimmt einen Selektor auf, der auf eine Dateieingabe und entweder einen vollständigen Pfad als Zeichenfolge oder eine native java.io.File
-Instanz verweist. Die Datei sollte existieren oder sonst erhalten Sie eine Ausnahme. Verwendungsbeispiel:
( def driver ( chrome ))
; ; open a web page that serves uploaded files
( go driver " http://nervgh.github.io/pages/angular-file-upload/examples/simple/ " )
; ; bound selector to variable; you may also specify an id, class, etc
( def input { :tag :input :type :file })
; ; upload an image with the first one file input
( def my-file " /Users/ivan/Downloads/sample.png " )
( upload-file driver input my-file)
; ; or pass a native Java object:
( require '[clojure.java.io :as io])
( def my-file ( io/file " /Users/ivan/Downloads/sample.png " ))
( upload-file driver input my-file)
Das Aufrufen einer screenshot
-Funktion bringt die aktuelle Seite in ein PNG -Bild auf Ihrer Festplatte ein:
( screenshot driver " page.png " ) ; ; relative path
( screenshot driver " /Users/ivan/page.png " ) ; ; absolute path
Ein natives Java -Dateiobjekt wird ebenfalls unterstützt:
; ; when imported as `[clojure.java.io :as io]`
( screenshot driver ( io/file " test.png " ))
; ; native object
( screenshot driver ( java.io.File. " test-native.png " ))
Mit Firefox und Chrome können Sie nicht die gesamte Seite, sondern ein einzelnes Element erfassen, sagen ein Div, ein Eingabe -Widget oder was auch immer. Es funktioniert vorerst nicht mit anderen Browsern. Beispiel:
( screenshot-element driver { :tag :div :class :smart-widget } " smart_widget.png " )
Mit Makro with-screenshots
können Sie nach jedem Formular einen Screenshot erstellen
( with-screenshots driver " ../screenshots "
( fill driver :simple-input " 1 " )
( fill driver :simple-input " 2 " )
( fill driver :simple-input " 3 " ))
Was entspricht einer Aufzeichnung:
( fill driver :simple-input " 1 " )
( screenshot driver " ../screenshots/chrome-...123.png " )
( fill driver :simple-input " 2 " )
( screenshot driver " ../screenshots/chrome-...124.png " )
( fill driver :simple-input " 3 " )
( screenshot driver " ../screenshots/chrome-...125.png " )
Vor kurzem haben Google Chrome und später Firefox eine Funktion namens Headless Mode unterstützt. Wenn Sie kopflos sind, tritt keine von UI -Fenstern auf dem Bildschirm auf, nur die STDOut -Ausgabe geht in die Konsole. Mit dieser Funktion können Sie Integrationstests auf Servern ausführen, die kein grafisches Ausgabegerät haben.
Stellen Sie sicher, dass Ihr Browser den Kopflosenmodus unterstützt, indem Sie überprüft, ob es das Argument für das --headles
von Leitlinien akzeptiert, wenn Sie es vom Terminal ausführen. Phantom.js Treiber ist von Natur aus kopflos (er wurde nie für die Rendern von UI entwickelt).
Wenn Sie einen Fahrer starten, können Sie :headless
-Boolean -Flag zum Kopflosenmodus umstellen. Beachten Sie, dass nur die neueste Version von Chrome und Firefox unterstützt wird. Für andere Fahrer wird die Flagge ignoriert.
( def driver ( chrome { :headless true })) ; ; runs headless Chrome
oder
( def driver ( firefox { :headless true })) ; ; runs headless Firefox
Verwenden Sie kopflos, um zu überprüfen, ob ein Fahrer im Kopf ohne Kopf headless?
Prädikat:
( headless? driver) ; ; true
Beachten Sie, dass es immer für Phantom.js -Instanzen zutreffend zurückgibt.
Es gibt mehrere Verknüpfungen, die standardmäßig Chrom oder Firefox im Kopflosenmodus ausführen können:
( def driver ( chrome-headless ))
; ; or
( def driver ( firefox-headless {...})) ; ; with extra settings
; ; or
( with-chrome-headless nil driver
( go driver " http://example.com " ))
( with-firefox-headless {...} driver ; ; extra settings
( go driver " http://example.com " ))
Es gibt auch, when-headless
und when-not-headless
Makrosen ist, die es nur dann ermöglichen, eine Reihe von Befehlen auszuführen, wenn sich ein Browser im kopflosen Modus befindet oder nicht:
( with-chrome nil driver
...
( when-not-headless driver
... some actions that might be not available in headless mode)
... common actions for both versions)
Um eine Verbindung zu einem Treiber herzustellen, der bereits auf einem lokalen oder Remote -Host ausgeführt wird, müssen Sie den :host
-Parameter angeben, der entweder ein Hostname (localhost, einige.remote.host.net) oder eine IP -Adresse (127.0.0.1, 183.102.156.31 sein könnte ) und der :port
. Wenn der Port nicht angegeben ist, wird der Standardport festgelegt.
Beispiel:
; ; Chrome
( def driver ( chrome { :host " 127.0.0.1 " :port 9515 })) ; ; for connection to driver on localhost on port 9515
; ; Firefox
( def driver ( firefox { :host " 192.168.1.11 " })) ; ; the default port for firefox is 4444
Um mit dem Fahrer in Docker zusammenzuarbeiten, können Sie fertige Bilder aufnehmen:
Beispiel für Chrome:
docker run --name chromedriver -p 9515:4444 -d -e CHROMEDRIVER_WHITELISTED_IPS='' robcherry/docker-chromedriver:latest
für Firefox:
docker run --name geckodriver -p 4444:4444 -d instrumentisto/geckodriver
Um eine Verbindung zum Treiber herzustellen, müssen Sie nur den :host
-Parameter als localhost
oder 127.0.0.1
und den :port
auf dem er ausgeführt wird, angeben. Wenn der Port nicht angegeben ist, wird der Standardport festgelegt.
( def driver ( chrome-headless { :host " localhost " :port 9515 :args [ " --no-sandbox " ]}))
( def driver ( firefox-headless { :host " localhost " })) ; ; will try to connect to port 4444
Um die Proxy -Einstellungen zu setzen, verwenden Sie Umgebungsvariablen HTTP_PROXY
/ HTTPS_PROXY
oder übergeben Sie eine Karte des folgenden Typs:
{ :proxy { :http " some.proxy.com:8080 "
:ftp " some.proxy.com:8080 "
:ssl " some.proxy.com:8080 "
:socks { :host " myproxy:1080 " :version 5 }
:bypass [ " http://this.url " " http://that.url " ]
:pac-url " localhost:8888 " }}
; ; example
( chrome { :proxy { :http " some.proxy.com:8080 "
:ssl " some.proxy.com:8080 " }})
HINWEIS: A: PAC-URL für eine Proxy-Autokonfigurationsdatei. Wird mit Safari verwendet, als die anderen Proxy -Optionen in diesem Browser nicht funktionieren.
Um den Stellvertreter einzustellen, können Sie das ursprüngliche Objekt verwenden und an die Funktionen weitergeben:
{ :capabilities { :proxy { :proxyType " manual "
:proxyAutoconfigUrl " some.proxy.com:8080 "
:ftpProxy " some.proxy.com:8080 "
:httpProxy " some.proxy.com:8080 "
:noProxy [ " http://this.url " " http://that.url " ]
:sslProxy " some.proxy.com:8080 "
:socksProxy " some.proxy.com:1080 "
:socksVersion 5 }}}
( chrome { :capabilities { :proxy {...}}})
Mit aktuellen Updates bietet die Bibliothek eine großartige Funktion. Jetzt können Sie Ereignisse verfolgen, die aus dem Devtools -Panel stammen. Es bedeutet, dass alles, was Sie in der Entwicklerkonsole sehen, jetzt über API verfügbar ist. Das funktioniert jetzt nur mit Google Chrome.
Um einen Treiber mit speziellen Entwicklungseinstellungen zu starten, übergeben Sie einfach eine leere Karte an das Feld :dev
wenn Sie einen Fahrer ausführen:
( def c ( chrome { :dev {}}))
Der Wert darf nicht nil
sein. Wenn es sich um eine leere Karte handelt, übernimmt eine spezielle Funktion Standardeinstellungen. Hier finden Sie eine Vollversion der Entwicklereinstellungen mit allen angegebenen möglichen Werten.
( def c ( chrome { :dev
{ :perf
{ :level :all
:network? true
:page? true
:interval 1000
:categories [ :devtools
:devtools.network
:devtools.timeline ]}}}))
Unter der Motorhaube füllt es ein spezielles perfLoggingPrefs
-Wörterbuch innerhalb des chromeOptions
-Objekts.
Nachdem Ihr Browser diese Ereignisse akkumuliert, können Sie sie mit einem speziellen dev
lesen.
( go c " http://google.com " )
; ; wait until the page gets loaded
; ; load the namespace
( require '[etaoin.dev :as dev])
Lassen Sie uns eine Liste aller HTTP -Anforderungen haben, die während der Seite geladen wurden.
( def reqs ( dev/get-requests c))
; ; reqs is a vector of maps
( count reqs)
; ; 19
; ; what were their types?
( set ( map :type reqs))
; ; #{:script :other :document :image :xhr}
; ; we've got Js requests, images, AJAX and other stuff
; ; check the last one request, it's an image named tia.png
( -> reqs last clojure.pprint/pprint)
{ :state 4 ,
:id " 1000052292.8 " ,
:type :image ,
:xhr? false ,
:url " https://www.gstatic.com/inputtools/images/tia.png " ,
:with-data? nil,
:request
{ :method :get ,
:headers
{ :Referer " https://www.google.com/ " ,
:User-Agent
" Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36 " }},
:response
{ :status 200 ,
:headers {}, ; ; truncated
:mime " image/png " ,
:remote-ip " 173.194.73.94 " },
:done? true }
Da wir hauptsächlich an AJAX-Anfragen interessiert sind, gibt es eine Funktion get-ajax
, die dasselbe tut, filtert jedoch XHR-Anfragen:
( -> c dev/get-ajax last clojure.pprint/pprint)
{ :state 4 ,
:id " 1000051989.41 " ,
:type :xhr ,
:xhr? true ,
:url
" https://www.google.com/complete/search?q=clojure%20spec&cp=12&client=psy-ab&xssi=t&gs_ri=gws-wiz&hl=ru&authuser=0&psi=4iUbXdapJsbmrgTVt7H4BA.1562060259137&ei=4iUbXdapJsbmrgTVt7H4BA " ,
:with-data? nil,
:request
{ :method :get ,
:headers
{ :Referer " https://www.google.com/ " ,
:User-Agent
" Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36 " }},
:response
{ :status 200 ,
:headers {}, ; ; truncated
:mime " application/json " ,
:remote-ip " 74.125.131.99 " },
:done? true }
Ein typisches Muster der Nutzung von get-ajax
ist das folgende. Sie möchten überprüfen, ob eine bestimmte Anfrage auf den Server abgefeuert wurde. Sie drücken also eine Taste, warten Sie eine Weile und lesen dann die von Ihrem Browser gestellten Anfragen.
Wenn Sie eine Liste von Anfragen haben, suchen Sie nach dem, den Sie benötigen (z. B. durch die URL), und überprüfen dann den Zustand. Das :state
Field hat die gleiche Semantik wie die XMLHttpRequest.readyState
hat. Es ist eine Ganzzahl von 1 bis 4 mit dem gleichen Verhalten.
Um zu überprüfen, ob eine Anfrage abgeschlossen, fertig oder fehlgeschlagen ist, verwenden Sie diese Prädikate:
( def req ( last reqs))
( dev/request-done? req)
; ; true
( dev/request-failed? req)
; ; false
( dev/request-success? req)
; ; true
Beachten Sie, dass der request-done?
Das heißt nicht, dass die Anfrage erfolgreich ist. Es bedeutet nur, dass seine Pipeline einen letzten Schritt erreicht hat.
Warnung: Wenn Sie Dev -Protokolle lesen, konsumieren Sie sie aus einem internen Puffer, der gespült wird. Der zweite Anruf bei get-requests
oder get-ajax
gibt eine leere Liste zurück.
Vielleicht möchten Sie diese Protokolle für Ihre eigenen sammeln. Eine Funktion dev/get-performance-logs
gibt eine Liste von Protokollen zurück, sodass Sie sie in einem Atom oder was auch immer ansammeln:
( def logs ( atom []))
; ; repeat that form from time to time
( do ( swap! logs concat ( dev/get-performance-logs c))
true )
( count @logs)
; ; 76
Es gibt logs->requests
und logs->ajax
-Funktionen, die Protokolle in Anforderungen umwandeln. Im Gegensatz zu get-requests
und get-ajax
sind sie reine Funktionen und werden nichts spülen.
( dev/logs->requests @logs)
Achten Sie bei der Arbeit mit Protokollen und Anfragen auf ihre Anzahl und Größe. Die Karten haben viele Schlüssel und die Anzahl der Artikel in Sammlungen könnte riesig sein. Wenn Sie eine ganze Reihe von Veranstaltungen drucken, können Sie Ihren Redakteur einfrieren. Erwägen Sie clojure.pprint/pprint
-Funktion zu verwenden, da sie auf maximaler Ebene und Längengrenzen beruht.
Manchmal ist es möglicherweise schwierig zu entdecken, was während der letzten Sitzung der UI -Tests schief gelaufen ist. Ein spezielles Makro with-postmortem
spart einige nützliche Daten auf der Festplatte, bevor die Ausnahme ausgelöst wurde. Diese Daten sind ein Screenshot-, HTML -Code und JS -Konsolenprotokolle. HINWEIS: Nicht alle Browser unterstützen es, JS -Protokolle zu erhalten.
Beispiel:
( def driver ( chrome ))
( with-postmortem driver { :dir " /Users/ivan/artifacts " }
( click driver :non-existing-element ))
Eine Ausnahme wird steigen, aber in /Users/ivan/artifacts
gibt es drei Dateien, die von einer Vorlage <browser>-<host>-<port>-<datetime>.<ext>
firefox-127.0.0.1-4444-2017-03-26-02-45-07.png
: Ein tatsächlicher Screenshot der Browser-Seite;firefox-127.0.0.1-4444-2017-03-26-02-45-07.html
: Der HTML-Inhalt des aktuellen Browsers;firefox-127.0.0.1-4444-2017-03-26-02-45-07.json
: Eine JSON-Datei mit Konsolenprotokollen; Das sind ein Vektor von Objekten.Der Handler nimmt eine Karte mit Optionen mit den folgenden Schlüssel. Alle von ihnen könnten abwesend sein.
{ ; ; default directory where to store artifacts
; ; might not exist, will be created otherwise. pwd is used when not passed
:dir " /home/ivan/UI-tests "
; ; a directory where to store screenshots; :dir is used when not passed
:dir-img " /home/ivan/UI-tests/screenshots "
; ; the same but for HTML sources
:dir-src " /home/ivan/UI-tests/HTML "
; ; the same but for console logs
:dir-log " /home/ivan/UI-tests/console "
; ; a string template to format a date; See SimpleDateFormat Java class
:date-format " yyyy-MM-dd-HH-mm-ss " }
Funktion (get-logs driver)
Gibt die Protokolle des Browsers als Kartenvektor zurück. Jede Karte hat die folgende Struktur:
{ :level :warning ,
:message " 1,2,3,4 anonymous (:1) " ,
:timestamp 1511449388366 ,
:source nil,
:datetime #inst " 2017-11-23T15:03:08.366-00:00 " }
Derzeit sind Protokolle nur in Chrome und Phantom.js erhältlich. Bitte beachten Sie, dass der Nachrichtentext und der Quellentyp stark vom Browser abhängen. Chrome wischt die Protokolle ab, sobald sie gelesen wurden. Phantom.js behält sie jedoch nur, bis Sie die Seite ändern.
Beim Ausführen einer Treiberinstanz kann eine Karte zusätzlicher Parameter übergeben werden, um das Verhalten des Browsers zu optimieren:
( def driver ( chrome { :path " /path/to/driver/binary " }))
Im Folgenden finden Sie hier eine Karte der Parameter, die die Bibliotheksunterstützung unterstützt. Alle von ihnen können übersprungen werden oder Nullwerte haben. Einige von ihnen, wenn sie nicht bestanden werden, werden aus der defaults
entnommen.
{ ; ; Host and port for webdriver's process. Both are taken from defaults
; ; when are not passed. If you pass a port that has been already taken,
; ; the library will try to take a random one instead.
:host " 127.0.0.1 "
:port 9999
; ; Path to webdriver's binary file. Taken from defaults when not passed.
:path-driver " /Users/ivan/Downloads/geckodriver "
; ; Path to the driver's binary file. When not passed, the driver discovers it
; ; by its own.
:path-browser " /Users/ivan/Downloads/firefox/firefox "
; ; Extra command line arguments sent to the browser's process. See your browser's
; ; supported flags.
:args [ " --incognito " " --app " " http://example.com " ]
; ; Extra command line arguments sent to the webdriver's process.
:args-driver [ " -b " " /path/to/firefox/binary " ]
; ; Sets browser's minimal logging level. Only messages with level above
; ; that one will be collected. Useful for fetching Javascript logs. Possible
; ; values are: nil (aliases :off, :none), :debug, :info, :warn (alias :warning),
; ; :err (aliases :error, :severe, :crit, :critical), :all. When not passed,
; ; :all is set.
:log-level :err ; ; to show only errors but not debug
; ; Sets driver's log level.
; ; The value is a string. Possible values are:
; ; chrome: [ALL, DEBUG, INFO, WARNING, SEVERE, OFF]
; ; phantomjs: [ERROR, WARN, INFO, DEBUG] (default INFO)
; ; firefox [fatal, error, warn, info, config, debug, trace]
:driver-log-level
; ; Paths to the driver's log files as strings.
; ; When not set, the output goes to /dev/null (or NUL on Windows)
:log-stdout
:log-stderr
; ; Path to a custorm browser profile. See the section below.
:profile " /Users/ivan/Library/Application Support/Firefox/Profiles/iy4iitbg.Test "
; ; Env variables sent to the driver's process.
:env { :MOZ_CRASHREPORTER_URL " http://test.com " }
; ; Initial window size.
:size [ 1024 680 ]
; ; Default URL to open. Works only in FF for now.
:url " http://example.com "
; ; Override the default User-Agent. Useful for headless mode.
:user-agent " Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1) "
; ; Where to download files.
:download-dir " /Users/ivan/Desktop "
; ; Driver-specific options. Make sure you have read the docs before setting them.
:capabilities { :chromeOptions { :args [ " --headless " ]}}}
Wenn Sie zu einer bestimmten Seite navigieren, wartet der Fahrer, bis die gesamte Seite vollständig geladen wurde. Was in den meisten Fällen in Ordnung ist, spiegelt jedoch nicht die Art und Weise wider, wie Menschen mit dem Internet interagieren.
Ändern Sie dieses Standardverhalten mit der Option :load-strategy
. Dafür gibt es drei mögliche Werte ::: :none
,: :eager
und :normal
was die Standardeinstellung ist, wenn sie nicht bestanden wird.
Wenn Sie bestehen :none
, antwortet der Fahrer sofort, damit Sie gerne die nächsten Anweisungen ausführen. Zum Beispiel:
( def c ( chrome ))
( go c " http://some.slow.site.com " )
; ; you'll hang on this line until the page loads
( do-something )
Wenn Sie nun die Option Laststrategie übergeben:
( def c ( chrome { :load-strategy :none }))
( go c " http://some.slow.site.com " )
; ; no pause, acts immediately
( do-something )
Für die :eager
Option funktioniert es nur mit Firefox zum Hinzufügen der Funktion zur Bibliothek.
Es gibt eine Option, um gleichzeitig eine Reihe von Schlüssel einzugeben. Dies ist nützlich, um das Halten eines Systemschlüssels wie Steuerung, Verschiebung oder was auch immer beim Tippen imitieren.
Der Namespace etaoin.keys
trägt eine Reihe von Schlüsselkonstanten sowie eine Reihe von Funktionen, die sich auf die Eingabe beziehen.
( require '[etaoin.keys :as keys])
Ein kurzes Beispiel für die Eingabe gewöhnlicher Charaktere, die Verschiebung halten:
( def c ( chrome ))
( go c " http://google.com " )
( fill-active c ( keys/with-shift " caps is great " ))
Die Haupteingabe wird mit "Caps is großartig" besiedelt. Jetzt möchten Sie das letzte Wort löschen. In Chrom wird dies durchgeführt, indem Sie den Back -Taste -Haltestand gedrückt haben. Lass uns das machen:
( fill-active c ( keys/with-alt keys/backspace))
Jetzt haben Sie nur "Caps is" in der Eingabe.
Betrachten Sie ein komplexeres Beispiel, das das Verhalten der realen Benutzer wiederholt. Sie möchten alles aus der Eingabe löschen. Zuerst bewegen Sie die Pflege am Anfang. Bewegen Sie es dann auf die Verschiebung der Verschiebung, damit alles ausgewählt wird. Schließlich drücken Sie Delete, um den ausgewählten Text zu löschen.
Die Kombination ist:
( fill-active c keys/home ( keys/with-shift keys/end) keys/delete)
Es gibt auch with-ctrl
und with-command
Funktionen, die gleich wirken.
Achten Sie darauf, dass diese Funktionen nicht für die Abkürzungen des globalen Browsers gelten. Zum Beispiel weder "Befehl + R" noch "Befehl + T" laden Sie die Seite neu oder öffnen Sie eine neue Registerkarte.
Alle keys/with-*
-Funktionen sind nur Wrapper für die keys/chord
Akkordfunktion, die möglicherweise für komplexe Fälle verwendet werden.
Um Ihr eigenes Verzeichnis anzugeben, wo Dateien heruntergeladen werden soll, übergeben Sie :download-dir
Parameter in eine Optionskarte beim Ausführen eines Treibers:
( def driver ( chrome { :download-dir " /Users/ivan/Desktop " }))
Wenn Sie nun auf einen Link klicken, sollte eine Datei in diesen Ordner eingebaut werden. Derzeit werden nur Chrome und Firefox unterstützt.
Firefox muss MIME-Typen der Dateien angeben, die ohne angezeigte Systemdialog heruntergeladen werden sollten. Wenn der Parameter :download-dir
übergeben wird, fügt die Bibliothek standardmäßig die häufigsten Mime-Typen hinzu: Archive, Mediendateien, Office-Dokumente usw. Wenn Sie Ihre eigene hinzufügen müssen, überschreiben Sie diese Präferenz manuell:
( def driver ( firefox { :download-dir " /Users/ivan/Desktop "
:prefs { :browser.helperApps.neverAsk.saveToDisk
" some-mime/type-1;other-mime/type-2 " }}))
Um zu überprüfen, ob eine Datei während UI -Tests heruntergeladen wurde, finden Sie im folgenden Testabschnitt.
Legen Sie einen benutzerdefinierten Benutzer-Agent-Header mit der Option :user-agent
beim Erstellen eines Treibers fest, zum Beispiel:
( def f ( firefox { :user-agent " Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1) " }))
Verwenden Sie die Funktion, um den aktuellen Wert des Headers in der Laufzeit zu erhalten:
( get-user-agent f)
; ; Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)
Die Einstellung dieser Header ist für kopflose Browser sehr wichtig, da die meisten Websites prüfen, ob der Benutzer-Agent die Zeichenfolge "kopflos" enthält. Dies könnte zu einer Reaktion von 403 oder einem seltsamen Verhalten der Website führen.
Beim Ausführen von Chrome oder Firefox können Sie ein spezielles Profil angeben, das für Testzwecke erstellt wurde. Ein Profil ist ein Ordner, in dem Browsereinstellungen, Verlauf, Lesezeichen und andere benutzerspezifische Daten aufbewahrt werden.
Stellen Sie sich vor, Sie möchten Ihre Integrationstests gegen einen Benutzer ausführen, der die Ausführung oder das Bildwiederieren von JavaScript ausgeschaltet hat. Ein spezielles Profil für diese Aufgabe vorzubereiten, wäre eine gute Wahl.
chrome://version/
Seite. Kopieren Sie den Dateipfad, der sich unter der Profile Path
befindet.-P
, -p
oder -ProfileManager
-Schlüssel aus, wie die offizielle Seite beschreibt.about:support
-Seite. In der Nähe der Profile Folder
drücken Sie die Show in Finder
. Ein neues Ordnerfenster sollte angezeigt werden. Kopieren Sie seinen Weg von dort. Sobald Sie einen Profilpfad haben, starten Sie einen Treiber mit einem Special :profile
wie folgt:
; ; Chrome
( def chrome-profile
" /Users/ivan/Library/Application Support/Google/Chrome/Profile 2/Default " )
( def chrm ( chrome { :profile chrome-profile}))
; ; Firefox
( def ff-profile
" /Users/ivan/Library/Application Support/Firefox/Profiles/iy4iitbg.Test " )
( def ff ( firefox { :profile ff-profile}))
Die Bibliothek versendet eine Reihe von Funktionen, um die Seite zu scrollen.
Das wichtigste, scroll-query
springt das erste Element, das mit dem Abfragebegriff gefunden wurde:
( def driver ( chrome ))
; ; the form button placed somewhere below
( scroll-query driver :button-submit )
; ; the main article
( scroll-query driver { :tag :h1 })
Um in die absolute Position zu springen, verwenden Sie einfach scroll
wie folgt:
( scroll driver 100 600 )
; ; or pass a map with x and y keys
( scroll driver { :x 100 :y 600 })
Um relativ zu scrollen, verwenden Sie scroll-by
mit Offset-Werten:
; ; keeps the same horizontal position, goes up for 100 pixels
( scroll-by driver 0 -100 ) ; ; map parameter is also supported
Es gibt zwei Abkürzungen, die oben oder unten auf der Seite springen:
( scroll-bottom driver) ; ; you'll see the footer...
( scroll-top driver) ; ; ...and the header again
Die folgenden Funktionen scrollen Sie die Seite in alle Anweisungen:
( scroll-down driver 200 ) ; ; scrolls down by 200 pixels
( scroll-down driver) ; ; scrolls down by the default (100) number of pixels
( scroll-up driver 200 ) ; ; the same, but scrolls up...
( scroll-up driver)
( scroll-left driver 200 ) ; ; ...left
( scroll-left driver)
( scroll-right driver 200 ) ; ; ... and right
( scroll-right driver)
Ein Hinweis, in allen Fällen werden die Bildlaufaktionen mit JavaScript bedient. Stellen Sie sicher, dass Ihr Browser es aktiviert hat.
Während der Arbeit mit der Seite können Sie nicht mit den Elementen interagieren, die in einen Rahmen oder einen Iframe eingebaut werden. Die folgenden Funktionen wechseln den aktuellen Kontext auf einem bestimmten Frame:
( switch-frame driver :frameId ) ; ; now you are inside an iframe with id="frameId"
( click driver :someButton ) ; ; click on a button inside that iframe
( switch-frame-top driver) ; ; switches on the top of the page again
Frames konnten einen in einen anderen verschachtelt werden. Die Funktionen berücksichtigen das. Sagen Sie, Sie haben ein HTML -Layout wie folgt:
< iframe src =" ... " >
< iframe src =" ... " >
< button id =" the-goal " >
</ frame >
</ frame >
So können Sie die Schaltfläche mit dem folgenden Code erreichen:
( switch-frame-first driver) ; ; switches to the first top-level iframe
( switch-frame-first driver) ; ; the same for an iframe inside the previous one
( click driver :the-goal )
( switch-frame-parent driver) ; ; you are in the first iframe now
( switch-frame-parent driver) ; ; you are at the top
Um die Anzahl der Codezeilen zu reduzieren, gibt es ein Special with-frame
-Makro. Es schaltet vorübergehend Frames, während der Körper seinen letzten Ausdruck zurückgibt und danach zum vorherigen Frame umschaltet.
( with-frame driver { :id :first-frame }
( with-frame driver { :id :nested-frame }
( click driver { :id :nested-button })
42 ))
Der obige Code gibt 42
zurück und bleibt im selben Frame, der vor der Bewertung der Makros bestand.
Um einen JavaScript -Code in einem Browser zu bewerten, rennen Sie:
( js-execute driver " alert(1) " )
Sie können alle zusätzlichen Parameter in den Anruf übergeben und in einem Skript mit dem arguments
-Array-ähnlichen Objekt in ein Skript kathieren:
( js-execute driver " alert(arguments[2].foo) " 1 false { :foo " hello! " })
Als Ergebnis, hello!
String wird im Dialog angezeigt.
Um Daten in Clojure zurückzugeben, fügen Sie einfach in Ihr Skript return
.
( js-execute driver " return {foo: arguments[2].foo, bar: [1, 2, 3]} "
1 false { :foo " hello! " })
; ; {:bar [1 2 3], :foo "hello!"}
Wenn Ihr Skript AJAX -Anfragen ausführt oder auf setTimeout
oder anderen asynchronisierten Dingen arbeitet, können Sie das Ergebnis nicht einfach return
. Stattdessen sollte ein spezieller Rückruf gegen die Daten gerufen werden, die Sie erreichen möchten. Der Webdriver gibt diesen Rückruf als letztes Argument für Ihr Skript über und kann mit dem arguments
Array-ähnlichen Objekt erreicht werden.
Beispiel:
( js-async
driver
" var args = arguments; // preserve the global args
var callback = args[args.length-1];
setTimeout(function() {
callback(args[0].foo.bar.baz);
},
1000); "
{ :foo { :bar { :baz 42 }}})
Gibt 42
in den Clojure -Code zurück.
Um ein asynchrones Skript zu bewerten, müssen Sie entweder eine spezielle Auszeit dafür einrichten:
( set-script-timeout driver 5 ) ; ; in seconds
oder wickeln Sie den Code in einen Makros ein, der es vorübergehend macht:
( with-script-timeout driver 30
( js-async driver " some long script " ))
Der Hauptunterschied zwischen einem Programm und einem Menschen ist, dass der erste sehr schnell funktioniert. Es bedeutet so schnell, dass ein Browser manchmal nicht rechtzeitig neu rendern kann. Nach jeder Aktion sollten Sie also besser wait-<something>
Funktion, die nur einen Browser befragt, bis das Prädikat in true bewertet wird. Oder einfach (wait <seconds>)
, wenn Sie sich nicht für die Optimierung interessieren.
Das with-wait
-Makro ist möglicherweise hilfreich, wenn Sie jede Aktion mit (wait n)
vorbereiten müssen. Zum Beispiel das folgende Formular
( with-chrome {} driver
( with-wait 3
( go driver " http://site.com " )
( click driver { :id " search_button " })))
verwandelt sich in so etwas:
( with-chrome {} driver
( wait 3 )
( go driver " http://site.com " )
( wait 3 )
( click driver { :id " search_button " }))
und gibt so das Ergebnis der letzten Form des ursprünglichen Körpers zurück.
Es gibt ein weiteres Makro (doto-wait n driver & body)
, das wie der Standard- doto
wirkt, aber jede Form mit (wait n)
vorbereitet. Zum Beispiel:
( with-chrome {} driver
( doto-wait 1 driver
( go " http://site.com " )
( click :this-link )
( click :that-button )
...etc))
Die endgültige Form wäre so etwas:
( with-chrome {} driver
( doto driver
( wait 1 )
( go " http://site.com " )
( wait 1 )
( click :this-link )
( wait 1 )
( click :that-button )
...etc))
Zusätzlich zu with-wait
und do-wait
gibt es eine Reihe von Wartefunktionen: wait-visible
, wait-has-alert
, wait-predicate
usw. (siehe die vollständige Liste im Abschnitt "Corresponsing). Sie akzeptieren Standard-Timeout-/Intervallwerte, die mit dem with-wait-timeout
bzw. with-wait-interval
Makros neu definiert werden können.
Beispiel aus dem Etaoin -Test:
( deftest test-wait-has-text
( testing " wait for text simple "
( with-wait-timeout 15 ; ; time in seconds
( doto *driver*
( refresh )
( wait-visible { :id :document-end })
( click { :id :wait-button })
( wait-has-text :wait-span " -secret- " ))
( is true " text found " ))))
Warten Sie Text:
wait-has-text
wartet, bis ein Element überall ein Text hat (einschließlich innerer HTML).
( wait-has-text driver :wait-span " -secret- " )
wait-has-text-everywhere
wie wait-has-text
sucht aber auf der gesamten Seite nach Text
( wait-has-text-everywhere driver " -secret- " )
Damit Ihr Test nicht voneinander abhängt, müssen Sie sie in ein Gerät einwickeln, das eine neue Instanz eines Treibers erzeugt und am Ende ordnungsgemäß abgeschaltet wird, wenn jeder Test.
Eine gute Lösung könnte darin bestehen, eine globale Variable (standardmäßig ungebunden) zu haben, die während der Tests auf den Zielfahrer hinweist.
( ns project.test.integration
" A module for integration tests "
( :require [clojure.test :refer :all ]
[etaoin.api :refer :all ]))
( def ^:dynamic *driver*)
( defn fixture-driver
" Executes a test running a driver. Bounds a driver
with the global *driver* variable. "
[f]
( with-chrome {} driver
( binding [*driver* driver]
( f ))))
( use-fixtures
:each ; ; start and stop driver for each test
fixture-driver)
; ; now declare your tests
( deftest ^:integration
test-some-case
( doto *driver*
( go url-project)
( click :some-button )
( refresh )
...
))
Wenn Sie aus irgendeinem Grund eine einzelne Instanz verwenden möchten, können Sie solche Feortures verwenden:
( ns project.test.integration
" A module for integration tests "
( :require [clojure.test :refer :all ]
[etaoin.api :refer :all ]))
( def ^:dynamic *driver*)
( defn fixture-browser [f]
( with-chrome-headless { :args [ " --no-sandbox " ]} driver
( disconnect-driver driver)
( binding [*driver* driver]
( f ))
( connect-driver driver)))
; ; creating a session every time that automatically erases resources
( defn fixture-clear-browser [f]
( connect-driver *driver*)
( go *driver* " http://google.com " )
( f )
( disconnect-driver *driver*))
; ; this is run `once` before running the tests
( use-fixtures
:once
fixture-browser)
; ; this is run `every` time before each test
( use-fixtures
:each
fixture-clear-browser)
...some tests
Für schnellere Tests können Sie dieses Beispiel verwenden:
.....
( defn fixture-browser [f]
( with-chrome-headless { :args [ " --no-sandbox " ]} driver
( binding [*driver* driver]
( f ))))
; ; note that resources, such as cookies, are deleted manually,
; ; so this does not guarantee that the tests are clean
( defn fixture-clear-browser [f]
( delete-cookies *driver*)
( go *driver* " http://google.com " )
( f ))
......
Im obigen Beispiel haben wir einen Fall untersucht, wenn Sie Tests gegen einen einzelnen Treibertyp ausführen. Möglicherweise möchten Sie Ihre Website jedoch auf mehreren Treibern testen, z. B. Chrome und Firefox. In diesem Fall kann Ihre Leuchte etwas komplexer werden:
( def driver-type [ :firefox :chrome ])
( defn fixture-drivers [f]
( doseq [type driver-types]
( with-driver type {} driver
( binding [*driver* driver]
( testing ( format " Testing in %s browser " ( name type))
( f ))))))
Jetzt wird jeder Test zweimal in Firefox- und Chrom -Browsern durchgeführt. Bitte beachten Sie, dass der Testanruf mit testing
Testmakro erstellt wird, der den Treibernamen in den Bericht einbringt. Sobald Sie einen Fehler gemacht haben, werden Sie leicht herausfinden, welcher Treiber die Tests genau fehlgeschlagen hat.
Um einige Artefakte im Falle einer Ausnahme zu retten, wickeln Sie den Körper Ihres Tests wie folgt with-postmortem
ein:
( deftest test-user-login
( with-postmortem *driver* { :dir " /path/to/folder " }
( doto *driver*
( go " http://127.0.0.1:8080 " )
( click-visible :login )
; ; any other actions...
)))
Wenn in diesem Test eine Ausnahme auftritt, werden Artefakte gespeichert.
Um die Optionskarte nicht zu kopieren und einfügen, deklarieren Sie sie oben im Modul. Wenn Sie Circle CI verwenden, ist es großartig, die Daten in einem speziellen Artefaktverzeichnis zu speichern, das möglicherweise als Zip -Datei heruntergeladen wird, sobald der Build fertig ist:
( def pm-dir
( or ( System/getenv " CIRCLE_ARTIFACTS " ) ; ; you are on CI
" /some/local/path " )) ; ; local machine
( def pm-opt
{ :dir pm-dir})
Geben Sie nun diese Karte überall in PM -Handler weiter:
; ; test declaration
( with-postmortem *driver* pm-opt
; ; test body goes here
)
Sobald ein Fehler aufgetreten ist, finden Sie ein PNG -Bild, das Ihre Browser -Seite im Moment der Ausnahme und HTML -Dump darstellt.
Da UI -Tests viel Zeit in Anspruch nehmen können, ist es definitiv eine gute Praxis, sowohl Server- als auch UI -Tests unabhängig voneinander zu bestehen.
Fügen Sie zunächst ^:integration
-Tag zu allen Tests hinzu, die den Browser ausführen, wie folgt:
( deftest ^:integration
test-password-reset-pipeline
( doto *driver*
( go url-password-reset)
( click :reset-btn )
...
Öffnen Sie dann Ihre Datei project.clj
und fügen Sie Testauswahlmöglichkeiten hinzu:
:test-selectors { :default ( complement :integration )
:integration :integration }
Wenn Sie lein test
nun starten, führen Sie alle Tests mit Ausnahme der Browsers durch. Um Integrationstests auszuführen, starten Sie lein test :integration
.
Der Hauptunterschied zwischen einem Programm und einem Menschen besteht darin, dass der erste sehr schnell funktioniert. Es bedeutet so schnell, dass ein Browser manchmal nicht rechtzeitig neu rendern kann. Nach jeder Aktion müssen Sie wait-<something>
einsetzen, die nur einen Browser über ein Prädikat überprüft. O Nur (wait <seconds>)
, wenn Sie sich nicht für die Optimierung interessieren.
Manchmal beginnt eine Datei automatisch herunterzuladen, sobald Sie auf einen Link geklickt oder einfach eine Seite besucht haben. In Tests müssen Sie sicherstellen, dass eine Datei wirklich erfolgreich heruntergeladen wurde. Ein gemeinsames Szenario wäre:
Beispiel:
; ; Local helper that checks whether it is really an Excel file.
( defn xlsx? [file]
( -> file
.getAbsolutePath
( str/ends-with? " .xlsx " )))
; ; Top-level declarations
( def DL-DIR " /Users/ivan/Desktop " )
( def driver ( chrome { :download-dir DL-DIR}))
; ; Later, in tests...
( click-visible driver :download-that-application )
( wait driver 7 ) ; ; wait for a file has been downloaded
; ; Now, scan the directory and try to find a file:
( let [files ( file-seq ( io/file DL-DIR))
found ( some xlsx? files)]
( is found ( format " No *.xlsx file found in %s directory. " DL-DIR)))
Etaoin kann die von Selenium IDE erzeugten Dateien abspielen. Es ist ein offizielles Dienstprogramm, Szenarien interaktiv zu erstellen. Die IDE ist eine Erweiterung Ihres Browsers. Nach der Installation werden Sie Aktionen in einer JSON -Datei mit der .side
. Sie können diese Datei speichern und mit etaoin ausführen.
Stellen Sie uns vor, Sie haben die IDE installiert und einige Aktionen aufgezeichnet, wie die offizielle Dokumentation vorschreibt. Wenn Sie nun eine test.side
-Datei haben, tun Sie Folgendes:
( require '[etaoin.ide.flow :as flow])
( def driver ( chrome ))
( def ide-file ( io/resource " ide/test.side " ))
( def opt
{ ; ; The base URL redefines the one from the file.
; ; For example, the file was written on the local machine
; ; (http://localhost:8080), and we want to perform the scenario
; ; on staging (https://preprod-001.company.com)
:base-url " https://preprod-001.company.com "
; ; keywords :test-.. and :suite-.. (id, ids, name, names)
; ; are used to select specific tests. When not passed,
; ; all tests get run. For example:
:test-id " xxxx-xxxx... " ; ; a single test by its UUID
:test-name " some-test " ; ; a single test by its name
:test-ids [ " xxxx-xxxx... " , ...] ; ; multiple tests by their ids
:test-names [ " some-test1 " , ...] ; ; multiple tests by their names
; ; the same for suites:
:suite-id ...
:suite-name ...
:suite-ids [...]
:suite-names [...]})
( flow/run-ide-script driver ide-file opt)
Alles, was mit der IDE zu tun hat, wird im etaoin.ide
-Paket gespeichert.
Sie können auch ein Skript aus der Befehlszeile ausführen. Hier ist das Beispiel lein run
:
lein run -m etaoin.ide.main -d firefox -p ' {:port 8888 :args ["--no-sandbox"]} ' -r ide/test.side
Sowie von einem Uberjar. In diesem Fall muss Etaoin in den primären Abhängigkeiten stehen, nicht in den :dev
oder :test
.
java -cp .../poject.jar -m etaoin.ide.main -d firefox -p ' {:port 8888} ' -f ide/test.side
Wir unterstützen die folgenden Argumente (überprüfen Sie sie mit dem Befehl lein run -m etaoin.ide.main -h
):
-d, --driver-name name :chrome The name of driver. The default is `:chrome`
-p, --params params {} Parameters for the driver represented as an
EDN string, e.g '{:port 8080}'
-f, --file path Path to an IDE file on disk
-r, --resource path Path to an IDE resource
--test-ids ids Comma-separeted test ID(s)
--suite-ids ids Comma-separeted suite ID(s)
--test-names names Comma-separeted test name(s)
--suite-names names Comma-separeted suite name(s)
--base-url url Base URL for tests
-h, --help
Achten Sie auf die --params
. Dies muss eine EDN -Zeichenfolge sein, die eine Clojure -Karte darstellt. Das ist die gleiche Karte, die Sie beim Initiieren in einen Fahrer übergeben.
Bitte beachten Sie, dass die IDE -Unterstützung immer noch experimentell ist. Wenn Sie auf unerwartetes Verhalten stoßen, können Sie ein Problem öffnen. Im Moment unterstützen wir nur Chrome und Firefox für IDE -Dateien.
Beispiel:
etaoin.api> ( def driver ( chrome ))
#'etaoin.api/driver
etaoin.api> ( maximize driver)
ExceptionInfo throw+: { :response {
:sessionId " 2672b934de785aabb730fd19330cf40c " ,
:status 13 ,
:value { :message " unknown error: cannot get automation extension n from unknown error: page could not be found: chrome-extension://aapnijgdinlhnhlmodcfapnahmbfebeb/_generated_background_page.html n
(Session info: chrome=57.0.2987.133) n (Driver info: chromedriver=2.27.440174
(e97a722caafc2d3a8b807ee115bfb307f7d2cfd9),platform=Mac OS X 10.11.6 x86_64) " }},
...
Lösung: Aktualisieren Sie einfach Ihren chromedriver
auf die letzte Version. Mit 2.29 getestet, funktioniert einwandfrei. Die Leute sagen es auch seit 2.28 Woks.
Denken Sie daran, brew
Package Manager hat die veraltete Version 2.27. Sie müssen wahrscheinlich Binärdateien von der offiziellen Website herunterladen.
Siehe das zugehörige Problem im Selenium -Projekt.
Wenn Sie eine vektorähnliche Abfrage übergeben, sagen Sie [{:tag :p} "//*[text()='foo')]]"}]
achten Sie vorsichtig mit handgeschriebenen XPath-Ausdrücken. In Vector sucht jeder Ausdruck von der vorherigen in einer Schleife. Hier gibt es einen versteckten Fehler: Ohne einen führenden Punkt bedeutet die Klausel "//..."
ein Element aus der Wurzel der gesamten Seite zu finden. Mit einem Punkt bedeutet es, aus dem aktuellen Knoten zu finden, der aus der vorherigen Abfrage und so weiter stammt.
Deshalb ist es einfach, etwas völlig anderes auszuwählen, das Sie möchten. Ein richtiger Ausdruck wäre: [{:tag :p} ".//*[text()='foo')]]"}]
.
Beispiel:
etaoin.api> ( click driver :some-id )
ExceptionInfo throw+: { :response {
:sessionId " d112ce8ddb49accdae78a769d5809eae " ,
:status 11 ,
:value { :message " element not visible n (Session info: chrome=57.0.2987.133) n
(Driver info: chromedriver=2.29.461585
(0be2cd95f834e9ee7c46bcc7cf405b483f5ae83b),platform=Mac OS X 10.11.6 x86_64) " }},
...
Lösung: Sie versuchen, auf ein Element zu klicken, das nicht sichtbar ist, oder seine Diments sind so gering, wie es für einen Menschen unmöglich ist, darauf zu klicken. Sie sollten einen anderen Selektor übergeben.
Problem: Wenn Sie sich auf ein anderes Fenster konzentrieren, schlägt die Webdriver -Sitzung, die unter Google Chrome ausgeführt wird, fehl.
Lösung: Google Chrome kann eine Registerkarte suspendieren, wenn sie seit einiger Zeit inaktiv ist. Wenn die Seite suspendiert wird, kann kein Betrieb durchgeführt werden. Keine Klicks, JS -Ausführung usw. Versuchen Sie also, das Chrome -Fenster während der Testsitzung aktiv zu halten.
Problem: Wenn Sie versuchen, den Treiber zu starten, erhalten Sie einen Fehler:
user=> ( use 'etaoin.api)
user=> ( def driver ( firefox { :headless true }))
Syntaxfehler (ExceptionInfo) Kompilierung bei (Repl: 1: 13). Throw+: {: Antwort {: Wert {: Fehler "Unbekannter Fehler",: meldung "Ungültiges Argument: Ein beendetes Prozess kann nicht absutieren" ....
Mögliche Ursache: "Firefox als Root in der Sitzung eines regulären Benutzers wird nicht unterstützt."
Lösung: Um zu überprüfen, führen Sie den Treiber mit dem Pfad zu den Protokolldateien und der "Trace" -Protokollebene aus und erkunden Sie deren Ausgabe.
( def driver ( firefox { :log-stdout " ffout.log " :log-stderr " fferr.log " :driver-log-level " trace " }))
Ähnliches Problem: Mozilla/Geckodriver#1655
Problem: Wenn Sie versuchen, den Chromedriver zu starten, erhalten Sie einen Fehler:
clojure.lang.ExceptionInfo: throw+: {: response {: sessionID ".....",: Status 13,: Wert {: meldung "Unbekannter Fehler: Chrome startet nicht: Abnormal beendet. n (Unbekannter Fehler: DevtoolsActivePort Datei existiert nicht) ...
Mögliche Ursache:
Eine häufige Ursache für Chrome zum Absturz während des Starts besteht darin, Chrome als Root -Benutzer (Administrator) unter Linux auszuführen. Es ist zwar möglich, dieses Problem durch das Bestehen-kein Sandbox-Flag beim Erstellen Ihrer Webdriver-Sitzung zu bearbeiten, aber eine solche Konfiguration ist nicht unterstützt und stark entmutigt. You need to configure your environment to run Chrome as a regular user instead.
Solution: Run driver with an argument --no-sandbox
. Vorsicht! This is a bypass OS security model.
( def driver ( chrome { :args [ " --no-sandbox " ]}))
A similar problem is described here
The etaoin.api2
namespace brings some bits of alternative macros and functions. They provide better syntax and live in a separate namespace to prevent the old API from breaking.
At the moment, the api2
module provides a set of with-...
macros with a let
-like binding form:
( ns ...
( :require
[etaoin.api :as api]
[etaoin.api2 :as api2]))
( api2/with-chrome [driver {}]
( api/go driver " http://ya.ru " ))
The options map can be skipped so you have only a binding symbol:
( api2/with-firefox [ff]
( api/go ff " http://ya.ru " ))
The project is open for your improvements and ideas. If any of unit tests fall on your machine please submit an issue giving your OS version, browser and console output.
Copyright © 2017—2020 Ivan Grishaev.
Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.