Implementasi murni protokol webdriver. Gunakan perpustakaan itu untuk mengotomatiskan browser, uji perilaku frontend Anda, simulasikan tindakan manusia atau apa pun yang Anda inginkan.
Diberi nama setelah Etaoin Shrdlu - mesin pengetik yang menjadi hidup setelah catatan misteri diproduksi di atasnya.
Sejak 0.4.0
, instance driver adalah peta tetapi bukan atom seperti dulu. Itu adalah solusi yang sulit untuk diputuskan, namun kami menyingkirkan Atom untuk mengikuti Clojure Way dalam kode kami. Secara umum, Anda tidak pernah melakukan pensiunan pengemudi atau menyimpan sesuatu di dalamnya. Semua fungsi internal yang digunakan untuk memodifikasi instance sekarang cukup kembalikan versi baru peta. Jika Anda memiliki swap!
Atau sesuatu yang serupa dalam kode Anda untuk driver, silakan refal kode Anda sebelum Anda memperbarui.
Sejak 0.4.0
, perpustakaan mendukung tindakan webdriver. Tindakan adalah perintah yang dikirim ke pengemudi dalam batch. Lihat bagian terkait terperinci di TOC.
Sejak 0.4.0
, etaoin dapat memutar file skrip yang dibuat dalam IDE selenium interaktif. Lihat bagian terkait di bawah ini.
Ct t
seperti biasa.Anda dipersilakan untuk mengirimkan perusahaan Anda ke dalam daftar itu.
Ada dua langkah untuk diinstal:
etaoin
ke dalam kode clojure Anda Tambahkan yang berikut :dependencies
dalam file project.clj
Anda:
[etaoin "0.4.6"]
Bekerja dengan Clojure 1.9 ke atas.
Halaman ini memberikan instruksi tentang cara menginstal driver yang Anda butuhkan untuk mengotomatisasi browser Anda.
Instal Browser Chrome dan Firefox mengunduhnya dari situs resmi. Tidak akan ada masalah di semua platform.
Instal driver spesifik yang Anda butuhkan:
Driver Google Chrome:
brew cask install chromedriver
untuk pengguna mac2.28
versi yang diinstal. 2.27
dan di bawah ini memiliki bug terkait memaksimalkan jendela (lihat [[pemecahan masalah]]).Geckodriver, pengemudi untuk Firefox:
brew install geckodriver
untuk pengguna macBrowser Phantom.js:
brew install phantomjs
untuk pengguna macSafari Driver (hanya untuk Mac):
Sekarang, periksa instalasi Anda meluncurkan salah satu perintah ini. Untuk setiap perintah, proses tanpa akhir dengan server HTTP lokal harus dimulai.
chromedriver
geckodriver
phantomjs --wd
safaridriver -p 0
Anda dapat menjalankan tes untuk peluncuran perpustakaan ini:
lein test
Anda akan melihat Windows Browser terbuka dan tutup secara seri. Tes menggunakan file HTML lokal dengan tata letak khusus untuk memvalidasi sebagian besar kasus.
Lihat di bawah untuk bagian pemecahan masalah jika Anda memiliki masalah
Kabar baik Anda dapat mengotomatiskan browser Anda langsung dari repl:
( 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)
Anda lihat, fungsi apa pun memerlukan instance driver sebagai argumen pertama. Jadi Anda dapat menyederhanakannya menggunakan doto
Macro:
( 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 ))
Dalam hal ini, kode Anda terlihat seperti DSL yang dirancang hanya untuk tujuan tersebut.
Anda dapat menggunakan fill-multi
untuk mempersingkat kode seperti:
( fill driver :login " login " )
( fill driver :password " pass " )
( fill driver :textarea " some text " )
ke dalam
( fill-multi driver { :login " login "
:password " pass "
:textarea " some text " })
Jika ada pengecualian yang terjadi selama sesi browser, proses eksternal mungkin menggantung selamanya sampai Anda membunuhnya secara manual. Untuk mencegahnya, gunakan with-<browser>
makro sebagai berikut:
( with-firefox {} ff ; ; additional options first, then bind name
( doto ff
( go " https://google.com " )
...))
Apa pun yang terjadi selama sesi, prosesnya akan dihentikan.
Sebagian besar fungsi seperti click
, fill
, dll memerlukan istilah kueri untuk menemukan elemen pada halaman. Misalnya:
( click driver { :tag :button })
( fill driver { :id " searchInput " } " Clojure " )
Perpustakaan mendukung jenis dan nilai kueri berikut.
:active
untuk elemen aktif saat ini. Saat membuka halaman Google misalnya, ini memfokuskan kursor pada input pencarian utama. Jadi tidak perlu mengklik secara manual. Contoh:
( fill driver :active " Let's search for something " keys/enter)
kata kunci lain yang menunjukkan ID elemen. Untuk halaman Google, ini adalah :lst-ib
atau "lst-ib"
(string juga didukung). Registry penting. Contoh:
( fill driver :lst-ib " What is the Matrix? " keys/enter)
string dengan ekspresi xpath. Hati -hati saat menulisnya secara manual. Periksa bagian Troubleshooting
di bawah ini. Contoh:
( fill driver " .//input[@id='lst-ib'][@name='q'] " " XPath in action! " keys/enter)
Peta dengan :xpath
atau :css
Key dengan ekspresi string sintaks yang sesuai. Contoh:
( fill driver { :xpath " .//input[@id='lst-ib'] " } " XPath selector " keys/enter)
( fill driver { :css " input#lst-ib[name='q'] " } " CSS selector " keys/enter)
Lihat Manual Pemilih CSS untuk info lebih lanjut.
Kueri mungkin merupakan peta lain yang mewakili ekspresi XPath sebagai data. Aturannya adalah:
:tag
mewakili nama tag. Itu menjadi *
saat tidak lulus.:index
memperluas ke klausa trailing [x]
. Berguna saat Anda perlu memilih baris ketiga dari tabel misalnya.:fn/
namespace dan memperluas menjadi sesuatu yang spesifik.Contoh:
Temukan tag div
pertama
( query driver { :tag :div })
; ; expands into .//div
Temukan tag div
ke-N
( query driver { :tag :div :index 1 })
; ; expands into .//div[1]
Temukan tag a
dengan atribut kelas sama dengan active
( query driver { :tag :a :class " active " })
; ; ".//a[@class="active"]"
Temukan formulir dengan atributnya:
( query driver { :tag :form :method :GET :class :message })
; ; expands into .//form[@method="GET"][@class="message"]
Temukan tombol dengan teksnya (kecocokan persis):
( query driver { :tag :button :fn/text " Press Me " })
; ; .//button[text()="Press Me"]
Temukan elemen ke -n ( p
, a
, apa pun) dengan teks "unduh":
( query driver { :fn/has-text " download " :index 3 })
; ; .//*[contains(text(), "download")][3]
Temukan elemen yang memiliki kelas berikut:
( query driver { :tag :div :fn/has-class " overlay " })
; ; .//div[contains(@class, "overlay")]
Temukan elemen yang memiliki domain berikut di href:
( query driver { :tag :a :fn/link " google.com " })
; ; .//a[contains(@href, "google.com")]
Temukan elemen yang memiliki kelas berikut sekaligus:
( query driver { :fn/has-classes [ :active :sticky :marked ]})
; ; .//*[contains(@class, "active")][contains(@class, "sticky")][contains(@class, "marked")]
Temukan widget input yang diaktifkan/dinonaktifkan:
; ; 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()]
Kueri mungkin merupakan vektor yang terdiri dari ekspresi apa pun yang disebutkan di atas. Dalam pertanyaan seperti itu, setiap istilah berikutnya pencarian dari yang sebelumnya secara rekursif.
Contoh sederhana:
( click driver [{ :tag :html } { :tag :body } { :tag :a }])
Anda dapat menggabungkan ekspresi XPath dan CSS juga (perhatikan titik terkemuka dalam ekspresi XPath:
( click driver [{ :tag :html } { :css " div.class " } " .//a[@class='download'] " ])
Terkadang Anda mungkin perlu berinteraksi dengan elemen ke -n kueri, misalnya saat ingin mengklik tautan kedua dalam contoh ini:
< 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 >
Dalam hal ini Anda dapat menggunakan arahan :index
yang didukung untuk ekspresi xpath seperti ini:
( click driver [{ :tag :li :class :search-result :index 2 } { :tag :a }])
Atau Anda dapat menggunakan trik-anak-n dengan ekspresi CSS seperti ini:
( click driver { :css " li.search-result:nth-child(2) a " })
Akhirnya juga dimungkinkan untuk mendapatkan elemen ke-n secara langsung dengan menggunakan query-all
:
( click-el driver ( nth ( query-all driver { :css " li.search-result a " }) 2 ))
Perhatikan penggunaan click-el
di sini, karena query-all
mengembalikan elemen, bukan pemilih yang dapat diteruskan untuk click
secara langsung.
query-tree
mengambil penyeleksi dan bertindak seperti pohon. Setiap pemilih berikutnya menanyakan elemen dari yang sebelumnya. Pemilih Fist bergantung pada elemen-temuan, dan yang lainnya menggunakan elemen-find-from
( query-tree driver { :tag :div } { :tag :a })
cara
{:tag :div} -> [div1 div2 div3]
div1 -> [a1 a2 a3]
div2 -> [a4 a5 a6]
div3 -> [a7 a8 a9]
Jadi hasilnya adalah [A1 ... A9]
Untuk berinteraksi dengan elemen yang ditemukan melalui kueri, Anda harus meneruskan hasil kueri ke click-el
atau fill-el
:
( click-el driver ( first ( query-all driver { :tag :a })))
Jadi Anda dapat mengumpulkan elemen ke dalam vektor dan berinteraksi secara sewenang -wenang dengan mereka kapan saja:
( 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! " )
Untuk tujuan meniru input manusia, Anda dapat menggunakan fungsi fill-human
. Opsi berikut diaktifkan secara default:
{ :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
Dan Anda dapat mendefinisikannya kembali:
( 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)
Untuk beberapa input dengan emulasi manusia, gunakan fill-human-multi
( fill-human-multi driver { :login " login "
:pass " password "
:textarea " some text " }
{ :mistake-prob 0.5
:pause-max 1 })
Fungsi click
memicu klik mouse kiri pada elemen yang ditemukan oleh istilah kueri:
( click driver { :tag :button })
Fungsi click
hanya menggunakan elemen pertama yang ditemukan oleh kueri, yang kadang -kadang mengarah pada mengklik item yang salah. Untuk memastikan ada satu dan hanya satu elemen yang ditemukan, gunakan fungsi click-single
. Itu bertindak sama tetapi meningkatkan pengecualian saat menanyakan halaman mengembalikan beberapa elemen:
( click-single driver { :tag :button :name " search " })
Klik ganda jarang digunakan di web namun dimungkinkan dengan fungsi double-click
(chrome, phantom.js):
( double-click driver { :tag :dbl-click-btn })
Ada juga banyak fungsi klik "buta". Mereka memicu klik mouse pada posisi mouse saat ini:
( left-click driver)
( middle-click driver)
( right-click driver)
Sekelompok fungsi lain melakukan hal yang sama tetapi pindahkan pointer mouse ke elemen yang ditentukan sebelum mengkliknya:
( left-click-on driver { :tag :img })
( middle-click-on driver { :tag :img })
( right-click-on driver { :tag :img })
Klik mouse tengah berguna saat membuka tautan di tab latar belakang baru. Klik kanan terkadang digunakan untuk meniru menu konteks dalam aplikasi web.
Perpustakaan mendukung tindakan webdriver. Secara umum, tindakan adalah perintah yang menggambarkan perangkat input virtual.
{ :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 }]}]}
Anda dapat membuat peta secara manual dan mengirimkannya ke metode perform-actions
:
( 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)
atau gunakan pembungkus. Pertama, Anda perlu membuat perangkat input virtual, misalnya:
( def keyboard ( make-key-input ))
dan kemudian isi dengan tindakan yang diperlukan:
( -> keyboard
( add-key-down keys/shift-left)
( add-key-down " a " )
( add-key-up " a " )
( add-key-up keys/shift-left))
Contoh diperpanjang:
( 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))
Untuk menghapus keadaan perangkat input virtual, rilis semua tombol tertekan dll, gunakan metode release-actions
:
( release-actions driver)
Mengklik tombol input file membuka dialog spesifik OS yang tidak diizinkan untuk berinteraksi dengan menggunakan protokol WebDriver. Gunakan fungsi upload-file
untuk melampirkan file lokal ke widget input file. Fungsi mengambil pemilih yang menunjuk ke input file dan jalur lengkap sebagai string atau instance java.io.File
asli. File harus ada atau Anda akan mendapatkan pengecualian sebaliknya. Contoh Penggunaan:
( 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)
Memanggil fungsi screenshot
mencampakkan halaman saat ini ke dalam gambar PNG di disk Anda:
( screenshot driver " page.png " ) ; ; relative path
( screenshot driver " /Users/ivan/page.png " ) ; ; absolute path
Objek file Java asli juga didukung:
; ; when imported as `[clojure.java.io :as io]`
( screenshot driver ( io/file " test.png " ))
; ; native object
( screenshot driver ( java.io.File. " test-native.png " ))
Dengan Firefox dan Chrome, Anda tidak dapat menangkap seluruh halaman tetapi satu elemen, katakanlah div, widget input atau apa pun. Itu tidak bekerja dengan browser lain untuk saat ini. Contoh:
( screenshot-element driver { :tag :div :class :smart-widget } " smart_widget.png " )
Dengan makro with-screenshots
, Anda dapat membuat tangkapan layar setelah setiap formulir
( with-screenshots driver " ../screenshots "
( fill driver :simple-input " 1 " )
( fill driver :simple-input " 2 " )
( fill driver :simple-input " 3 " ))
Apa yang setara dengan catatan:
( 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 " )
Baru -baru ini, Google Chrome dan kemudian Firefox mulai mendukung fitur bernama Headless Mode. Saat menjadi tanpa kepala, tidak ada jendela UI yang terjadi di layar, hanya output stdout yang masuk ke konsol. Fitur ini memungkinkan Anda untuk menjalankan tes integrasi pada server yang tidak memiliki perangkat output grafis.
Pastikan browser Anda mendukung mode tanpa kepala dengan memeriksa jika menerima --headles
argumen baris perintah saat menjalankannya dari terminal. Pengemudi Phantom.js berdasarkan sifatnya (tidak pernah dikembangkan untuk Rendering UI).
Saat memulai driver, lulus :headless
untuk beralih ke mode tanpa kepala. Catatan, hanya versi terbaru dari Chrome dan Firefox yang didukung. Untuk pengemudi lain, bendera akan diabaikan.
( def driver ( chrome { :headless true })) ; ; runs headless Chrome
atau
( def driver ( firefox { :headless true })) ; ; runs headless Firefox
Untuk memeriksa driver mana pun telah dijalankan dalam mode tanpa kepala, gunakan headless?
predikat:
( headless? driver) ; ; true
Catatan, itu akan selalu kembali benar untuk instance phantom.js.
Ada beberapa jalan pintas untuk menjalankan Chrome atau Firefox dalam mode tanpa kepala secara default:
( 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 " ))
Ada juga macroses when-headless
dan when-not-headless
yang memungkinkan untuk melakukan banyak perintah hanya jika browser berada dalam mode tanpa kepala atau tidak: masing-masing:
( with-chrome nil driver
...
( when-not-headless driver
... some actions that might be not available in headless mode)
... common actions for both versions)
Untuk terhubung ke driver yang sudah berjalan pada host lokal atau jarak jauh, Anda harus menentukan :host
yang mungkin merupakan nama host (localhost, some.remote.host.net) atau alamat IP (127.0.0.1, 183.102.156.31 ) dan :port
. Jika port tidak ditentukan, port default diatur.
Contoh:
; ; 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
Untuk bekerja dengan pengemudi di Docker, Anda dapat mengambil gambar siap pakai:
Contoh untuk Chrome:
docker run --name chromedriver -p 9515:4444 -d -e CHROMEDRIVER_WHITELISTED_IPS='' robcherry/docker-chromedriver:latest
untuk firefox:
docker run --name geckodriver -p 4444:4444 -d instrumentisto/geckodriver
Untuk menghubungkan ke driver, Anda hanya perlu menentukan :host
sebagai localhost
atau 127.0.0.1
dan :port
yang dijalankannya. Jika port tidak ditentukan, port default diatur.
( def driver ( chrome-headless { :host " localhost " :port 9515 :args [ " --no-sandbox " ]}))
( def driver ( firefox-headless { :host " localhost " })) ; ; will try to connect to port 4444
Untuk mengatur pengaturan proxy, gunakan variabel lingkungan HTTP_PROXY
/ HTTPS_PROXY
atau lulus peta jenis berikut:
{ :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 " }})
CATATAN: A: PAC-URL untuk file konfigurasi autokonfigurasi proxy. Digunakan dengan Safari sebagai opsi proxy lainnya tidak berfungsi di browser itu.
Untuk menyempurnakan proxy, Anda dapat menggunakan objek asli dan meneruskannya ke kemampuan:
{ :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 {...}}})
Dengan pembaruan terbaru, perpustakaan membawa fitur yang hebat. Sekarang Anda dapat melacak acara yang berasal dari panel Devtools. Artinya, semua yang Anda lihat di konsol pengembang sekarang tersedia melalui API. Itu hanya berfungsi dengan Google Chrome sekarang.
Untuk memulai driver dengan pengaturan pengembangan khusus yang ditentukan, cukup lewati peta kosong ke bidang :dev
saat menjalankan driver:
( def c ( chrome { :dev {}}))
Nilainya tidak boleh nil
. Ketika itu adalah peta kosong, fungsi khusus mengambil default. Berikut adalah versi lengkap pengaturan dev dengan semua nilai yang mungkin ditentukan.
( def c ( chrome { :dev
{ :perf
{ :level :all
:network? true
:page? true
:interval 1000
:categories [ :devtools
:devtools.network
:devtools.timeline ]}}}))
Di bawah kap, ia mengisi kamus perfLoggingPrefs
khusus di dalam objek chromeOptions
.
Sekarang browser Anda mengumpulkan acara ini, Anda dapat membacanya menggunakan namespace dev
khusus.
( go c " http://google.com " )
; ; wait until the page gets loaded
; ; load the namespace
( require '[etaoin.dev :as dev])
Mari kita memiliki daftar semua permintaan HTTP yang terjadi selama halaman sedang dimuat.
( 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 }
Karena kami sebagian besar tertarik pada permintaan AJAX, ada fungsi get-ajax
yang melakukan hal yang sama tetapi menyaring permintaan XHR:
( -> 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 }
Pola khas penggunaan get-ajax
adalah sebagai berikut. Anda ingin memeriksa apakah permintaan tertentu telah ditembakkan ke server. Jadi Anda menekan tombol, tunggu sebentar dan kemudian baca permintaan yang dibuat oleh browser Anda.
Memiliki daftar permintaan, Anda mencari yang Anda butuhkan (misalnya dengan URL -nya) dan kemudian periksa keadaannya. The :state
Field memiliki semantik yang sama seperti XMLHttpRequest.readyState
HAS. Ini adalah bilangan bulat dari 1 hingga 4 dengan perilaku yang sama.
Untuk memeriksa apakah permintaan telah selesai, dilakukan atau gagal, gunakan predikat ini:
( def req ( last reqs))
( dev/request-done? req)
; ; true
( dev/request-failed? req)
; ; false
( dev/request-success? req)
; ; true
Perhatikan bahwa request-done?
Bukan berarti permintaan telah berhasil. Ini hanya berarti pipa telah mencapai langkah terakhir.
PERINGATAN: Saat Anda membaca log dev, Anda mengkonsumsinya dari buffer internal yang memerah. Panggilan kedua untuk get-requests
atau get-ajax
akan mengembalikan daftar kosong.
Mungkin Anda ingin mengumpulkan log ini dengan sandaran Anda. Fungsi dev/get-performance-logs
mengembalikan daftar log sehingga Anda mengumpulkannya dalam atom atau apa pun:
( def logs ( atom []))
; ; repeat that form from time to time
( do ( swap! logs concat ( dev/get-performance-logs c))
true )
( count @logs)
; ; 76
Ada logs->requests
dan logs->ajax
yang mengubah log menjadi permintaan. Tidak seperti get-requests
dan get-ajax
, mereka adalah fungsi murni dan tidak akan menyiram apapun.
( dev/logs->requests @logs)
Saat bekerja dengan log dan permintaan, perhatikan jumlah dan ukurannya. Peta memiliki banyak kunci dan jumlah barang dalam koleksi mungkin sangat besar. Mencetak banyak acara mungkin membekukan editor Anda. Pertimbangkan untuk menggunakan fungsi clojure.pprint/pprint
karena bergantung pada level maks dan batas panjang.
Terkadang, mungkin sulit untuk menemukan apa yang salah selama sesi tes UI terakhir. Makro khusus with-postmortem
menyimpan beberapa data yang berguna pada disk sebelum pengecualian dipicu. Data tersebut adalah tangkapan layar, kode HTML dan log konsol JS. Catatan: Tidak semua browser mendukung mendapatkan log JS.
Contoh:
( def driver ( chrome ))
( with-postmortem driver { :dir " /Users/ivan/artifacts " }
( click driver :non-existing-element ))
Pengecualian akan naik, tetapi di /Users/ivan/artifacts
akan ada tiga file yang dinamai oleh template <browser>-<host>-<port>-<datetime>.<ext>
:
firefox-127.0.0.1-4444-2017-03-26-02-45-07.png
: tangkapan layar aktual dari halaman browser;firefox-127.0.0.1-4444-2017-03-26-02-45-07.html
: Konten HTML browser saat ini;firefox-127.0.0.1-4444-2017-03-26-02-45-07.json
: File JSON dengan log konsol; Itu adalah vektor objek.Handler mengambil peta opsi dengan kunci berikut. Semuanya mungkin tidak ada.
{ ; ; 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 " }
Fungsi (get-logs driver)
Mengembalikan log browser sebagai vektor peta. Setiap peta memiliki struktur berikut:
{ :level :warning ,
:message " 1,2,3,4 anonymous (:1) " ,
:timestamp 1511449388366 ,
:source nil,
:datetime #inst " 2017-11-23T15:03:08.366-00:00 " }
Saat ini, log hanya tersedia di Chrome dan Phantom.js. Harap dicatat, teks pesan dan jenis sumber sangat bergantung pada browser. Chrome menyapu log setelah dibaca. Phantom.js menyimpannya tetapi hanya sampai Anda mengubah halaman.
Saat menjalankan instance driver, peta parameter tambahan mungkin diteruskan untuk mengubah perilaku browser:
( def driver ( chrome { :path " /path/to/driver/binary " }))
Di bawah ini, berikut adalah peta parameter dukungan perpustakaan. Semuanya mungkin dilewati atau memiliki nilai NIL. Beberapa dari mereka, jika tidak lulus, diambil dari peta defaults
.
{ ; ; 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 " ]}}}
Saat Anda menavigasi ke halaman tertentu, pengemudi menunggu sampai seluruh halaman telah sepenuhnya dimuat. Apa yang baik dalam sebagian besar kasus namun tidak mencerminkan cara manusia berinteraksi dengan internet.
Ubah perilaku default ini dengan opsi :load-strategy
. Ada tiga nilai yang mungkin untuk itu :: :none
, :eager
dan :normal
yang merupakan default saat tidak dilewati.
Ketika Anda lulus :none
, pengemudi segera merespons sehingga Anda dipersilakan untuk menjalankan instruksi berikutnya. Misalnya:
( def c ( chrome ))
( go c " http://some.slow.site.com " )
; ; you'll hang on this line until the page loads
( do-something )
Sekarang saat melewati opsi strategi muat:
( def c ( chrome { :load-strategy :none }))
( go c " http://some.slow.site.com " )
; ; no pause, acts immediately
( do-something )
Untuk :eager
, hanya berfungsi dengan Firefox pada saat menambahkan fitur ke perpustakaan.
Ada opsi untuk memasukkan serangkaian kunci secara bersamaan. Itu berguna untuk meniru memegang kunci sistem seperti kontrol, shift atau apa pun saat mengetik.
Namespace etaoin.keys
membawa banyak konstanta kunci serta serangkaian fungsi yang terkait dengan input.
( require '[etaoin.keys :as keys])
Contoh cepat memasuki karakter biasa yang memegang shift:
( def c ( chrome ))
( go c " http://google.com " )
( fill-active c ( keys/with-shift " caps is great " ))
Input utama diisi dengan "Caps is Great". Sekarang Anda ingin menghapus kata terakhir. Di Chrome, ini dilakukan dengan menekan backspace holding alt. Mari kita lakukan itu:
( fill-active c ( keys/with-alt keys/backspace))
Sekarang Anda hanya memiliki "topi adalah" di input.
Pertimbangkan contoh yang lebih kompleks yang mengulangi perilaku pengguna nyata. Anda ingin menghapus semuanya dari input. Pertama, Anda memindahkan caret di awal. Kemudian pindahkan ke ujung memegang shift sehingga semuanya dipilih. Akhirnya, Anda menekan Hapus untuk menghapus teks yang dipilih.
Kombonya adalah:
( fill-active c keys/home ( keys/with-shift keys/end) keys/delete)
Ada juga with-ctrl
dan with-command
yang bertindak sama.
Perhatikan, fungsi -fungsi ini tidak berlaku untuk pintasan browser global. Misalnya, tidak ada "perintah + r" atau "command + t" Muat ulang halaman atau buka tab baru.
Semua keys/with-*
fungsi hanyalah pembungkus pada fungsi keys/chord
yang mungkin digunakan untuk kasus kompleks.
Untuk menentukan direktori Anda sendiri tempat mengunduh file, lulus :download-dir
parameter ke peta opsi saat menjalankan driver:
( def driver ( chrome { :download-dir " /Users/ivan/Desktop " }))
Sekarang, setelah Anda mengklik tautan, file harus dimasukkan ke dalam folder itu. Saat ini, hanya Chrome dan Firefox yang didukung.
Firefox mengharuskan menentukan tipe-mime dari file-file yang harus diunduh tanpa menunjukkan dialog sistem. Secara default, ketika :download-dir
dilewati, perpustakaan menambahkan tipe-mime yang paling umum: arsip, file media, dokumen kantor, dll. Jika Anda perlu menambahkan yang Anda sendiri, angkanya preferensi itu secara manual:
( def driver ( firefox { :download-dir " /Users/ivan/Desktop "
:prefs { :browser.helperApps.neverAsk.saveToDisk
" some-mime/type-1;other-mime/type-2 " }}))
Untuk memeriksa apakah suatu file diunduh selama tes UI, lihat bagian pengujian di bawah ini.
Tetapkan header agen pengguna khusus dengan :user-agent
saat membuat driver, misalnya:
( def f ( firefox { :user-agent " Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1) " }))
Untuk mendapatkan nilai header saat ini dalam runtime, gunakan fungsi:
( get-user-agent f)
; ; Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)
Pengaturan header itu cukup penting untuk browser tanpa kepala karena sebagian besar situs memeriksa apakah agen pengguna menyertakan string "tanpa kepala". Ini dapat menyebabkan 403 respons atau perilaku aneh situs.
Saat menjalankan Chrome atau Firefox, Anda dapat menentukan profil khusus yang dibuat untuk tujuan pengujian. Profil adalah folder yang menyimpan pengaturan browser, riwayat, bookmark, dan data khusus pengguna lainnya.
Bayangkan Anda ingin menjalankan tes integrasi Anda terhadap pengguna yang mematikan eksekusi JavaScript atau rendering gambar. Untuk menyiapkan profil khusus untuk tugas itu akan menjadi pilihan yang baik.
chrome://version/
Halaman. Salin jalur file yang berada di bawah keterangan Profile Path
.-P
, -p
atau -ProfileManager
seperti yang dijelaskan halaman resmi.about:support
. Di dekat keterangan Profile Folder
, tekan tombol Show in Finder
. Jendela folder baru akan muncul. Salin jalurnya dari sana. Setelah Anda memiliki jalur profil, luncurkan driver dengan kunci khusus :profile
sebagai berikut:
; ; 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}))
Perpustakaan mengirimkan satu set fungsi untuk menggulir halaman.
Yang paling penting, scroll-query
melompat elemen pertama yang ditemukan dengan istilah kueri:
( def driver ( chrome ))
; ; the form button placed somewhere below
( scroll-query driver :button-submit )
; ; the main article
( scroll-query driver { :tag :h1 })
Untuk melompat ke posisi absolut, cukup gunakan scroll
sebagai berikut:
( scroll driver 100 600 )
; ; or pass a map with x and y keys
( scroll driver { :x 100 :y 600 })
Untuk menggulir secara relatif, gunakan scroll-by
dengan nilai offset:
; ; keeps the same horizontal position, goes up for 100 pixels
( scroll-by driver 0 -100 ) ; ; map parameter is also supported
Ada dua pintasan untuk melompat ke atas atau bawah halaman:
( scroll-bottom driver) ; ; you'll see the footer...
( scroll-top driver) ; ; ...and the header again
Fungsi berikut menggulir halaman ke segala arah:
( 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)
Satu catatan, dalam semua kasus tindakan gulir dilayani dengan JavaScript. Pastikan browser Anda diaktifkan.
Saat bekerja dengan halaman, Anda tidak dapat berinteraksi dengan item -item yang dimasukkan ke dalam bingkai atau iframe. Fungsi di bawah ini beralih konteks saat ini pada bingkai tertentu:
( 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
Bingkai bisa bersarang menjadi yang lain. Fungsi memperhitungkannya. Katakanlah Anda memiliki tata letak HTML seperti ini:
< iframe src =" ... " >
< iframe src =" ... " >
< button id =" the-goal " >
</ frame >
</ frame >
Jadi Anda dapat mencapai tombol dengan kode berikut:
( 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
Untuk mengurangi jumlah baris kode, ada makro with-frame
khusus. Itu sementara beralih bingkai saat mengeksekusi tubuh mengembalikan ekspresi terakhirnya dan beralih ke bingkai sebelumnya.
( with-frame driver { :id :first-frame }
( with-frame driver { :id :nested-frame }
( click driver { :id :nested-button })
42 ))
Kode di atas mengembalikan 42
tetap pada bingkai yang sama sebelum sebelum mengevaluasi makro.
Untuk mengevaluasi kode JavaScript di browser, jalankan:
( js-execute driver " alert(1) " )
Anda dapat meneruskan parameter tambahan ke dalam panggilan dan catat mereka di dalam skrip dengan objek seperti array arguments
:
( js-execute driver " alert(arguments[2].foo) " 1 false { :foo " hello! " })
Sebagai hasilnya, hello!
String akan muncul di dalam dialog.
Untuk mengembalikan data apa pun ke Clojure, cukup tambahkan return
ke skrip Anda:
( js-execute driver " return {foo: arguments[2].foo, bar: [1, 2, 3]} "
1 false { :foo " hello! " })
; ; {:bar [1 2 3], :foo "hello!"}
Jika skrip Anda melakukan permintaan AJAX atau beroperasi di setTimeout
atau hal -hal async lainnya, Anda tidak bisa hanya return
hasilnya. Sebaliknya, panggilan balik khusus harus dipanggil terhadap data yang ingin Anda capai. Webdriver melewati panggilan balik ini sebagai argumen terakhir untuk skrip Anda dan dapat dihubungi dengan objek seperti array arguments
.
Contoh:
( 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 }}})
Mengembalikan 42
ke kode clojure.
Untuk mengevaluasi skrip asinkron, Anda perlu mengatur batas waktu khusus untuk itu:
( set-script-timeout driver 5 ) ; ; in seconds
Atau bungkus kode menjadi makro yang melakukannya sementara:
( with-script-timeout driver 30
( js-async driver " some long script " ))
Perbedaan utama antara program dan manusia adalah yang pertama beroperasi sangat cepat. Ini berarti sangat cepat, sehingga kadang -kadang browser tidak dapat membuat HTML baru pada waktunya. Jadi setelah setiap tindakan, Anda sebaiknya menempatkan fungsi wait-<something>
yang hanya polling browser sampai predikat mengevaluasi menjadi benar. Atau hanya (wait <seconds>)
jika Anda tidak peduli tentang optimasi.
Makro with-wait
mungkin bermanfaat ketika Anda perlu menyiapkan setiap tindakan dengan (wait n)
. Misalnya, formulir berikut
( with-chrome {} driver
( with-wait 3
( go driver " http://site.com " )
( click driver { :id " search_button " })))
berubah menjadi sesuatu seperti ini:
( with-chrome {} driver
( wait 3 )
( go driver " http://site.com " )
( wait 3 )
( click driver { :id " search_button " }))
dan dengan demikian mengembalikan hasil dari bentuk terakhir dari tubuh asli.
Ada makro lain (doto-wait n driver & body)
yang bertindak seperti doto
Standar tetapi menyiapkan setiap formulir dengan (wait n)
. Misalnya:
( with-chrome {} driver
( doto-wait 1 driver
( go " http://site.com " )
( click :this-link )
( click :that-button )
...etc))
Bentuk terakhir adalah sesuatu seperti ini:
( with-chrome {} driver
( doto driver
( wait 1 )
( go " http://site.com " )
( wait 1 )
( click :this-link )
( wait 1 )
( click :that-button )
...etc))
Selain with-wait
dan do-wait
ada sejumlah fungsi tunggu: wait-visible
, wait-has-alert
, wait-predicate
, dll (lihat daftar lengkap di bagian corresponsing). Mereka menerima nilai batas waktu/interval default yang dapat didefinisikan ulang menggunakan makro with-wait-timeout
dan with-wait-interval
, masing-masing.
Contoh dari tes etaoin:
( 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 " ))))
Tunggu Teks:
wait-has-text
menunggu sampai suatu elemen memiliki teks di mana saja di dalamnya (termasuk HTML dalam).
( wait-has-text driver :wait-span " -secret- " )
wait-has-text-everywhere
seperti wait-has-text
tetapi mencari teks di seluruh halaman
( wait-has-text-everywhere driver " -secret- " )
Untuk membuat tes Anda tidak bergantung satu sama lain, Anda perlu membungkusnya menjadi fixture yang akan membuat instance baru dari pengemudi dan mematikannya dengan benar di akhir jika setiap tes.
Solusi yang baik mungkin memiliki variabel global (tidak terikat secara default) yang akan menunjuk ke driver target selama pengujian.
( 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 )
...
))
Jika karena alasan tertentu Anda ingin menggunakan satu contoh, Anda dapat menggunakan perlengkapan seperti ini:
( 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
Untuk pengujian yang lebih cepat Anda dapat menggunakan contoh ini:
.....
( 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 ))
......
Dalam contoh di atas, kami memeriksa suatu kasus ketika Anda menjalankan tes terhadap satu jenis driver. Namun, Anda mungkin ingin menguji situs Anda di beberapa driver, katakanlah, Chrome dan Firefox. Dalam hal ini, perlengkapan Anda mungkin menjadi sedikit lebih kompleks:
( 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 ))))))
Sekarang, setiap tes akan dijalankan dua kali di browser Firefox dan Chrome. Harap perhatikan panggilan tes di muka dengan testing
makro yang memasukkan nama pengemudi ke dalam laporan. Setelah Anda mengalami kesalahan, Anda akan mudah menemukan pengemudi apa yang gagal dengan tepat.
Untuk menyimpan beberapa artefak jika pengecualian, bungkus tubuh Anda menjadi penangan with-postmortem
sebagai berikut:
( 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...
)))
Sekarang, jika ada pengecualian yang terjadi dalam tes itu, artefak akan disimpan.
Untuk tidak menyalin dan menempelkan peta opsi, nyatakan di bagian atas modul. Jika Anda menggunakan Circle CI, akan sangat bagus untuk menyimpan data ke direktori artefak khusus yang mungkin diunduh sebagai file zip setelah build selesai:
( def pm-dir
( or ( System/getenv " CIRCLE_ARTIFACTS " ) ; ; you are on CI
" /some/local/path " )) ; ; local machine
( def pm-opt
{ :dir pm-dir})
Sekarang lewati peta itu di mana -mana ke pm handler:
; ; test declaration
( with-postmortem *driver* pm-opt
; ; test body goes here
)
Setelah kesalahan terjadi, Anda akan menemukan gambar PNG yang mewakili halaman browser Anda pada saat pengecualian dan dump html.
Karena tes UI mungkin membutuhkan banyak waktu untuk dilewati, itu pasti praktik yang baik untuk lulus tes server dan UI secara independen satu sama lain.
Pertama, tambahkan ^:integration
untuk semua tes yang menjalankan peramban seperti berikut:
( deftest ^:integration
test-password-reset-pipeline
( doto *driver*
( go url-password-reset)
( click :reset-btn )
...
Kemudian, buka file project.clj
Anda dan tambahkan pemilih tes:
:test-selectors { :default ( complement :integration )
:integration :integration }
Sekarang, setelah Anda meluncurkan lein test
Anda akan menjalankan semua tes kecuali browser. Untuk menjalankan tes integrasi, luncurkan lein test :integration
.
Perbedaan utama antara program dan manusia adalah yang pertama beroperasi sangat cepat. Ini berarti sangat cepat, sehingga kadang -kadang browser tidak dapat membuat HTML baru pada waktunya. Jadi setelah setiap tindakan, Anda perlu melakukan fungsi wait-<something>
yang hanya polling browser yang memeriksa predikat. O Just (wait <seconds>)
jika Anda tidak peduli tentang optimasi.
Terkadang, file mulai mengunduh secara otomatis setelah Anda mengklik tautan atau hanya mengunjungi beberapa halaman. Dalam tes, Anda perlu memastikan file benar -benar telah diunduh dengan sukses. Skenario umum adalah:
Contoh:
; ; 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 dapat memutar file yang diproduksi oleh Selenium IDE. Ini adalah utilitas resmi untuk membuat skenario secara interaktif. IDE datang sebagai ekstensi ke browser Anda. Setelah diinstal, ia mencatat tindakan Anda ke dalam file JSON dengan ekstensi .side
. Anda dapat menyimpan file itu dan menjalankannya dengan Etaoin.
Mari kita bayangkan Anda telah menginstal IDE dan merekam beberapa tindakan seperti yang ditentukan oleh dokumentasi resmi. Sekarang Anda memiliki file test.side
, lakukan ini:
( 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)
Segala sesuatu yang terkait dengan IDE disimpan di bawah paket etaoin.ide
.
Anda juga dapat menjalankan skrip dari baris perintah. Inilah contoh lein run
:
lein run -m etaoin.ide.main -d firefox -p ' {:port 8888 :args ["--no-sandbox"]} ' -r ide/test.side
Serta dari uberjar. Dalam hal ini, etaoin harus dalam dependensi primer, bukan :dev
atau :test
terkait.
java -cp .../poject.jar -m etaoin.ide.main -d firefox -p ' {:port 8888} ' -f ide/test.side
Kami mendukung argumen berikut (periksa menggunakan perintah 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
Perhatikan --params
satu. Ini harus berupa string EDN yang mewakili peta clojure. Itu peta yang sama dengan yang Anda masuk ke pengemudi saat memulainya.
Harap dicatat dukungan IDE masih eksperimental. Jika Anda menghadapi perilaku yang tidak terduga, jangan ragu untuk membuka masalah. Saat ini, kami hanya mendukung Chrome dan Firefox untuk file IDE.
Contoh:
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) " }},
...
Solusi: Cukup perbarui chromedriver
Anda ke versi terakhir. Diuji dengan 2.29, berfungsi dengan baik. Orang -orang mengatakan itu wajan juga sejak 2.28.
Ingat, brew
Package Manager memiliki versi 2.27 yang sudah ketinggalan zaman. Anda mungkin harus mengunduh binari dari situs resmi.
Lihat masalah terkait dalam proyek Selenium.
Saat melewati kueri seperti vektor, katakan [{:tag :p} "//*[text()='foo')]]"}]
hati-hati dengan ekspresi xpath yang ditulis tangan. Di vektor, setiap ekspresinya mencari dari yang sebelumnya dalam satu loop. Ada kesalahan tersembunyi di sini: Tanpa titik terkemuka, klausa "//..."
berarti menemukan elemen dari akar seluruh halaman. Dengan titik, itu berarti menemukan dari simpul saat ini, yang merupakan satu dari kueri sebelumnya, dan sebagainya.
Itu sebabnya, mudah untuk memilih sesuatu yang sama sekali berbeda dengan apa yang Anda inginkan. Ekspresi yang tepat adalah: [{:tag :p} ".//*[text()='foo')]]"}]
.
Contoh:
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) " }},
...
SOLUSI: Anda mencoba mengklik elemen yang tidak terlihat atau dimensi -nya sesedikit tidak mungkin bagi manusia untuk mengkliknya. Anda harus melewati pemilih lain.
Masalah: Ketika Anda fokus pada jendela lain, sesi WebDriver yang dijalankan di bawah Google Chrome gagal.
Solusi: Google Chrome dapat menangguhkan tab ketika itu tidak aktif untuk beberapa waktu. Ketika halaman ditangguhkan, tidak ada operasi yang bisa dilakukan di atasnya. Tidak ada klik, eksekusi JS, dll. Jadi cobalah untuk menjaga jendela chrome tetap aktif selama sesi tes.
Masalah: Saat Anda mencoba memulai driver, Anda mendapatkan kesalahan:
user=> ( use 'etaoin.api)
user=> ( def driver ( firefox { :headless true }))
Kesalahan Sintaks (ExceptionInfo) Mengompilasi AT (REPL: 1: 13). Throw+: {: response {: value {: error "tidak diketahui kesalahan" ,: pesan "argumen tidak valid: tidak dapat membunuh proses keluar" ....
Kemungkinan Penyebab: "Menjalankan Firefox Sebagai Root Dalam Sesi Pengguna Reguler Tidak Didukung"
Solusi: Untuk memeriksa, jalankan driver dengan jalur ke file log dan level log "jejak" dan jelajahi output mereka.
( def driver ( firefox { :log-stdout " ffout.log " :log-stderr " fferr.log " :driver-log-level " trace " }))
Masalah serupa: Mozilla/Geckodriver#1655
Masalah: Saat Anda mencoba memulai chromedriver, Anda mendapatkan kesalahan:
clojure.lang.ExceptionInfo: Throw+: {: response {: sessionId "....." ,: status 13 ,: value {: pesan "Kesalahan Tidak Diketahui: Chrome Gagal Memulai: Keluar Abnormal. N (Kesalahan Tidak Diketahui: DevToolSactivePort file tidak ada) ...
Kemungkinan Penyebab:
Penyebab umum untuk chrome crash selama startup menjalankan chrome sebagai pengguna root (administrator) di linux. Meskipun dimungkinkan untuk mengatasi masalah ini dengan menyampaikan bendera-tidak ada sandbox saat membuat sesi webdriver Anda, konfigurasi seperti itu tidak didukung dan sangat berkecil hati. Anda perlu mengkonfigurasi lingkungan Anda untuk menjalankan Chrome sebagai pengguna biasa sebagai gantinya.
Solution: Run driver with an argument --no-sandbox
. Peringatan! 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.